require("dotenv").config(); const sql = require("mssql"); const isProduction = process.env.NODE_ENV === "production"; // Config SQL Server const config = { user: process.env.SQL_USERNAME, password: process.env.SQL_PASSWORD, database: process.env.SQL_DATABASE, server: process.env.SQL_HOST, port: parseInt(process.env.SQL_PORT, 10), options: { encrypt: false, // true kalau Azure trustServerCertificate: true, }, }; // Buat pool global const poolPromise = new sql.ConnectionPool(config) .connect() .then(pool => { console.log("✅ Koneksi SQL Server berhasil"); return pool; }) .catch(err => { console.error("❌ Gagal koneksi SQL Server:", err); process.exit(1); }); /** * Wrapper query (auto konversi $1 → @p1) */ async function query(text, params = []) { const pool = await poolPromise; const request = pool.request(); params.forEach((param, i) => { request.input(`p${i + 1}`, param); }); // Ubah $1, $2 jadi @p1, @p2 const sqlText = text.replace(/\$(\d+)/g, (_, num) => `@p${num}`); console.log(sqlText, params); return request.query(sqlText); } /** * Build filter query */ function buildFilterQuery(filterQuery = [], fixedParams = []) { let whereConditions = []; let queryParams = [...fixedParams]; filterQuery.forEach((f) => { if (f.param === undefined || f.param === null || f.param === "") return; switch (f.type) { case "string": queryParams.push(`%${f.param}%`); whereConditions.push(`${f.column} LIKE $${queryParams.length} COLLATE SQL_Latin1_General_CP1_CI_AS`); break; case "number": queryParams.push(f.param); whereConditions.push(`${f.column} = $${queryParams.length}`); break; case "boolean": queryParams.push(f.param ? 1 : 0); whereConditions.push(`${f.column} = $${queryParams.length}`); break; } }); return { whereConditions, queryParams }; } /** * Build OR ILIKE (SQL Server pakai LIKE + COLLATE) */ function buildStringOrIlike(columnParam, criteria, fixedParams = []) { if (!criteria) return { whereClause: "", whereParam: fixedParams }; let orStringConditions = []; let queryParams = [...fixedParams]; columnParam.forEach((column) => { if (!column) return; queryParams.push(`%${criteria}%`); orStringConditions.push(`${column} LIKE $${queryParams.length} COLLATE SQL_Latin1_General_CP1_CI_AS`); }); const whereClause = orStringConditions.length ? `AND (${orStringConditions.join(" OR ")})` : ""; return { whereOrConditions: whereClause, whereParam: queryParams }; } /** * Build dynamic UPDATE */ function buildDynamicUpdate(table, data, where) { const setParts = []; const values = []; let index = 1; for (const [key, value] of Object.entries(data)) { if (value !== undefined && value !== null) { setParts.push(`${key} = $${index++}`); values.push(value); } } if (setParts.length === 0) { throw new Error("Tidak ada kolom untuk diupdate"); } // updated_at otomatis pakai GETDATE() setParts.push(`updated_at = GETDATE()`); const whereParts = []; for (const [key, value] of Object.entries(where)) { whereParts.push(`${key} = $${index++}`); values.push(value); } const query = ` UPDATE ${table} SET ${setParts.join(", ")} WHERE ${whereParts.join(" AND ")} `; return { query, values }; } /** * Build dynamic INSERT */ function buildDynamicInsert(table, data) { const columns = []; const placeholders = []; const values = []; let index = 1; for (const [key, value] of Object.entries(data)) { if (value !== undefined && value !== null) { columns.push(key); placeholders.push(`$${index++}`); values.push(value); } } if (columns.length === 0) { throw new Error("Tidak ada kolom untuk diinsert"); } // created_at & updated_at otomatis columns.push("created_at", "updated_at"); placeholders.push("GETDATE()", "GETDATE()"); const query = ` INSERT INTO ${table} (${columns.join(", ")}) VALUES (${placeholders.join(", ")}); SELECT SCOPE_IDENTITY() as inserted_id; `; return { query, values }; } /** * Generate kode otomatis */ async function generateKode(prefix, tableName, columnName) { const pool = await poolPromise; const result = await pool.request() .input("prefix", sql.VarChar, prefix + "%") .query(` SELECT TOP 1 ${columnName} as kode FROM ${tableName} WHERE ${columnName} LIKE @prefix ORDER BY ${columnName} DESC `); let nextNumber = 1; if (result.recordset.length > 0) { const lastKode = result.recordset[0].kode; const lastNumber = parseInt(lastKode.replace(prefix, ""), 10); nextNumber = lastNumber + 1; } return prefix + String(nextNumber).padStart(3, "0"); } module.exports = { query, buildFilterQuery, buildStringOrIlike, buildDynamicInsert, buildDynamicUpdate, generateKode, };