zet-lib 3.2.4 → 3.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/Form.js +8 -2
- package/lib/Mail.js +4 -3
- package/lib/Model.js +9 -2
- package/lib/connection.js +164 -88
- package/lib/generatorApp.js +19 -19
- package/lib/generatorModel.js +22 -9
- package/lib/io.js +1 -13
- package/lib/zAppRouter.js +16 -15
- package/lib/zGeneratorRouter.js +40 -40
- package/lib/zMenuRouter.js +2 -1
- package/lib/zPage.js +14 -13
- package/lib/zRoute.js +46 -44
- package/lib/zViewGenerator.js +22 -21
- package/package.json +1 -1
package/lib/Form.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const Util = require("./Util");
|
|
7
|
+
const path = require('path');
|
|
7
8
|
|
|
8
9
|
const Form = {};
|
|
9
10
|
// Menghitung jumlah tanda "[" dalam str
|
|
@@ -550,7 +551,12 @@ Form.field = (obj) => {
|
|
|
550
551
|
break;
|
|
551
552
|
|
|
552
553
|
case "html":
|
|
553
|
-
|
|
554
|
+
let obj_code = typeof obj.code === 'string' ? obj.code : '';
|
|
555
|
+
if (!obj_code && obj.code) {
|
|
556
|
+
const dir = path.join(dirRoot,'public','runtime','html',obj.routeName,obj.id+'.txt')
|
|
557
|
+
obj_code = Util.readFile(dir);
|
|
558
|
+
}
|
|
559
|
+
displayForm = obj_code;
|
|
554
560
|
break;
|
|
555
561
|
|
|
556
562
|
case "location":
|
|
@@ -1038,7 +1044,7 @@ Form.build = (obj) => {
|
|
|
1038
1044
|
}
|
|
1039
1045
|
|
|
1040
1046
|
if(obj.type == "html") {
|
|
1041
|
-
html = obj
|
|
1047
|
+
html = Form.field(obj)
|
|
1042
1048
|
}
|
|
1043
1049
|
return html;
|
|
1044
1050
|
};
|
package/lib/Mail.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require("dotenv").config();
|
|
2
|
+
const path = require('path');
|
|
2
3
|
const nodemailer = require('nodemailer')
|
|
3
4
|
const ejs = require('ejs')
|
|
4
5
|
const Util = require('./Util')
|
|
@@ -47,7 +48,7 @@ MAIL.send = function (options = {}, transporter = null) {
|
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
MAIL.forgotPassword = (datas = {}, options = {}) => {
|
|
50
|
-
ejs.renderFile(
|
|
51
|
+
ejs.renderFile(path.join(dirRoot, 'views', 'layouts', 'email', 'forgot_password.ejs'), { data: datas, Util: Util }, function (err, data) {
|
|
51
52
|
let option = Object.assign(mailOptions, options)
|
|
52
53
|
option.html = data
|
|
53
54
|
if (err) {
|
|
@@ -59,7 +60,7 @@ MAIL.forgotPassword = (datas = {}, options = {}) => {
|
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
MAIL.register = (datas = {}, options = {}) => {
|
|
62
|
-
ejs.renderFile(
|
|
63
|
+
ejs.renderFile(path.join(dirRoot, 'views', 'layouts', 'email', 'register.ejs'), { data: datas, Util: Util }, function (err, data) {
|
|
63
64
|
let option = Object.assign(mailOptions, options)
|
|
64
65
|
option.html = data
|
|
65
66
|
if (err) {
|
|
@@ -88,7 +89,7 @@ MAIL.approval = (req, res, data, email) => {
|
|
|
88
89
|
});
|
|
89
90
|
|
|
90
91
|
ejs.renderFile(
|
|
91
|
-
dirRoot
|
|
92
|
+
path.join(dirRoot, 'views', 'layouts', 'email', 'approval.ejs'),
|
|
92
93
|
{ data: data, Util: Util },
|
|
93
94
|
function (err, html) {
|
|
94
95
|
if (err) {
|
package/lib/Model.js
CHANGED
|
@@ -2394,7 +2394,8 @@ Model.relation = {
|
|
|
2394
2394
|
"isSearch",
|
|
2395
2395
|
"order_by",
|
|
2396
2396
|
"import_field",
|
|
2397
|
-
"relation_info"
|
|
2397
|
+
"relation_info",
|
|
2398
|
+
"isAttributes",
|
|
2398
2399
|
],
|
|
2399
2400
|
labels: {
|
|
2400
2401
|
required: "Required",
|
|
@@ -2413,7 +2414,8 @@ Model.relation = {
|
|
|
2413
2414
|
isSearch:"Auto complete Search",
|
|
2414
2415
|
order_by : "Order By",
|
|
2415
2416
|
import_field :"Default Import Field (id)",
|
|
2416
|
-
relation_info : "Relation Data"
|
|
2417
|
+
relation_info : "Relation Data",
|
|
2418
|
+
isAttributes:"Show all attributes data",
|
|
2417
2419
|
},
|
|
2418
2420
|
defaultValues: {
|
|
2419
2421
|
required: false,
|
|
@@ -2423,6 +2425,7 @@ Model.relation = {
|
|
|
2423
2425
|
name: "username",
|
|
2424
2426
|
concat: "CONCAT(fullname, ' ',email)",
|
|
2425
2427
|
where:"",
|
|
2428
|
+
isAttributes:false,
|
|
2426
2429
|
isChain: false,
|
|
2427
2430
|
onChange: "",
|
|
2428
2431
|
information: "",
|
|
@@ -2463,6 +2466,10 @@ Model.relation = {
|
|
|
2463
2466
|
tag: "input",
|
|
2464
2467
|
type: "text",
|
|
2465
2468
|
},
|
|
2469
|
+
isAttributes: {
|
|
2470
|
+
tag: "input",
|
|
2471
|
+
type: "checkbox",
|
|
2472
|
+
},
|
|
2466
2473
|
isChain: {
|
|
2467
2474
|
tag: "input",
|
|
2468
2475
|
type: "checkbox",
|
package/lib/connection.js
CHANGED
|
@@ -21,6 +21,77 @@ pool.on('error', (err) => {
|
|
|
21
21
|
console.error('Unexpected error on idle client', err)
|
|
22
22
|
})
|
|
23
23
|
|
|
24
|
+
// Validasi identifier (nama tabel) untuk mencegah SQL injection
|
|
25
|
+
const SAFE_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)?$/
|
|
26
|
+
const sanitizeTableName = (table) => {
|
|
27
|
+
if (typeof table !== 'string' || !table.trim()) return ''
|
|
28
|
+
const t = table.trim()
|
|
29
|
+
if (!SAFE_IDENTIFIER.test(t)) {
|
|
30
|
+
throw new Error(`Invalid table name: ${t}`)
|
|
31
|
+
}
|
|
32
|
+
return t
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Validasi limit/offset sebagai integer non-negatif
|
|
36
|
+
const toSafeNonNegativeInt = (val, fallback) => {
|
|
37
|
+
if (val === undefined || val === null) return fallback
|
|
38
|
+
const n = parseInt(val, 10)
|
|
39
|
+
if (!Number.isInteger(n) || n < 0) return fallback
|
|
40
|
+
return n
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Validasi satu segmen ORDER BY (identifier + optional ASC/DESC) untuk mencegah SQL injection
|
|
44
|
+
const SAFE_IDENTIFIER_PART = /^[a-zA-Z_][a-zA-Z0-9_]*$/
|
|
45
|
+
const sanitizeOrderBySegment = (segment) => {
|
|
46
|
+
if (typeof segment !== 'string' || !segment.trim()) return ''
|
|
47
|
+
const parts = segment.trim().split(/\s+/)
|
|
48
|
+
const ident = parts[0]
|
|
49
|
+
const dir = parts[1] ? parts[1].toUpperCase() : ''
|
|
50
|
+
if (dir && dir !== 'ASC' && dir !== 'DESC') return ''
|
|
51
|
+
const identParts = ident.split('.')
|
|
52
|
+
const valid = identParts.length > 0 && identParts.every(p => SAFE_IDENTIFIER_PART.test(p))
|
|
53
|
+
if (!valid) return ''
|
|
54
|
+
const quotedIdent = identParts.map(p => `"${p}"`).join('.')
|
|
55
|
+
return dir ? `${quotedIdent} ${dir}` : quotedIdent
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Validasi nama kolom (identifier tunggal) untuk INSERT/UPDATE
|
|
59
|
+
const sanitizeColumnName = (key) => SAFE_IDENTIFIER_PART.test(String(key).trim()) ? String(key).trim() : null
|
|
60
|
+
|
|
61
|
+
// Validasi klausa RETURNING: hanya RETURNING * atau RETURNING col1, col2, ...
|
|
62
|
+
const SAFE_RETURNING = /^RETURNING \*$/i
|
|
63
|
+
const SAFE_RETURNING_COLS = /^RETURNING ([a-zA-Z_][a-zA-Z0-9_]*)(,\s*[a-zA-Z_][a-zA-Z0-9_]*)*$/i
|
|
64
|
+
const sanitizeReturning = (clause) => {
|
|
65
|
+
if (typeof clause !== 'string' || !clause.trim()) return 'RETURNING *'
|
|
66
|
+
const s = clause.trim()
|
|
67
|
+
if (SAFE_RETURNING.test(s)) return s
|
|
68
|
+
if (SAFE_RETURNING_COLS.test(s)) return s
|
|
69
|
+
return 'RETURNING *'
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Validasi field di WHERE (identifier atau schema.table)
|
|
73
|
+
const sanitizeWhereField = (field) => {
|
|
74
|
+
if (field == null || typeof field !== 'string' || !field.trim()) return ''
|
|
75
|
+
const identParts = String(field).trim().split('.')
|
|
76
|
+
const valid = identParts.length > 0 && identParts.every(p => SAFE_IDENTIFIER_PART.test(p))
|
|
77
|
+
if (!valid) return ''
|
|
78
|
+
return identParts.map(p => `"${p}"`).join('.')
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Whitelist operator perbandingan untuk WHERE
|
|
82
|
+
const SAFE_WHERE_OPERATORS = new Set(['=', '!=', '<>', '<', '>', '<=', '>=', 'LIKE', 'ILIKE', 'IS NOT', 'IS'])
|
|
83
|
+
const sanitizeWhereOption = (option) => {
|
|
84
|
+
if (option == null || typeof option !== 'string') return ''
|
|
85
|
+
const s = String(option).trim().toUpperCase()
|
|
86
|
+
return SAFE_WHERE_OPERATORS.has(s) ? s : ''
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const sanitizeWhereConnector = (op) => {
|
|
90
|
+
if (op == null || typeof op !== 'string') return ' AND '
|
|
91
|
+
const s = String(op).trim().toUpperCase()
|
|
92
|
+
return (s === 'OR' ? ' OR ' : ' AND ')
|
|
93
|
+
}
|
|
94
|
+
|
|
24
95
|
const connection = {}
|
|
25
96
|
|
|
26
97
|
connection.query = async (string, arr) => {
|
|
@@ -53,9 +124,11 @@ const orderByFn = (obj) => {
|
|
|
53
124
|
}
|
|
54
125
|
}
|
|
55
126
|
}
|
|
56
|
-
if (obj.hasOwnProperty('order_by')) {
|
|
57
|
-
|
|
58
|
-
|
|
127
|
+
if (obj.hasOwnProperty('order_by') && Array.isArray(obj.order_by) && obj.order_by.length) {
|
|
128
|
+
const segments = obj.order_by.map(sanitizeOrderBySegment).filter(Boolean)
|
|
129
|
+
if (segments.length) {
|
|
130
|
+
orderBy = ` ORDER BY ${segments.join(', ')} `
|
|
131
|
+
}
|
|
59
132
|
}
|
|
60
133
|
|
|
61
134
|
return orderBy
|
|
@@ -67,7 +140,7 @@ const whereFn = (obj) => {
|
|
|
67
140
|
const whereArray = obj.whereArray || []
|
|
68
141
|
let increment = 1
|
|
69
142
|
let arr = [],
|
|
70
|
-
|
|
143
|
+
wherequery = []
|
|
71
144
|
for (const key in where) {
|
|
72
145
|
wherequery.push(key.indexOf('.') > -1 ? ` ${key} = $${increment} ` : ` "${key}" = $${increment}`)
|
|
73
146
|
arr.push(where[key])
|
|
@@ -93,14 +166,17 @@ const whereFn = (obj) => {
|
|
|
93
166
|
} else {
|
|
94
167
|
field = item.field.indexOf('.') > -1 ? item.field : item.field ? ` "${item.field}" ` : ''
|
|
95
168
|
}
|
|
96
|
-
//
|
|
97
|
-
//JSON_CONTAINS(color, '"Red"' ,'$')
|
|
169
|
+
// PostgreSQL: jsonb containment (@>) dengan parameter untuk keamanan
|
|
98
170
|
if (item.isJSON) {
|
|
99
|
-
wherequery += andOr + `
|
|
171
|
+
wherequery += andOr + ` (${String(field).trim()})::jsonb @> $${increment}::jsonb ${operator}`
|
|
172
|
+
arr.push(JSON.stringify(item.value))
|
|
173
|
+
increment++
|
|
100
174
|
hasWhere = true
|
|
101
175
|
} else {
|
|
102
176
|
if (type == 'json') {
|
|
103
|
-
wherequery += andOr + `
|
|
177
|
+
wherequery += andOr + ` (${String(field).trim()})::jsonb @> $${increment}::jsonb ${operator}`
|
|
178
|
+
arr.push(JSON.stringify(item.value))
|
|
179
|
+
increment++
|
|
104
180
|
hasWhere = true
|
|
105
181
|
} else if (type == 'inline') {
|
|
106
182
|
//select * from attendance where employee_id = 4803 and date IN ('2023-12-21','2023-12-22')
|
|
@@ -150,11 +226,13 @@ const whereFn = (obj) => {
|
|
|
150
226
|
}
|
|
151
227
|
|
|
152
228
|
connection.results = async (obj) => {
|
|
229
|
+
const table = sanitizeTableName(obj.table || '')
|
|
153
230
|
const select = obj.select || '*'
|
|
154
|
-
const table = obj.table || ''
|
|
155
231
|
const statement = obj.statement || ''
|
|
156
|
-
const
|
|
157
|
-
const
|
|
232
|
+
const limitVal = toSafeNonNegativeInt(obj.limit, null)
|
|
233
|
+
const offsetVal = obj.hasOwnProperty('offset') ? toSafeNonNegativeInt(obj.offset, 0) : (obj.limit ? 0 : null)
|
|
234
|
+
const limit = limitVal !== null ? ` LIMIT ${limitVal} ` : ''
|
|
235
|
+
const offset = offsetVal !== null ? ` OFFSET ${offsetVal} ` : ''
|
|
158
236
|
const orderBy = orderByFn(obj)
|
|
159
237
|
const values = obj.values || []
|
|
160
238
|
const objJoin = obj.joins || []
|
|
@@ -166,35 +244,16 @@ connection.results = async (obj) => {
|
|
|
166
244
|
const wheres = whereObj.where
|
|
167
245
|
const arr = whereObj.arr
|
|
168
246
|
|
|
169
|
-
|
|
170
|
-
if (select.trim().toLowerCase() === 'count(id) as count' && !wheres) {
|
|
171
|
-
// No filter, use fast count from pg_class
|
|
172
|
-
const sql = `SELECT reltuples::bigint AS count FROM pg_class WHERE relname = '${table}'`;
|
|
173
|
-
try {
|
|
174
|
-
const start = Date.now();
|
|
175
|
-
const result = await pool.query(sql);
|
|
176
|
-
const elapsed = Date.now() - start;
|
|
177
|
-
console.log(`[zet-lib] Fast count for ${table}: ${elapsed}ms`);
|
|
178
|
-
return result.rows;
|
|
179
|
-
} catch (e) {
|
|
180
|
-
console.log(sql)
|
|
181
|
-
console.log(e.toString())
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const sql = `SELECT ${select} FROM "${table}" ${join} ${wheres} ${statement} ${orderBy} ${limit} ${offset}`
|
|
247
|
+
const sql = `SELECT ${select} FROM "${table}" ${join} ${wheres} ${statement} ${orderBy} ${limit} ${offset}`.replace(/\s+/g, ' ').trim()
|
|
186
248
|
try {
|
|
187
|
-
const start = Date.now()
|
|
249
|
+
const start = Date.now()
|
|
188
250
|
const result = await pool.query(sql, arr.length ? arr : values.length ? values : null)
|
|
189
|
-
const elapsed = Date.now() - start;
|
|
190
|
-
/*if (select.trim().toLowerCase() === 'count(id) as count') {
|
|
191
|
-
console.log(`[zet-lib] Count query for ${table}: ${elapsed}ms`);
|
|
192
|
-
}*/
|
|
193
251
|
return !result.rows ? [] : result.rows
|
|
194
252
|
} catch (e) {
|
|
195
253
|
console.log(sql)
|
|
196
254
|
console.log(arr)
|
|
197
255
|
console.log(e.toString())
|
|
256
|
+
throw e
|
|
198
257
|
}
|
|
199
258
|
}
|
|
200
259
|
|
|
@@ -229,25 +288,26 @@ connection.result = async (obj) => {
|
|
|
229
288
|
}
|
|
230
289
|
|
|
231
290
|
connection.insert = async (obj) => {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const data = obj.data
|
|
291
|
+
const table = sanitizeTableName(obj.table || '')
|
|
292
|
+
if (!table) throw new Error('Table name is required for insert')
|
|
293
|
+
const data = { ...obj.data }
|
|
294
|
+
const returning = sanitizeReturning(data.returning || 'RETURNING *')
|
|
295
|
+
delete data.returning
|
|
235
296
|
let increment = 1
|
|
236
297
|
const datas = []
|
|
237
298
|
const values = []
|
|
238
299
|
const arr = []
|
|
239
|
-
const returning = data.returning || 'RETURNING *'
|
|
240
|
-
delete data.returning
|
|
241
300
|
for (const key in data) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
301
|
+
const col = sanitizeColumnName(key)
|
|
302
|
+
if (col) {
|
|
303
|
+
datas.push(col)
|
|
304
|
+
values.push(`$${increment}`)
|
|
305
|
+
arr.push(data[key])
|
|
306
|
+
increment++
|
|
307
|
+
}
|
|
246
308
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
console.log(arr)
|
|
250
|
-
*/
|
|
309
|
+
if (datas.length === 0) throw new Error('At least one valid column is required for insert')
|
|
310
|
+
const sql = `INSERT INTO "${table}" ("${datas.join('","')}") VALUES (${values.join(',')}) ${returning}`
|
|
251
311
|
|
|
252
312
|
try {
|
|
253
313
|
const results = await pool.query(sql, arr)
|
|
@@ -261,45 +321,59 @@ connection.insert = async (obj) => {
|
|
|
261
321
|
}
|
|
262
322
|
|
|
263
323
|
connection.update = async (obj) => {
|
|
264
|
-
const table = obj.table
|
|
265
|
-
|
|
266
|
-
const
|
|
324
|
+
const table = sanitizeTableName(obj.table || '')
|
|
325
|
+
if (!table) throw new Error('Table name is required for update')
|
|
326
|
+
const data = { ...obj.data }
|
|
327
|
+
const returning = sanitizeReturning(data.returning || 'RETURNING *')
|
|
267
328
|
delete data.returning
|
|
268
329
|
|
|
269
330
|
const where = obj.where || {}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const
|
|
273
|
-
dataArr = []
|
|
331
|
+
const whereArray = obj.whereArray || []
|
|
332
|
+
const arr = []
|
|
333
|
+
const dataArr = []
|
|
274
334
|
let wherequery = []
|
|
275
335
|
let increment = 1
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
336
|
+
|
|
337
|
+
for (const key in data) {
|
|
338
|
+
const col = sanitizeColumnName(key)
|
|
339
|
+
if (col) {
|
|
340
|
+
dataArr.push(`"${col}" = $${increment}`)
|
|
341
|
+
arr.push(data[key])
|
|
342
|
+
increment++
|
|
343
|
+
}
|
|
280
344
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
345
|
+
if (dataArr.length === 0) throw new Error('At least one valid column is required for update')
|
|
346
|
+
|
|
347
|
+
for (const key in where) {
|
|
348
|
+
const col = sanitizeColumnName(key)
|
|
349
|
+
if (col) {
|
|
350
|
+
wherequery.push(`"${col}" = $${increment}`)
|
|
351
|
+
arr.push(where[key])
|
|
352
|
+
increment++
|
|
353
|
+
}
|
|
285
354
|
}
|
|
286
|
-
wherequery =
|
|
355
|
+
wherequery = wherequery.length ? wherequery.join(' AND ') : ''
|
|
356
|
+
|
|
287
357
|
if (whereArray.length) {
|
|
288
358
|
let andOr = wherequery ? ' AND ' : ''
|
|
289
359
|
whereArray.forEach((item, index) => {
|
|
290
360
|
if (index > 0) andOr = ''
|
|
291
|
-
const
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
361
|
+
const field = sanitizeWhereField(item.field)
|
|
362
|
+
const option = sanitizeWhereOption(item.option)
|
|
363
|
+
const connector = sanitizeWhereConnector(item.operator)
|
|
364
|
+
if (field && option) {
|
|
365
|
+
wherequery += `${andOr} ${field} ${option} $${increment} ${connector}`
|
|
366
|
+
arr.push(item.value)
|
|
367
|
+
increment++
|
|
368
|
+
}
|
|
296
369
|
})
|
|
297
|
-
wherequery = wherequery.slice(0, -5)
|
|
370
|
+
if (wherequery.endsWith(' AND ')) wherequery = wherequery.slice(0, -5)
|
|
371
|
+
else if (wherequery.endsWith(' OR ')) wherequery = wherequery.slice(0, -4)
|
|
298
372
|
}
|
|
299
|
-
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
373
|
+
|
|
374
|
+
const wheres = wherequery ? ' WHERE ' + wherequery : ''
|
|
375
|
+
const sql = `UPDATE "${table}" SET ${dataArr.join(', ')}${wheres} ${returning}`
|
|
376
|
+
|
|
303
377
|
try {
|
|
304
378
|
const result = await pool.query(sql, arr)
|
|
305
379
|
return result.rows[0]
|
|
@@ -312,21 +386,23 @@ connection.update = async (obj) => {
|
|
|
312
386
|
}
|
|
313
387
|
|
|
314
388
|
connection.delete = async (obj) => {
|
|
315
|
-
const table = obj.table
|
|
389
|
+
const table = sanitizeTableName(obj.table || '')
|
|
390
|
+
if (!table) throw new Error('Table name is required for delete')
|
|
316
391
|
const where = obj.where || {}
|
|
317
|
-
|
|
318
|
-
|
|
392
|
+
const arr = []
|
|
393
|
+
const wherequery = []
|
|
319
394
|
let increment = 1
|
|
320
395
|
for (const key in where) {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
396
|
+
const col = sanitizeColumnName(key)
|
|
397
|
+
if (col) {
|
|
398
|
+
wherequery.push(`"${col}" = $${increment}`)
|
|
399
|
+
arr.push(where[key])
|
|
400
|
+
increment++
|
|
401
|
+
}
|
|
324
402
|
}
|
|
325
|
-
|
|
326
|
-
const wheres =
|
|
327
|
-
const sql = `DELETE FROM "${table}"
|
|
328
|
-
/*console.log(sql);
|
|
329
|
-
console.log(arr)*/
|
|
403
|
+
const whereClause = wherequery.length ? wherequery.join(' AND ') : ''
|
|
404
|
+
const wheres = whereClause ? ' WHERE ' + whereClause : ''
|
|
405
|
+
const sql = `DELETE FROM "${table}"${wheres} RETURNING *`
|
|
330
406
|
try {
|
|
331
407
|
return await pool.query(sql, arr)
|
|
332
408
|
} catch (e) {
|
|
@@ -346,7 +422,7 @@ connection.insertData = async(tableName, data) => {
|
|
|
346
422
|
}
|
|
347
423
|
|
|
348
424
|
pgPool = new Pool(configPG);
|
|
349
|
-
|
|
425
|
+
|
|
350
426
|
const columns = Object.keys(data);
|
|
351
427
|
if (columns.length === 0) {
|
|
352
428
|
throw new Error('No columns found in the data object');
|
|
@@ -381,7 +457,7 @@ connection.deleteData = async(tableName, where) => {
|
|
|
381
457
|
}
|
|
382
458
|
|
|
383
459
|
pgPool = new Pool(configPG);
|
|
384
|
-
|
|
460
|
+
|
|
385
461
|
const whereColumns = Object.keys(where);
|
|
386
462
|
if (whereColumns.length === 0) {
|
|
387
463
|
throw new Error('No where conditions provided for delete');
|
|
@@ -417,13 +493,13 @@ connection.insertMultipleRecords = async(tableName,records) => {
|
|
|
417
493
|
}
|
|
418
494
|
|
|
419
495
|
pgPool = new Pool(configPG);
|
|
420
|
-
|
|
496
|
+
|
|
421
497
|
// Validasi bahwa semua records memiliki struktur yang sama
|
|
422
498
|
const firstRecord = records[0];
|
|
423
499
|
if (!firstRecord || typeof firstRecord !== 'object') {
|
|
424
500
|
throw new Error('First record is invalid or empty');
|
|
425
501
|
}
|
|
426
|
-
|
|
502
|
+
|
|
427
503
|
const columns = Object.keys(firstRecord);
|
|
428
504
|
if (columns.length === 0) {
|
|
429
505
|
throw new Error('No columns found in the first record');
|
|
@@ -444,7 +520,7 @@ connection.insertMultipleRecords = async(tableName,records) => {
|
|
|
444
520
|
const placeholders = records.map((_, rowIndex) =>
|
|
445
521
|
`(${columns.map((_, colIndex) => `$${rowIndex * columns.length + colIndex + 1}`).join(',')})`
|
|
446
522
|
).join(',');
|
|
447
|
-
|
|
523
|
+
|
|
448
524
|
// Create the INSERT query
|
|
449
525
|
const insertQuery = `
|
|
450
526
|
INSERT INTO "${tableName}" (${columns.map(col => `"${col}"`).join(',')})
|
package/lib/generatorApp.js
CHANGED
|
@@ -34,7 +34,7 @@ generatorApp.CREATE_MODEL_API = async (columns, constraintList, MYMODEL) => {
|
|
|
34
34
|
generatorApp.CREATE_ROUTER = async (MYMODEL, zFields) => {
|
|
35
35
|
let CONTENT =
|
|
36
36
|
zFields.router ||
|
|
37
|
-
fs.readFileSync(generatorApp.DIRECTORY
|
|
37
|
+
fs.readFileSync(path.join(generatorApp.DIRECTORY, "routerApp.ejs"), "utf-8");
|
|
38
38
|
const zfields = await connection.result({
|
|
39
39
|
table: "zfields",
|
|
40
40
|
where: { table: MYMODEL.table },
|
|
@@ -58,7 +58,7 @@ generatorApp.CREATE_ROUTER = async (MYMODEL, zFields) => {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
fs.writeFileSync(
|
|
61
|
-
generatorApp.DIRECTORY_ROUTES
|
|
61
|
+
path.join(generatorApp.DIRECTORY_ROUTES, MYMODEL.table + ".js"),
|
|
62
62
|
CONTENT,
|
|
63
63
|
"utf-8"
|
|
64
64
|
);
|
|
@@ -67,7 +67,7 @@ generatorApp.CREATE_ROUTER = async (MYMODEL, zFields) => {
|
|
|
67
67
|
generatorApp.CREATE_ROUTER_API = (MYMODEL, zfields) => {
|
|
68
68
|
let CONTENT =
|
|
69
69
|
zfields.router ||
|
|
70
|
-
fs.readFileSync(generatorApp.DIRECTORY
|
|
70
|
+
fs.readFileSync(path.join(generatorApp.DIRECTORY, "routerApp.ejs"), "utf-8");
|
|
71
71
|
//var zfields = await connection.result({table: "zfields", where: {table: MYMODEL.table}});
|
|
72
72
|
const ADDITIONAL_FIELDS = generatorApp.ADDITIONAL_FIELDS(MYMODEL);
|
|
73
73
|
const OBJ = {
|
|
@@ -86,7 +86,7 @@ generatorApp.CREATE_ROUTER_API = (MYMODEL, zfields) => {
|
|
|
86
86
|
for (let KEY in OBJ) {
|
|
87
87
|
CONTENT = Util.replaceAll(CONTENT, "[[[" + KEY + "]]]", OBJ[KEY]);
|
|
88
88
|
}
|
|
89
|
-
//fs.writeFileSync(generatorApp.DIRECTORY_ROUTES
|
|
89
|
+
//fs.writeFileSync(path.join(generatorApp.DIRECTORY_ROUTES, MYMODEL.table + ".js"), CONTENT, 'utf-8');
|
|
90
90
|
return {
|
|
91
91
|
filename: MYMODEL.table + ".js",
|
|
92
92
|
content: CONTENT,
|
|
@@ -123,7 +123,7 @@ generatorApp.CREATE_VIEWS = async (MYMODEL, zFields) => {
|
|
|
123
123
|
"[[[GENERATE_VIEW]]]": GENERATE_VIEW,
|
|
124
124
|
};
|
|
125
125
|
|
|
126
|
-
const DIR_VIEW = generatorApp.DIRECTORY_VIEWS
|
|
126
|
+
const DIR_VIEW = path.join(generatorApp.DIRECTORY_VIEWS, MYMODEL.table);
|
|
127
127
|
if (!fs.existsSync(DIR_VIEW)) {
|
|
128
128
|
fs.mkdirSync(DIR_VIEW);
|
|
129
129
|
}
|
|
@@ -133,16 +133,16 @@ generatorApp.CREATE_VIEWS = async (MYMODEL, zFields) => {
|
|
|
133
133
|
name = name.replace(".", "_");
|
|
134
134
|
let source = zFields[name];
|
|
135
135
|
if (source) {
|
|
136
|
-
fs.writeFileSync(DIR_VIEW
|
|
136
|
+
fs.writeFileSync(path.join(DIR_VIEW, item), source);
|
|
137
137
|
} else {
|
|
138
|
-
source = generatorApp.DIRECTORY_GENERATOR_VIEWS
|
|
139
|
-
fs.copySync(source, DIR_VIEW
|
|
138
|
+
source = path.join(generatorApp.DIRECTORY_GENERATOR_VIEWS, item);
|
|
139
|
+
fs.copySync(source, path.join(DIR_VIEW, item));
|
|
140
140
|
}
|
|
141
|
-
console.log(DIR_VIEW
|
|
141
|
+
console.log(path.join(DIR_VIEW, item));
|
|
142
142
|
for (let KEY in OBJ) {
|
|
143
143
|
generatorApp.MODIFIY(
|
|
144
|
-
DIR_VIEW
|
|
145
|
-
DIR_VIEW
|
|
144
|
+
path.join(DIR_VIEW, item),
|
|
145
|
+
path.join(DIR_VIEW, item),
|
|
146
146
|
KEY,
|
|
147
147
|
OBJ[KEY]
|
|
148
148
|
);
|
|
@@ -183,7 +183,7 @@ generatorApp.CREATE_VIEWS_API = (MYMODEL, zFields) => {
|
|
|
183
183
|
"[[[GENERATE_VIEW]]]": GENERATE_VIEW,
|
|
184
184
|
};
|
|
185
185
|
|
|
186
|
-
const DIR_VIEW =
|
|
186
|
+
const DIR_VIEW = path.join(dirRoot, "public", "temp", token);
|
|
187
187
|
if (!fs.existsSync(DIR_VIEW)) {
|
|
188
188
|
fs.mkdirSync(DIR_VIEW);
|
|
189
189
|
}
|
|
@@ -194,23 +194,23 @@ generatorApp.CREATE_VIEWS_API = (MYMODEL, zFields) => {
|
|
|
194
194
|
name = name.replace(".", "_");
|
|
195
195
|
let source = zFields.hasOwnProperty(name)
|
|
196
196
|
? zFields[name]
|
|
197
|
-
: fs.readFileSync(generatorApp.DIRECTORY_GENERATOR_VIEWS
|
|
197
|
+
: fs.readFileSync(path.join(generatorApp.DIRECTORY_GENERATOR_VIEWS, item), {
|
|
198
198
|
encoding: "utf8",
|
|
199
199
|
flag: "r",
|
|
200
200
|
});
|
|
201
201
|
if (source) {
|
|
202
|
-
fs.writeFileSync(DIR_VIEW
|
|
202
|
+
fs.writeFileSync(path.join(DIR_VIEW, item), source);
|
|
203
203
|
} else {
|
|
204
204
|
source = fs.readFileSync(
|
|
205
|
-
generatorApp.DIRECTORY_GENERATOR_VIEWS
|
|
205
|
+
path.join(generatorApp.DIRECTORY_GENERATOR_VIEWS, item),
|
|
206
206
|
{ encoding: "utf8", flag: "r" }
|
|
207
207
|
);
|
|
208
|
-
fs.copySync(source, DIR_VIEW
|
|
208
|
+
fs.copySync(source, path.join(DIR_VIEW, item));
|
|
209
209
|
}
|
|
210
210
|
for (let KEY in OBJ) {
|
|
211
211
|
generatorApp.MODIFIY(
|
|
212
|
-
DIR_VIEW
|
|
213
|
-
DIR_VIEW
|
|
212
|
+
path.join(DIR_VIEW, item),
|
|
213
|
+
path.join(DIR_VIEW, item),
|
|
214
214
|
KEY,
|
|
215
215
|
OBJ[KEY]
|
|
216
216
|
);
|
|
@@ -218,7 +218,7 @@ generatorApp.CREATE_VIEWS_API = (MYMODEL, zFields) => {
|
|
|
218
218
|
}
|
|
219
219
|
for (let i = 0; i < views.length; i++) {
|
|
220
220
|
const item = views[i];
|
|
221
|
-
datas[item] = fs.readFileSync(
|
|
221
|
+
datas[item] = fs.readFileSync(path.join(DIR_VIEW, item), {
|
|
222
222
|
encoding: "utf8",
|
|
223
223
|
flag: "r",
|
|
224
224
|
});
|