beech-api 3.9.0-beta.9-rc → 3.9.75

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.
Files changed (59) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +1715 -1649
  3. package/index.js +2 -2
  4. package/package.json +92 -84
  5. package/packages/cli/beech +9 -10
  6. package/packages/cli/bin/beech-app.js +390 -390
  7. package/packages/cli/bin/beech-service.js +263 -170
  8. package/packages/cli/core/auth/Credentials.js +174 -174
  9. package/packages/cli/core/auth/Passport.js +664 -664
  10. package/packages/cli/core/auth/_Request.js +12 -12
  11. package/packages/cli/core/configure/_gitignore +16 -15
  12. package/packages/cli/core/configure/_sequelizerc +9 -9
  13. package/packages/cli/core/configure/app.config-basic.js +55 -55
  14. package/packages/cli/core/configure/app.config-sequelize.js +88 -88
  15. package/packages/cli/core/configure/beech.config.js +9 -9
  16. package/packages/cli/core/configure/global.config-basic.js +8 -8
  17. package/packages/cli/core/configure/global.config-sequelize.js +8 -8
  18. package/packages/cli/core/configure/jest.config.js +6 -6
  19. package/packages/cli/core/configure/jsconfig.json +8 -7
  20. package/packages/cli/core/configure/passport.config.js +97 -97
  21. package/packages/cli/core/databases/mysql.js +94 -95
  22. package/packages/cli/core/databases/sequelize.js +187 -188
  23. package/packages/cli/core/databases/test.js +250 -255
  24. package/packages/cli/core/file-walk/file-walk.js +35 -35
  25. package/packages/cli/core/generator/_endpoints +15 -15
  26. package/packages/cli/core/generator/_endpoints_basic +42 -42
  27. package/packages/cli/core/generator/_help +26 -18
  28. package/packages/cli/core/generator/_help_create +10 -10
  29. package/packages/cli/core/generator/_help_service +10 -10
  30. package/packages/cli/core/generator/_helpers +9 -9
  31. package/packages/cli/core/generator/_helpers_basic +22 -22
  32. package/packages/cli/core/generator/_models +6 -6
  33. package/packages/cli/core/generator/_models_basic +13 -13
  34. package/packages/cli/core/generator/_package +23 -19
  35. package/packages/cli/core/generator/_scheduler +32 -32
  36. package/packages/cli/core/generator/_spec +29 -29
  37. package/packages/cli/core/generator/index.js +1081 -992
  38. package/packages/cli/core/helpers/2fa.js +106 -106
  39. package/packages/cli/core/helpers/math.js +115 -115
  40. package/packages/cli/core/helpers/poolEntity.js +103 -103
  41. package/packages/cli/core/index.js +264 -266
  42. package/packages/cli/core/middleware/express/duplicateRequest.js +16 -16
  43. package/packages/cli/core/middleware/express/jwtCheckAllow.js +85 -85
  44. package/packages/cli/core/middleware/express/rateLimit.js +29 -29
  45. package/packages/cli/core/middleware/express/slowDown.js +2 -2
  46. package/packages/cli/core/middleware/index.js +6 -6
  47. package/packages/cli/core/middleware/origin/guard/advance.js +75 -75
  48. package/packages/cli/core/middleware/origin/whitelist/cors.js +94 -94
  49. package/packages/cli/core/services/http.express.js +481 -481
  50. package/packages/cli/core/test/check-node.js +21 -21
  51. package/packages/cli/core/test/utils.js +7 -7
  52. package/packages/cli/entry +10 -0
  53. package/packages/lib/index.js +6 -6
  54. package/packages/lib/src/endpoint.js +947 -885
  55. package/packages/lib/src/guard.js +60 -60
  56. package/packages/lib/src/salt.js +3 -3
  57. package/packages/lib/src/schema.js +96 -96
  58. package/packages/lib/src/specificExpress.js +7 -7
  59. package/packages/lib/src/user.js +271 -271
@@ -1,885 +1,947 @@
1
- const walk = require("walk");
2
- const fs = require("fs");
3
- const moment = require("moment");
4
- const { checkRoleMiddlewareWithDefaultProject } = require("../../cli/core/middleware/express/jwtCheckAllow");
5
- const { Limiter, Duplicater } = require("../../cli/core/middleware/index");
6
-
7
- function walkModel(cb) {
8
- try {
9
- let jsfiles = [];
10
- let walker = walk.walk(appRoot + "/src/models", { followLinks: false });
11
- // Walk file on push
12
- walker.on("file", (root, stat, next) => {
13
- jsfiles.push(root + "/" + stat.name);
14
- next();
15
- });
16
- // Walking
17
- walker.on("end", () => {
18
- let preProject = [];
19
- if(jsfiles.length) {
20
- jsfiles.forEach((file, k) => {
21
- let schemaPath = "@/models" + file.split("models")[1].replace(/\\/g, "/");
22
- let endpoint = file
23
- .split("models")[1]
24
- .replace(/\\/g, "/")
25
- .replace(/_/g, "-")
26
- .toLowerCase()
27
- .slice(0, -3);
28
- preProject.push([schemaPath, endpoint]);
29
- if (jsfiles.length == k + 1) {
30
- cb(null, preProject);
31
- }
32
- });
33
- } else {
34
- // model not found.
35
- cb(null, preProject);
36
- }
37
- });
38
- } catch (error) {
39
- cb(error, null);
40
- }
41
- }
42
-
43
- function filterProject(Projects, reqUrl, params = "", method = "", req, res, cb) {
44
- try {
45
- let pj = Projects.shift();
46
- let leaveParamsAlone = params.slice(0);
47
- let paramsItem = leaveParamsAlone.replace(/^\/|\/$/g, "").split('/');
48
- let leaveReqUrlAlone = reqUrl.slice(0);
49
- let newParams = params.split("/").filter((e) => (e !== 'undefined')).join("/");
50
- let urlWithoutParams = leaveReqUrlAlone.replace(newParams, '').split("?")[0].replace(/\/$/, "");
51
- // sub-way for PATCH method
52
- urlWithoutParams = (method == "PATCH" || method == "DELETE") ? urlWithoutParams.substring(0, urlWithoutParams.lastIndexOf('/')) : urlWithoutParams;
53
- // check match project by url
54
- if(pj[1] == urlWithoutParams) {
55
- return checkOffset(Object.values(require(pj[0]))[0], req, res, (thenChecked) => {
56
- if(thenChecked) {
57
- return cb(null, Object.values(require(pj[0]))[0], paramsItem);
58
- }
59
- });
60
- }
61
- // Finally recursive filterProject function
62
- if (Projects.length > 0) {
63
- filterProject(Projects, reqUrl, params, method, req, res, cb);
64
- } else {
65
- // not match
66
- return notfound(res);
67
- }
68
- } catch (error) {
69
- console.log(error);
70
- cb(error, null, []);
71
- }
72
- }
73
-
74
- function filterProjectForByPass(Projects, reqUrl, req, res, cb) {
75
- try {
76
- let pj = Projects.shift();
77
- let regx = new RegExp(pj[1] + "?[^\/].?[a-zA-Z0-9].*$", 'g');
78
- let regxMatch = reqUrl.match(regx);
79
- let regxMatchLength = (regxMatch) ? regxMatch.length: 0;
80
- if (pj[1] == reqUrl) {
81
- return checkOffset(Object.values(require(pj[0]))[0], req, res, (thenChecked) => {
82
- if(thenChecked) {
83
- return cb(null, Object.values(require(pj[0]))[0]);
84
- }
85
- });
86
- } else if(regxMatchLength) {
87
- return checkOffset(Object.values(require(pj[0]))[0], req, res, (thenChecked) => {
88
- if(thenChecked) {
89
- return cb(null, Object.values(require(pj[0]))[0]);
90
- }
91
- });
92
- }
93
- // Finally recursive filterProjectForByPass function
94
- if (Projects.length > 0) {
95
- filterProjectForByPass(Projects, reqUrl, req, res, cb);
96
- } else {
97
- // not match
98
- return notfound(res);
99
- }
100
- } catch (error) {
101
- cb(error, null);
102
- }
103
- }
104
-
105
- function checkOffset(Project, req, res, cb) {
106
- if (!Project) {
107
- return notfound(res);
108
- }
109
- // check offset is Ingeter as well as Zero number
110
- let offset = req.params.offset || undefined;
111
- let limit = req.params.limit || undefined;
112
- let offsetRegxMatch = (offset !== undefined) ? offset.match(/^[0-9]+$/) : undefined;
113
- let limitRegxMatch = (limit !== undefined) ? limit.match(/^[0-9]+$/) : undefined;
114
- if (offsetRegxMatch && limitRegxMatch) {
115
- cb(true);
116
- } else {
117
- if(offset === undefined) {
118
- cb(true);
119
- } else {
120
- return notfound(res);
121
- }
122
- }
123
- }
124
-
125
- function notfound(res) {
126
- return res.status(404).json({
127
- code: 404,
128
- status: "404_NOT_FOUND",
129
- message: "The Endpoint not found!.",
130
- });
131
- }
132
-
133
- function errMessage(err, res) {
134
- let errTurnOffDbDefine = JSON.stringify(err.toString()).match(/'define'/);
135
- let errTurnOffDbOption = JSON.stringify(err.toString()).match(/'options'/);
136
- if(errTurnOffDbDefine || errTurnOffDbOption) {
137
- // @return
138
- return res.status(500).json({
139
- code: 500,
140
- status: "ERR_INTERNAL_SERVER",
141
- message: "Database connection name is CLOSED.",
142
- });
143
- } else {
144
- // @return
145
- return res.status(500).json({
146
- code: 500,
147
- status: "ERR_INTERNAL_SERVER",
148
- message: err.toString(),
149
- });
150
- }
151
- }
152
-
153
- function whereCond(objectCond, cb) {
154
- if(typeof objectCond === 'object' && Object.keys(objectCond).length) {
155
- let keys = Object.keys(objectCond);
156
- let where = {};
157
- let orderBy = {};
158
- let groupBy = {};
159
- // Start Recursive for Check Where condition OR GroupBy OR OrderBy
160
- recursiveWhereCond(keys, objectCond, where, groupBy, orderBy, (err, cbWhere, cbGroupBy, cbOrderBy) => {
161
- cb(err, cbWhere, cbGroupBy, cbOrderBy);
162
- });
163
- } else {
164
- cb(null, {}, { group: { groupby: [] } }, { order: { orderby: [] } });
165
- }
166
- }
167
-
168
- function getValueType(value) {
169
- if (typeof value === 'string') {
170
- // Check for ISO-like date string with optional time part
171
- const isoDateRegex = /^\d{4}-\d{2}-\d{2}(?:[ T]\d{2}:\d{2}:\d{2})?$/;
172
- const date = new Date(value);
173
- if (!isNaN(date.getTime()) && isoDateRegex.test(value)) {
174
- return 'Date';
175
- }
176
- return 'String';
177
- }
178
- if (typeof value === 'number' && !isNaN(value)) return 'Number';
179
- if (typeof value === 'boolean') return 'Boolean';
180
- if (value instanceof Date && !isNaN(value)) return 'Date';
181
- if (value === null) return 'Null';
182
- if (typeof value === 'undefined') return 'Undefined';
183
- if (Array.isArray(value)) return 'Array';
184
- if (typeof value === 'function') return 'Function';
185
- if (typeof value === 'symbol') return 'Symbol';
186
- if (typeof value === 'bigint') return 'BigInt';
187
- if (typeof value === 'object') return 'Object';
188
- return 'Unknown';
189
- }
190
-
191
- async function recursiveWhereCond(keys, objectCond, where, groupBy, orderBy, cb) {
192
- if(keys.length > 0) {
193
- let k = keys.shift();
194
- // Check param key for Where condition OR GroupBy OR OrderBy
195
- if(k == 'orderby') {
196
- let oderByValueItemFromQueryString = objectCond[k].replace(/\w+/g, '"$&"').replace(/\[\s*\]/g, '[]');
197
- // Check Array syntax from Query String
198
- isValidArrayFormat(oderByValueItemFromQueryString, (err, isArray, strArr) => {
199
- if(err) {
200
- cb(["SyntaxError: Unexpected end of Array or String input", ` (${k})`], null, null, null);
201
- } else {
202
- let order = JSON.parse(strArr);
203
- orderBy[k] = order.length ? order : null;
204
- // Recursive re-call function
205
- recursiveWhereCond(keys, objectCond, where, groupBy, orderBy, cb);
206
- }
207
- });
208
- } else if(k == 'groupby') {
209
- let groupByValueItemFromQueryString = objectCond[k].replace(/\w+/g, '"$&"').replace(/\[\s*\]/g, '[]');
210
- // Check Array syntax from Query String
211
- isValidArrayFormat(groupByValueItemFromQueryString, (err, isArray, strArr) => {
212
- if(err) {
213
- cb(["SyntaxError: Unexpected end of Array or String input", ` (${k})`], null, null, null);
214
- } else {
215
- let group = JSON.parse(strArr);
216
- groupBy[k] = group.length ? group : null;
217
- // Recursive re-call function
218
- recursiveWhereCond(keys, objectCond, where, groupBy, orderBy, cb);
219
- }
220
- });
221
- } else {
222
- let fieldValueItemFromQueryString = objectCond[k].replace(/([^[\],]+)/g, '"$1"');
223
- let valueItem = objectCond[k].replace(/[\[\]']+/g, '').split(','); //replace(/[\[\]']+/g, '')
224
- let cleanValueItem = valueItem.map((e) => e.trim()).filter((e) => e !== '');
225
- // Check Array syntax from Query String
226
- isValidArrayFormat(fieldValueItemFromQueryString, async (err) => {
227
- if(err || cleanValueItem.length < 1) {
228
- cb(["SyntaxError: Unexpected end of Array or String input", ` (${k})`], null, null, null);
229
- } else {
230
- try {
231
- where[k] = await (cleanValueItem[1])
232
- ? {
233
- [Op[cleanValueItem[0]]]: (cleanValueItem[1] == 'null') ? null :
234
- (cleanValueItem[1] == 'true') ? true :
235
- (cleanValueItem[1] == 'false') ? false :
236
- (cleanValueItem[0] == 'between' || cleanValueItem[0] == 'notBetween') ? [
237
- (getValueType(cleanValueItem[1]) == 'Date')
238
- ? cleanValueItem[1].split(' ').map(e => e.trim()).length > 1
239
- ? moment(new Date(cleanValueItem[1]).toISOString())
240
- : moment(new Date(cleanValueItem[1] + ' 00:00:00').toISOString())
241
- : cleanValueItem[1],
242
- (getValueType(cleanValueItem[2]) == 'Date')
243
- ? cleanValueItem[2].split(' ').map(e => e.trim()).length > 1
244
- ? moment(new Date(cleanValueItem[2]).toISOString())
245
- : moment(new Date(cleanValueItem[2] + ' 23:59:59').toISOString())
246
- : cleanValueItem[2],
247
- ] :
248
- (cleanValueItem[0] == 'or' || cleanValueItem[0] == 'in' || cleanValueItem[0] == 'notIn') ? [...cleanValueItem.slice(1)] :
249
- (cleanValueItem[0] == 'like') ? [cleanValueItem[1],cleanValueItem[2],cleanValueItem[3]].join("") :
250
- (cleanValueItem[0] == 'notLike') ? [cleanValueItem[1],cleanValueItem[2],cleanValueItem[3]].join("") :
251
- (cleanValueItem[0] == 'startsWith') ? cleanValueItem[1] :
252
- (cleanValueItem[0] == 'endsWith') ? cleanValueItem[1] :
253
- (cleanValueItem[0] == 'substring') ? cleanValueItem[1] :
254
- [cleanValueItem[1]]
255
- }
256
- : cleanValueItem[0];
257
- // Recursive re-call function
258
- recursiveWhereCond(keys, objectCond, where, groupBy, orderBy, cb);
259
- } catch (error) {
260
- return cb([`Error with (${k})`, error], null, null, null);
261
- }
262
- }
263
- });
264
- }
265
- } else {
266
- // End of recursive function
267
- cb(null, where, groupBy, orderBy);
268
- }
269
- }
270
-
271
- function isValidArrayFormat(str, cb) {
272
- try {
273
- const parsed = JSON.parse(str);
274
- cb(null, Array.isArray(parsed), str);
275
- } catch (err) {
276
- cb(err, false, null);
277
- }
278
- }
279
-
280
- // function parseRawQuery(search) {
281
- // const rawParams = {};
282
- // if (search.startsWith('?')) {
283
- // const queryString = search.slice(1); // Remove '?'
284
- // const pairs = queryString.split('&');
285
-
286
- // for (const pair of pairs) {
287
- // const [key, value = ''] = pair.split('=');
288
- // if (key) {
289
- // rawParams[key] = value;
290
- // }
291
- // }
292
- // }
293
- // return rawParams;
294
- // }
295
-
296
- async function findAll(Project, where, offset, group, order, limitRow, cb) {
297
- try {
298
- await Project.findAll({
299
- where,
300
- group: (group.groupby) ? group.groupby : [],
301
- order: (order.orderby) ? [order.orderby] : [],
302
- offset: offset,
303
- limit: limitRow,
304
- }).then((results) => {
305
- cb(null, results)
306
- }).catch((error) => {
307
- cb(error, null);
308
- });
309
- } catch (error) {
310
- cb(error, null);
311
- }
312
- }
313
-
314
- async function retrieving(authEndpoint, Projects, req, res, next) {
315
- let params = req.params;
316
- let hash = "/" + req.params.hash;
317
- // allow official stetragy
318
- if(hash == authEndpoint && (params[0] == "/facebook" || params[0] == "/facebook/callback" || params[0] == "/google" || params[0] == "/google/callback")) {
319
- return next();
320
- }
321
- // declare variable for check request with params
322
- let leaveMeAlone = await Projects.slice(0);
323
- let mergeDuoVar = [req.params.limit, req.params.offset].map((e) => (e || 'undefined')).join("/");
324
- let reqUrl = req.originalUrl.replace(_publicPath_, '/');
325
- let checkConditionIsQueryOrId = Object.keys(req.query).length
326
- ? req.query
327
- : 'undefined';
328
- /**
329
- * Function whereCond with callback property
330
- *
331
- * @where Object|String
332
- *
333
- */
334
- whereCond(checkConditionIsQueryOrId, async (err, where, groupBy, orderBy) => {
335
- if(err) {
336
- res.status(400).json({
337
- code: 400,
338
- status: "BAD_REQUEST",
339
- err: String(err),
340
- });
341
- } else {
342
- /**
343
- * Filter Project with callback property
344
- *
345
- * @err String
346
- * @Project Require
347
- * @params Object [0=limit, 1=offset]
348
- *
349
- */
350
- await filterProject(leaveMeAlone, reqUrl, "/".concat(mergeDuoVar), "", req, res, async (err, Project, params) => {
351
- if (!err) {
352
- try {
353
- // declare default limit offset
354
- let offset = 0;
355
- let limitRow = await (Project.options.limitRows) ? Project.options.limitRows : 100;
356
- // check assign limit, offset ?
357
- if ((params[0] && params[0] != 'undefined') && ((params[1] && params[1] != 'undefined') || parseInt(params[1]) === 0)) {
358
- // Only case: /limit/offset
359
- limitRow = parseInt(params[0]);
360
- offset = parseInt(params[1]);
361
- }
362
- // findAll data
363
- await findAll(Project, where, offset, groupBy, orderBy, limitRow, (err, results) => {
364
- if(err) {
365
- res.status(500).json({
366
- code: 500,
367
- status: "READ_CATCH",
368
- err: String(err),
369
- });
370
- } else {
371
- // @ return findAll
372
- res.json({
373
- code: 200,
374
- status: "SUCCESS",
375
- results,
376
- length: results.length,
377
- limitRow,
378
- });
379
- }
380
- });
381
- } catch (error) {
382
- // @return
383
- return errMessage(error, res);
384
- }
385
- } else {
386
- // @return
387
- return errMessage(err, res);
388
- }
389
- });
390
- }
391
- });
392
- }
393
-
394
- // Duplicater cache for each config, avoid create new instance with same config
395
- const duplicaterCache = new Map();
396
- function getDuplicaterInstance(dupConfig = {}) {
397
- const key = JSON.stringify(dupConfig || {});
398
- if (!duplicaterCache.has(key)) {
399
- duplicaterCache.set(key, Duplicater(dupConfig || {}));
400
- }
401
- return duplicaterCache.get(key);
402
- }
403
-
404
- // Limiter cache for each config, avoid create new instance with same config
405
- const limiterCache = new Map();
406
- function getLimiterInstance(rateConfig = {}) {
407
- const key = JSON.stringify(rateConfig);
408
- if (!limiterCache.has(key)) {
409
- limiterCache.set(key, Limiter(rateConfig));
410
- }
411
- return limiterCache.get(key);
412
- }
413
-
414
- const byPassCheckRole = (Projects, method, passport_config) => {
415
- return async function (req, res, next) {
416
- try {
417
- if(passport_config[0] !== undefined) {
418
- if(req.params.hash == passport_config[0].replace(/^\/|\/$/g, "")) {
419
- return next();
420
- } else {
421
- if(passport_config[1].jwt_allow === true) {
422
- let leaveMeAlone = await Projects.slice(0);
423
- await filterProjectForByPass(leaveMeAlone, req.originalUrl.replace(_publicPath_, '/'), req, res, async (err, Project) => {
424
- if(!err) {
425
- // Set Duplicater and Limiter for each project with method
426
- const dupConfig = (Project.options?.defaultEndpoint?.[method]?.duplicate_request) ? Project.options.defaultEndpoint[method].duplicate_request : {};
427
- const duplicaterMiddleware = getDuplicaterInstance(dupConfig);
428
- return duplicaterMiddleware(req, res, (err) => {
429
- if (!err) {
430
- const rateConfig = (Project.options?.defaultEndpoint?.[method]?.rate_limit) ? Project.options.defaultEndpoint[method].rate_limit : {};
431
- const limiterMiddleware = getLimiterInstance(rateConfig);
432
- limiterMiddleware(req, res, (err) => {
433
- if (!err) {
434
- if(Project.options.defaultEndpoint === undefined || Project.options.defaultEndpoint === true) {
435
- // Project is not use options
436
- return Credentials(req, res, () => {
437
- return checkRoleMiddlewareWithDefaultProject(null)(req, res, next);
438
- });
439
- } else {
440
- // Method is set
441
- if(Project.options.defaultEndpoint[method]) {
442
- if(Project.options.defaultEndpoint[method]["allow"] === undefined || Project.options.defaultEndpoint[method]["allow"] === true) {
443
- if(Project.options.defaultEndpoint[method]["jwt"]?.allow === false) {
444
- // by project jwt_allow is false
445
- return checkRoleMiddlewareWithDefaultProject(null)(req, res, next);
446
- } else {
447
- //return Credentials(req, res, () => {
448
- // return checkRoleMiddlewareWithDefaultProject(Project.options.defaultEndpoint[method].jwt?.broken_role)(req, res, next);
449
- //});
450
- return Credentials(req, res, () => checkRoleMiddlewareWithDefaultProject(Project.options.defaultEndpoint[method].jwt?.broken_role || null)(req, res, next));
451
- }
452
- } else {
453
- // METHOD.allow is false || METHOD.allow is undefined
454
- return notfound(res);
455
- }
456
- } else if(Project.options.defaultEndpoint[method] === true || Project.options.defaultEndpoint[method] === undefined) {
457
- // Method is not set
458
- return Credentials(req, res, () => {
459
- return checkRoleMiddlewareWithDefaultProject(null)(req, res, next);
460
- });
461
- } else {
462
- // METHOD is false || METHOD is not undefined
463
- return notfound(res);
464
- }
465
- }
466
- } else {
467
- return res.status(429).json({
468
- code: 429,
469
- status: "TOO_MANY_REQUEST",
470
- message: "Too Many Requests.",
471
- });
472
- }
473
- });
474
- } else {
475
- return res.status(409).json({
476
- code: 409,
477
- status: "DUPLICATE_REQUEST",
478
- message: "Duplicate request detected.",
479
- });
480
- }
481
- });
482
- } else {
483
- return errMessage(err, res);
484
- }
485
- });
486
- } else {
487
- // global jwt_allow is false
488
- return next();
489
- }
490
- }
491
- } else {
492
- // passport config not found
493
- return next();
494
- }
495
- } catch (error) {
496
- return errMessage(error, res);
497
- }
498
- }
499
- }
500
-
501
- function Base() {
502
- return new Promise((resolve, reject) => {
503
- try {
504
- walkModel((err, Projects) => {
505
- if (err) {
506
- reject(err);
507
- } else {
508
- const checkPassport = new Promise((resolve) => {
509
- const passport_config_file = appRoot + "/passport.config.js";
510
- let passport_config_auth = undefined;
511
- if (fs.existsSync(passport_config_file)) {
512
- const _passport_config_ = require(passport_config_file);
513
- resolve([_passport_config_.auth_endpoint || "/authentication", _passport_config_]);
514
- } else {
515
- // passport config not found
516
- resolve([passport_config_auth, {}]);
517
- }
518
- });
519
- // passport conifg promise
520
- checkPassport.then((passport_config) => {
521
- if(Projects.length) {
522
- // GET method with /:limit/:offset
523
- endpoint.get("/:hash([a-zA-Z0-9-]+)*/:limit([0-9]+)/:offset([0-9]+)", (req, res, next) => byPassCheckRole(Projects, "GET", passport_config)(req, res, next), async (req, res, next) => {
524
- await retrieving(passport_config[0], Projects, req, res, next);
525
- });
526
-
527
- // GET method only hash/*
528
- endpoint.get("/:hash([a-zA-Z0-9-]+)*", (req, res, next) => byPassCheckRole(Projects, "GET", passport_config)(req, res, next), async (req, res, next) => {
529
- await retrieving(passport_config[0], Projects, req, res, next);
530
- });
531
-
532
- // POST method
533
- endpoint.post("/:hash*", (req, res, next) => byPassCheckRole(Projects, "POST", passport_config)(req, res, next), async (req, res, next) => {
534
- // Check auth request match send next
535
- if(passport_config[0] !== undefined) {
536
- if(req.params.hash == passport_config[0].replace(/^\/|\/$/g, "")) {
537
- return next();
538
- }
539
- }
540
- // When lost IF
541
- let leaveMeAlone = await Projects.slice(0);
542
- let reqUrl = req.originalUrl.replace(_publicPath_, '/');
543
- let bodyIsArray = Array.isArray(req.body);
544
- await filterProject(leaveMeAlone, reqUrl, "", "", req, res, async (err, Project) => {
545
- if (!err) {
546
- try {
547
- // Leave pool by project for check pool error
548
- let pool = Project.sequelize;
549
- // Transaction create
550
- Project.transaction(async t => {
551
- try {
552
- if (bodyIsArray) {
553
- // Store with bulk body
554
- const bulkCreated = await Project.bulkCreate(req.body, { transaction: t });
555
- await t.commit();
556
- // @return
557
- res.status(201).json({
558
- code: 201,
559
- status: "CREATE_SUCCESS",
560
- mode: {
561
- type: "BULK",
562
- affectedRows: bulkCreated.length,
563
- },
564
- createdId: [
565
- ...bulkCreated.map(item => (item.id) ? item.id : item[Project.primaryKeyAttributes[0]]),
566
- ],
567
- });
568
- } else {
569
- // Store with single body
570
- const singleCreated = await Project.create(req.body, { transaction: t });
571
- await t.commit();
572
- // @return
573
- res.status(201).json({
574
- code: 201,
575
- status: "CREATE_SUCCESS",
576
- mode: {
577
- type: "SINGLE",
578
- affectedRows: 1,
579
- },
580
- createdId: (singleCreated.id) ? singleCreated.id : singleCreated[Project.primaryKeyAttributes[0]],
581
- });
582
- }
583
- } catch (error) {
584
- await t.rollback();
585
- if(pool.options.logging) {
586
- // @return with all error
587
- res.status(501).json({
588
- code: 501,
589
- status: "CREATE_FAILED",
590
- mode: {
591
- debug: true,
592
- type: (bodyIsArray) ? "BULK" : "SINGLE",
593
- affectedRows: 0,
594
- },
595
- error: error,
596
- });
597
- } else {
598
- if(error.sql) {
599
- delete error.sql;
600
- if(error.errors) {
601
- delete error.errors;
602
- }
603
- if(error.parent) {
604
- delete error.parent;
605
- }
606
- if(error.original) {
607
- delete error.original.sql;
608
- if(error.original.parameters) {
609
- delete error.original.parameters;
610
- }
611
- }
612
- if(error.parameters) {
613
- delete error.parameters;
614
- }
615
- // @return with some error
616
- res.status(501).json({
617
- code: 501,
618
- status: "CREATE_FAILED",
619
- mode: {
620
- debug: false,
621
- type: (bodyIsArray) ? "BULK" : "SINGLE",
622
- affectedRows: 0,
623
- },
624
- error: error,
625
- });
626
- } else {
627
- // @return with some string error
628
- res.status(501).json({
629
- code: 501,
630
- status: "CREATE_FAILED",
631
- mode: {
632
- debug: false,
633
- type: (bodyIsArray) ? "BULK" : "SINGLE",
634
- affectedRows: 0,
635
- },
636
- error: String(error),
637
- });
638
- }
639
- }
640
- }
641
- });
642
- } catch (error) {
643
- // @return
644
- return errMessage(error, res);
645
- }
646
- } else {
647
- // @return
648
- return errMessage(err, res);
649
- }
650
- });
651
- });
652
-
653
- // PATCH method
654
- endpoint.patch("/:hash*/:id([a-zA-Z0-9-]+)", (req, res, next) => byPassCheckRole(Projects, "PATCH", passport_config)(req, res, next), async (req, res, next) => {
655
- let leaveMeAlone = await Projects.slice(0);
656
- let reqUrl = req.originalUrl.replace(_publicPath_, '/');
657
- await filterProject(leaveMeAlone, reqUrl, "", "PATCH", req, res, async (err, Project) => {
658
- if (!err) {
659
- try {
660
- // Check body is empty
661
- if(Object.keys(req.body).length === 0) {
662
- return res.status(400).json({
663
- code: 400,
664
- status: "BAD_REQUEST",
665
- message: "Bad request.",
666
- info: {
667
- status: "BAD_ENTIRY",
668
- message: "Unprocessable Entity.",
669
- },
670
- });
671
- }
672
- // Leave pool by project for check pool error
673
- let pool = Project.sequelize;
674
- // Transaction update|patch
675
- Project.transaction(async t => {
676
- try {
677
- // Assign update pk
678
- let updatePk = { [Project.primaryKeyAttributes[0]]: req.params.id };
679
- // Patch with body
680
- const updated = await Project.update(req.body, {
681
- where: updatePk,
682
- }, { transaction: t });
683
- if(updated[0]) {
684
- await t.commit();
685
- // @return
686
- res.status(200).json({
687
- code: 200,
688
- status: "UPDATE_SUCCESS",
689
- mode: {
690
- debug: true,
691
- },
692
- result: {
693
- updateId: req.params.id,
694
- affectedRows: updated[0],
695
- },
696
- });
697
- } else {
698
- await t.rollback();
699
- res.status(406).json({
700
- code: 406,
701
- status: "NOT_ACCEPTABLE",
702
- result: {
703
- affectedRows: updated[0],
704
- },
705
- });
706
- }
707
- } catch (error) {
708
- await t.rollback();
709
- if(pool.options.logging) {
710
- // @return with all error
711
- res.status(501).json({
712
- code: 501,
713
- status: "UPDATE_FAILED",
714
- mode: {
715
- debug: false,
716
- },
717
- error: error,
718
- });
719
- } else {
720
- if(error.sql) {
721
- delete error.sql;
722
- if(error.parent) {
723
- delete error.parent;
724
- }
725
- if(error.original) {
726
- delete error.original.sql;
727
- if(error.original.parameters) {
728
- delete error.original.parameters;
729
- }
730
- }
731
- if(error.parameters) {
732
- delete error.parameters;
733
- }
734
- if(error.errors) {
735
- delete error.errors;
736
- }
737
- // @return with some error
738
- res.status(501).json({
739
- code: 501,
740
- status: "UPDATE_FAILED",
741
- mode: {
742
- debug: false,
743
- },
744
- error: error,
745
- });
746
- } else {
747
- // @return with some string error
748
- res.status(501).json({
749
- code: 501,
750
- status: "UPDATE_FAILED",
751
- mode: {
752
- debug: false,
753
- },
754
- error: String(error),
755
- });
756
- }
757
- }
758
- }
759
- });
760
- } catch (error) {
761
- // @return
762
- return errMessage(error, res);
763
- }
764
- } else {
765
- // @return
766
- return errMessage(err, res);
767
- }
768
- });
769
- });
770
-
771
- // DELETE method
772
- endpoint.delete("/:hash*/:id([a-zA-Z0-9-]+)", (req, res, next) => byPassCheckRole(Projects, "DELETE", passport_config)(req, res, next), async (req, res, next) => {
773
- let leaveMeAlone = await Projects.slice(0);
774
- await filterProject(leaveMeAlone, req.originalUrl.replace(_publicPath_, '/'), "", "DELETE", req, res, async (err, Project) => {
775
- if (!err) {
776
- try {
777
- // Leave pool by project for check pool error
778
- let pool = Project.sequelize;
779
- // Transaction update|patch
780
- Project.transaction(async t => {
781
- try {
782
- // Assign delete pk
783
- let deletePk = { [Project.primaryKeyAttributes[0]]: req.params.id };
784
- // Delete with params
785
- const deleted = await Project.destroy({
786
- where: deletePk,
787
- }, { transaction: t });
788
- if (deleted) {
789
- await t.commit();
790
- // @return
791
- res.status(200).json({
792
- code: 200,
793
- status: "DELETE_SUCCESS",
794
- result: {
795
- deleteId: req.params.id,
796
- affectedRows: deleted,
797
- },
798
- });
799
- } else {
800
- await t.rollback();
801
- res.status(406).json({
802
- code: 406,
803
- status: "NOT_ACCEPTABLE",
804
- result: {
805
- affectedRows: deleted,
806
- },
807
- });
808
- }
809
- } catch (error) {
810
- if(pool.options.logging) {
811
- // @return with all error
812
- res.status(501).json({
813
- code: 501,
814
- status: "DELETE_FAILED",
815
- mode: {
816
- debug: true,
817
- },
818
- error: error,
819
- });
820
- } else {
821
- if(error.sql) {
822
- delete error.sql;
823
- if(error.parent) {
824
- delete error.parent;
825
- }
826
- if(error.original) {
827
- delete error.original.sql;
828
- if(error.original.parameters) {
829
- delete error.original.parameters;
830
- }
831
- }
832
- if(error.parameters) {
833
- delete error.parameters;
834
- }
835
- if(error.errors) {
836
- delete error.errors;
837
- }
838
- // @return with some error
839
- res.status(501).json({
840
- code: 501,
841
- status: "DELETE_FAILED",
842
- mode: {
843
- debug: false,
844
- },
845
- error: error,
846
- });
847
- } else {
848
- // @return with some string error
849
- res.status(501).json({
850
- code: 501,
851
- status: "DELETE_FAILED",
852
- mode: {
853
- debug: false,
854
- },
855
- error: String(error),
856
- });
857
- }
858
- }
859
- }
860
- });
861
- } catch (error) {
862
- // @return
863
- return errMessage(error, res);
864
- }
865
- } else {
866
- // @return
867
- return errMessage(err, res);
868
- }
869
- });
870
- });
871
- }
872
- // resolve it.
873
- resolve(true);
874
- }).catch((err) => {
875
- reject(err);
876
- });
877
- }
878
- });
879
- } catch (error) {
880
- reject(error);
881
- }
882
- });
883
- }
884
-
885
- module.exports = { Base };
1
+ const walk = require("walk");
2
+ const fs = require("fs");
3
+ const moment = require("moment");
4
+ const { checkRoleMiddlewareWithDefaultProject } = require("../../cli/core/middleware/express/jwtCheckAllow");
5
+ const { Limiter, Duplicater } = require("../../cli/core/middleware/index");
6
+
7
+ function walkModel(cb) {
8
+ try {
9
+ let jsfiles = [];
10
+ let walker = walk.walk(appRoot + "/src/models", { followLinks: false });
11
+ // Walk file on push
12
+ walker.on("file", (root, stat, next) => {
13
+ jsfiles.push(root + "/" + stat.name);
14
+ next();
15
+ });
16
+ // Walking
17
+ walker.on("end", () => {
18
+ let preProject = [];
19
+ if(jsfiles.length) {
20
+ jsfiles.forEach((file, k) => {
21
+ let schemaPath = "@/models" + file.split("models")[1].replace(/\\/g, "/");
22
+ let endpoint = file
23
+ .split("models")[1]
24
+ .replace(/\\/g, "/")
25
+ .replace(/_/g, "-")
26
+ .toLowerCase()
27
+ .slice(0, -3);
28
+ preProject.push([schemaPath, endpoint]);
29
+ if (jsfiles.length == k + 1) {
30
+ cb(null, preProject);
31
+ }
32
+ });
33
+ } else {
34
+ // model not found.
35
+ cb(null, preProject);
36
+ }
37
+ });
38
+ } catch (error) {
39
+ cb(error, null);
40
+ }
41
+ }
42
+
43
+ function filterProject(Projects, reqUrl, params = "", method = "", req, res, cb) {
44
+ try {
45
+ let pj = Projects.shift();
46
+ let leaveParamsAlone = params.slice(0);
47
+ let paramsItem = leaveParamsAlone.replace(/^\/|\/$/g, "").split('/');
48
+ let leaveReqUrlAlone = reqUrl.slice(0);
49
+ let newParams = params.split("/").filter((e) => (e !== 'undefined')).join("/");
50
+ let urlWithoutParams = leaveReqUrlAlone.replace(newParams, '').split("?")[0].replace(/\/$/, "");
51
+ // sub-way for PATCH method
52
+ urlWithoutParams = (method == "PATCH" || method == "DELETE") ? urlWithoutParams.substring(0, urlWithoutParams.lastIndexOf('/')) : urlWithoutParams;
53
+ // check match project by url
54
+ if(pj[1] == urlWithoutParams) {
55
+ return checkOffset(Object.values(require(pj[0]))[0], req, res, (thenChecked) => {
56
+ if(thenChecked) {
57
+ return cb(null, Object.values(require(pj[0]))[0], paramsItem);
58
+ }
59
+ });
60
+ }
61
+ // Finally recursive filterProject function
62
+ if (Projects.length > 0) {
63
+ filterProject(Projects, reqUrl, params, method, req, res, cb);
64
+ } else {
65
+ // not match
66
+ return notfound(res);
67
+ }
68
+ } catch (error) {
69
+ console.log(error);
70
+ cb(error, null, []);
71
+ }
72
+ }
73
+
74
+ function filterProjectForByPass(Projects, reqUrl, req, res, cb) {
75
+ try {
76
+ let pj = Projects.shift();
77
+ let regx = new RegExp(pj[1] + "?[^\/].?[a-zA-Z0-9].*$", 'g');
78
+ let regxMatch = reqUrl.match(regx);
79
+ let regxMatchLength = (regxMatch) ? regxMatch.length: 0;
80
+ if (pj[1] == reqUrl) {
81
+ return checkOffset(Object.values(require(pj[0]))[0], req, res, (thenChecked) => {
82
+ if(thenChecked) {
83
+ return cb(null, Object.values(require(pj[0]))[0]);
84
+ }
85
+ });
86
+ } else if(regxMatchLength) {
87
+ return checkOffset(Object.values(require(pj[0]))[0], req, res, (thenChecked) => {
88
+ if(thenChecked) {
89
+ return cb(null, Object.values(require(pj[0]))[0]);
90
+ }
91
+ });
92
+ }
93
+ // Finally recursive filterProjectForByPass function
94
+ if (Projects.length > 0) {
95
+ filterProjectForByPass(Projects, reqUrl, req, res, cb);
96
+ } else {
97
+ // not match
98
+ return notfound(res);
99
+ }
100
+ } catch (error) {
101
+ cb(error, null);
102
+ }
103
+ }
104
+
105
+ function checkOffset(Project, req, res, cb) {
106
+ if (!Project) {
107
+ return notfound(res);
108
+ }
109
+ // check offset is Ingeter as well as Zero number
110
+ let offset = req.params.offset || undefined;
111
+ let limit = req.params.limit || undefined;
112
+ let offsetRegxMatch = (offset !== undefined) ? offset.match(/^[0-9]+$/) : undefined;
113
+ let limitRegxMatch = (limit !== undefined) ? limit.match(/^[0-9]+$/) : undefined;
114
+ if (offsetRegxMatch && limitRegxMatch) {
115
+ cb(true);
116
+ } else {
117
+ if(offset === undefined) {
118
+ cb(true);
119
+ } else {
120
+ return notfound(res);
121
+ }
122
+ }
123
+ }
124
+
125
+ function notfound(res) {
126
+ return res.status(404).json({
127
+ code: 404,
128
+ status: "404_NOT_FOUND",
129
+ message: "The Endpoint not found!.",
130
+ });
131
+ }
132
+
133
+ function errMessage(err, res) {
134
+ let errTurnOffDbDefine = JSON.stringify(err.toString()).match(/'define'/);
135
+ let errTurnOffDbOption = JSON.stringify(err.toString()).match(/'options'/);
136
+ if(errTurnOffDbDefine || errTurnOffDbOption) {
137
+ // @return
138
+ return res.status(500).json({
139
+ code: 500,
140
+ status: "ERR_INTERNAL_SERVER",
141
+ message: "Database connection name is CLOSED.",
142
+ });
143
+ } else {
144
+ // @return
145
+ return res.status(500).json({
146
+ code: 500,
147
+ status: "ERR_INTERNAL_SERVER",
148
+ message: err.toString(),
149
+ });
150
+ }
151
+ }
152
+
153
+ function whereCond(objectCond, cb) {
154
+ if(typeof objectCond === 'object' && Object.keys(objectCond).length) {
155
+ let keys = Object.keys(objectCond);
156
+ let where = {};
157
+ let orderBy = {};
158
+ let groupBy = {};
159
+ // Start Recursive for Check Where condition OR GroupBy OR OrderBy
160
+ recursiveWhereCond(keys, objectCond, where, groupBy, orderBy, (err, cbWhere, cbGroupBy, cbOrderBy) => {
161
+ cb(err, cbWhere, cbGroupBy, cbOrderBy);
162
+ });
163
+ } else {
164
+ cb(null, {}, { group: { groupby: [] } }, { order: { orderby: [] } });
165
+ }
166
+ }
167
+
168
+ function getValueType(value) {
169
+ if (typeof value === 'string') {
170
+ // Check for ISO-like date string with optional time part
171
+ const isoDateRegex = /^\d{4}-\d{2}-\d{2}(?:[ T]\d{2}:\d{2}:\d{2})?$/;
172
+ const date = new Date(value);
173
+ if (!isNaN(date.getTime()) && isoDateRegex.test(value)) {
174
+ return 'Date';
175
+ }
176
+ return 'String';
177
+ }
178
+ if (typeof value === 'number' && !isNaN(value)) return 'Number';
179
+ if (typeof value === 'boolean') return 'Boolean';
180
+ if (value instanceof Date && !isNaN(value)) return 'Date';
181
+ if (value === null) return 'Null';
182
+ if (typeof value === 'undefined') return 'Undefined';
183
+ if (Array.isArray(value)) return 'Array';
184
+ if (typeof value === 'function') return 'Function';
185
+ if (typeof value === 'symbol') return 'Symbol';
186
+ if (typeof value === 'bigint') return 'BigInt';
187
+ if (typeof value === 'object') return 'Object';
188
+ return 'Unknown';
189
+ }
190
+
191
+ async function recursiveWhereCond(keys, objectCond, where, groupBy, orderBy, cb) {
192
+ if(keys.length > 0) {
193
+ let k = keys.shift();
194
+ // Check param key for Where condition OR GroupBy OR OrderBy
195
+ if(k == 'orderby') {
196
+ let oderByValueItemFromQueryString = objectCond[k].replace(/\w+/g, '"$&"').replace(/\[\s*\]/g, '[]');
197
+ // Check Array syntax from Query String
198
+ isValidArrayFormat(oderByValueItemFromQueryString, (err, isArray, strArr) => {
199
+ if(err) {
200
+ cb(["SyntaxError: Unexpected end of Array or String input", ` (${k})`], null, null, null);
201
+ return;
202
+ } else {
203
+ let order = JSON.parse(strArr);
204
+ orderBy[k] = order.length ? order : null;
205
+ // Recursive re-call function
206
+ recursiveWhereCond(keys, objectCond, where, groupBy, orderBy, cb);
207
+ }
208
+ });
209
+ } else if(k == 'groupby') {
210
+ let groupByValueItemFromQueryString = objectCond[k].replace(/\w+/g, '"$&"').replace(/\[\s*\]/g, '[]');
211
+ // Check Array syntax from Query String
212
+ isValidArrayFormat(groupByValueItemFromQueryString, (err, isArray, strArr) => {
213
+ if(err) {
214
+ cb(["SyntaxError: Unexpected end of Array or String input", ` (${k})`], null, null, null);
215
+ return;
216
+ } else {
217
+ let group = JSON.parse(strArr);
218
+ groupBy[k] = group.length ? group : null;
219
+ // Recursive re-call function
220
+ recursiveWhereCond(keys, objectCond, where, groupBy, orderBy, cb);
221
+ }
222
+ });
223
+ } else {
224
+ // 1. Get raw value, force to String and replace %22 to double quote "
225
+ let rawInput = objectCond[k];
226
+ if (Array.isArray(rawInput)) {
227
+ rawInput = rawInput.join(",");
228
+ }
229
+ let decodedQueryString = String(rawInput).replace(/%22/g, '"').replace(/'/g, '"');
230
+ // 2. Advanced decoding logic (works for all operations like LIKE, EQUAL, IN, BETWEEN)
231
+ // Force to look for groups of language codes (% followed by two hexadecimal digits like %E0, %B8)
232
+ // It will capture consecutive codes as a single group and decode them together for accuracy
233
+ decodedQueryString = decodedQueryString.replace(/(?:%[0-9A-F]{2})+/gi, (match) => {
234
+ try {
235
+ return decodeURIComponent(match);
236
+ } catch (e) {
237
+ // This fallback will attempt to decode each %XX sequence individually, which can help recover partial decoding even if the full group is malformed
238
+ return match.replace(/%([0-9A-F]{2})/gi, (subMatch) => {
239
+ try { return decodeURIComponent(subMatch); } catch { return subMatch; }
240
+ });
241
+ }
242
+ });
243
+ // 3. Special handling for BETWEEN and NOT BETWEEN operations to ensure date formats are preserved correctly
244
+ let isBetweenOp = decodedQueryString.toLowerCase().includes('between') || decodedQueryString.toLowerCase().includes('notbetween');
245
+ if (isBetweenOp) {
246
+ try {
247
+ let arrayMatch = decodedQueryString.match(/\[\s*(between|notbetween)\s*,\s*(\[.*\])\s*\]/i);
248
+ if (arrayMatch && arrayMatch[2]) {
249
+ let op = arrayMatch[1];
250
+ let dateArrayStr = arrayMatch[2];
251
+ if (!dateArrayStr.includes('"')) {
252
+ dateArrayStr = dateArrayStr.replace(/([^\[\],]+)/g, '"$1"');
253
+ }
254
+ let parsedDates = JSON.parse(dateArrayStr);
255
+ if (Array.isArray(parsedDates) && parsedDates.length >= 2) {
256
+ // Trim and remove any extraneous quotes from the parsed date strings
257
+ let dateStart = parsedDates[0].trim().replace(/"/g, '');
258
+ let dateEnd = parsedDates[1].trim().replace(/"/g, '');
259
+ // Ensure that if the date string is in a format like "2026-05-17 00:00:00", it is converted to "2026-05-17T00:00:00" for proper Date parsing
260
+ dateStart = dateStart.replace(' ', 'T');
261
+ dateEnd = dateEnd.replace(' ', 'T');
262
+ decodedQueryString = `[${op},${dateStart},${dateEnd}]`;
263
+ }
264
+ }
265
+ } catch (err) {
266
+ cb(["SyntaxError: Invalid format for BETWEEN/NOT BETWEEN operation", ` (${k})`], null, null, null);
267
+ return;
268
+ }
269
+ }
270
+ // 4. Format the decoded query string to prepare for Sequelize operators
271
+ let fieldValueItemFromQueryString = decodedQueryString.replace(/([^[\]," ]+)/g, '"$1"').replace(/""/g, '"');
272
+ let valueItem = decodedQueryString.replace(/[\[\]']+/g, '').replace(/"/g, '').split(',');
273
+ let cleanValueItem = valueItem.map((e) => e.trim()).filter((e) => e !== '');
274
+ // Check Array syntax from Query String
275
+ isValidArrayFormat(fieldValueItemFromQueryString, async (err) => {
276
+ if(err || cleanValueItem.length < 1) {
277
+ cb(["SyntaxError: Unexpected end of Array or String input", ` (${k})`], null, null, null);
278
+ return;
279
+ } else {
280
+ try {
281
+ let targetValue = cleanValueItem[1];
282
+ if (cleanValueItem[0] === 'like' || cleanValueItem[0] === 'notLike') {
283
+ targetValue = cleanValueItem.slice(1).join("");
284
+ }
285
+ where[k] = (cleanValueItem[1])
286
+ ? {
287
+ [Op[cleanValueItem[0]]]: (cleanValueItem[1] == 'null') ? null :
288
+ (cleanValueItem[1] == 'true') ? true :
289
+ (cleanValueItem[1] == 'false') ? false :
290
+ (cleanValueItem[0] == 'between' || cleanValueItem[0] == 'notBetween') ? [
291
+ (getValueType(cleanValueItem[1]) == 'Date')
292
+ ? (() => {
293
+ let mDate = moment(cleanValueItem[1]);
294
+ if (cleanValueItem[1].length <= 10 && !cleanValueItem[1].includes(':')) {
295
+ mDate.startOf('day');
296
+ }
297
+ return mDate.format('YYYY-MM-DD HH:mm:ss');
298
+ })()
299
+ : cleanValueItem[1],
300
+ (getValueType(cleanValueItem[2]) == 'Date')
301
+ ? (() => {
302
+ let mDate = moment(cleanValueItem[2]);
303
+ if (cleanValueItem[2].length <= 10 && !cleanValueItem[2].includes(':')) {
304
+ mDate.endOf('day');
305
+ }
306
+ return mDate.format('YYYY-MM-DD HH:mm:ss');
307
+ })()
308
+ : cleanValueItem[2],
309
+ ] :
310
+ (cleanValueItem[0] == 'or' || cleanValueItem[0] == 'in' || cleanValueItem[0] == 'notIn') ? [...cleanValueItem.slice(1)] :
311
+ (cleanValueItem[0] == 'like') ? targetValue :
312
+ (cleanValueItem[0] == 'notLike') ? targetValue :
313
+ (cleanValueItem[0] == 'startsWith') ? cleanValueItem[1] :
314
+ (cleanValueItem[0] == 'endsWith') ? cleanValueItem[1] :
315
+ (cleanValueItem[0] == 'substring') ? cleanValueItem[1] :
316
+ [cleanValueItem[1]]
317
+ }
318
+ : cleanValueItem[0];
319
+ // Recursive re-call function
320
+ recursiveWhereCond(keys, objectCond, where, groupBy, orderBy, cb);
321
+ } catch (error) {
322
+ return cb([`Error with (${k})`, error], null, null, null);
323
+ }
324
+ }
325
+ });
326
+ }
327
+ } else {
328
+ // End of recursive function
329
+ cb(null, where, groupBy, orderBy);
330
+ }
331
+ }
332
+
333
+ function isValidArrayFormat(str, cb) {
334
+ try {
335
+ const parsed = JSON.parse(str);
336
+ cb(null, Array.isArray(parsed), str);
337
+ } catch (err) {
338
+ cb(err, false, null);
339
+ }
340
+ }
341
+
342
+ // function parseRawQuery(search) {
343
+ // const rawParams = {};
344
+ // if (search.startsWith('?')) {
345
+ // const queryString = search.slice(1); // Remove '?'
346
+ // const pairs = queryString.split('&');
347
+
348
+ // for (const pair of pairs) {
349
+ // const [key, value = ''] = pair.split('=');
350
+ // if (key) {
351
+ // rawParams[key] = value;
352
+ // }
353
+ // }
354
+ // }
355
+ // return rawParams;
356
+ // }
357
+
358
+ async function findAll(Project, where, offset, group, order, limitRow, cb) {
359
+ try {
360
+ await Project.findAll({
361
+ where,
362
+ group: (group.groupby) ? group.groupby : [],
363
+ order: (order.orderby) ? [order.orderby] : [],
364
+ offset: offset,
365
+ limit: limitRow,
366
+ }).then((results) => {
367
+ cb(null, results)
368
+ }).catch((error) => {
369
+ cb(error, null);
370
+ });
371
+ } catch (error) {
372
+ cb(error, null);
373
+ }
374
+ }
375
+
376
+ async function retrieving(authEndpoint, Projects, req, res, next) {
377
+ let params = req.params;
378
+ let hash = "/" + req.params.hash;
379
+ // allow official stetragy
380
+ if(hash == authEndpoint && (params[0] == "/facebook" || params[0] == "/facebook/callback" || params[0] == "/google" || params[0] == "/google/callback")) {
381
+ return next();
382
+ }
383
+ // declare variable for check request with params
384
+ let leaveMeAlone = await Projects.slice(0);
385
+ let mergeDuoVar = [req.params.limit, req.params.offset].map((e) => (e || 'undefined')).join("/");
386
+ let reqUrl = req.originalUrl.replace(_publicPath_, '/');
387
+ let checkConditionIsQueryOrId = Object.keys(req.query).length
388
+ ? req.query
389
+ : 'undefined';
390
+ /**
391
+ * Function whereCond with callback property
392
+ *
393
+ * @where Object|String
394
+ *
395
+ */
396
+ whereCond(checkConditionIsQueryOrId, async (err, where, groupBy, orderBy) => {
397
+ if(err) {
398
+ res.status(400).json({
399
+ code: 400,
400
+ status: "BAD_REQUEST",
401
+ err: String(err),
402
+ });
403
+ } else {
404
+ /**
405
+ * Filter Project with callback property
406
+ *
407
+ * @err String
408
+ * @Project Require
409
+ * @params Object [0=limit, 1=offset]
410
+ *
411
+ */
412
+ await filterProject(leaveMeAlone, reqUrl, "/".concat(mergeDuoVar), "", req, res, async (err, Project, params) => {
413
+ if (!err) {
414
+ try {
415
+ // declare default limit offset
416
+ let offset = 0;
417
+ let limitRow = await (Project.options.limitRows) ? Project.options.limitRows : 100;
418
+ // check assign limit, offset ?
419
+ if ((params[0] && params[0] != 'undefined') && ((params[1] && params[1] != 'undefined') || parseInt(params[1]) === 0)) {
420
+ // Only case: /limit/offset
421
+ limitRow = parseInt(params[0]);
422
+ offset = parseInt(params[1]);
423
+ }
424
+ // findAll data
425
+ await findAll(Project, where, offset, groupBy, orderBy, limitRow, (err, results) => {
426
+ if(err) {
427
+ res.status(500).json({
428
+ code: 500,
429
+ status: "READ_CATCH",
430
+ err: String(err),
431
+ });
432
+ } else {
433
+ // @ return findAll
434
+ res.json({
435
+ code: 200,
436
+ status: "SUCCESS",
437
+ results,
438
+ length: results.length,
439
+ limitRow,
440
+ });
441
+ }
442
+ });
443
+ } catch (error) {
444
+ // @return
445
+ return errMessage(error, res);
446
+ }
447
+ } else {
448
+ // @return
449
+ return errMessage(err, res);
450
+ }
451
+ });
452
+ }
453
+ });
454
+ }
455
+
456
+ // Duplicater cache for each config, avoid create new instance with same config
457
+ const duplicaterCache = new Map();
458
+ function getDuplicaterInstance(dupConfig = {}) {
459
+ const key = JSON.stringify(dupConfig || {});
460
+ if (!duplicaterCache.has(key)) {
461
+ duplicaterCache.set(key, Duplicater(dupConfig || {}));
462
+ }
463
+ return duplicaterCache.get(key);
464
+ }
465
+
466
+ // Limiter cache for each config, avoid create new instance with same config
467
+ const limiterCache = new Map();
468
+ function getLimiterInstance(rateConfig = {}) {
469
+ const key = JSON.stringify(rateConfig);
470
+ if (!limiterCache.has(key)) {
471
+ limiterCache.set(key, Limiter(rateConfig));
472
+ }
473
+ return limiterCache.get(key);
474
+ }
475
+
476
+ const byPassCheckRole = (Projects, method, passport_config) => {
477
+ return async function (req, res, next) {
478
+ try {
479
+ if(passport_config[0] !== undefined) {
480
+ if(req.params.hash == passport_config[0].replace(/^\/|\/$/g, "")) {
481
+ return next();
482
+ } else {
483
+ if(passport_config[1].jwt_allow === true) {
484
+ let leaveMeAlone = await Projects.slice(0);
485
+ await filterProjectForByPass(leaveMeAlone, req.originalUrl.replace(_publicPath_, '/'), req, res, async (err, Project) => {
486
+ if(!err) {
487
+ // Set Duplicater and Limiter for each project with method
488
+ const dupConfig = (Project.options?.defaultEndpoint?.[method]?.duplicate_request) ? Project.options.defaultEndpoint[method].duplicate_request : {};
489
+ const duplicaterMiddleware = getDuplicaterInstance(dupConfig);
490
+ return duplicaterMiddleware(req, res, (err) => {
491
+ if (!err) {
492
+ const rateConfig = (Project.options?.defaultEndpoint?.[method]?.rate_limit) ? Project.options.defaultEndpoint[method].rate_limit : {};
493
+ const limiterMiddleware = getLimiterInstance(rateConfig);
494
+ limiterMiddleware(req, res, (err) => {
495
+ if (!err) {
496
+ if(Project.options.defaultEndpoint === undefined || Project.options.defaultEndpoint === true) {
497
+ // Project is not use options
498
+ return Credentials(req, res, () => {
499
+ return checkRoleMiddlewareWithDefaultProject(null)(req, res, next);
500
+ });
501
+ } else {
502
+ // Method is set
503
+ if(Project.options.defaultEndpoint[method]) {
504
+ if(Project.options.defaultEndpoint[method]["allow"] === undefined || Project.options.defaultEndpoint[method]["allow"] === true) {
505
+ if(Project.options.defaultEndpoint[method]["jwt"]?.allow === false) {
506
+ // by project jwt_allow is false
507
+ return checkRoleMiddlewareWithDefaultProject(null)(req, res, next);
508
+ } else {
509
+ //return Credentials(req, res, () => {
510
+ // return checkRoleMiddlewareWithDefaultProject(Project.options.defaultEndpoint[method].jwt?.broken_role)(req, res, next);
511
+ //});
512
+ return Credentials(req, res, () => checkRoleMiddlewareWithDefaultProject(Project.options.defaultEndpoint[method].jwt?.broken_role || null)(req, res, next));
513
+ }
514
+ } else {
515
+ // METHOD.allow is false || METHOD.allow is undefined
516
+ return notfound(res);
517
+ }
518
+ } else if(Project.options.defaultEndpoint[method] === true || Project.options.defaultEndpoint[method] === undefined) {
519
+ // Method is not set
520
+ return Credentials(req, res, () => {
521
+ return checkRoleMiddlewareWithDefaultProject(null)(req, res, next);
522
+ });
523
+ } else {
524
+ // METHOD is false || METHOD is not undefined
525
+ return notfound(res);
526
+ }
527
+ }
528
+ } else {
529
+ return res.status(429).json({
530
+ code: 429,
531
+ status: "TOO_MANY_REQUEST",
532
+ message: "Too Many Requests.",
533
+ });
534
+ }
535
+ });
536
+ } else {
537
+ return res.status(409).json({
538
+ code: 409,
539
+ status: "DUPLICATE_REQUEST",
540
+ message: "Duplicate request detected.",
541
+ });
542
+ }
543
+ });
544
+ } else {
545
+ return errMessage(err, res);
546
+ }
547
+ });
548
+ } else {
549
+ // global jwt_allow is false
550
+ return next();
551
+ }
552
+ }
553
+ } else {
554
+ // passport config not found
555
+ return next();
556
+ }
557
+ } catch (error) {
558
+ return errMessage(error, res);
559
+ }
560
+ }
561
+ }
562
+
563
+ function Base() {
564
+ return new Promise((resolve, reject) => {
565
+ try {
566
+ walkModel((err, Projects) => {
567
+ if (err) {
568
+ reject(err);
569
+ } else {
570
+ const checkPassport = new Promise((resolve) => {
571
+ const passport_config_file = appRoot + "/passport.config.js";
572
+ let passport_config_auth = undefined;
573
+ if (fs.existsSync(passport_config_file)) {
574
+ const _passport_config_ = require(passport_config_file);
575
+ resolve([_passport_config_.auth_endpoint || "/authentication", _passport_config_]);
576
+ } else {
577
+ // passport config not found
578
+ resolve([passport_config_auth, {}]);
579
+ }
580
+ });
581
+ // passport conifg promise
582
+ checkPassport.then((passport_config) => {
583
+ if(Projects.length) {
584
+ // GET method with /:limit/:offset
585
+ endpoint.get("/:hash([a-zA-Z0-9-]+)*/:limit([0-9]+)/:offset([0-9]+)", (req, res, next) => byPassCheckRole(Projects, "GET", passport_config)(req, res, next), async (req, res, next) => {
586
+ await retrieving(passport_config[0], Projects, req, res, next);
587
+ });
588
+
589
+ // GET method only hash/*
590
+ endpoint.get("/:hash([a-zA-Z0-9-]+)*", (req, res, next) => byPassCheckRole(Projects, "GET", passport_config)(req, res, next), async (req, res, next) => {
591
+ await retrieving(passport_config[0], Projects, req, res, next);
592
+ });
593
+
594
+ // POST method
595
+ endpoint.post("/:hash*", (req, res, next) => byPassCheckRole(Projects, "POST", passport_config)(req, res, next), async (req, res, next) => {
596
+ // Check auth request match send next
597
+ if(passport_config[0] !== undefined) {
598
+ if(req.params.hash == passport_config[0].replace(/^\/|\/$/g, "")) {
599
+ return next();
600
+ }
601
+ }
602
+ // When lost IF
603
+ let leaveMeAlone = await Projects.slice(0);
604
+ let reqUrl = req.originalUrl.replace(_publicPath_, '/');
605
+ let bodyIsArray = Array.isArray(req.body);
606
+ await filterProject(leaveMeAlone, reqUrl, "", "", req, res, async (err, Project) => {
607
+ if (!err) {
608
+ try {
609
+ // Leave pool by project for check pool error
610
+ let pool = Project.sequelize;
611
+ // Transaction create
612
+ Project.transaction(async t => {
613
+ try {
614
+ if (bodyIsArray) {
615
+ // Store with bulk body
616
+ const bulkCreated = await Project.bulkCreate(req.body, { transaction: t });
617
+ await t.commit();
618
+ // @return
619
+ res.status(201).json({
620
+ code: 201,
621
+ status: "CREATE_SUCCESS",
622
+ mode: {
623
+ type: "BULK",
624
+ affectedRows: bulkCreated.length,
625
+ },
626
+ createdId: [
627
+ ...bulkCreated.map(item => (item.id) ? item.id : item[Project.primaryKeyAttributes[0]]),
628
+ ],
629
+ });
630
+ } else {
631
+ // Store with single body
632
+ const singleCreated = await Project.create(req.body, { transaction: t });
633
+ await t.commit();
634
+ // @return
635
+ res.status(201).json({
636
+ code: 201,
637
+ status: "CREATE_SUCCESS",
638
+ mode: {
639
+ type: "SINGLE",
640
+ affectedRows: 1,
641
+ },
642
+ createdId: (singleCreated.id) ? singleCreated.id : singleCreated[Project.primaryKeyAttributes[0]],
643
+ });
644
+ }
645
+ } catch (error) {
646
+ await t.rollback();
647
+ if(pool.options.logging) {
648
+ // @return with all error
649
+ res.status(501).json({
650
+ code: 501,
651
+ status: "CREATE_FAILED",
652
+ mode: {
653
+ debug: true,
654
+ type: (bodyIsArray) ? "BULK" : "SINGLE",
655
+ affectedRows: 0,
656
+ },
657
+ error: error,
658
+ });
659
+ } else {
660
+ if(error.sql) {
661
+ delete error.sql;
662
+ if(error.errors) {
663
+ delete error.errors;
664
+ }
665
+ if(error.parent) {
666
+ delete error.parent;
667
+ }
668
+ if(error.original) {
669
+ delete error.original.sql;
670
+ if(error.original.parameters) {
671
+ delete error.original.parameters;
672
+ }
673
+ }
674
+ if(error.parameters) {
675
+ delete error.parameters;
676
+ }
677
+ // @return with some error
678
+ res.status(501).json({
679
+ code: 501,
680
+ status: "CREATE_FAILED",
681
+ mode: {
682
+ debug: false,
683
+ type: (bodyIsArray) ? "BULK" : "SINGLE",
684
+ affectedRows: 0,
685
+ },
686
+ error: error,
687
+ });
688
+ } else {
689
+ // @return with some string error
690
+ res.status(501).json({
691
+ code: 501,
692
+ status: "CREATE_FAILED",
693
+ mode: {
694
+ debug: false,
695
+ type: (bodyIsArray) ? "BULK" : "SINGLE",
696
+ affectedRows: 0,
697
+ },
698
+ error: String(error),
699
+ });
700
+ }
701
+ }
702
+ }
703
+ });
704
+ } catch (error) {
705
+ // @return
706
+ return errMessage(error, res);
707
+ }
708
+ } else {
709
+ // @return
710
+ return errMessage(err, res);
711
+ }
712
+ });
713
+ });
714
+
715
+ // PATCH method
716
+ endpoint.patch("/:hash*/:id([a-zA-Z0-9-]+)", (req, res, next) => byPassCheckRole(Projects, "PATCH", passport_config)(req, res, next), async (req, res, next) => {
717
+ let leaveMeAlone = await Projects.slice(0);
718
+ let reqUrl = req.originalUrl.replace(_publicPath_, '/');
719
+ await filterProject(leaveMeAlone, reqUrl, "", "PATCH", req, res, async (err, Project) => {
720
+ if (!err) {
721
+ try {
722
+ // Check body is empty
723
+ if(Object.keys(req.body).length === 0) {
724
+ return res.status(400).json({
725
+ code: 400,
726
+ status: "BAD_REQUEST",
727
+ message: "Bad request.",
728
+ info: {
729
+ status: "BAD_ENTIRY",
730
+ message: "Unprocessable Entity.",
731
+ },
732
+ });
733
+ }
734
+ // Leave pool by project for check pool error
735
+ let pool = Project.sequelize;
736
+ // Transaction update|patch
737
+ Project.transaction(async t => {
738
+ try {
739
+ // Assign update pk
740
+ let updatePk = { [Project.primaryKeyAttributes[0]]: req.params.id };
741
+ // Patch with body
742
+ const updated = await Project.update(req.body, {
743
+ where: updatePk,
744
+ }, { transaction: t });
745
+ if(updated[0]) {
746
+ await t.commit();
747
+ // @return
748
+ res.status(200).json({
749
+ code: 200,
750
+ status: "UPDATE_SUCCESS",
751
+ mode: {
752
+ debug: true,
753
+ },
754
+ result: {
755
+ updateId: req.params.id,
756
+ affectedRows: updated[0],
757
+ },
758
+ });
759
+ } else {
760
+ await t.rollback();
761
+ res.status(406).json({
762
+ code: 406,
763
+ status: "NOT_ACCEPTABLE",
764
+ result: {
765
+ affectedRows: updated[0],
766
+ },
767
+ });
768
+ }
769
+ } catch (error) {
770
+ await t.rollback();
771
+ if(pool.options.logging) {
772
+ // @return with all error
773
+ res.status(501).json({
774
+ code: 501,
775
+ status: "UPDATE_FAILED",
776
+ mode: {
777
+ debug: false,
778
+ },
779
+ error: error,
780
+ });
781
+ } else {
782
+ if(error.sql) {
783
+ delete error.sql;
784
+ if(error.parent) {
785
+ delete error.parent;
786
+ }
787
+ if(error.original) {
788
+ delete error.original.sql;
789
+ if(error.original.parameters) {
790
+ delete error.original.parameters;
791
+ }
792
+ }
793
+ if(error.parameters) {
794
+ delete error.parameters;
795
+ }
796
+ if(error.errors) {
797
+ delete error.errors;
798
+ }
799
+ // @return with some error
800
+ res.status(501).json({
801
+ code: 501,
802
+ status: "UPDATE_FAILED",
803
+ mode: {
804
+ debug: false,
805
+ },
806
+ error: error,
807
+ });
808
+ } else {
809
+ // @return with some string error
810
+ res.status(501).json({
811
+ code: 501,
812
+ status: "UPDATE_FAILED",
813
+ mode: {
814
+ debug: false,
815
+ },
816
+ error: String(error),
817
+ });
818
+ }
819
+ }
820
+ }
821
+ });
822
+ } catch (error) {
823
+ // @return
824
+ return errMessage(error, res);
825
+ }
826
+ } else {
827
+ // @return
828
+ return errMessage(err, res);
829
+ }
830
+ });
831
+ });
832
+
833
+ // DELETE method
834
+ endpoint.delete("/:hash*/:id([a-zA-Z0-9-]+)", (req, res, next) => byPassCheckRole(Projects, "DELETE", passport_config)(req, res, next), async (req, res, next) => {
835
+ let leaveMeAlone = await Projects.slice(0);
836
+ await filterProject(leaveMeAlone, req.originalUrl.replace(_publicPath_, '/'), "", "DELETE", req, res, async (err, Project) => {
837
+ if (!err) {
838
+ try {
839
+ // Leave pool by project for check pool error
840
+ let pool = Project.sequelize;
841
+ // Transaction update|patch
842
+ Project.transaction(async t => {
843
+ try {
844
+ // Assign delete pk
845
+ let deletePk = { [Project.primaryKeyAttributes[0]]: req.params.id };
846
+ // Delete with params
847
+ const deleted = await Project.destroy({
848
+ where: deletePk,
849
+ }, { transaction: t });
850
+ if (deleted) {
851
+ await t.commit();
852
+ // @return
853
+ res.status(200).json({
854
+ code: 200,
855
+ status: "DELETE_SUCCESS",
856
+ result: {
857
+ deleteId: req.params.id,
858
+ affectedRows: deleted,
859
+ },
860
+ });
861
+ } else {
862
+ await t.rollback();
863
+ res.status(406).json({
864
+ code: 406,
865
+ status: "NOT_ACCEPTABLE",
866
+ result: {
867
+ affectedRows: deleted,
868
+ },
869
+ });
870
+ }
871
+ } catch (error) {
872
+ if(pool.options.logging) {
873
+ // @return with all error
874
+ res.status(501).json({
875
+ code: 501,
876
+ status: "DELETE_FAILED",
877
+ mode: {
878
+ debug: true,
879
+ },
880
+ error: error,
881
+ });
882
+ } else {
883
+ if(error.sql) {
884
+ delete error.sql;
885
+ if(error.parent) {
886
+ delete error.parent;
887
+ }
888
+ if(error.original) {
889
+ delete error.original.sql;
890
+ if(error.original.parameters) {
891
+ delete error.original.parameters;
892
+ }
893
+ }
894
+ if(error.parameters) {
895
+ delete error.parameters;
896
+ }
897
+ if(error.errors) {
898
+ delete error.errors;
899
+ }
900
+ // @return with some error
901
+ res.status(501).json({
902
+ code: 501,
903
+ status: "DELETE_FAILED",
904
+ mode: {
905
+ debug: false,
906
+ },
907
+ error: error,
908
+ });
909
+ } else {
910
+ // @return with some string error
911
+ res.status(501).json({
912
+ code: 501,
913
+ status: "DELETE_FAILED",
914
+ mode: {
915
+ debug: false,
916
+ },
917
+ error: String(error),
918
+ });
919
+ }
920
+ }
921
+ }
922
+ });
923
+ } catch (error) {
924
+ // @return
925
+ return errMessage(error, res);
926
+ }
927
+ } else {
928
+ // @return
929
+ return errMessage(err, res);
930
+ }
931
+ });
932
+ });
933
+ }
934
+ // resolve it.
935
+ resolve(true);
936
+ }).catch((err) => {
937
+ reject(err);
938
+ });
939
+ }
940
+ });
941
+ } catch (error) {
942
+ reject(error);
943
+ }
944
+ });
945
+ }
946
+
947
+ module.exports = { Base };