zet-lib 3.3.0 → 3.3.2

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 CHANGED
@@ -512,11 +512,11 @@ Form.field = (obj) => {
512
512
  });
513
513
 
514
514
  displayForm += `<div id="dragdrop_${obj.id}" class="row contentfields">
515
- <div class="col-md-6">
515
+ <div class="col-6">
516
516
  <h5>${obj.attributes.left}</h5>
517
517
  <ol class="mydragable${obj.id} divboxlittle" data-type="left" data-name="${obj.name}[]">${leftButtons}</ol>
518
518
  </div>
519
- <div class="col-md-6">
519
+ <div class="col-6">
520
520
  <h5>${obj.attributes.right}</h5>
521
521
  <ol class="mydragable${obj.id} divboxlittle" data-type="right" data-name="trashx">
522
522
  ${rightButtons}
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
- orderBy = ` ORDER BY `
58
- orderBy += obj.order_by.join(',')
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
- wherequery = []
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
- //is JSON is field is JSON
97
- //JSON_CONTAINS(color, '"Red"' ,'$')
169
+ // PostgreSQL: jsonb containment (@>) dengan parameter untuk keamanan
98
170
  if (item.isJSON) {
99
- wherequery += andOr + ` JSON_CONTAINS(${field}, '"${item.value}"' ,'$') ${operator}`
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 + ` JSON_CONTAINS(${field}, '"${item.value}"' ,'$') ${operator}`
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 limit = obj.limit ? ` LIMIT ${obj.limit} ` : ''
157
- const offset = obj.hasOwnProperty('offset') ? ` OFFSET ${obj.offset} ` : obj.limit ? 'OFFSET 0' : ''
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
- // --- OPTIMIZATION: Fast count for count(id) as count ---
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
- let result
233
- const table = obj.table
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
- datas.push(key)
243
- values.push(`$${increment}`)
244
- arr.push(data[key])
245
- increment++
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
- const sql = `INSERT INTO "${table}" ("${datas.join('","')}") VALUES (${values.join(',')}) ${returning}`
248
- /* console.log("ON INSERT " + sql)
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
- const data = obj.data
266
- const returning = data.returning || 'RETURNING *'
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
- //[{field:"your_field",option:"=>",value:"12",operator:"AND"}]
271
- let whereArray = obj.whereArray || []
272
- const arr = [],
273
- dataArr = []
331
+ const whereArray = obj.whereArray || []
332
+ const arr = []
333
+ const dataArr = []
274
334
  let wherequery = []
275
335
  let increment = 1
276
- for (let key in data) {
277
- dataArr.push(` "${key}" = $${increment}`)
278
- arr.push(data[key])
279
- increment++
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
- for (var key in where) {
282
- wherequery.push(` "${key}" = $${increment}`)
283
- arr.push(where[key])
284
- increment++
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 = arr.length ? wherequery.join(' AND ') : ''
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 operator = !item.operator ? ' AND ' : item.operator
292
- const field = item.field.indexOf('.') > -1 ? item.field : `"${item.field}"`
293
- wherequery += `${andOr} ${field} ${item.option} $${increment} ${operator}`
294
- arr.push(item.value)
295
- increment++
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
- const wheres = arr.length ? ' WHERE ' + wherequery : ''
300
- const sql = `UPDATE "${table}" SET ${dataArr.join(', ')} ${wheres} ${returning}`
301
- /* console.log(sql);
302
- console.log(arr);*/
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
- let arr = [],
318
- wherequery = []
392
+ const arr = []
393
+ const wherequery = []
319
394
  let increment = 1
320
395
  for (const key in where) {
321
- wherequery.push(` "${key}" = $${increment}`)
322
- arr.push(where[key])
323
- increment++
396
+ const col = sanitizeColumnName(key)
397
+ if (col) {
398
+ wherequery.push(`"${col}" = $${increment}`)
399
+ arr.push(where[key])
400
+ increment++
401
+ }
324
402
  }
325
- wherequery = arr.length ? wherequery.join(' AND ') : ''
326
- const wheres = arr.length ? ' WHERE ' + wherequery : ''
327
- const sql = `DELETE FROM "${table}" ${wheres} RETURNING *`
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(',')})
@@ -1,359 +1,359 @@
1
- const router = require('express').Router();
2
- const {csrf} = require('zet-lib');
3
- const csrfProtection = csrf({cookie: true});
4
- const {Util, access, connection, moduleLib, zDebug, zRoute, zRole, zDataTable, zForm} = require('zet-lib');
5
- const MYMODEL = require('./../models/[[[TABLE_NAME]]]');
6
-
7
- router.get('/', async (req, res) => {
8
- await zRoute.attributeData(res, MYMODEL);
9
- let dataTable = new zDataTable(res.locals);
10
- dataTable.filterMODEL = await zRoute.dataTableFilterSync(req,res, MYMODEL, res.locals.gridFilter);
11
- const levels = zRole.myLevel(req, res, MYMODEL.table);
12
- dataTable.levels = levels;
13
- res.render(`layouts/${layout}`, {
14
- dataTable: dataTable,
15
- levels : levels,
16
- titleHeader: `<strong class="text text-info">${MYMODEL.title}</strong>`,
17
- breadcrumb : zForm.breadcrumbIndex(),
18
- myModal : zForm.modal({
19
- attributeData: res.locals.attributeData,
20
- visibles: res.locals.visibles,
21
- invisibles: res.locals.invisibles,
22
- routeName: MYMODEL.table,
23
- }, LANGUAGE),
24
- renderHead: `${MYMODEL.routeName}/indexcss.ejs`,
25
- renderBody: `${MYMODEL.routeName}/index.ejs`,
26
- renderEnd: `${MYMODEL.routeName}/indexjs.ejs`
27
- });
28
- });
29
-
30
- router.post('/list', async (req, res) => {
31
- const data = await zRoute.listData(req, res, MYMODEL, zRole);
32
- res.json(data);
33
- });
34
-
35
- router.get('/create', csrfProtection, async(req, res) => {
36
- let data = MYMODEL.datas;
37
- const zForms = await zRoute.formsFieldSync(req, res, MYMODEL, data);
38
- res.render(`layouts/${layout}`, {
39
- data: data,
40
- zForms: zForms,
41
- titleHeader: `${LANGUAGE['form_create']} : <strong><a href="/${MYMODEL.routeName}">${MYMODEL.title}</a></strong>`,
42
- csrfToken: req.csrfToken(),
43
- breadcrumb : zForm.breadcrumbCreate(MYMODEL.routeName),
44
- renderBody: `${MYMODEL.routeName}/create.ejs`,
45
- renderEnd: `${MYMODEL.routeName}/createjs.ejs`
46
- });
47
- });
48
-
49
- router.post('/create', csrfProtection, async (req, res) => {
50
- let json = Util.jsonSuccess(LANGUAGE['data_saved']);
51
- try {
52
- let data = zRoute.post(req,res, MYMODEL)[MYMODEL.routeName];
53
- const validator = zRoute.validator(data, MYMODEL);
54
- if(validator.status == 0) return res.json(validator.message);
55
- const result = await zRoute.insertSQL(req, res, MYMODEL.table, data);
56
- json.data=result;
57
- } catch (err) {
58
- if(Object.hasOwn(err,"sqlMessage")){
59
- json = Util.flashError(err.sqlMessage);
60
- } else {
61
- json = Util.flashError(err.toString());
62
- }
63
- zDebug(req, res, err);
64
- }
65
- res.json(json);
66
- });
67
-
68
- router.get('/update/:id', csrfProtection, async (req, res) => {
69
- const id = parseInt(req.params.id);
70
- const results = await connection.results({
71
- select:zRoute.selectData(MYMODEL),
72
- table: MYMODEL.table,
73
- where: {
74
- id: id,
75
- company_id: res.locals.companyId
76
- }
77
- });
78
- if (results.length == 0) {
79
- req.session.sessionFlash = Util.flashError(LANGUAGE['data_not_found']);
80
- return res.redirect('/' + MYMODEL.routeName);
81
- }
82
- let data = results[0];
83
- const zForms = await zRoute.formsFieldSync(req, res, MYMODEL, data);
84
- res.render(`layouts/${layout}`, {
85
- data: data,
86
- zForms: zForms,
87
- titleHeader: `${LANGUAGE['form_update']} : <strong><a href="/${MYMODEL.routeName}">${MYMODEL.title}</a></strong>`,
88
- csrfToken: req.csrfToken(),
89
- breadcrumb : zForm.breadcrumbUpdate(MYMODEL.routeName, id),
90
- renderBody: `${MYMODEL.routeName}/update.ejs`,
91
- renderEnd: `${MYMODEL.routeName}/updatejs.ejs`,
92
- });
93
- });
94
-
95
- router.post('/update/:id', csrfProtection, async (req, res) => {
96
- let json = Util.jsonSuccess(LANGUAGE['data_saved']);
97
- try {
98
- const id = parseInt(req.params.id);
99
- let data = zRoute.post(req,res, MYMODEL)[MYMODEL.routeName];
100
- const validator = zRoute.validator(data, MYMODEL);
101
- if(validator.status == 0) return res.json(validator.message);
102
- const result = await zRoute.updateSQL(req, res,MYMODEL.table, data, {id:id});
103
- json.data=result;
104
- } catch (err) {
105
- console.log(err)
106
- if(Object.hasOwn(err,"sqlMessage")){
107
- json = Util.flashError(err.sqlMessage);
108
- } else {
109
- json = Util.flashError(err.toString());
110
- }
111
- zDebug(req, res, err)
112
- }
113
- res.json(json);
114
- });
115
-
116
- router.delete('/delete', async (req, res) => {
117
- const id = req.body.id;
118
- let json = Util.jsonSuccess(LANGUAGE['data_delete']);
119
- try {
120
- await zRoute.deleteSQL(MYMODEL.table, id, res.locals.companyId);
121
- } catch (err) {
122
- json = Util.flashError(err.toString());
123
- zDebug(req, res, err)
124
- }
125
- res.json(json);
126
- });
127
-
128
- router.get('/view/:id', async (req, res) => {
129
- await zRoute.attributeData(res, MYMODEL);
130
- const id = req.params.id;
131
- let data = {};
132
- let zForms = {};
133
- try {
134
- const results = await connection.results({select:zRoute.selectData(MYMODEL),table:MYMODEL.table,where:{id:id}});
135
- if (results.length == 0) {
136
- req.session.sessionFlash = Util.flashError(LANGUAGE['data_not_found']);
137
- return res.redirect('/' + MYMODEL.routeName);
138
- }
139
- data = results[0];
140
- zForms = await zRoute.viewFormsSync(req, res, MYMODEL, data);
141
- } catch (err){
142
- zDebug(req, res, err);
143
- res.send("Err");
144
- }
145
- res.render(`layouts/${layout}`, {
146
- data: data,
147
- Util:Util,
148
- zForms:zForms,
149
- titleHeader: `View : <strong><a href="/${MYMODEL.routeName}">${MYMODEL.title}</a></strong>`,
150
- breadcrumb : zForm.breadcrumbView(MYMODEL.routeName),
151
- levels: zRole.myLevel(req, res, MYMODEL.table),
152
- renderBody: `${MYMODEL.routeName}/view.ejs`
153
- });
154
- });
155
-
156
- router.get('/excel-query', async (req,res) => {
157
- await zRoute.excelQuery(req, res, MYMODEL);
158
- });
159
-
160
- router.get('/excel', async (req, res) => {
161
- const fields = MYMODEL.keysExcel;
162
- const results = await connection.results({
163
- table: MYMODEL.table,
164
- where: {company_id: res.locals.companyId},
165
- orderBy: ['id', 'DESC']
166
- });
167
- await zRoute.excel(req, res, MYMODEL, fields, results);
168
- });
169
-
170
- router.get('/sample', async (req, res) => {
171
- let fields = [];
172
- for (let i = 0; i < MYMODEL.keysExcel.length; i++) {
173
- if (!Util.in_array(MYMODEL.keysExcel[i], Util.nots)) {
174
- fields.push(MYMODEL.keysExcel[i]);
175
- }
176
- }
177
- const results = await connection.results({
178
- table:MYMODEL.table,
179
- where:{
180
- company_id:res.locals.companyId
181
- },
182
- orderBy:["id","desc"], limit:10
183
- });
184
- let callback=(result,field)=> {
185
- return MYMODEL.widgets[field] && MYMODEL.widgets[field].name == 'datepicker' ? Util.dateSql(result[field]) : result[field];
186
- };
187
- await zRoute.excel(req, res, MYMODEL, fields, results, callback);
188
- });
189
-
190
- router.get('/import',csrfProtection, async (req, res) => {
191
- const room = res.locals.token;
192
- res.render(`layouts/${layout}`, {
193
- room: room,
194
- titleHeader: `Import / Upload data : <strong><a href="/${MYMODEL.routeName}">${MYMODEL.title}</a></strong>`,
195
- csrfToken: req.csrfToken(),
196
- breadcrumb : zForm.breadcrumbImport(MYMODEL.routeName),
197
- renderBody: `${MYMODEL.routeName}/import.ejs`,
198
- renderEnd: `${MYMODEL.routeName}/importjs.ejs`
199
- });
200
- });
201
-
202
-
203
- router.post('/import', async(req, res) => {
204
- res.json(await zRoute.import(req,res,MYMODEL));
205
- });
206
-
207
- router.get('/approval/:id', async (req, res) => {
208
- await zRoute.attributeData(res, MYMODEL);
209
- const id = req.params.id;
210
- let data = {};
211
- let data2 = {};
212
- let zForms = {};
213
- let zForms2 = {};
214
- let result = {};
215
- try {
216
- result = await connection.result({table: MYMODEL.table, where: {id: id}});
217
- if (!result.id) {
218
- req.session.sessionFlash = Util.flashError(LANGUAGE['data_not_found']);
219
- return res.redirect('/' + MYMODEL.routeName);
220
- }
221
- data=result;
222
- moduleLib.editor(req, res);
223
- zForms = await zRoute.viewFormsSync(req, res, MYMODEL, result);
224
- const contentScript = `$("#submit_draft").on("click", function () {$("#status").val(1);ajaxPost("/zzapproval",$("#form-group").serializeArray(), function (data) {
225
- if (data.status == 0) {
226
- toastr.error(data.message, data.title);
227
- } else {
228
- toastr.success(data.title, data.message);
229
- }
230
- });});
231
- $("#submit").on("click", function () {
232
- $('[name="zapprovals[status]"]').val(2);
233
- ajaxPost("/zzapproval",$("#form-group").serializeArray(), function (data) {
234
- if (data.status == 0) {
235
- toastr.error(data.message, data.title);
236
- } else {
237
- toastr.success(data.title, data.message);
238
- location.href = "";
239
- }
240
- });
241
- });
242
- `;
243
- await moduleLib.addScript(req, res, contentScript);
244
- const MODEL_APROVALS = require("./../models/zapprovals");
245
- const rows = await connection.results({
246
- table: "zapprovals",
247
- where: {table: MYMODEL.table, company_id: res.locals.companyId, id_data: id}
248
- });
249
- if (rows.length) {
250
- data2 = rows[0];
251
- } else {
252
- let obj = {};
253
- obj.token = Util.uuid();
254
- obj.status = 1;
255
- obj.table = MYMODEL.table;
256
- obj.company_id = res.locals.companyId;
257
- obj.id_data = id;
258
- obj.created_by = res.locals.userId;
259
- obj.updated_by = res.locals.userId;
260
- obj.created_at = Util.now();
261
- let zfields = await connection.result({table: "zfields", where: {table: MYMODEL.table}});
262
- let jsonApproval = zfields.approval_json || {}
263
- obj.template = jsonApproval.content || {}
264
- let title = jsonApproval.title;
265
- if (title) {
266
- let titleData = title;
267
- for (var key in data) {
268
- titleData = Util.replaceAll(titleData, "{{" + key + "}}", data[key])
269
- }
270
- obj.title = titleData
271
- }
272
- if (jsonApproval.hasOwnProperty("approvers")) {
273
- obj.approvers = JSON.stringify(jsonApproval.approvers);
274
- obj.knowings = JSON.stringify(jsonApproval.knowings);
275
- obj.type = 1;
276
- }
277
- //insert default data
278
- await connection.insert({table: "zapprovals", data: obj});
279
- data2 = obj;
280
- }
281
- zForms2 = await zRoute.formsFieldSync(req, res, MODEL_APROVALS, data2);
282
- } catch (err) {
283
- zDebug(req, res, err);
284
- res.send("Err");
285
- }
286
- res.render(`layouts/${layout}`, {
287
- zForms: zForms,
288
- zForms2: zForms2,
289
- data: result,
290
- result:result,
291
- data2: data2,
292
- Util: Util,
293
- levels: zRole.myLevel(req, res, MYMODEL.table),
294
- breadcrumb : zForm.breadcrumbApproval(MYMODEL.routeName, id),
295
- renderBody: `${MYMODEL.routeName}/approval.ejs`
296
- });
297
- });
298
-
299
- router.get('/preview/:token', async (req, res) => {
300
- let token = req.params.token;
301
- let data = {}, data2 = {};
302
- let template = "";
303
- let footers = "";
304
- try {
305
- let zfields = await connection.result({table: "zfields", where: {table: MYMODEL.table}});
306
- let jsonApproval = zfields.approval_json || {}
307
- let mytemplate = jsonApproval.content || {}
308
- let results = await connection.results({table: "zapprovals", where: {token: token}});
309
- if (results.length == 0) {
310
- req.session.sessionFlash = Util.flashError(LANGUAGE['data_not_found']);
311
- return res.redirect('/' + MYMODEL.routeName);
312
- }
313
- if(results[0].status == 1){
314
- await connection.update({
315
- table : "zapprovals",
316
- data: {
317
- template : mytemplate
318
- },
319
- where : {
320
- token:token
321
- }
322
- })
323
- results = await connection.results({table: "zapprovals", where: {token: token}});
324
- }
325
- let MYMODEL2 = require("./../models/zapprovals")
326
- data = await zRoute.viewTable(req, res, MYMODEL2, results[0], true);
327
- let row = await connection.result({table: results[0].table, where: {id: results[0].id_data}});
328
- data2 = await zRoute.viewTable(req, res, MYMODEL, row, true);
329
- template = data.template;
330
- let title = data.title || ""
331
- for (var key in data2) {
332
- template = Util.replaceAll(template, "{{" + key + "}}", data2[key])
333
- title = Util.replaceAll(title, "{{" + key + "}}", data2[key])
334
- }
335
- data.title = title;
336
- var details = await connection.results({table: "zapprovals_details", where: {title_id: results[0].id}});
337
- if (details.length) {
338
- footers = await zRoute.approversFooter(details);
339
- }
340
- } catch (err) {
341
- zDebug(req, res, err);
342
- res.send("Err");
343
- }
344
- res.render(`layouts/blank`, {
345
- template: template,
346
- data: data,
347
- footers: footers,
348
- Util: Util,
349
- details: details,
350
- renderBody: 'index/zapprovals_preview.ejs'
351
- });
352
- });
353
-
354
- router.post('/grid', access, zRoute.postGrid);
355
- router.post('/reload', access, zRoute.postGridReload);
356
- router.post('/chains', access, zRoute.chains);
357
- router.post('/get_attributes',access, zRoute.getAttributes);
358
-
359
- module.exports = router;
1
+ const router = require('express').Router();
2
+ const {csrf} = require('zet-lib');
3
+ const csrfProtection = csrf({cookie: true});
4
+ const {Util, access, connection, moduleLib, zDebug, zRoute, zRole, zDataTable, zForm} = require('zet-lib');
5
+ const MYMODEL = require('./../models/[[[TABLE_NAME]]]');
6
+
7
+ router.get('/', async (req, res) => {
8
+ await zRoute.attributeData(res, MYMODEL);
9
+ let dataTable = new zDataTable(res.locals);
10
+ dataTable.filterMODEL = await zRoute.dataTableFilterSync(req,res, MYMODEL, res.locals.gridFilter);
11
+ const levels = zRole.myLevel(req, res, MYMODEL.table);
12
+ dataTable.levels = levels;
13
+ res.render(`layouts/${layout}`, {
14
+ dataTable: dataTable,
15
+ levels : levels,
16
+ titleHeader: `<strong class="text text-info">${MYMODEL.title}</strong>`,
17
+ breadcrumb : zForm.breadcrumbIndex(),
18
+ myModal : zForm.modal({
19
+ attributeData: res.locals.attributeData,
20
+ visibles: res.locals.visibles,
21
+ invisibles: res.locals.invisibles,
22
+ routeName: MYMODEL.table,
23
+ }, LANGUAGE),
24
+ renderHead: `${MYMODEL.routeName}/indexcss.ejs`,
25
+ renderBody: `${MYMODEL.routeName}/index.ejs`,
26
+ renderEnd: `${MYMODEL.routeName}/indexjs.ejs`
27
+ });
28
+ });
29
+
30
+ router.post('/list', async (req, res) => {
31
+ const data = await zRoute.listData(req, res, MYMODEL, zRole);
32
+ res.json(data);
33
+ });
34
+
35
+ router.get('/create', csrfProtection, async(req, res) => {
36
+ let data = MYMODEL.datas;
37
+ const zForms = await zRoute.formsFieldSync(req, res, MYMODEL, data);
38
+ res.render(`layouts/${layout}`, {
39
+ data: data,
40
+ zForms: zForms,
41
+ titleHeader: `${LANGUAGE['form_create']} : <strong><a href="/${MYMODEL.routeName}">${MYMODEL.title}</a></strong>`,
42
+ csrfToken: req.csrfToken(),
43
+ breadcrumb : zForm.breadcrumbCreate(MYMODEL.routeName),
44
+ renderBody: `${MYMODEL.routeName}/create.ejs`,
45
+ renderEnd: `${MYMODEL.routeName}/createjs.ejs`
46
+ });
47
+ });
48
+
49
+ router.post('/create', csrfProtection, async (req, res) => {
50
+ let json = Util.jsonSuccess(LANGUAGE['data_saved']);
51
+ try {
52
+ let data = zRoute.post(req,res, MYMODEL)[MYMODEL.routeName];
53
+ const validator = zRoute.validator(data, MYMODEL);
54
+ if(validator.status == 0) return res.json(validator.message);
55
+ const result = await zRoute.insertSQL(req, res, MYMODEL.table, data);
56
+ json.data=result;
57
+ } catch (err) {
58
+ if(Object.hasOwn(err,"sqlMessage")){
59
+ json = Util.flashError(err.sqlMessage);
60
+ } else {
61
+ json = Util.flashError(err.toString());
62
+ }
63
+ zDebug(req, res, err);
64
+ }
65
+ res.json(json);
66
+ });
67
+
68
+ router.get('/update/:id', csrfProtection, async (req, res) => {
69
+ const id = parseInt(req.params.id);
70
+ const results = await connection.results({
71
+ select:zRoute.selectData(MYMODEL),
72
+ table: MYMODEL.table,
73
+ where: {
74
+ id: id,
75
+ company_id: res.locals.companyId
76
+ }
77
+ });
78
+ if (results.length == 0) {
79
+ req.session.sessionFlash = Util.flashError(LANGUAGE['data_not_found']);
80
+ return res.redirect('/' + MYMODEL.routeName);
81
+ }
82
+ let data = results[0];
83
+ const zForms = await zRoute.formsFieldSync(req, res, MYMODEL, data);
84
+ res.render(`layouts/${layout}`, {
85
+ data: data,
86
+ zForms: zForms,
87
+ titleHeader: `${LANGUAGE['form_update']} : <strong><a href="/${MYMODEL.routeName}">${MYMODEL.title}</a></strong>`,
88
+ csrfToken: req.csrfToken(),
89
+ breadcrumb : zForm.breadcrumbUpdate(MYMODEL.routeName, id),
90
+ renderBody: `${MYMODEL.routeName}/update.ejs`,
91
+ renderEnd: `${MYMODEL.routeName}/updatejs.ejs`,
92
+ });
93
+ });
94
+
95
+ router.post('/update/:id', csrfProtection, async (req, res) => {
96
+ let json = Util.jsonSuccess(LANGUAGE['data_saved']);
97
+ try {
98
+ const id = parseInt(req.params.id);
99
+ let data = zRoute.post(req,res, MYMODEL)[MYMODEL.routeName];
100
+ const validator = zRoute.validator(data, MYMODEL);
101
+ if(validator.status == 0) return res.json(validator.message);
102
+ const result = await zRoute.updateSQL(req, res,MYMODEL.table, data, {id:id});
103
+ json.data=result;
104
+ } catch (err) {
105
+ console.log(err)
106
+ if(Object.hasOwn(err,"sqlMessage")){
107
+ json = Util.flashError(err.sqlMessage);
108
+ } else {
109
+ json = Util.flashError(err.toString());
110
+ }
111
+ zDebug(req, res, err)
112
+ }
113
+ res.json(json);
114
+ });
115
+
116
+ router.delete('/delete', async (req, res) => {
117
+ const id = req.body.id;
118
+ let json = Util.jsonSuccess(LANGUAGE['data_delete']);
119
+ try {
120
+ await zRoute.deleteSQL(MYMODEL.table, id, res.locals.companyId);
121
+ } catch (err) {
122
+ json = Util.flashError(err.toString());
123
+ zDebug(req, res, err)
124
+ }
125
+ res.json(json);
126
+ });
127
+
128
+ router.get('/view/:id', async (req, res) => {
129
+ await zRoute.attributeData(res, MYMODEL);
130
+ const id = req.params.id;
131
+ let data = {};
132
+ let zForms = {};
133
+ try {
134
+ const results = await connection.results({select:zRoute.selectData(MYMODEL),table:MYMODEL.table,where:{id:id}});
135
+ if (results.length == 0) {
136
+ req.session.sessionFlash = Util.flashError(LANGUAGE['data_not_found']);
137
+ return res.redirect('/' + MYMODEL.routeName);
138
+ }
139
+ data = results[0];
140
+ zForms = await zRoute.viewFormsSync(req, res, MYMODEL, data);
141
+ } catch (err){
142
+ zDebug(req, res, err);
143
+ res.send("Err");
144
+ }
145
+ res.render(`layouts/${layout}`, {
146
+ data: data,
147
+ Util:Util,
148
+ zForms:zForms,
149
+ titleHeader: `View : <strong><a href="/${MYMODEL.routeName}">${MYMODEL.title}</a></strong>`,
150
+ breadcrumb : zForm.breadcrumbView(MYMODEL.routeName),
151
+ levels: zRole.myLevel(req, res, MYMODEL.table),
152
+ renderBody: `${MYMODEL.routeName}/view.ejs`
153
+ });
154
+ });
155
+
156
+ router.get('/excel-query', async (req,res) => {
157
+ await zRoute.excelQuery(req, res, MYMODEL);
158
+ });
159
+
160
+ router.get('/excel', async (req, res) => {
161
+ const fields = MYMODEL.keysExcel;
162
+ const results = await connection.results({
163
+ table: MYMODEL.table,
164
+ where: {company_id: res.locals.companyId},
165
+ orderBy: ['id', 'DESC']
166
+ });
167
+ await zRoute.excel(req, res, MYMODEL, fields, results);
168
+ });
169
+
170
+ router.get('/sample', async (req, res) => {
171
+ let fields = [];
172
+ for (let i = 0; i < MYMODEL.keysExcel.length; i++) {
173
+ if (!Util.in_array(MYMODEL.keysExcel[i], Util.nots)) {
174
+ fields.push(MYMODEL.keysExcel[i]);
175
+ }
176
+ }
177
+ const results = await connection.results({
178
+ table:MYMODEL.table,
179
+ where:{
180
+ company_id:res.locals.companyId
181
+ },
182
+ orderBy:["id","desc"], limit:10
183
+ });
184
+ let callback=(result,field)=> {
185
+ return MYMODEL.widgets[field] && MYMODEL.widgets[field].name == 'datepicker' ? Util.dateSql(result[field]) : result[field];
186
+ };
187
+ await zRoute.excel(req, res, MYMODEL, fields, results, callback);
188
+ });
189
+
190
+ router.get('/import',csrfProtection, async (req, res) => {
191
+ const room = res.locals.token;
192
+ res.render(`layouts/${layout}`, {
193
+ room: room,
194
+ titleHeader: `Import / Upload data : <strong><a href="/${MYMODEL.routeName}">${MYMODEL.title}</a></strong>`,
195
+ csrfToken: req.csrfToken(),
196
+ breadcrumb : zForm.breadcrumbImport(MYMODEL.routeName),
197
+ renderBody: `${MYMODEL.routeName}/import.ejs`,
198
+ renderEnd: `${MYMODEL.routeName}/importjs.ejs`
199
+ });
200
+ });
201
+
202
+
203
+ router.post('/import', async(req, res) => {
204
+ res.json(await zRoute.import(req,res,MYMODEL));
205
+ });
206
+
207
+ router.get('/approval/:id', async (req, res) => {
208
+ await zRoute.attributeData(res, MYMODEL);
209
+ const id = req.params.id;
210
+ let data = {};
211
+ let data2 = {};
212
+ let zForms = {};
213
+ let zForms2 = {};
214
+ let result = {};
215
+ try {
216
+ result = await connection.result({table: MYMODEL.table, where: {id: id}});
217
+ if (!result.id) {
218
+ req.session.sessionFlash = Util.flashError(LANGUAGE['data_not_found']);
219
+ return res.redirect('/' + MYMODEL.routeName);
220
+ }
221
+ data=result;
222
+ moduleLib.editor(req, res);
223
+ zForms = await zRoute.viewFormsSync(req, res, MYMODEL, result);
224
+ const contentScript = `$("#submit_draft").on("click", function () {$("#status").val(1);ajaxPost("/zzapproval",$("#form-group").serializeArray(), function (data) {
225
+ if (data.status == 0) {
226
+ toastr.error(data.message, data.title);
227
+ } else {
228
+ toastr.success(data.title, data.message);
229
+ }
230
+ });});
231
+ $("#submit").on("click", function () {
232
+ $('[name="zapprovals[status]"]').val(2);
233
+ ajaxPost("/zzapproval",$("#form-group").serializeArray(), function (data) {
234
+ if (data.status == 0) {
235
+ toastr.error(data.message, data.title);
236
+ } else {
237
+ toastr.success(data.title, data.message);
238
+ location.href = "";
239
+ }
240
+ });
241
+ });
242
+ `;
243
+ await moduleLib.addScript(req, res, contentScript);
244
+ const MODEL_APROVALS = require("./../models/zapprovals");
245
+ const rows = await connection.results({
246
+ table: "zapprovals",
247
+ where: {table: MYMODEL.table, company_id: res.locals.companyId, id_data: id}
248
+ });
249
+ if (rows.length) {
250
+ data2 = rows[0];
251
+ } else {
252
+ let obj = {};
253
+ obj.token = Util.uuid();
254
+ obj.status = 1;
255
+ obj.table = MYMODEL.table;
256
+ obj.company_id = res.locals.companyId;
257
+ obj.id_data = id;
258
+ obj.created_by = res.locals.userId;
259
+ obj.updated_by = res.locals.userId;
260
+ obj.created_at = Util.now();
261
+ let zfields = await connection.result({table: "zfields", where: {table: MYMODEL.table}});
262
+ let jsonApproval = zfields.approval_json || {}
263
+ obj.template = jsonApproval.content || {}
264
+ let title = jsonApproval.title;
265
+ if (title) {
266
+ let titleData = title;
267
+ for (var key in data) {
268
+ titleData = Util.replaceAll(titleData, "{{" + key + "}}", data[key])
269
+ }
270
+ obj.title = titleData
271
+ }
272
+ if (jsonApproval.hasOwnProperty("approvers")) {
273
+ obj.approvers = JSON.stringify(jsonApproval.approvers);
274
+ obj.knowings = JSON.stringify(jsonApproval.knowings);
275
+ obj.type = 1;
276
+ }
277
+ //insert default data
278
+ await connection.insert({table: "zapprovals", data: obj});
279
+ data2 = obj;
280
+ }
281
+ zForms2 = await zRoute.formsFieldSync(req, res, MODEL_APROVALS, data2);
282
+ } catch (err) {
283
+ zDebug(req, res, err);
284
+ res.send("Err");
285
+ }
286
+ res.render(`layouts/${layout}`, {
287
+ zForms: zForms,
288
+ zForms2: zForms2,
289
+ data: result,
290
+ result:result,
291
+ data2: data2,
292
+ Util: Util,
293
+ levels: zRole.myLevel(req, res, MYMODEL.table),
294
+ breadcrumb : zForm.breadcrumbApproval(MYMODEL.routeName, id),
295
+ renderBody: `${MYMODEL.routeName}/approval.ejs`
296
+ });
297
+ });
298
+
299
+ router.get('/preview/:token', async (req, res) => {
300
+ let token = req.params.token;
301
+ let data = {}, data2 = {};
302
+ let template = "";
303
+ let footers = "";
304
+ try {
305
+ let zfields = await connection.result({table: "zfields", where: {table: MYMODEL.table}});
306
+ let jsonApproval = zfields.approval_json || {}
307
+ let mytemplate = jsonApproval.content || {}
308
+ let results = await connection.results({table: "zapprovals", where: {token: token}});
309
+ if (results.length == 0) {
310
+ req.session.sessionFlash = Util.flashError(LANGUAGE['data_not_found']);
311
+ return res.redirect('/' + MYMODEL.routeName);
312
+ }
313
+ if(results[0].status == 1){
314
+ await connection.update({
315
+ table : "zapprovals",
316
+ data: {
317
+ template : mytemplate
318
+ },
319
+ where : {
320
+ token:token
321
+ }
322
+ })
323
+ results = await connection.results({table: "zapprovals", where: {token: token}});
324
+ }
325
+ let MYMODEL2 = require("./../models/zapprovals")
326
+ data = await zRoute.viewTable(req, res, MYMODEL2, results[0], true);
327
+ let row = await connection.result({table: results[0].table, where: {id: results[0].id_data}});
328
+ data2 = await zRoute.viewTable(req, res, MYMODEL, row, true);
329
+ template = data.template;
330
+ let title = data.title || ""
331
+ for (var key in data2) {
332
+ template = Util.replaceAll(template, "{{" + key + "}}", data2[key])
333
+ title = Util.replaceAll(title, "{{" + key + "}}", data2[key])
334
+ }
335
+ data.title = title;
336
+ var details = await connection.results({table: "zapprovals_details", where: {title_id: results[0].id}});
337
+ if (details.length) {
338
+ footers = await zRoute.approversFooter(details);
339
+ }
340
+ } catch (err) {
341
+ zDebug(req, res, err);
342
+ res.send("Err");
343
+ }
344
+ res.render(`layouts/blank`, {
345
+ template: template,
346
+ data: data,
347
+ footers: footers,
348
+ Util: Util,
349
+ details: details,
350
+ renderBody: 'index/zapprovals_preview.ejs'
351
+ });
352
+ });
353
+
354
+ router.post('/grid', access, zRoute.postGrid);
355
+ router.post('/reload', access, zRoute.postGridReload);
356
+ router.post('/chains', access, zRoute.chains);
357
+ router.post('/get_attributes',access, zRoute.getAttributes);
358
+
359
+ module.exports = router;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zet-lib",
3
- "version": "3.3.0",
3
+ "version": "3.3.2",
4
4
  "description": "zet is a library that part of zet generator.",
5
5
  "engines": {
6
6
  "node": ">=18"