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

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.
@@ -1,6 +1,8 @@
1
1
  const walk = require("walk");
2
2
  const fs = require("fs");
3
+ const moment = require("moment");
3
4
  const { checkRoleMiddlewareWithDefaultProject } = require("../../cli/core/middleware/express/jwtCheckAllow");
5
+ const { Limiter, Duplicater } = require("../../cli/core/middleware/index");
4
6
 
5
7
  function walkModel(cb) {
6
8
  try {
@@ -38,7 +40,38 @@ function walkModel(cb) {
38
40
  }
39
41
  }
40
42
 
41
- function filterProject(Projects, reqUrl, req, res, cb) {
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) {
42
75
  try {
43
76
  let pj = Projects.shift();
44
77
  let regx = new RegExp(pj[1] + "?[^\/].?[a-zA-Z0-9].*$", 'g');
@@ -57,9 +90,9 @@ function filterProject(Projects, reqUrl, req, res, cb) {
57
90
  }
58
91
  });
59
92
  }
60
- // Finally recursive filterProject function
93
+ // Finally recursive filterProjectForByPass function
61
94
  if (Projects.length > 0) {
62
- filterProject(Projects, reqUrl, req, res, cb);
95
+ filterProjectForByPass(Projects, reqUrl, req, res, cb);
63
96
  } else {
64
97
  // not match
65
98
  return notfound(res);
@@ -117,52 +150,350 @@ function errMessage(err, res) {
117
150
  }
118
151
  }
119
152
 
120
- const byPassCheckRole = (Projects, method, passport_config) => {
121
- return async function (req, res, next) {
122
- if(passport_config[0] !== undefined) {
123
- if(req.params.hash == passport_config[0].replace(/^\/|\/$/g, "")) {
124
- return next();
125
- } else {
126
- if(passport_config[1].jwt_allow === true) {
127
- let leaveMeAlone = await Projects.slice(0);
128
- await filterProject(leaveMeAlone, req.originalUrl.replace(_publicPath_, '/'), req, res, async (err, Project) => {
129
- if(!err) {
130
- if(Project.options.defaultEndpoint === undefined || Project.options.defaultEndpoint === true) {
131
- // Project is not use options
132
- return Credentials(req, res, () => {
133
- return checkRoleMiddlewareWithDefaultProject(null)(req, res, next);
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),
134
369
  });
135
370
  } else {
136
- // Method is set
137
- if(Project.options.defaultEndpoint[method]) {
138
- if(Project.options.defaultEndpoint[method]["allow"] === undefined || Project.options.defaultEndpoint[method]["allow"] === true) {
139
- if(Project.options.defaultEndpoint[method]["jwt"]?.allow === false) {
140
- // by project jwt_allow is false
141
- return checkRoleMiddlewareWithDefaultProject(null)(req, res, next);
142
- } else {
143
- return Credentials(req, res, () => {
144
- return checkRoleMiddlewareWithDefaultProject(Project.options.defaultEndpoint[method].jwt?.broken_role)(req, res, next);
145
- });
146
- }
147
- } else {
148
- return notfound(res);
149
- }
150
- } else {
151
- // Method is not set
152
- return Credentials(req, res, () => {
153
- return checkRoleMiddlewareWithDefaultProject(null)(req, res, next);
154
- });
155
- }
371
+ // @ return findAll
372
+ res.json({
373
+ code: 200,
374
+ status: "SUCCESS",
375
+ results,
376
+ length: results.length,
377
+ limitRow,
378
+ });
156
379
  }
157
- } else {
158
- return errMessage(err, res);
159
- }
160
- });
380
+ });
381
+ } catch (error) {
382
+ // @return
383
+ return errMessage(error, res);
384
+ }
161
385
  } else {
162
- // global jwt_allow is false
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, "")) {
163
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
+ }
164
490
  }
491
+ } else {
492
+ // passport config not found
493
+ return next();
165
494
  }
495
+ } catch (error) {
496
+ return errMessage(error, res);
166
497
  }
167
498
  }
168
499
  }
@@ -182,101 +513,24 @@ function Base() {
182
513
  resolve([_passport_config_.auth_endpoint || "/authentication", _passport_config_]);
183
514
  } else {
184
515
  // passport config not found
185
- resolve([passport_config_auth, _passport_config_]);
516
+ resolve([passport_config_auth, {}]);
186
517
  }
187
518
  });
188
519
  // passport conifg promise
189
520
  checkPassport.then((passport_config) => {
190
521
  if(Projects.length) {
191
- // GET method with ALL data, default: limit rows 100
192
- endpoint.get("/:hash", (req, res, next) => byPassCheckRole(Projects, "GET", passport_config)(req, res, next), async (req, res, next) => {
193
- let leaveMeAlone = await Projects.slice(0);
194
- await filterProject(leaveMeAlone, req.originalUrl.replace(_publicPath_, '/'), req, res, async (err, Project) => {
195
- if (!err) {
196
- try {
197
- const results = await Project.findAll({
198
- offset: 0,
199
- limit: (Project.options.limitRows) ? Project.options.limitRows : 100,
200
- });
201
- // @ return
202
- await res.json({
203
- code: 200,
204
- status: "SUCCESS",
205
- results,
206
- length: results.length,
207
- });
208
- } catch (error) {
209
- // @return
210
- return errMessage(error, res);
211
- }
212
- } else {
213
- // @return
214
- return errMessage(err, res);
215
- }
216
- });
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);
217
525
  });
218
- // GET method with id
219
- endpoint.get("/:hash/:id", (req, res, next) => byPassCheckRole(Projects, "GET", passport_config)(req, res, next), async (req, res, next) => {
220
- // allow official stetragy
221
- if(req.params.id == "google" || req.params.id == "facebook") {
222
- return next();
223
- }
224
- // filter GET project
225
- let leaveMeAlone = await Projects.slice(0);
226
- await filterProject(leaveMeAlone, req.originalUrl.replace(_publicPath_, '/'), req, res, async (err, Project) => {
227
- if (!err) {
228
- try {
229
- const results = await Project.findByPk(req.params.id);
230
- // @ return
231
- await res.json({
232
- code: 200,
233
- status: "SUCCESS",
234
- results,
235
- });
236
- } catch (error) {
237
- // @return
238
- return errMessage(error, res);
239
- }
240
- } else {
241
- // @return
242
- return errMessage(err, res);
243
- }
244
- });
245
- });
246
- // GET method with :limit and :offset
247
- endpoint.get("/:hash/:limit/:offset", (req, res, next) => byPassCheckRole(Projects, "GET", passport_config)(req, res, next), async (req, res, next) => {
248
- // allow official stetragy
249
- if(req.params.limit == "google" || req.params.limit == "facebook" || req.params.offset == "callback") {
250
- return next();
251
- }
252
- // filter GET limit,offset project
253
- let leaveMeAlone = await Projects.slice(0);
254
- await filterProject(leaveMeAlone, req.originalUrl.replace(_publicPath_, '/'), req, res, async (err, Project) => {
255
- if (!err) {
256
- try {
257
- const results = await Project.findAll({
258
- offset: parseInt(req.params.offset) || 0,
259
- limit: (parseInt(req.params.limit) === 0) ? 0 : parseInt(req.params.limit),
260
- });
261
- // @ return
262
- await res.json({
263
- code: 200,
264
- status: "SUCCESS",
265
- results,
266
- length: results.length,
267
- });
268
- } catch (error) {
269
- // @return
270
- return errMessage(error, res);
271
- }
272
- } else {
273
- // @return
274
- return errMessage(err, res);
275
- }
276
- });
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);
277
530
  });
531
+
278
532
  // POST method
279
- endpoint.post("/:hash", (req, res, next) => byPassCheckRole(Projects, "POST", passport_config)(req, res, next), async (req, res, next) => {
533
+ endpoint.post("/:hash*", (req, res, next) => byPassCheckRole(Projects, "POST", passport_config)(req, res, next), async (req, res, next) => {
280
534
  // Check auth request match send next
281
535
  if(passport_config[0] !== undefined) {
282
536
  if(req.params.hash == passport_config[0].replace(/^\/|\/$/g, "")) {
@@ -285,58 +539,103 @@ function Base() {
285
539
  }
286
540
  // When lost IF
287
541
  let leaveMeAlone = await Projects.slice(0);
288
- await filterProject(leaveMeAlone, req.originalUrl.replace(_publicPath_, '/'), req, res, async (err, Project) => {
542
+ let reqUrl = req.originalUrl.replace(_publicPath_, '/');
543
+ let bodyIsArray = Array.isArray(req.body);
544
+ await filterProject(leaveMeAlone, reqUrl, "", "", req, res, async (err, Project) => {
289
545
  if (!err) {
290
546
  try {
291
547
  // Leave pool by project for check pool error
292
548
  let pool = Project.sequelize;
293
- // Store with body
294
- await Project.create(req.body).then((created) => {
295
- // @return
296
- res.status(201).json({
297
- code: 201,
298
- status: "CREATE_SUCCESS",
299
- createdId: (created.id) ? created.id : created[Project.primaryKeyAttributes[0]],
300
- });
301
- }).catch((error) => {
302
- if(pool.options.logging) {
303
- // @return with all error
304
- res.status(501).json({
305
- code: 501,
306
- status: "CREATE_FAILED",
307
- error: error,
308
- });
309
- } else {
310
- if(error.sql) {
311
- delete error.sql;
312
- if(error.errors) {
313
- delete error.errors;
314
- }
315
- if(error.parent) {
316
- delete error.parent;
317
- }
318
- if(error.original) {
319
- delete error.original.sql;
320
- if(error.original.parameters) {
321
- delete error.original.parameters;
322
- }
323
- }
324
- if(error.parameters) {
325
- delete error.parameters;
326
- }
327
- // @return with some error
328
- res.status(501).json({
329
- code: 501,
330
- status: "CREATE_FAILED",
331
- error: error,
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
+ ],
332
567
  });
333
568
  } else {
334
- // @return with some string error
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
335
587
  res.status(501).json({
336
588
  code: 501,
337
589
  status: "CREATE_FAILED",
338
- error: String(error),
590
+ mode: {
591
+ debug: true,
592
+ type: (bodyIsArray) ? "BULK" : "SINGLE",
593
+ affectedRows: 0,
594
+ },
595
+ error: error,
339
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
+ }
340
639
  }
341
640
  }
342
641
  });
@@ -350,70 +649,111 @@ function Base() {
350
649
  }
351
650
  });
352
651
  });
652
+
353
653
  // PATCH method
354
- endpoint.patch("/:hash/:id", (req, res, next) => byPassCheckRole(Projects, "PATCH", passport_config)(req, res, next), async (req, res, next) => {
654
+ endpoint.patch("/:hash*/:id([a-zA-Z0-9-]+)", (req, res, next) => byPassCheckRole(Projects, "PATCH", passport_config)(req, res, next), async (req, res, next) => {
355
655
  let leaveMeAlone = await Projects.slice(0);
356
- await filterProject(leaveMeAlone, req.originalUrl.replace(_publicPath_, '/'), req, res, async (err, Project) => {
656
+ let reqUrl = req.originalUrl.replace(_publicPath_, '/');
657
+ await filterProject(leaveMeAlone, reqUrl, "", "PATCH", req, res, async (err, Project) => {
357
658
  if (!err) {
358
659
  try {
359
- // Leave pool by project for check pool error
360
- let pool = Project.sequelize;
361
- // Assign update pk
362
- let updatePk = {
363
- [Project.primaryKeyAttributes[0]]: req.params.id
364
- };
365
- // Patch with body
366
- await Project.update(req.body, {
367
- where: updatePk,
368
- }).then((updated) => {
369
- // @return
370
- res.status(200).json({
371
- code: 200,
372
- status: "UPDATE_SUCCESS",
373
- result: {
374
- updateId: req.params.id,
375
- affectedRows: updated[0],
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.",
376
669
  },
377
670
  });
378
- }).catch((error) => {
379
- if(pool.options.logging) {
380
- // @return with all error
381
- res.status(501).json({
382
- code: 501,
383
- status: "UPDATE_FAILED",
384
- error: error,
385
- });
386
- } else {
387
- if(error.sql) {
388
- delete error.sql;
389
- if(error.parent) {
390
- delete error.parent;
391
- }
392
- if(error.original) {
393
- delete error.original.sql;
394
- if(error.original.parameters) {
395
- delete error.original.parameters;
396
- }
397
- }
398
- if(error.parameters) {
399
- delete error.parameters;
400
- }
401
- if(error.errors) {
402
- delete error.errors;
403
- }
404
- // @return with some error
405
- res.status(501).json({
406
- code: 501,
407
- status: "UPDATE_FAILED",
408
- error: error,
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
+ },
409
696
  });
410
697
  } else {
411
- // @return with some string error
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
412
711
  res.status(501).json({
413
712
  code: 501,
414
713
  status: "UPDATE_FAILED",
415
- error: String(error),
714
+ mode: {
715
+ debug: false,
716
+ },
717
+ error: error,
416
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
+ }
417
757
  }
418
758
  }
419
759
  });
@@ -427,81 +767,94 @@ function Base() {
427
767
  }
428
768
  });
429
769
  });
770
+
430
771
  // DELETE method
431
- endpoint.delete("/:hash/:id", (req, res, next) => byPassCheckRole(Projects, "DELETE", passport_config)(req, res, next), async (req, res, next) => {
772
+ endpoint.delete("/:hash*/:id([a-zA-Z0-9-]+)", (req, res, next) => byPassCheckRole(Projects, "DELETE", passport_config)(req, res, next), async (req, res, next) => {
432
773
  let leaveMeAlone = await Projects.slice(0);
433
- await filterProject(leaveMeAlone, req.originalUrl.replace(_publicPath_, '/'), req, res, async (err, Project) => {
774
+ await filterProject(leaveMeAlone, req.originalUrl.replace(_publicPath_, '/'), "", "DELETE", req, res, async (err, Project) => {
434
775
  if (!err) {
435
776
  try {
436
777
  // Leave pool by project for check pool error
437
778
  let pool = Project.sequelize;
438
- // Assign delete pk
439
- let deletePk = {
440
- [Project.primaryKeyAttributes[0]]: req.params.id
441
- };
442
- // Delete with params
443
- await Project.destroy({
444
- where: deletePk,
445
- }).then((deleted) => {
446
- if (deleted) {
447
- // @return
448
- res.status(200).json({
449
- code: 200,
450
- status: "DELETE_SUCCESS",
451
- result: {
452
- deleteId: req.params.id,
453
- affectedRows: deleted,
454
- },
455
- });
456
- } else {
457
- res.status(406).json({
458
- code: 406,
459
- status: "NOT_ACCEPTABLE",
460
- result: {
461
- deleteId: req.params.id,
462
- affectedRows: deleted,
463
- },
464
- });
465
- }
466
- }).catch((error) => {
467
- if(pool.options.logging) {
468
- // @return with all error
469
- res.status(501).json({
470
- code: 501,
471
- status: "DELETE_FAILED",
472
- error: error,
473
- });
474
- } else {
475
- if(error.sql) {
476
- delete error.sql;
477
- if(error.parent) {
478
- delete error.parent;
479
- }
480
- if(error.original) {
481
- delete error.original.sql;
482
- if(error.original.parameters) {
483
- delete error.original.parameters;
484
- }
485
- }
486
- if(error.parameters) {
487
- delete error.parameters;
488
- }
489
- if(error.errors) {
490
- delete error.errors;
491
- }
492
- // @return with some error
493
- res.status(501).json({
494
- code: 501,
495
- status: "DELETE_FAILED",
496
- error: error,
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
+ },
497
798
  });
498
799
  } else {
499
- // @return with some string error
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
500
812
  res.status(501).json({
501
813
  code: 501,
502
814
  status: "DELETE_FAILED",
503
- error: String(error),
815
+ mode: {
816
+ debug: true,
817
+ },
818
+ error: error,
504
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
+ }
505
858
  }
506
859
  }
507
860
  });