Add skeleton
This commit is contained in:
204
config/index.js
Normal file
204
config/index.js
Normal file
@@ -0,0 +1,204 @@
|
||||
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,
|
||||
};
|
||||
Reference in New Issue
Block a user