beech-api 3.8.0 → 3.9.0-beta.8-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,5 +1,6 @@
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");
4
5
 
5
6
  function walkModel(cb) {
@@ -38,7 +39,38 @@ function walkModel(cb) {
38
39
  }
39
40
  }
40
41
 
41
- function filterProject(Projects, reqUrl, req, res, cb) {
42
+ function filterProject(Projects, reqUrl, params = "", method = "", req, res, cb) {
43
+ try {
44
+ let pj = Projects.shift();
45
+ let leaveParamsAlone = params.slice(0);
46
+ let paramsItem = leaveParamsAlone.replace(/^\/|\/$/g, "").split('/');
47
+ let leaveReqUrlAlone = reqUrl.slice(0);
48
+ let newParams = params.split("/").filter((e) => (e !== 'undefined')).join("/");
49
+ let urlWithoutParams = leaveReqUrlAlone.replace(newParams, '').split("?")[0].replace(/\/$/, "");
50
+ // sub-way for PATCH method
51
+ urlWithoutParams = (method == "PATCH" || method == "DELETE") ? urlWithoutParams.substring(0, urlWithoutParams.lastIndexOf('/')) : urlWithoutParams;
52
+ // check match project by url
53
+ if(pj[1] == urlWithoutParams) {
54
+ return checkOffset(Object.values(require(pj[0]))[0], req, res, (thenChecked) => {
55
+ if(thenChecked) {
56
+ return cb(null, Object.values(require(pj[0]))[0], paramsItem);
57
+ }
58
+ });
59
+ }
60
+ // Finally recursive filterProject function
61
+ if (Projects.length > 0) {
62
+ filterProject(Projects, reqUrl, params, method, req, res, cb);
63
+ } else {
64
+ // not match
65
+ return notfound(res);
66
+ }
67
+ } catch (error) {
68
+ console.log(error);
69
+ cb(error, null, []);
70
+ }
71
+ }
72
+
73
+ function filterProjectForByPass(Projects, reqUrl, req, res, cb) {
42
74
  try {
43
75
  let pj = Projects.shift();
44
76
  let regx = new RegExp(pj[1] + "?[^\/].?[a-zA-Z0-9].*$", 'g');
@@ -57,9 +89,9 @@ function filterProject(Projects, reqUrl, req, res, cb) {
57
89
  }
58
90
  });
59
91
  }
60
- // Finally recursive filterProject function
92
+ // Finally recursive filterProjectForByPass function
61
93
  if (Projects.length > 0) {
62
- filterProject(Projects, reqUrl, req, res, cb);
94
+ filterProjectForByPass(Projects, reqUrl, req, res, cb);
63
95
  } else {
64
96
  // not match
65
97
  return notfound(res);
@@ -117,6 +149,247 @@ function errMessage(err, res) {
117
149
  }
118
150
  }
119
151
 
152
+ function whereCond(objectCond, cb) {
153
+ if(typeof objectCond === 'object' && Object.keys(objectCond).length) {
154
+ let keys = Object.keys(objectCond);
155
+ let where = {};
156
+ let orderBy = {};
157
+ let groupBy = {};
158
+ // Start Recursive for Check Where condition OR GroupBy OR OrderBy
159
+ recursiveWhereCond(keys, objectCond, where, groupBy, orderBy, (err, cbWhere, cbGroupBy, cbOrderBy) => {
160
+ cb(err, cbWhere, cbGroupBy, cbOrderBy);
161
+ });
162
+ } else {
163
+ cb(null, {}, { group: { groupby: [] } }, { order: { orderby: [] } });
164
+ }
165
+ }
166
+
167
+ function getValueType(value) {
168
+ if (typeof value === 'string') {
169
+ // Check for ISO-like date string with optional time part
170
+ const isoDateRegex = /^\d{4}-\d{2}-\d{2}(?:[ T]\d{2}:\d{2}:\d{2})?$/;
171
+ const date = new Date(value);
172
+ if (!isNaN(date.getTime()) && isoDateRegex.test(value)) {
173
+ return 'Date';
174
+ }
175
+ return 'String';
176
+ }
177
+ if (typeof value === 'number' && !isNaN(value)) return 'Number';
178
+ if (typeof value === 'boolean') return 'Boolean';
179
+ if (value instanceof Date && !isNaN(value)) return 'Date';
180
+ if (value === null) return 'Null';
181
+ if (typeof value === 'undefined') return 'Undefined';
182
+ if (Array.isArray(value)) return 'Array';
183
+ if (typeof value === 'function') return 'Function';
184
+ if (typeof value === 'symbol') return 'Symbol';
185
+ if (typeof value === 'bigint') return 'BigInt';
186
+ if (typeof value === 'object') return 'Object';
187
+ return 'Unknown';
188
+ }
189
+
190
+ async function recursiveWhereCond(keys, objectCond, where, groupBy, orderBy, cb) {
191
+ if(keys.length > 0) {
192
+ let k = keys.shift();
193
+ // Check param key for Where condition OR GroupBy OR OrderBy
194
+ if(k == 'orderby') {
195
+ let oderByValueItemFromQueryString = objectCond[k].replace(/\w+/g, '"$&"').replace(/\[\s*\]/g, '[]');
196
+ // Check Array syntax from Query String
197
+ isValidArrayFormat(oderByValueItemFromQueryString, (err, isArray, strArr) => {
198
+ if(err) {
199
+ cb(["SyntaxError: Unexpected end of Array or String input", ` (${k})`], null, null, null);
200
+ } else {
201
+ let order = JSON.parse(strArr);
202
+ orderBy[k] = order.length ? order : null;
203
+ // Recursive re-call function
204
+ recursiveWhereCond(keys, objectCond, where, groupBy, orderBy, cb);
205
+ }
206
+ });
207
+ } else if(k == 'groupby') {
208
+ let groupByValueItemFromQueryString = objectCond[k].replace(/\w+/g, '"$&"').replace(/\[\s*\]/g, '[]');
209
+ // Check Array syntax from Query String
210
+ isValidArrayFormat(groupByValueItemFromQueryString, (err, isArray, strArr) => {
211
+ if(err) {
212
+ cb(["SyntaxError: Unexpected end of Array or String input", ` (${k})`], null, null, null);
213
+ } else {
214
+ let group = JSON.parse(strArr);
215
+ groupBy[k] = group.length ? group : null;
216
+ // Recursive re-call function
217
+ recursiveWhereCond(keys, objectCond, where, groupBy, orderBy, cb);
218
+ }
219
+ });
220
+ } else {
221
+ let fieldValueItemFromQueryString = objectCond[k].replace(/([^[\],]+)/g, '"$1"');
222
+ let valueItem = objectCond[k].replace(/[\[\]']+/g, '').split(','); //replace(/[\[\]']+/g, '')
223
+ let cleanValueItem = valueItem.map((e) => e.trim()).filter((e) => e !== '');
224
+ // Check Array syntax from Query String
225
+ isValidArrayFormat(fieldValueItemFromQueryString, async (err) => {
226
+ if(err || cleanValueItem.length < 1) {
227
+ cb(["SyntaxError: Unexpected end of Array or String input", ` (${k})`], null, null, null);
228
+ } else {
229
+ try {
230
+ where[k] = await (cleanValueItem[1])
231
+ ? {
232
+ [Op[cleanValueItem[0]]]: (cleanValueItem[1] == 'null') ? null :
233
+ (cleanValueItem[1] == 'true') ? true :
234
+ (cleanValueItem[1] == 'false') ? false :
235
+ (cleanValueItem[0] == 'between' || cleanValueItem[0] == 'notBetween') ? [
236
+ (getValueType(cleanValueItem[1]) == 'Date')
237
+ ? cleanValueItem[1].split(' ').map(e => e.trim()).length > 1
238
+ ? moment(new Date(cleanValueItem[1]).toISOString())
239
+ : moment(new Date(cleanValueItem[1] + ' 00:00:00').toISOString())
240
+ : cleanValueItem[1],
241
+ (getValueType(cleanValueItem[2]) == 'Date')
242
+ ? cleanValueItem[2].split(' ').map(e => e.trim()).length > 1
243
+ ? moment(new Date(cleanValueItem[2]).toISOString())
244
+ : moment(new Date(cleanValueItem[2] + ' 23:59:59').toISOString())
245
+ : cleanValueItem[2],
246
+ ] :
247
+ (cleanValueItem[0] == 'or' || cleanValueItem[0] == 'in' || cleanValueItem[0] == 'notIn') ? [...cleanValueItem.slice(1)] :
248
+ (cleanValueItem[0] == 'like') ? [cleanValueItem[1],cleanValueItem[2],cleanValueItem[3]].join("") :
249
+ (cleanValueItem[0] == 'notLike') ? [cleanValueItem[1],cleanValueItem[2],cleanValueItem[3]].join("") :
250
+ (cleanValueItem[0] == 'startsWith') ? cleanValueItem[1] :
251
+ (cleanValueItem[0] == 'endsWith') ? cleanValueItem[1] :
252
+ (cleanValueItem[0] == 'substring') ? cleanValueItem[1] :
253
+ [cleanValueItem[1]]
254
+ }
255
+ : cleanValueItem[0];
256
+ // Recursive re-call function
257
+ recursiveWhereCond(keys, objectCond, where, groupBy, orderBy, cb);
258
+ } catch (error) {
259
+ return cb([`Error with (${k})`, error], null, null, null);
260
+ }
261
+ }
262
+ });
263
+ }
264
+ } else {
265
+ // End of recursive function
266
+ cb(null, where, groupBy, orderBy);
267
+ }
268
+ }
269
+
270
+ function isValidArrayFormat(str, cb) {
271
+ try {
272
+ const parsed = JSON.parse(str);
273
+ cb(null, Array.isArray(parsed), str);
274
+ } catch (err) {
275
+ cb(err, false, null);
276
+ }
277
+ }
278
+
279
+ // function parseRawQuery(search) {
280
+ // const rawParams = {};
281
+ // if (search.startsWith('?')) {
282
+ // const queryString = search.slice(1); // Remove '?'
283
+ // const pairs = queryString.split('&');
284
+
285
+ // for (const pair of pairs) {
286
+ // const [key, value = ''] = pair.split('=');
287
+ // if (key) {
288
+ // rawParams[key] = value;
289
+ // }
290
+ // }
291
+ // }
292
+ // return rawParams;
293
+ // }
294
+
295
+ async function findAll(Project, where, offset, group, order, limitRow, cb) {
296
+ try {
297
+ await Project.findAll({
298
+ where,
299
+ group: (group.groupby) ? group.groupby : [],
300
+ order: (order.orderby) ? [order.orderby] : [],
301
+ offset: offset,
302
+ limit: limitRow,
303
+ }).then((results) => {
304
+ cb(null, results)
305
+ }).catch((error) => {
306
+ cb(error, null);
307
+ });
308
+ } catch (error) {
309
+ cb(error, null);
310
+ }
311
+ }
312
+
313
+ async function retrieving(authEndpoint, Projects, req, res, next) {
314
+ let params = req.params;
315
+ let hash = "/" + req.params.hash;
316
+ // allow official stetragy
317
+ if(hash == authEndpoint && (params[0] == "/facebook" || params[0] == "/facebook/callback" || params[0] == "/google" || params[0] == "/google/callback")) {
318
+ return next();
319
+ }
320
+ // declare variable for check request with params
321
+ let leaveMeAlone = await Projects.slice(0);
322
+ let mergeDuoVar = [req.params.limit, req.params.offset].map((e) => (e || 'undefined')).join("/");
323
+ let reqUrl = req.originalUrl.replace(_publicPath_, '/');
324
+ let checkConditionIsQueryOrId = Object.keys(req.query).length
325
+ ? req.query
326
+ : 'undefined';
327
+ /**
328
+ * Function whereCond with callback property
329
+ *
330
+ * @where Object|String
331
+ *
332
+ */
333
+ whereCond(checkConditionIsQueryOrId, async (err, where, groupBy, orderBy) => {
334
+ if(err) {
335
+ res.status(400).json({
336
+ code: 400,
337
+ status: "BAD_REQUEST",
338
+ err: String(err),
339
+ });
340
+ } else {
341
+ /**
342
+ * Filter Project with callback property
343
+ *
344
+ * @err String
345
+ * @Project Require
346
+ * @params Object [0=limit, 1=offset]
347
+ *
348
+ */
349
+ await filterProject(leaveMeAlone, reqUrl, "/".concat(mergeDuoVar), "", req, res, async (err, Project, params) => {
350
+ if (!err) {
351
+ try {
352
+ // declare default limit offset
353
+ let offset = 0;
354
+ let limitRow = await (Project.options.limitRows) ? Project.options.limitRows : 100;
355
+ // check assign limit, offset ?
356
+ if ((params[0] && params[0] != 'undefined') && ((params[1] && params[1] != 'undefined') || parseInt(params[1]) === 0)) {
357
+ // Only case: /limit/offset
358
+ limitRow = parseInt(params[0]);
359
+ offset = parseInt(params[1]);
360
+ }
361
+ // findAll data
362
+ await findAll(Project, where, offset, groupBy, orderBy, limitRow, (err, results) => {
363
+ if(err) {
364
+ res.status(500).json({
365
+ code: 500,
366
+ status: "READ_CATCH",
367
+ err: String(err),
368
+ });
369
+ } else {
370
+ // @ return findAll
371
+ res.json({
372
+ code: 200,
373
+ status: "SUCCESS",
374
+ results,
375
+ length: results.length,
376
+ limitRow,
377
+ });
378
+ }
379
+ });
380
+ } catch (error) {
381
+ // @return
382
+ return errMessage(error, res);
383
+ }
384
+ } else {
385
+ // @return
386
+ return errMessage(err, res);
387
+ }
388
+ });
389
+ }
390
+ });
391
+ }
392
+
120
393
  const byPassCheckRole = (Projects, method, passport_config) => {
121
394
  return async function (req, res, next) {
122
395
  if(passport_config[0] !== undefined) {
@@ -125,7 +398,7 @@ const byPassCheckRole = (Projects, method, passport_config) => {
125
398
  } else {
126
399
  if(passport_config[1].jwt_allow === true) {
127
400
  let leaveMeAlone = await Projects.slice(0);
128
- await filterProject(leaveMeAlone, req.originalUrl.replace(_publicPath_, '/'), req, res, async (err, Project) => {
401
+ await filterProjectForByPass(leaveMeAlone, req.originalUrl.replace(_publicPath_, '/'), req, res, async (err, Project) => {
129
402
  if(!err) {
130
403
  if(Project.options.defaultEndpoint === undefined || Project.options.defaultEndpoint === true) {
131
404
  // Project is not use options
@@ -145,13 +418,17 @@ const byPassCheckRole = (Projects, method, passport_config) => {
145
418
  });
146
419
  }
147
420
  } else {
421
+ // METHOD.allow is false || METHOD.allow is undefined
148
422
  return notfound(res);
149
423
  }
150
- } else {
424
+ } else if(Project.options.defaultEndpoint[method] === true || Project.options.defaultEndpoint[method] === undefined) {
151
425
  // Method is not set
152
426
  return Credentials(req, res, () => {
153
427
  return checkRoleMiddlewareWithDefaultProject(null)(req, res, next);
154
428
  });
429
+ } else {
430
+ // METHOD is false || METHOD is not undefined
431
+ return notfound(res);
155
432
  }
156
433
  }
157
434
  } else {
@@ -163,6 +440,9 @@ const byPassCheckRole = (Projects, method, passport_config) => {
163
440
  return next();
164
441
  }
165
442
  }
443
+ } else {
444
+ // passport config not found
445
+ return next();
166
446
  }
167
447
  }
168
448
  }
@@ -182,101 +462,24 @@ function Base() {
182
462
  resolve([_passport_config_.auth_endpoint || "/authentication", _passport_config_]);
183
463
  } else {
184
464
  // passport config not found
185
- resolve([passport_config_auth, _passport_config_]);
465
+ resolve([passport_config_auth, {}]);
186
466
  }
187
467
  });
188
468
  // passport conifg promise
189
469
  checkPassport.then((passport_config) => {
190
470
  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
- });
217
- });
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
- });
471
+ // GET method with /:limit/:offset
472
+ 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) => {
473
+ await retrieving(passport_config[0], Projects, req, res, next);
245
474
  });
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
- });
475
+
476
+ // GET method only hash/*
477
+ endpoint.get("/:hash([a-zA-Z0-9-]+)*", (req, res, next) => byPassCheckRole(Projects, "GET", passport_config)(req, res, next), async (req, res, next) => {
478
+ await retrieving(passport_config[0], Projects, req, res, next);
277
479
  });
480
+
278
481
  // POST method
279
- endpoint.post("/:hash", (req, res, next) => byPassCheckRole(Projects, "POST", passport_config)(req, res, next), async (req, res, next) => {
482
+ endpoint.post("/:hash*", (req, res, next) => byPassCheckRole(Projects, "POST", passport_config)(req, res, next), async (req, res, next) => {
280
483
  // Check auth request match send next
281
484
  if(passport_config[0] !== undefined) {
282
485
  if(req.params.hash == passport_config[0].replace(/^\/|\/$/g, "")) {
@@ -285,7 +488,8 @@ function Base() {
285
488
  }
286
489
  // When lost IF
287
490
  let leaveMeAlone = await Projects.slice(0);
288
- await filterProject(leaveMeAlone, req.originalUrl.replace(_publicPath_, '/'), req, res, async (err, Project) => {
491
+ let reqUrl = req.originalUrl.replace(_publicPath_, '/');
492
+ await filterProject(leaveMeAlone, reqUrl, "", "", req, res, async (err, Project) => {
289
493
  if (!err) {
290
494
  try {
291
495
  // Leave pool by project for check pool error
@@ -350,18 +554,18 @@ function Base() {
350
554
  }
351
555
  });
352
556
  });
557
+
353
558
  // PATCH method
354
- endpoint.patch("/:hash/:id", (req, res, next) => byPassCheckRole(Projects, "PATCH", passport_config)(req, res, next), async (req, res, next) => {
559
+ endpoint.patch("/:hash*/:id([a-zA-Z0-9-]+)", (req, res, next) => byPassCheckRole(Projects, "PATCH", passport_config)(req, res, next), async (req, res, next) => {
355
560
  let leaveMeAlone = await Projects.slice(0);
356
- await filterProject(leaveMeAlone, req.originalUrl.replace(_publicPath_, '/'), req, res, async (err, Project) => {
561
+ let reqUrl = req.originalUrl.replace(_publicPath_, '/');
562
+ await filterProject(leaveMeAlone, reqUrl, "", "PATCH", req, res, async (err, Project) => {
357
563
  if (!err) {
358
564
  try {
359
565
  // Leave pool by project for check pool error
360
566
  let pool = Project.sequelize;
361
567
  // Assign update pk
362
- let updatePk = {
363
- [Project.primaryKeyAttributes[0]]: req.params.id
364
- };
568
+ let updatePk = { [Project.primaryKeyAttributes[0]]: req.params.id };
365
569
  // Patch with body
366
570
  await Project.update(req.body, {
367
571
  where: updatePk,
@@ -427,18 +631,17 @@ function Base() {
427
631
  }
428
632
  });
429
633
  });
634
+
430
635
  // DELETE method
431
- endpoint.delete("/:hash/:id", (req, res, next) => byPassCheckRole(Projects, "DELETE", passport_config)(req, res, next), async (req, res, next) => {
636
+ endpoint.delete("/:hash*/:id([a-zA-Z0-9-]+)", (req, res, next) => byPassCheckRole(Projects, "DELETE", passport_config)(req, res, next), async (req, res, next) => {
432
637
  let leaveMeAlone = await Projects.slice(0);
433
- await filterProject(leaveMeAlone, req.originalUrl.replace(_publicPath_, '/'), req, res, async (err, Project) => {
638
+ await filterProject(leaveMeAlone, req.originalUrl.replace(_publicPath_, '/'), "", "DELETE", req, res, async (err, Project) => {
434
639
  if (!err) {
435
640
  try {
436
641
  // Leave pool by project for check pool error
437
642
  let pool = Project.sequelize;
438
643
  // Assign delete pk
439
- let deletePk = {
440
- [Project.primaryKeyAttributes[0]]: req.params.id
441
- };
644
+ let deletePk = { [Project.primaryKeyAttributes[0]]: req.params.id };
442
645
  // Delete with params
443
646
  await Project.destroy({
444
647
  where: deletePk,
@@ -5,9 +5,12 @@ function Schema(Sequelize) {
5
5
  define: (table, schemaProps = {}, elementProps = {}) => {
6
6
  try {
7
7
  let Project = Sequelize.define(table, schemaProps, elementProps);
8
+ //Sequelize.options.charset = "utf8";
9
+ //Sequelize.options.define.charset = "utf8";
10
+ //Sequelize.options.define.dialectOptions.collate = "utf8_unicode_ci";
8
11
  return Object.assign(Project, {
9
12
  query: (
10
- rawSql,
13
+ rawSql = "",
11
14
  props = {
12
15
  model: Project,
13
16
  mapToModel: false,