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

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 +1717 -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 +29 -19
  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 +166 -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 +1229 -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,993 +1,1230 @@
1
- #!/usr/bin/env node
2
- const logUpdate = require("log-update");
3
- const inquirer = require('inquirer');
4
- const walk = require("walk");
5
- const { connectForGenerateModel } = require("../databases/test");
6
-
7
- class Generator {
8
- constructor() {
9
- this.embed(process.argv)
10
- .then(() => this.init()
11
- .then(status => console.log(status))
12
- .catch(err => {
13
- throw err;
14
- })
15
- )
16
- .catch(err => {
17
- throw err
18
- });
19
- }
20
-
21
- init() {
22
- return new Promise((resolve, reject) => {
23
- try {
24
- if (this.option == '-v' || this.option == '--version') {
25
- // check beech version
26
- resolve("v" + require(__dirname + "/../../../../package.json").version);
27
- } else if (this.option == '-h' || this.option == '?' || this.option == '--help') {
28
- // help for see avaliable command
29
- this.help()
30
- .then(help => resolve(help))
31
- .catch(err => reject(err));
32
- } else if (this.option == 'make') {
33
- // generate endpoint
34
- if (!this.argument) {
35
- resolve("\n Warning  Please specify endpoints name.");
36
- } else {
37
- if (!this.special || this.special == 'undefined') {
38
- this.make()
39
- .then(make => resolve(make))
40
- .catch(err => reject(err));
41
- } else if (this.special == '--require' || this.special == '-R') {
42
- // walking model files
43
- const walkModelPromise = new Promise((resolve) => {
44
- let walker = walk.walk("./src/models", { followLinks: false });
45
- let modelFiles = [];
46
- walker.on("file", (root, stat, next) => {
47
- let subFolderModel = root.split("src/models\\")[1];
48
- modelFiles.push((subFolderModel ? subFolderModel + "/" : "") + stat.name.split('.')[0]);
49
- next();
50
- });
51
- walker.on("end", () => {
52
- if(modelFiles.length) {
53
- inquirer.prompt([ {
54
- type: "checkbox",
55
- name: "selectModel",
56
- message: "Please select Models:",
57
- choices: modelFiles.map(e => e.replace(/\\/g, "/")),
58
- } ]).then(selected => {
59
- resolve(selected.selectModel);
60
- });
61
- } else {
62
- // model file not found, Only create endpoint
63
- resolve();
64
- }
65
- });
66
- });
67
- Promise.all([walkModelPromise]).then((modelSelected) => {
68
- let myModel = modelSelected[0];
69
- // check require model exists
70
- const modelExistsPromise = new Promise((resolve, reject) => {
71
- this.isModelFound(myModel)
72
- .then(existsModel => {
73
- // check exists model
74
- if (existsModel == false) {
75
- inquirer.prompt([ {
76
- type: "confirm",
77
- name: "confirmModelNF",
78
- message: "Model is not found, Do you only create Endpoint ?:",
79
- } ]).then(confirm => {
80
- if(confirm.confirmModelNF) {
81
- resolve([true, []]);
82
- } else {
83
- resolve([false, []]);
84
- }
85
- });
86
- } else {
87
- resolve([true, myModel]);
88
- }
89
- })
90
- .catch(err => reject(err));
91
- });
92
- // promise all check choose model(s)
93
- Promise.all([modelExistsPromise]).then((modelRes) => {
94
- // Say Yes, Making...
95
- if(modelRes[0][0]) {
96
- const poolBasePromise = new Promise((resolve) => {
97
- // Check global config for prepare tmp endpoint
98
- this.fs.readFile("./global.config.js", 'utf8', (err, data) => {
99
- if (err) {
100
- console.log("\n Faltal  Can't read `global.config.js` file.\n", err);
101
- return; // break;
102
- } else {
103
- let buffer = Buffer.from(data);
104
- let buf2str = buffer.toString();
105
- let buf2json = JSON.parse(JSON.stringify(buf2str));
106
- let pool_base = /global.pool_base\s+=\s+(?:"|')([^"]+)(?:"|')(?:\r|\n|$|;|\r)/i.exec(buf2json);
107
- if (pool_base) {
108
- let myRequire = modelRes[0][1];
109
- if (pool_base[ 1 ] == "basic") {
110
- if(myRequire.length) {
111
- // declare basic require model file
112
- let rqr = "";
113
- myRequire.map((data, key) => {
114
- let modelName = data.split('/');
115
- let modelFolder = "";
116
- modelName = modelName.pop();
117
- modelName = modelName.charAt(0).toUpperCase() + modelName.slice(1);
118
- let newModel = modelName.split("_").map(e => e.charAt(0).toUpperCase() + e.slice(1)).join("");
119
- modelFolder = data.substring(0, data.lastIndexOf('/') + 1).replace(/\\/g, "/");
120
- rqr += `const ${newModel} = require(\"@/models/${modelFolder + modelName}\");\n`;
121
- if(myRequire.length == key+1) {
122
- resolve([[rqr], myRequire]);
123
- }
124
- });
125
- } else {
126
- resolve([[]]);
127
- }
128
- } else if (pool_base[ 1 ] == "sequelize") {
129
- // check null require resolve it
130
- if(myRequire.length) {
131
- // declare basic require model file
132
- let rqr = "";
133
- myRequire.map((data, key) => {
134
- let modelName = data.split('/');
135
- let modelFolder = "";
136
- modelName = modelName.pop();
137
- modelName = modelName.charAt(0).toUpperCase() + modelName.slice(1);
138
- let newModel = modelName.split("_").map(e => e.charAt(0).toUpperCase() + e.slice(1)).join("");
139
- modelFolder = data.substring(0, data.lastIndexOf('/') + 1).replace(/\\/g, "/");
140
- rqr += `const { ${newModel} } = require(\"@/models/${modelFolder + modelName}\");\n`;
141
- if(myRequire.length == key+1) {
142
- resolve([[rqr], myRequire]);
143
- }
144
- });
145
- } else {
146
- resolve([[]]);
147
- }
148
- } else {
149
- console.log("\n Faltal  The pool_base in `global.config.js` file does not match the specific.");
150
- }
151
- } else {
152
- console.log("\n Faltal  The pool_base in `global.config.js` file is not found.");
153
- }
154
- }
155
- });
156
- });
157
- // Final promise for make
158
- Promise.all([poolBasePromise]).then((rqrRes) => {
159
- // make with require model file
160
- this.make(rqrRes[0])
161
- .then(make => resolve(make))
162
- .catch(err => reject(err));
163
- });
164
- } else {
165
- // Say No, Nothing...
166
- resolve(": Say no.");
167
- }
168
- });
169
- });
170
- } else if (this.special == '--model' || this.special == '-M') {
171
- this.makeModel()
172
- .then(make => resolve(make))
173
- .catch(err => reject(err));
174
- } else if (this.special == '--helper') {
175
- this.makeHelper()
176
- .then(make => resolve(make))
177
- .catch(err => reject(err));
178
- } else {
179
- resolve("\n Faltal  commnad it's not available.");
180
- }
181
- }
182
- } else if (this.option == 'update') {
183
- console.log('------------------------------');
184
- if(this.argument == 'model') {
185
- // this.special = model name
186
- // if (!this.special) {
187
- // resolve("\n [103m [90m Warning [0m [0m Please specify model name to update.");
188
- // } else {
189
- // this.updateModel()
190
- // .then(res => resolve(res))
191
- // .catch(err => reject(err));
192
- // }
193
- }
194
- } else if (this.option == 'passport') {
195
- if (this.argument == "init") {
196
- this.makePassportInit()
197
- .then(make => resolve(make))
198
- .catch(err => reject(err));
199
- } else {
200
- resolve("\n Warning  Using `passport init` for initiate passport-jwt.");
201
- }
202
- } else if (this.option == "key:generate" || this.option == "key:gen") {
203
- this.generateKeyConfigFile()
204
- .then(resGenKey => resolve(resGenKey))
205
- .catch(err => reject(err));
206
- } else if (this.option && this.option.slice(0, 5) == "hash:") {
207
- const { HashIt, Z } = require(__dirname + "/../helpers/math");
208
- if(this.option.length > 5) {
209
- Z((err, ak) => {
210
- if(err) {
211
- logUpdate(err);
212
- } else {
213
- let txt = this.option.split(":");
214
- HashIt(txt, ak, null, (5).toString().length, (hashed) => {
215
- logUpdate("\n" + hashed);
216
- });
217
- }
218
- });
219
- } else {
220
- inquirer.prompt([ {
221
- type: "confirm",
222
- name: "confirmHashNoText",
223
- message: "No text to hash, Do you want to continue ?:",
224
- } ]).then(confirm => {
225
- if(confirm.confirmHashNoText) {
226
- Z((err, ak) => {
227
- if(err) {
228
- logUpdate(err);
229
- } else {
230
- let txt = this.option.split(":");
231
- HashIt(txt, ak, null, (5).toString().length, (hashed) => {
232
- logUpdate("\n" + hashed);
233
- });
234
- }
235
- });
236
- } else {
237
- // no text to hash say no.
238
- resolve(": Say no.");
239
- }
240
- });
241
-
242
- }
243
- } else if (this.option == "skd") {
244
- if (this.argument == "init") {
245
- this.makeAddOnInit()
246
- .then(make => resolve(make))
247
- .catch(err => reject(err));
248
- } else {
249
- resolve("\n Warning  Using `skd init` for initiate Job Scheduler.");
250
- }
251
- } else {
252
- // help for see avaliable command
253
- this.help()
254
- .then(help => resolve(help))
255
- .catch(err => reject(err));
256
- }
257
- } catch (error) {
258
- reject(error);
259
- }
260
- });
261
- }
262
-
263
- make(rq = null) {
264
- return new Promise((resolve, reject) => {
265
- try {
266
- // prepare data
267
- let tmpEndpointsPath = __dirname;
268
- let tmpSpecPath = __dirname + '/_spec';
269
- let endpointsPath = './src/endpoints/';
270
- let testPath = './__tests__/unit/endpoints/';
271
- // argument join `slash`
272
- let arg = this.argument.replace(/^\/+|\/+$/g, '');
273
- arg = arg.split('/');
274
- let endpoints = arg.pop();
275
- let subFolder = arg.join('/');
276
- // endpoints file
277
- let fullEndpoints = endpointsPath + subFolder.concat('/') + endpoints.concat('-endpoints.js');
278
- let routeEndpoints = ((arg.length > 0) ? '/' : '') + subFolder.concat('/') + endpoints;
279
- // test file
280
- let fullTest = testPath + subFolder + '/' + endpoints.concat('-endpoints.spec.js');
281
-
282
- // Check exists endpoint file
283
- if (!this.fs.existsSync(fullEndpoints)) {
284
- // STEP 0 : Check global config for prepare tmp endpoint
285
- const promise0 = new Promise((resolve) => {
286
- this.fs.readFile("./global.config.js", 'utf8', (err, data) => {
287
- if (err) {
288
- console.log("\n Faltal  Can't read `global.config.js` file.", err);
289
- resolve([false, null, null]);
290
- } else {
291
- let buffer = Buffer.from(data);
292
- let buf2str = buffer.toString();
293
- let buf2json = JSON.parse(JSON.stringify(buf2str));
294
- let pool_base = /global.pool_base\s+=\s+(?:"|')([^"]+)(?:"|')(?:\r|\n|$|;|\r)/i.exec(buf2json);
295
- if (pool_base) {
296
- if (pool_base[ 1 ] == "basic") {
297
- resolve([true, tmpEndpointsPath += '/_endpoints_basic', pool_base[ 1 ]]);
298
- } else if (pool_base[ 1 ] == "sequelize") {
299
- resolve([true, tmpEndpointsPath += '/_endpoints', pool_base[ 1 ]]);
300
- } else {
301
- console.log("\n Faltal  The pool_base in `global.config.js` file does not match the specific.");
302
- resolve([false, null, null]);
303
- }
304
- } else {
305
- console.log("\n Faltal  The pool_base in `global.config.js` file is not found.");
306
- resolve([false, null, null]);
307
- }
308
- }
309
- });
310
- });
311
-
312
- // STEP 1 : Format Require model file
313
- const promise1 = new Promise((resolve) => {
314
- // prepare state require file if `rq[0]` not exists
315
- let requireFile = '// You can require something \n';
316
- // check exists requrie files
317
- if(rq) {
318
- if (rq[0].length) {
319
- requireFile = '';
320
- // make require multiples line
321
- rq[0].map((data, key) => {
322
- requireFile += data;
323
- if(rq[0].length == key+1) {
324
- resolve(requireFile);
325
- }
326
- })
327
- } else {
328
- resolve(requireFile);
329
- }
330
- } else {
331
- resolve(requireFile);
332
- }
333
- });
334
- // promise all generate endpoint with require(s)
335
- Promise.all([promise0, promise1]).then((rqFileRes) => {
336
- /**
337
- * @return
338
- *
339
- * rqFileRes[0] : Array[0 = global file true, 1 = tmp endpoint file, 2 = pool_base type ]
340
- * rqFileRes[1] : Text require file
341
- *
342
- */
343
- // check global file exists.
344
- if(rqFileRes[0][0]) {
345
- logUpdate(": Initialize...");
346
- // check for remove first slash/backslash and replace / or \ or multiple slash/backslash to one slash for route endpoint name
347
- routeEndpoints = routeEndpoints.replace(/^[\\\/]+/, "").replace(/[\\\/]+/g, "/");
348
- // timeout generate endpoint and replace content
349
- setTimeout(() => {
350
- // generater endpoint
351
- this.makeFolder(endpointsPath + subFolder)
352
- .then(this.copy.bind(this, tmpEndpointsPath, fullEndpoints))
353
- .then(this.contentReplace.bind(this, fullEndpoints, {
354
- 'endpoint': routeEndpoints,
355
- 'endpointName': endpoints,
356
- 'rq': rqFileRes[1],
357
- }))
358
- // generater test
359
- .then(this.makeFolder.bind(this, testPath + subFolder))
360
- .then(this.copy.bind(this, tmpSpecPath, fullTest))
361
- .then(this.contentReplace.bind(this, fullTest, {
362
- 'endpoint': routeEndpoints,
363
- 'endpointName': endpoints
364
- }))
365
- .then(logUpdate("\n Processing   The endpoint `" + endpoints + "` it's generating..."))
366
- .then(generated => logUpdate(generated))
367
- .catch(err => {
368
- throw err;
369
- });
370
- }, 2000);
371
- }
372
- });
373
- } else {
374
- resolve("\n Warning  The endpoint `" + endpoints + "` it's duplicated.");
375
- }
376
- } catch (error) {
377
- reject(error);
378
- }
379
- });
380
- }
381
-
382
- isModelFound(modelArr) {
383
- return new Promise((resolve, reject) => {
384
- try {
385
- if(modelArr !== undefined) {
386
- if(modelArr.length) {
387
- modelArr.map((data, key) => {
388
- if (!this.fs.existsSync('./src/models/' + data.concat('.js'))) {
389
- resolve(data);
390
- }
391
- if (modelArr.length == key+1) {
392
- resolve(true);
393
- }
394
- });
395
- } else {
396
- resolve(false);
397
- }
398
- } else {
399
- resolve(false);
400
- }
401
- } catch (error) {
402
- reject(error);
403
- }
404
- });
405
- }
406
-
407
- makeModel() {
408
- return new Promise((resolve, reject) => {
409
- try {
410
- // declare path model file
411
- let tmpModelsPath = __dirname;
412
- // read global.config.js file for check pool_base for generate model file
413
- this.fs.readFile("./global.config.js", 'utf8', (err, data) => {
414
- if (err) {
415
- resolve("\n Faltal  Can't read `global.config.js` file.", err);
416
- } else {
417
- let buffer = Buffer.from(data);
418
- let buf2str = buffer.toString();
419
- let buf2json = JSON.parse(JSON.stringify(buf2str));
420
- let pool_base = /global.pool_base\s+=\s+(?:"|')([^"]+)(?:"|')(?:\r|\n|$|;|\r)/i.exec(buf2json);
421
- if (pool_base) {
422
- // read app.config.js file for get db connect name
423
- this.fs.readFile("./app.config.js", 'utf8', (appErr, appData) => {
424
- if (appErr) {
425
- resolve("\n Faltal  Can't read `app.config.js` file.", appErr);
426
- } else {
427
- let appBuffer = Buffer.from(appData);
428
- let appBuf2str = appBuffer.toString();
429
- let appBuf2json = JSON.parse(JSON.stringify(appBuf2str));
430
- let appBuf2eval = eval(appBuf2json);
431
- // choose one of database connect name
432
- inquirer.prompt([ {
433
- type: "list",
434
- name: "selectDbConnect",
435
- message: "Please select database connect name:",
436
- choices: appBuf2eval.database_config.map(e => e.name),
437
- } ]).then(dbSelected => {
438
- // check pool_base
439
- if (pool_base[ 1 ] == "basic") {
440
- tmpModelsPath += '/_models_basic';
441
- this.generateModel(tmpModelsPath, dbSelected.selectDbConnect, appBuf2eval, pool_base[1])
442
- .then(console.log)
443
- .catch(console.log);
444
- } else if (pool_base[ 1 ] == "sequelize") {
445
- tmpModelsPath += '/_models';
446
- this.generateModel(tmpModelsPath, dbSelected.selectDbConnect, appBuf2eval, pool_base[1])
447
- .then(console.log)
448
- .catch(console.log);
449
- } else {
450
- resolve("\n Faltal  The pool_base in `global.config.js` file does not match the specific.");
451
- }
452
- });
453
- }
454
- });
455
- } else {
456
- resolve("\n Faltal  The pool_base in `global.config.js` file is not found.");
457
- }
458
- }
459
- });
460
- } catch (error) {
461
- reject(error);
462
- }
463
- });
464
- }
465
-
466
- updateModel() {
467
- return new Promise((resolve, reject) => {
468
- try {
469
- // 1. อ่านการตั้งค่าจาก global.config และ app.config เหมือนตอนสร้าง Model
470
- this.fs.readFile("./global.config.js", 'utf8', (err, globalData) => {
471
- if (err) return resolve("\n [101m Faltal [0m Can't read `global.config.js` file.");
472
-
473
- this.fs.readFile("./app.config.js", 'utf8', (appErr, appData) => {
474
- if (appErr) return resolve("\n [101m Faltal [0m Can't read `app.config.js` file.");
475
-
476
- let appBuf2eval = eval(appData);
477
- let modelName = this.special; // <table_name>
478
- let modelPath = `./src/models/${modelName.charAt(0).toUpperCase() + modelName.slice(1)}.js`;
479
-
480
- // ตรวจสอบว่ามีไฟล์ Model เดิมอยู่หรือไม่
481
- if (!this.fs.existsSync(modelPath)) {
482
- return resolve(`\n [103m [90m Warning [0m [0m Model file \`${modelName}\` not found to update.`);
483
- }
484
-
485
- // ให้ผู้ใช้เลือก Connection ที่ต้องการดึง Schema ใหม่
486
- inquirer.prompt([{
487
- type: "list",
488
- name: "selectDbConnect",
489
- message: " [93mSelect database connection to sync schema: [0m",
490
- choices: appBuf2eval.database_config.map(e => e.name),
491
- }]).then(dbSelected => {
492
-
493
- // 2. ดึง Schema ใหม่จาก DB
494
- const { connectForGenerateModel } = require("../databases/test");
495
- let newModelClassName = modelName.split("_").map(e => e.charAt(0).toUpperCase() + e.slice(1)).join("");
496
-
497
- connectForGenerateModel(dbSelected.selectDbConnect, modelName, appBuf2eval.database_config, (err, tableSchema, tableName) => {
498
- if (err) return reject(err);
499
-
500
- // 3. แปลง Schema เป็น Code String
501
- this.rawSchemaTable(dbSelected.selectDbConnect, newModelClassName, tableName, tableSchema, (SchemaErr, rawSchema) => {
502
- if (SchemaErr) return reject(SchemaErr);
503
-
504
- // 4. อ่านไฟล์เดิมและทำการ Replace เฉพาะส่วน Schema
505
- this.fs.readFile(modelPath, 'utf8', (readErr, currentContent) => {
506
- if (readErr) return reject(readErr);
507
-
508
- // ใช้ Regex เพื่อหาช่วงของ Schema(...).define(...) และเปลี่ยนเฉพาะข้างใน
509
- // โดยจะหาตั้งแต่ Schema(sql.db).define("table", { จนถึง });
510
- const regex = /const\s+\w+\s+=\s+Schema\(sql\.\w+\)\.define\([\s\S]*?\}\);/;
511
-
512
- if (regex.test(currentContent)) {
513
- let updatedContent = currentContent.replace(regex, rawSchema);
514
-
515
- this.fs.writeFile(modelPath, updatedContent, 'utf8', (writeErr) => {
516
- if (writeErr) return reject(writeErr);
517
- resolve(`\n [102m [90m Passed [0m [0m The model \`${modelName}\` schema has been updated.`);
518
- });
519
- } else {
520
- resolve("\n [101m Faltal [0m Could not find Schema definition pattern in the file.");
521
- }
522
- });
523
- });
524
- });
525
- });
526
- });
527
- });
528
- } catch (error) {
529
- reject(error);
530
- }
531
- });
532
- }
533
-
534
- generateModel(tmpModelsPath, dbSelected, appBuf2eval, pool_base) {
535
- return new Promise((resolve, reject) => {
536
- try {
537
- // Show generating msg
538
- const frames = [
539
- '\n[-] Generating',
540
- '\n[\\] Generating.',
541
- '\n[|] Generating..',
542
- '\n[/] Generating...'
543
- ];
544
- let i = 0;
545
- var refreshGenerateIntervalId = null;
546
- // Save model folder
547
- let modelPath = './src/models/';
548
- // Argument join `slash`
549
- let arg = this.argument.replace(/^\/+|\/+$/g, '');
550
- arg = arg.split('/');
551
- let models = arg.pop();
552
- let oriModelsName = models.slice(0);
553
- models = models.charAt(0).toUpperCase() + models.slice(1);
554
- let newModel = models.split("_").map(e => e.charAt(0).toUpperCase() + e.slice(1)).join("");
555
- let subFolder = arg.join('/');
556
- // Declare models
557
- let fullModels = modelPath + subFolder.concat('/') + models.concat('.js');
558
-
559
- /**
560
- * All properties
561
- *
562
- * @tmpModelPath String : path to keep generate model file
563
- * @dbSelect String : database connection name
564
- * @appBug2eval Object : database connection object
565
- * @pool_base Object : pool base connection
566
- *
567
- * @i Number : loop count running...
568
- * @refreshGenerateIntervalId Object : for clear interval id
569
- *
570
- * @arg String : argument from typing
571
- * @models String : model name
572
- * @oriModelsName String : original model name from typing
573
- * @newModel String : model first upper case
574
- * @subFolder String : agument sub folder
575
- * @fullModels String : full model path and model file name
576
- *
577
- */
578
- // Check file exists
579
- if (!this.fs.existsSync(fullModels)) {
580
- // Check pool base is basic|sequelize
581
- if(pool_base == "basic") {
582
- // Generate basic model
583
- this.makeFolder(modelPath + subFolder)
584
- .then(this.copy.bind(this, tmpModelsPath, fullModels))
585
- .then(this.modelContentReplace.bind(this, fullModels, {
586
- 'modelName': oriModelsName,
587
- 'dbSelected': dbSelected,
588
- }))
589
- .then(
590
- refreshGenerateIntervalId = setInterval(() => {
591
- const frame = frames[i = ++i % frames.length];
592
- logUpdate(`${frame}`);
593
- }, 300)
594
- )
595
- .then(generated => {
596
- logUpdate(generated);
597
- clearInterval(refreshGenerateIntervalId);
598
- })
599
- .catch(err => {
600
- throw err;
601
- });
602
- } else if(pool_base == "sequelize") {
603
- // Gether table schema
604
- connectForGenerateModel(dbSelected, oriModelsName, appBuf2eval.database_config, (err, tableSchema, tableName) => {
605
- if(err) {
606
- throw logUpdate("\n Faltal ", String(err), "\n");
607
- } else {
608
- // Raw model schema
609
- this.rawSchemaTable(dbSelected, newModel, tableName, tableSchema, (SchemaErr, rawSchema) => {
610
- if(err) {
611
- throw logUpdate("\n Faltal  RAW Schema ERR:", String(SchemaErr), "\n");
612
- } else {
613
- // Generate sequelize model
614
- this.makeFolder(modelPath + subFolder)
615
- .then(this.copy.bind(this, tmpModelsPath, fullModels))
616
- .then(this.modelContentReplace.bind(this, fullModels, {
617
- 'modelName': oriModelsName,
618
- 'modelNameUppercase': newModel,
619
- 'modelStructure': rawSchema,
620
- }))
621
- .then(
622
- refreshGenerateIntervalId = setInterval(() => {
623
- const frame = frames[i = ++i % frames.length];
624
- logUpdate(`${frame}`);
625
- }, 300)
626
- )
627
- .then(generated => {
628
- logUpdate(generated);
629
- clearInterval(refreshGenerateIntervalId);
630
- })
631
- .catch(err => {
632
- throw err;
633
- });
634
- }
635
- });
636
- }
637
- });
638
- } else {
639
- // Fallback (When Accident)
640
- resolve("\n Faltal  The pool_base in `global.config.js` file does not match the specific.");
641
- }
642
- } else {
643
- resolve("\n Warning  The model `" + models + "` it's duplicated.");
644
- }
645
- } catch (error) {
646
- reject(error);
647
- }
648
- });
649
- }
650
-
651
- rawSchemaTable(dbNameSelected, newModelName, tableName, modelSchema, cb) {
652
- try {
653
- // Function map type
654
- const mapToSequelizeType = (rawType) => {
655
- const type = rawType.toUpperCase();
656
- // Assign all conditions
657
- if (type.includes('INT')) return 'DataTypes.INTEGER';
658
- if (type.includes('BIGINT')) return 'DataTypes.BIGINT';
659
- if (type.includes('FLOAT')) return 'DataTypes.FLOAT';
660
- if (type.includes('DOUBLE')) return 'DataTypes.DOUBLE';
661
- if (type.includes('DECIMAL')) return 'DataTypes.DECIMAL';
662
- if (type.includes('BOOLEAN') || type === 'TINYINT(1)') return 'DataTypes.BOOLEAN';
663
- if (type.includes('CHAR')) {
664
- const match = type.match(/\((\d+)\)/);
665
- const length = match ? match[1] : '255';
666
- return `DataTypes.STRING(${length})`;
667
- }
668
- if (type.includes('VARCHAR')) {
669
- const match = type.match(/\((\d+)\)/);
670
- const length = match ? match[1] : '255';
671
- return `DataTypes.STRING(${length})`;
672
- }
673
- if (type.includes('TEXT')) return 'DataTypes.TEXT';
674
- if (type.includes('DATE')) return 'DataTypes.DATE';
675
- if (type.includes('TIME')) return 'DataTypes.TIME';
676
- if (type.includes('JSON')) return 'DataTypes.JSON';
677
- if (type.includes('UUID')) return 'DataTypes.UUID';
678
- if (type.includes('BLOB')) return 'DataTypes.BLOB';
679
- if (type.includes('ENUM')) return 'DataTypes.ENUM'; // Needs manual values
680
- if (type.includes('GEOMETRY')) return 'DataTypes.GEOMETRY';
681
- // Fallback
682
- return `DataTypes.STRING`;
683
- };
684
- // Looping key fields
685
- const fields = Object.entries(modelSchema).map(([name, props]) => {
686
- // Declare line
687
- const lines = [];
688
- // Push line and assign space for beautiful
689
- lines.push(` type: ${mapToSequelizeType(props.type)},`);
690
- lines.push(` allowNull: ${props.allowNull},`);
691
- // Check is primary key
692
- if (props.primaryKey) lines.push(` primaryKey: true,`);
693
- if (props.autoIncrement) lines.push(` autoIncrement: true,`);
694
- //if (props.comment) lines.push(` comment: '${props.comment}',`);
695
-
696
- // Handle defaultValue
697
- if (props.defaultValue !== null && props.defaultValue !== undefined) {
698
- const defaultVal = String(props.defaultValue).toUpperCase();
699
- if (
700
- defaultVal === 'CURRENT_TIMESTAMP' ||
701
- defaultVal === 'NOW()' ||
702
- defaultVal.includes('CURRENT_TIMESTAMP')
703
- ) {
704
- lines.push(` defaultValue: DataTypes.NOW,`);
705
- } else if (defaultVal === 'UUID()' || defaultVal === 'uuid()' || defaultVal === 'UUID') {
706
- lines.push(` defaultValue: DataTypes.UUIDV4,`);
707
- } else if (typeof props.defaultValue === 'string') {
708
- lines.push(` defaultValue: '${props.defaultValue}',`);
709
- } else {
710
- lines.push(` defaultValue: ${props.defaultValue},`);
711
- }
712
- }
713
- // Finally
714
- return ` ${name}: {\n${lines.join('\n')}\n }`;
715
- });
716
- // Callback
717
- cb(null, `const ${newModelName} = Schema(sql.${dbNameSelected}).define("${tableName}", {\n${fields.join(',\n')}\n});`);
718
- } catch (error) {
719
- cb(error, null);
720
- }
721
- }
722
-
723
- makeHelper() {
724
- return new Promise((resolve, reject) => {
725
- try {
726
- let tmpHelpersPath = __dirname + '/_helpers';
727
- let helperPath = './src/helpers/';
728
- // argument join `slash`
729
- let arg = this.argument.replace(/^\/+|\/+$/g, '');
730
- arg = arg.split('/');
731
- let helpers = arg.pop();
732
- helpers = helpers.charAt(0).toUpperCase() + helpers.slice(1);
733
- let subFolder = arg.join('/');
734
- // helpers
735
- let fullHelpers = helperPath + subFolder.concat('/') + helpers.concat('.js');
736
- // check file exists
737
- if (!this.fs.existsSync(fullHelpers)) {
738
- // generater model
739
- this.makeFolder(helperPath + subFolder)
740
- .then(this.copy.bind(this, tmpHelpersPath, fullHelpers))
741
- .then(logUpdate("\n Processing  The helper `" + helpers + "` it's generating..."))
742
- .then(logUpdate("\n Passed  The helper `" + helpers + "` it's generated."))
743
- .catch(err => {
744
- throw err;
745
- });
746
- } else {
747
- resolve("\n Warning  The helper `" + helpers + "` it's duplicated.");
748
- }
749
- } catch (error) {
750
- reject(error);
751
- }
752
- });
753
- }
754
-
755
- makeFolder(path) {
756
- /**
757
- * @param path path to make
758
- *
759
- * @return new full path
760
- *
761
- */
762
- return new Promise((resolve, reject) => {
763
- try {
764
- const mkdirp = require("mkdirp");
765
- mkdirp(path)
766
- .then(p => resolve(p))
767
- .catch(err => reject(err));
768
- } catch (error) {
769
- reject(error);
770
- }
771
- });
772
- }
773
-
774
- makePassportInit() {
775
- return new Promise((resolve, reject) => {
776
- try {
777
- let passport_config_file = __dirname + '/../configure/passport.config.js';
778
- let passport_config_paste_point = "./passport.config.js";
779
- if (!this.fs.existsSync(passport_config_paste_point)) {
780
- this.copy(passport_config_file, passport_config_paste_point)
781
- .then(resolve("\n Passed  The `passport-jwt` is initialized."))
782
- .catch(err => console.log(err));
783
- } else {
784
- resolve("\n Warning  The `passport-jwt` already is initialized.");
785
- }
786
- } catch (error) {
787
- reject(error);
788
- }
789
- });
790
- }
791
-
792
- makeAddOnInit() {
793
- return new Promise((resolve, reject) => {
794
- try {
795
- let tmpEndpointsPath = __dirname + '/_scheduler';
796
- let add_on_paste_point = "Scheduler.js";
797
- let folder_add_on = "./src/";
798
- if (!this.fs.existsSync(folder_add_on + add_on_paste_point)) {
799
- this.makeFolder(folder_add_on)
800
- .then(this.copy.bind(this, tmpEndpointsPath, folder_add_on + add_on_paste_point))
801
- .then(resolve("\n Passed  The `Scheduler` is initialized."))
802
- .catch(err => console.log(err));
803
- } else {
804
- resolve("\n Warning  The `Scheduler` already is initialized.");
805
- }
806
- } catch (error) {
807
- reject(error);
808
- }
809
- });
810
- }
811
-
812
- copy(path, to) {
813
- /**
814
- * @param path old path file
815
- * @param to save to new path file
816
- *
817
- * @return new path file
818
- */
819
- return new Promise((resolve, reject) => {
820
- try {
821
- if (!this.fs.existsSync(to)) {
822
- if (this.fs.ReadStream(path).pipe(this.fs.createWriteStream(to))) {
823
- resolve(to);
824
- } else {
825
- throw err;
826
- }
827
- } else {
828
- resolve(to);
829
- }
830
- } catch (error) {
831
- reject(error);
832
- }
833
- });
834
- }
835
-
836
- contentReplace(pathFile, textCondition) {
837
- return new Promise((resolve, reject) => {
838
- try {
839
- let endpoint = textCondition.endpoint;
840
- let endpointName = textCondition.endpointName;
841
- let rq = textCondition.rq;
842
- let modelName = textCondition.modelName;
843
- // delay for generator
844
- setTimeout(() => {
845
- this.fs.readFile(pathFile, 'utf8', (err, data) => {
846
- if (err) {
847
- throw err;
848
- } else {
849
- // content replace
850
- let text = data.replace(new RegExp('{{endpoint}}', 'g'), endpoint);
851
- text = text.replace(new RegExp('{{endpointName}}', 'g'), endpointName);
852
- text = text.replace(new RegExp('{{requireSomething}}', 'g'), rq);
853
- text = text.replace(new RegExp('{{modelName}}', 'g'), modelName);
854
- setTimeout(() => {
855
- // writing the file
856
- this.fs.writeFile(pathFile, text, 'utf8', (err) => {
857
- if (err) {
858
- throw err;
859
- } else {
860
- resolve("\n Passed  The endpoint `" + endpointName + "` it's generated.");
861
- }
862
- });
863
- }, 1000);
864
- }
865
- })
866
- }, 1000);
867
- } catch (error) {
868
- reject(error);
869
- }
870
- });
871
- }
872
-
873
- modelContentReplace(pathFile, textCondition) {
874
- return new Promise((resolve, reject) => {
875
- try {
876
- let modelName = textCondition.modelName;
877
- // delay for generator
878
- setTimeout(() => {
879
- this.fs.readFile(pathFile, 'utf8', (err, data) => {
880
- if (err) {
881
- throw err;
882
- } else {
883
- // content replace
884
- let text = data.replace(new RegExp('{{modelName}}', 'g'), modelName);
885
- text = text.replace(new RegExp('{{dbSelected}}', 'g'), textCondition.dbSelected);
886
- // check add model name text uppercase
887
- if (Object.keys(textCondition).length > 1) {
888
- text = text.replace(new RegExp('{{modelStructure}}', 'g'), textCondition.modelStructure);
889
- text = text.replace(new RegExp('{{modelNameUppercase}}', 'g'), textCondition.modelNameUppercase);
890
- }
891
- // writing the file
892
- this.fs.writeFile(pathFile, text, 'utf8', (err) => {
893
- if (err) {
894
- throw err;
895
- } else {
896
- resolve("\n Passed  The model `" + modelName + "` it's generated.");
897
- }
898
- });
899
- }
900
- })
901
- }, 2000);
902
- } catch (error) {
903
- reject(error);
904
- }
905
- });
906
- }
907
-
908
- appKeyGenerator(length) {
909
- return new Promise((resolve, reject) => {
910
- try {
911
- let md5 = require("md5");
912
- let secret = require(__dirname + "/../../../lib/src/salt").salt;
913
- let result = '';
914
- let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
915
- let charactersLength = characters.length;
916
- for (var i = 0; i < length; i++) {
917
- result += characters.charAt(Math.floor(Math.random() * charactersLength));
918
- }
919
- resolve(md5(result + secret));
920
- } catch (error) {
921
- reject(error);
922
- }
923
- });
924
- }
925
-
926
- generateKeyConfigFile() {
927
- return new Promise((resolve, reject) => {
928
- try {
929
- this.fs.readFile("app.config.js", 'utf8', (err, data) => {
930
- if (err) {
931
- throw err;
932
- } else {
933
- // edit or add property
934
- let buffer = Buffer.from(data);
935
- let buf2str = buffer.toString();
936
- let buf2json = JSON.parse(JSON.stringify(buf2str));
937
- let buf2eval = eval(buf2json);
938
- let oldSecret = buf2eval.main_config.app_key;
939
- // generate new key secret
940
- this.appKeyGenerator(8).then(newAppSecret => {
941
- // content replace
942
- let text = data.replace(new RegExp(oldSecret, 'g'), newAppSecret);
943
- // writing the file
944
- this.fs.writeFile("app.config.js", text, 'utf8', (err) => {
945
- if (err) {
946
- throw err;
947
- } else {
948
- resolve("\n Passed  App secret it's new generated.");
949
- }
950
- });
951
- });
952
- }
953
- });
954
- } catch (error) {
955
- reject(error);
956
- }
957
- });
958
- }
959
-
960
- help() {
961
- return new Promise((resolve, reject) => {
962
- try {
963
- this.fs.readFile(__dirname + "/_help", 'utf8', function (err, data) {
964
- if (err) {
965
- throw err;
966
- }
967
- resolve(data);
968
- });
969
- } catch (error) {
970
- reject(err);
971
- }
972
- });
973
- }
974
-
975
- embed(argv) {
976
- return new Promise((resolve, reject) => {
977
- try {
978
- this.fs = require('fs');
979
- this.cmd = require('node-cmd');
980
- this.argv = argv;
981
- this.option = argv[ 2 ];
982
- this.argument = argv[ 3 ];
983
- this.special = argv[ 4 ];
984
- this.extra = argv[ 5 ];
985
- resolve(this);
986
- } catch (error) {
987
- reject(err);
988
- }
989
- });
990
- }
991
- }
992
-
1
+ #!/usr/bin/env node
2
+ const logUpdate = require("log-update");
3
+ const inquirer = require('inquirer');
4
+ const walk = require("walk");
5
+ const recast = require("recast");
6
+ const { builders } = require("ast-types");
7
+ const { connectForGenerateModel } = require("../databases/test");
8
+
9
+ class Generator {
10
+ constructor() {
11
+ this.embed(process.argv)
12
+ .then(() => this.init()
13
+ .then((status) => console.log(status))
14
+ .catch((err) => {
15
+ throw err;
16
+ })
17
+ )
18
+ .catch(console.log);
19
+ }
20
+
21
+ init() {
22
+ return new Promise((resolve, reject) => {
23
+ try {
24
+ if (this.option == '-v' || this.option == '--version') {
25
+ // check beech version
26
+ resolve("v" + require(__dirname + "/../../../../package.json").version);
27
+ } else if (this.option == '-h' || this.option == '?' || this.option == '--help') {
28
+ // help for see avaliable command
29
+ this.help()
30
+ .then(help => resolve(help))
31
+ .catch(err => reject(err));
32
+ } else if (this.option == 'make') {
33
+ // generate endpoint
34
+ if (!this.argument) {
35
+ resolve("\n Warning  Please specify endpoints name.");
36
+ } else {
37
+ if (!this.special || this.special == 'undefined') {
38
+ this.make()
39
+ .then(make => resolve(make))
40
+ .catch(err => reject(err));
41
+ } else if (this.special == '--require' || this.special == '-R') {
42
+ // walking model files
43
+ const walkModelPromise = new Promise((resolve) => {
44
+ let walker = walk.walk("./src/models", { followLinks: false });
45
+ let modelFiles = [];
46
+ walker.on("file", (root, stat, next) => {
47
+ let subFolderModel = root.split("src/models\\")[1];
48
+ modelFiles.push((subFolderModel ? subFolderModel + "/" : "") + stat.name.split('.')[0]);
49
+ next();
50
+ });
51
+ walker.on("end", () => {
52
+ if(modelFiles.length) {
53
+ inquirer.prompt([ {
54
+ type: "checkbox",
55
+ name: "selectModel",
56
+ message: "Please select Models:",
57
+ choices: modelFiles.map(e => e.replace(/\\/g, "/")),
58
+ } ]).then(selected => {
59
+ resolve(selected.selectModel);
60
+ });
61
+ } else {
62
+ // model file not found, Only create endpoint
63
+ resolve();
64
+ }
65
+ });
66
+ });
67
+ Promise.all([walkModelPromise]).then((modelSelected) => {
68
+ let myModel = modelSelected[0];
69
+ // check require model exists
70
+ const modelExistsPromise = new Promise((resolve, reject) => {
71
+ this.isModelFound(myModel)
72
+ .then(existsModel => {
73
+ // check exists model
74
+ if (existsModel == false) {
75
+ inquirer.prompt([ {
76
+ type: "confirm",
77
+ name: "confirmModelNF",
78
+ message: "No model found from your selection! Do you want to create the endpoint without requiring any model ?:",
79
+ default: false,
80
+ } ]).then(confirm => {
81
+ if(confirm.confirmModelNF) {
82
+ resolve([true, []]);
83
+ } else {
84
+ resolve([false, []]);
85
+ }
86
+ });
87
+ } else {
88
+ resolve([true, myModel]);
89
+ }
90
+ })
91
+ .catch(err => reject(err));
92
+ });
93
+ // promise all check choose model(s)
94
+ Promise.all([modelExistsPromise]).then((modelRes) => {
95
+ // Say Yes, Making...
96
+ if(modelRes[0][0]) {
97
+ const poolBasePromise = new Promise((resolve) => {
98
+ // Check global config for prepare tmp endpoint
99
+ this.fs.readFile("./global.config.js", 'utf8', (err, data) => {
100
+ if (err) {
101
+ console.log("\n Fatal  Can't read `global.config.js` file.\n", err);
102
+ return; // break;
103
+ } else {
104
+ let buffer = Buffer.from(data);
105
+ let buf2str = buffer.toString();
106
+ let buf2json = JSON.parse(JSON.stringify(buf2str));
107
+ let pool_base = /global.pool_base\s+=\s+(?:"|')([^"]+)(?:"|')(?:\r|\n|$|;|\r)/i.exec(buf2json);
108
+ if (pool_base) {
109
+ let myRequire = modelRes[0][1];
110
+ if (pool_base[ 1 ] == "basic") {
111
+ if(myRequire.length) {
112
+ // declare basic require model file
113
+ let rqr = "";
114
+ myRequire.map((data, key) => {
115
+ let modelName = data.split('/');
116
+ let modelFolder = "";
117
+ modelName = modelName.pop();
118
+ modelName = modelName.charAt(0).toUpperCase() + modelName.slice(1);
119
+ let newModel = modelName.split("_").map(e => e.charAt(0).toUpperCase() + e.slice(1)).join("");
120
+ modelFolder = data.substring(0, data.lastIndexOf('/') + 1).replace(/\\/g, "/");
121
+ rqr += `const ${newModel} = require(\"@/models/${modelFolder + modelName}\");\n`;
122
+ if(myRequire.length == key+1) {
123
+ resolve([[rqr], myRequire]);
124
+ }
125
+ });
126
+ } else {
127
+ resolve([[]]);
128
+ }
129
+ } else if (pool_base[ 1 ] == "sequelize") {
130
+ // check null require resolve it
131
+ if(myRequire.length) {
132
+ // declare basic require model file
133
+ let rqr = "";
134
+ myRequire.map((data, key) => {
135
+ let modelName = data.split('/');
136
+ let modelFolder = "";
137
+ modelName = modelName.pop();
138
+ modelName = modelName.charAt(0).toUpperCase() + modelName.slice(1);
139
+ let newModel = modelName.split("_").map(e => e.charAt(0).toUpperCase() + e.slice(1)).join("");
140
+ modelFolder = data.substring(0, data.lastIndexOf('/') + 1).replace(/\\/g, "/");
141
+ rqr += `const { ${newModel} } = require(\"@/models/${modelFolder + modelName}\");\n`;
142
+ if(myRequire.length == key+1) {
143
+ resolve([[rqr], myRequire]);
144
+ }
145
+ });
146
+ } else {
147
+ resolve([[]]);
148
+ }
149
+ } else {
150
+ console.log("\n Fatal  The pool_base in `global.config.js` file does not match the specific.");
151
+ }
152
+ } else {
153
+ console.log("\n Fatal  The pool_base in `global.config.js` file is not found.");
154
+ }
155
+ }
156
+ });
157
+ });
158
+ // Final promise for make
159
+ Promise.all([poolBasePromise]).then((rqrRes) => {
160
+ // make with require model file
161
+ this.make(rqrRes[0])
162
+ .then(make => resolve(make))
163
+ .catch(err => reject(err));
164
+ });
165
+ } else {
166
+ // Say No, Nothing...
167
+ resolve(": Say no.");
168
+ }
169
+ });
170
+ });
171
+ } else if (this.special == '--model' || this.special == '-M') {
172
+ this.makeModel()
173
+ .then(make => resolve(make))
174
+ .catch(err => reject(err));
175
+ } else if (this.special == '--helper') {
176
+ this.makeHelper()
177
+ .then(make => resolve(make))
178
+ .catch(err => reject(err));
179
+ } else {
180
+ resolve("\n Fatal  commnad it's not available.");
181
+ }
182
+ }
183
+ } else if (this.option == 'update') {
184
+ if(this.argument == 'model') {
185
+ if(this.special) {
186
+ if (!this.special) {
187
+ resolve("\n Warning  Please specify model name to update.");
188
+ } else {
189
+ let noComment = this.noComment || false;
190
+ this.updateModel(noComment)
191
+ .then(res => resolve(res))
192
+ .catch(err => reject(err));
193
+ }
194
+ } else {
195
+ resolve("\n Warning  Please specify model name to update.");
196
+ }
197
+ } else if(this.argument != 'model') {
198
+ resolve("\n Fatal  commnad it's not available.");
199
+ } else {
200
+ resolve("\n Warning  Please specify what you want to update.");
201
+ }
202
+ } else if (this.option == 'passport') {
203
+ if (this.argument == "init") {
204
+ this.makePassportInit()
205
+ .then(make => resolve(make))
206
+ .catch(err => reject(err));
207
+ } else {
208
+ resolve("\n Warning  Using `passport init` for initiate passport-jwt.");
209
+ }
210
+ } else if (this.option == "key:generate" || this.option == "key:gen") {
211
+ this.generateKeyConfigFile()
212
+ .then(resGenKey => resolve(resGenKey))
213
+ .catch(err => reject(err));
214
+ } else if (this.option && this.option.slice(0, 5) == "hash:") {
215
+ const { HashIt, Z } = require(__dirname + "/../helpers/math");
216
+ if(this.option.length > 5) {
217
+ Z((err, ak) => {
218
+ if(err) {
219
+ logUpdate(err);
220
+ } else {
221
+ let txt = this.option.split(":");
222
+ HashIt(txt, ak, null, (5).toString().length, (hashed) => {
223
+ logUpdate("\n" + hashed);
224
+ });
225
+ }
226
+ });
227
+ } else {
228
+ inquirer.prompt([ {
229
+ type: "confirm",
230
+ name: "confirmHashNoText",
231
+ message: "No text to hash, Do you want to continue ?:",
232
+ } ]).then(confirm => {
233
+ if(confirm.confirmHashNoText) {
234
+ Z((err, ak) => {
235
+ if(err) {
236
+ logUpdate(err);
237
+ } else {
238
+ let txt = this.option.split(":");
239
+ HashIt(txt, ak, null, (5).toString().length, (hashed) => {
240
+ logUpdate("\n" + hashed);
241
+ });
242
+ }
243
+ });
244
+ } else {
245
+ // no text to hash say no.
246
+ resolve(": Say no.");
247
+ }
248
+ });
249
+
250
+ }
251
+ } else if (this.option == "skd") {
252
+ if (this.argument == "init") {
253
+ this.makeAddOnInit()
254
+ .then(make => resolve(make))
255
+ .catch(err => reject(err));
256
+ } else {
257
+ resolve("\n Warning  Using `skd init` for initiate Job Scheduler.");
258
+ }
259
+ } else {
260
+ // help for see avaliable command
261
+ this.help()
262
+ .then(help => resolve(help))
263
+ .catch(err => reject(err));
264
+ }
265
+ } catch (error) {
266
+ reject(error);
267
+ }
268
+ });
269
+ }
270
+
271
+ make(rq = null) {
272
+ return new Promise((resolve, reject) => {
273
+ try {
274
+ // prepare data
275
+ let tmpEndpointsPath = __dirname;
276
+ let tmpSpecPath = __dirname + '/_spec';
277
+ let endpointsPath = './src/endpoints/';
278
+ let testPath = './__tests__/unit/endpoints/';
279
+ // argument join `slash`
280
+ let arg = this.argument.replace(/^\/+|\/+$/g, '');
281
+ arg = arg.split('/');
282
+ let endpoints = arg.pop();
283
+ let subFolder = arg.join('/');
284
+ // endpoints file
285
+ let fullEndpoints = endpointsPath + subFolder.concat('/') + endpoints.concat('-endpoints.js');
286
+ let routeEndpoints = ((arg.length > 0) ? '/' : '') + subFolder.concat('/') + endpoints;
287
+ // test file
288
+ let fullTest = testPath + subFolder + '/' + endpoints.concat('-endpoints.spec.js');
289
+
290
+ // Check exists endpoint file
291
+ if (!this.fs.existsSync(fullEndpoints)) {
292
+ // STEP 0 : Check global config for prepare tmp endpoint
293
+ const promise0 = new Promise((resolve) => {
294
+ this.fs.readFile("./global.config.js", 'utf8', (err, data) => {
295
+ if (err) {
296
+ console.log("\n Fatal  Can't read `global.config.js` file.", err);
297
+ resolve([false, null, null]);
298
+ } else {
299
+ let buffer = Buffer.from(data);
300
+ let buf2str = buffer.toString();
301
+ let buf2json = JSON.parse(JSON.stringify(buf2str));
302
+ let pool_base = /global.pool_base\s+=\s+(?:"|')([^"]+)(?:"|')(?:\r|\n|$|;|\r)/i.exec(buf2json);
303
+ if (pool_base) {
304
+ if (pool_base[ 1 ] == "basic") {
305
+ resolve([true, tmpEndpointsPath += '/_endpoints_basic', pool_base[ 1 ]]);
306
+ } else if (pool_base[ 1 ] == "sequelize") {
307
+ resolve([true, tmpEndpointsPath += '/_endpoints', pool_base[ 1 ]]);
308
+ } else {
309
+ console.log("\n Fatal  The pool_base in `global.config.js` file does not match the specific.");
310
+ resolve([false, null, null]);
311
+ }
312
+ } else {
313
+ console.log("\n Fatal  The pool_base in `global.config.js` file is not found.");
314
+ resolve([false, null, null]);
315
+ }
316
+ }
317
+ });
318
+ });
319
+
320
+ // STEP 1 : Format Require model file
321
+ const promise1 = new Promise((resolve) => {
322
+ // prepare state require file if `rq[0]` not exists
323
+ let requireFile = '// You can require something \n';
324
+ // check exists requrie files
325
+ if(rq) {
326
+ if (rq[0].length) {
327
+ requireFile = '';
328
+ // make require multiples line
329
+ rq[0].map((data, key) => {
330
+ requireFile += data;
331
+ if(rq[0].length == key+1) {
332
+ resolve(requireFile);
333
+ }
334
+ })
335
+ } else {
336
+ resolve(requireFile);
337
+ }
338
+ } else {
339
+ resolve(requireFile);
340
+ }
341
+ });
342
+ // promise all generate endpoint with require(s)
343
+ Promise.all([promise0, promise1]).then((rqFileRes) => {
344
+ /**
345
+ * @return
346
+ *
347
+ * rqFileRes[0] : Array[0 = global file true, 1 = tmp endpoint file, 2 = pool_base type ]
348
+ * rqFileRes[1] : Text require file
349
+ *
350
+ */
351
+ // check global file exists.
352
+ if(rqFileRes[0][0]) {
353
+ logUpdate(": Initialize...");
354
+ // check for remove first slash/backslash and replace / or \ or multiple slash/backslash to one slash for route endpoint name
355
+ routeEndpoints = routeEndpoints.replace(/^[\\\/]+/, "").replace(/[\\\/]+/g, "/");
356
+ // timeout generate endpoint and replace content
357
+ setTimeout(() => {
358
+ // generater endpoint
359
+ this.makeFolder(endpointsPath + subFolder)
360
+ .then(this.copy.bind(this, tmpEndpointsPath, fullEndpoints))
361
+ .then(this.contentReplace.bind(this, fullEndpoints, {
362
+ 'endpoint': routeEndpoints,
363
+ 'endpointName': endpoints,
364
+ 'rq': rqFileRes[1],
365
+ }))
366
+ // generater test
367
+ .then(this.makeFolder.bind(this, testPath + subFolder))
368
+ .then(this.copy.bind(this, tmpSpecPath, fullTest))
369
+ .then(this.contentReplace.bind(this, fullTest, {
370
+ 'endpoint': routeEndpoints,
371
+ 'endpointName': endpoints
372
+ }))
373
+ .then(logUpdate("\n Processing   The endpoint `" + endpoints + "` it's generating..."))
374
+ .then(generated => logUpdate(generated))
375
+ .catch(err => {
376
+ throw err;
377
+ });
378
+ }, 2000);
379
+ }
380
+ });
381
+ } else {
382
+ resolve("\n Warning  The endpoint `" + endpoints + "` it's duplicated.");
383
+ }
384
+ } catch (error) {
385
+ reject(error);
386
+ }
387
+ });
388
+ }
389
+
390
+ isModelFound(modelArr) {
391
+ return new Promise((resolve, reject) => {
392
+ try {
393
+ if(modelArr !== undefined) {
394
+ if(modelArr.length) {
395
+ modelArr.map((data, key) => {
396
+ if (!this.fs.existsSync('./src/models/' + data.concat('.js'))) {
397
+ resolve(data);
398
+ }
399
+ if (modelArr.length == key+1) {
400
+ resolve(true);
401
+ }
402
+ });
403
+ } else {
404
+ resolve(false);
405
+ }
406
+ } else {
407
+ resolve(false);
408
+ }
409
+ } catch (error) {
410
+ reject(error);
411
+ }
412
+ });
413
+ }
414
+
415
+ makeModel() {
416
+ return new Promise((resolve, reject) => {
417
+ try {
418
+ // declare path model file
419
+ let tmpModelsPath = __dirname;
420
+ // read global.config.js file for check pool_base for generate model file
421
+ this.fs.readFile("./global.config.js", 'utf8', (err, data) => {
422
+ if (err) {
423
+ resolve("\n Fatal  Can't read `global.config.js` file.", err);
424
+ } else {
425
+ let buffer = Buffer.from(data);
426
+ let buf2str = buffer.toString();
427
+ let buf2json = JSON.parse(JSON.stringify(buf2str));
428
+ let pool_base = /global.pool_base\s+=\s+(?:"|')([^"]+)(?:"|')(?:\r|\n|$|;|\r)/i.exec(buf2json);
429
+ if (pool_base) {
430
+ // read app.config.js file for get db connect name
431
+ this.fs.readFile("./app.config.js", 'utf8', (appErr, appData) => {
432
+ if (appErr) {
433
+ resolve("\n Fatal  Can't read `app.config.js` file.", appErr);
434
+ } else {
435
+ let appBuffer = Buffer.from(appData);
436
+ let appBuf2str = appBuffer.toString();
437
+ let appBuf2json = JSON.parse(JSON.stringify(appBuf2str));
438
+ let appBuf2eval = eval(appBuf2json);
439
+ // choose one of database connect name
440
+ inquirer.prompt([ {
441
+ type: "list",
442
+ name: "selectDbConnect",
443
+ message: "Please select database connect name:",
444
+ choices: appBuf2eval.database_config.map(e => e.name),
445
+ } ]).then(dbSelected => {
446
+ // check pool_base
447
+ if (pool_base[ 1 ] == "basic") {
448
+ tmpModelsPath += '/_models_basic';
449
+ this.generateModel(tmpModelsPath, dbSelected.selectDbConnect, appBuf2eval, pool_base[1])
450
+ .then(console.log)
451
+ .catch(console.log);
452
+ } else if (pool_base[ 1 ] == "sequelize") {
453
+ tmpModelsPath += '/_models';
454
+ this.generateModel(tmpModelsPath, dbSelected.selectDbConnect, appBuf2eval, pool_base[1])
455
+ .then(console.log)
456
+ .catch(console.log);
457
+ } else {
458
+ resolve("\n Fatal  The pool_base in `global.config.js` file does not match the specific.");
459
+ }
460
+ });
461
+ }
462
+ });
463
+ } else {
464
+ resolve("\n Fatal  The pool_base in `global.config.js` file is not found.");
465
+ }
466
+ }
467
+ });
468
+ } catch (error) {
469
+ reject(error);
470
+ }
471
+ });
472
+ }
473
+
474
+ updateModel(noComment) {
475
+ return new Promise((resolve, reject) => {
476
+ try {
477
+ this.fs.readFile("./global.config.js", 'utf8', (err, globalData) => {
478
+ if (err) return resolve("\n Fatal  Can't read `global.config.js` file.");
479
+ this.fs.readFile("./app.config.js", 'utf8', (appErr, appData) => {
480
+ if (appErr) return resolve("\n Fatal  Can't read `app.config.js` file.");
481
+ const appConfig = eval(appData);
482
+ const rawSpecial = this.special; // "fruit/tropical/banana"
483
+ const pathParts = rawSpecial.split('/');
484
+ const modelNameRaw = pathParts[pathParts.length - 1];
485
+ const subFolder = pathParts.slice(0, pathParts.length - 1).join('/');
486
+ const folderPrefix = subFolder ? `${subFolder}/` : '';
487
+ const finalTargetName = this.customName || modelNameRaw;
488
+ const modelName = this.realTableName || modelNameRaw;
489
+ const modelFileName = finalTargetName.charAt(0).toUpperCase() + finalTargetName.slice(1);
490
+ const modelPath = `./src/models/${folderPrefix}${modelFileName}.js`;
491
+ if (!this.fs.existsSync(modelPath)) {
492
+ return resolve(`\n Warning  Model file \`${modelFileName}\` not found.`);
493
+ }
494
+ // Choose connection for new schema
495
+ inquirer.prompt([{
496
+ type: "list",
497
+ name: "selectDbConnect",
498
+ message: "Select database connection to sync schema:",
499
+ choices: appConfig.database_config.map(e => e.name),
500
+ }]).then(dbSelected => {
501
+ const { connectForGenerateModel } = require("../databases/test");
502
+ // pull new schema from database
503
+ connectForGenerateModel(dbSelected.selectDbConnect, modelName, appConfig.database_config, (dbErr, tableSchema, tableName) => {
504
+ if (dbErr) {
505
+ console.log(dbErr);
506
+ throw dbErr;
507
+ } else {
508
+ inquirer.prompt([{
509
+ type: "confirm",
510
+ name: "confirmUpdateModel",
511
+ message: "Do you want to update the model with the latest database schema ?:",
512
+ default: false,
513
+ }]).then(confirm => {
514
+ if(confirm.confirmUpdateModel) {
515
+ // read current model file for AST
516
+ this.fs.readFile(modelPath, 'utf8', (readErr, code) => {
517
+ if (readErr) return reject(readErr);
518
+ const ast = recast.parse(code);
519
+ let isUpdated = false;
520
+ let definedTableName = null;
521
+ // Check defined table name
522
+ recast.visit(ast, {
523
+ visitCallExpression(path) {
524
+ const node = path.node;
525
+ // Check is .define(...)
526
+ if (node.callee.property && node.callee.property.name === 'define') {
527
+ // Get table name from .define(...)
528
+ if (node.arguments[0] && node.arguments[0].type === 'Literal') {
529
+ definedTableName = node.arguments[0].value;
530
+ }
531
+ }
532
+ return false; // End finding when found define
533
+ }
534
+ });
535
+ // Check match table name between database and model file
536
+ const targetTable = this.realTableName || this.special;
537
+ if (definedTableName !== targetTable) {
538
+ return resolve(`\n Error  Table name mismatch!
539
+ \n - In File defined as: "${definedTableName}"
540
+ \n - Your command requested: "${targetTable}"
541
+ \nNote: Please ensure you are updating the correct model/table.`);
542
+ } else {
543
+ // Function for map raw database type to Sequelize DataTypes AST Node
544
+ const getDataTypeNode = (rawType) => {
545
+ const type = rawType.toUpperCase();
546
+ if (type.includes('INT')) return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('INTEGER'));
547
+ if (type.includes('BIGINT')) return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('BIGINT'));
548
+ if (type.includes('FLOAT')) return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('FLOAT'));
549
+ if (type.includes('DOUBLE')) return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('DOUBLE'));
550
+ if (type.includes('DECIMAL')) return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('DECIMAL'));
551
+ if (type.includes('BOOLEAN') || type === 'TINYINT(1)') return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('BOOLEAN'));
552
+ if (type.includes('CHAR') || type.includes('VARCHAR')) {
553
+ const match = type.match(/\((\d+)\)/);
554
+ const length = match ? match[1] : '255';
555
+ return builders.callExpression(
556
+ builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('STRING')),
557
+ [builders.literal(parseInt(length))]
558
+ );
559
+ }
560
+ if (type.includes('TEXT')) return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('TEXT'));
561
+ if (type.includes('DATE')) return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('DATE'));
562
+ if (type.includes('TIME')) return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('TIME'));
563
+ if (type.includes('JSON')) return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('JSON'));
564
+ if (type.includes('UUID')) return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('UUID'));
565
+ if (type.includes('BLOB')) return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('BLOB'));
566
+ if (type.includes('ENUM')) return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('ENUM'));
567
+ if (type.includes('GEOMETRY')) return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('GEOMETRY'));
568
+ return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('STRING'));
569
+ };
570
+ // recast.visit for find position of Schema(...).define() and update fields
571
+ recast.visit(ast, {
572
+ visitCallExpression(path) {
573
+ const node = path.node;
574
+ if (node.callee.property && node.callee.property.name === 'define' && node.arguments[1] && node.arguments[1].type === 'ObjectExpression') {
575
+ const fieldsObject = node.arguments[1];
576
+ const newFieldNames = Object.keys(tableSchema);
577
+ const updatedProperties = [];
578
+ newFieldNames.forEach(dbFieldName => {
579
+ const dbFieldData = tableSchema[dbFieldName];
580
+ let existingProperty = fieldsObject.properties.find(p => {
581
+ const propName = p.key.name || p.key.value;
582
+ if (propName === dbFieldName) return true;
583
+ if (p.value && p.value.properties) {
584
+ const hasFieldMapping = p.value.properties.find(innerP => (innerP.key.name === 'field' || innerP.key.value === 'field') && (innerP.value.value === dbFieldName));
585
+ if (hasFieldMapping) return true;
586
+ }
587
+ return false;
588
+ });
589
+ const latestFieldProps = [];
590
+ // Prepare properties from Table DB (Type, PK, AI, Default, etc.)
591
+ latestFieldProps.push(builders.property('init', builders.identifier('type'), getDataTypeNode(dbFieldData.type)));
592
+ latestFieldProps.push(builders.property('init', builders.identifier('allowNull'), builders.literal(dbFieldData.allowNull === 'YES' || dbFieldData.allowNull === true)));
593
+ if (dbFieldData.primaryKey) latestFieldProps.push(builders.property('init', builders.identifier('primaryKey'), builders.literal(true)));
594
+ if (dbFieldData.autoIncrement) latestFieldProps.push(builders.property('init', builders.identifier('autoIncrement'), builders.literal(true)));
595
+ if (dbFieldData.unique) latestFieldProps.push(builders.property('init', builders.identifier('unique'), builders.literal(true)));
596
+ // Check if not have variable noComment '--no-comment' to raw add comment
597
+ if (!noComment && dbFieldData.comment) {
598
+ latestFieldProps.push(builders.property('init', builders.identifier('comment'), builders.literal(dbFieldData.comment)));
599
+ }
600
+ if (dbFieldData.defaultValue !== null && dbFieldData.defaultValue !== undefined) {
601
+ const dVal = String(dbFieldData.defaultValue).toUpperCase();
602
+ const defaultNode = (dVal === 'CURRENT_TIMESTAMP' || dVal === 'NOW()')
603
+ ? builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('NOW'))
604
+ : builders.literal(dbFieldData.defaultValue);
605
+ latestFieldProps.push(builders.property('init', builders.identifier('defaultValue'), defaultNode));
606
+ }
607
+ if (!existingProperty) {
608
+ existingProperty = builders.property('init', builders.identifier(dbFieldName), builders.objectExpression(latestFieldProps));
609
+ isUpdated = true;
610
+ } else {
611
+ const currentProps = existingProperty.value.properties;
612
+ latestFieldProps.forEach(newProp => {
613
+ const oldProp = currentProps.find(p => p.key.name === newProp.key.name);
614
+ if (!oldProp || recast.print(oldProp.value).code !== recast.print(newProp.value).code) {
615
+ isUpdated = true;
616
+ if (!oldProp) currentProps.push(newProp);
617
+ else oldProp.value = newProp.value;
618
+ }
619
+ });
620
+ let allowedPropNames = latestFieldProps.map(p => p.key.name);
621
+ allowedPropNames.push('field');
622
+ // remove comment from allowedPropNames if have noComment variable '--no-comment'
623
+ if (!noComment) {
624
+ allowedPropNames.push('comment');
625
+ }
626
+ const nextProps = currentProps.filter(p => allowedPropNames.includes(p.key.name));
627
+ if (nextProps.length !== currentProps.length) {
628
+ isUpdated = true;
629
+ existingProperty.value.properties = nextProps;
630
+ } else {
631
+ // Nothing.
632
+ }
633
+ }
634
+ updatedProperties.push(existingProperty);
635
+ });
636
+ const currentOrder = fieldsObject.properties.map(p => p.key.name || p.key.value).join(',');
637
+ const newOrder = updatedProperties.map(p => p.key.name || p.key.value).join(',');
638
+ if (currentOrder !== newOrder || fieldsObject.properties.length !== updatedProperties.length) isUpdated = true;
639
+ fieldsObject.properties = updatedProperties;
640
+ }
641
+ return false;
642
+ }
643
+ });
644
+ // Write back updated AST, Shout updated
645
+ if (isUpdated) {
646
+ // Write updated AST back to file
647
+ const output = recast.print(ast).code;
648
+ this.fs.writeFile(modelPath, output, 'utf8', (writeErr) => {
649
+ if (writeErr) return reject(writeErr);
650
+ resolve(`\n Updated  The model \`${modelFileName}\` has been updated via AST.`);
651
+ });
652
+ } else {
653
+ resolve(`\n Warning  No new fields found to update for \`${modelFileName}\`.`);
654
+ }
655
+ }
656
+ });
657
+ } else {
658
+ // Nothing.
659
+ resolve(": Say no.");
660
+ }
661
+ });
662
+ }
663
+ });
664
+ }).catch(console.log);
665
+ });
666
+ });
667
+ } catch (error) {
668
+ reject(error);
669
+ }
670
+ });
671
+ }
672
+
673
+ generateModel(tmpModelsPath, dbSelected, appBuf2eval, pool_base) {
674
+ return new Promise((resolve, reject) => {
675
+ try {
676
+ // Show generating msg
677
+ const frames = [
678
+ '\n[-] Generating',
679
+ '\n[\\] Generating.',
680
+ '\n[|] Generating..',
681
+ '\n[/] Generating...'
682
+ ];
683
+ let i = 0;
684
+ var refreshGenerateIntervalId = null;
685
+ // Save model folder
686
+ let modelPath = './src/models/';
687
+ // Argument join `slash`
688
+ let arg = this.argument.replace(/^\/+|\/+$/g, '');
689
+ arg = arg.split('/');
690
+ // New can custom model name using --name <rename_model_name>
691
+ let models = arg.pop();
692
+ let modelsChangeName = this.customModelName || models;
693
+ let oriModelsName = models.slice(0);
694
+ models = models.charAt(0).toUpperCase() + models.slice(1);
695
+ let oriModelsChangeName = modelsChangeName.slice(0);
696
+ modelsChangeName = oriModelsChangeName.charAt(0).toUpperCase() + modelsChangeName.slice(1);
697
+ // New model name with upper case and remove underscore for first letter of each word
698
+ let newModel = modelsChangeName.split(/[-_]/).map(e => e.charAt(0).toUpperCase() + e.slice(1)).join("");
699
+ let subFolder = arg.join('/');
700
+ // Declare models
701
+ let fullModels = modelPath + subFolder.concat('/') + modelsChangeName.concat('.js');
702
+
703
+ /**
704
+ * All properties
705
+ *
706
+ * @tmpModelPath String : path to keep generate model file
707
+ * @dbSelect String : database connection name
708
+ * @appBug2eval Object : database connection object
709
+ * @pool_base Object : pool base connection
710
+ *
711
+ * @i Number : loop count running...
712
+ * @refreshGenerateIntervalId Object : for clear interval id
713
+ *
714
+ * @arg String : argument from typing
715
+ * @models String : model name
716
+ * @oriModelsName String : original model name from typing
717
+ * @newModel String : model first upper case
718
+ * @subFolder String : agument sub folder
719
+ * @fullModels String : full model path and model file name
720
+ *
721
+ */
722
+ // Check file exists
723
+ if (!this.fs.existsSync(fullModels)) {
724
+ // Check pool base is basic|sequelize
725
+ if(pool_base == "basic") {
726
+ // Generate basic model
727
+ this.makeFolder(modelPath + subFolder)
728
+ .then(this.copy.bind(this, tmpModelsPath, fullModels))
729
+ .then(this.modelContentReplace.bind(this, fullModels, {
730
+ 'modelName': oriModelsName,
731
+ 'dbSelected': dbSelected,
732
+ }))
733
+ .then(
734
+ refreshGenerateIntervalId = setInterval(() => {
735
+ const frame = frames[i = ++i % frames.length];
736
+ logUpdate(`${frame}`);
737
+ }, 300)
738
+ )
739
+ .then(generated => {
740
+ logUpdate(generated);
741
+ clearInterval(refreshGenerateIntervalId);
742
+ })
743
+ .catch(err => {
744
+ throw err;
745
+ });
746
+ } else if(pool_base == "sequelize") {
747
+ // Gether table schema
748
+ connectForGenerateModel(dbSelected, oriModelsName, appBuf2eval.database_config, (err, tableSchema, tableName) => {
749
+ if(err) {
750
+ throw logUpdate("\n Fatal ", String(err), "\n");
751
+ } else {
752
+ // Raw model schema
753
+ this.rawSchemaTable(dbSelected, newModel, tableName, tableSchema, this.noComment || undefined, (SchemaErr, rawSchema) => {
754
+ if(err) {
755
+ throw logUpdate("\n Fatal  RAW Schema ERR:", String(SchemaErr), "\n");
756
+ } else {
757
+ // Generate sequelize model
758
+ this.makeFolder(modelPath + subFolder)
759
+ .then(this.copy.bind(this, tmpModelsPath, fullModels))
760
+ .then(this.modelContentReplace.bind(this, fullModels, {
761
+ 'modelName': oriModelsName,
762
+ 'modelNameUppercase': newModel,
763
+ 'modelStructure': rawSchema,
764
+ }))
765
+ .then(
766
+ refreshGenerateIntervalId = setInterval(() => {
767
+ const frame = frames[i = ++i % frames.length];
768
+ logUpdate(`${frame}`);
769
+ }, 300)
770
+ )
771
+ .then(generated => {
772
+ logUpdate(generated);
773
+ clearInterval(refreshGenerateIntervalId);
774
+ })
775
+ .catch(err => {
776
+ throw err;
777
+ });
778
+ }
779
+ });
780
+ }
781
+ });
782
+ } else {
783
+ // Fallback (When Accident)
784
+ resolve("\n Fatal  The pool_base in `global.config.js` file does not match the specific.");
785
+ }
786
+ } else {
787
+ resolve("\n Warning  The model `" + modelsChangeName + "` it's duplicated.");
788
+ }
789
+ } catch (error) {
790
+ reject(error);
791
+ }
792
+ });
793
+ }
794
+
795
+ rawSchemaTable(dbNameSelected, newModelName, tableName, modelSchema, noComment, cb) {
796
+ try {
797
+ // Function map type
798
+ const mapToSequelizeType = (rawType) => {
799
+ const type = rawType.toUpperCase();
800
+ // Assign all conditions
801
+ if (type.includes('INT')) return 'DataTypes.INTEGER';
802
+ if (type.includes('BIGINT')) return 'DataTypes.BIGINT';
803
+ if (type.includes('FLOAT')) return 'DataTypes.FLOAT';
804
+ if (type.includes('DOUBLE')) return 'DataTypes.DOUBLE';
805
+ if (type.includes('DECIMAL')) return 'DataTypes.DECIMAL';
806
+ if (type.includes('BOOLEAN') || type === 'TINYINT(1)') return 'DataTypes.BOOLEAN';
807
+ if (type.includes('CHAR')) {
808
+ const match = type.match(/\((\d+)\)/);
809
+ const length = match ? match[1] : '255';
810
+ return `DataTypes.STRING(${length})`;
811
+ }
812
+ if (type.includes('VARCHAR')) {
813
+ const match = type.match(/\((\d+)\)/);
814
+ const length = match ? match[1] : '255';
815
+ return `DataTypes.STRING(${length})`;
816
+ }
817
+ if (type.includes('TEXT')) return 'DataTypes.TEXT';
818
+ if (type.includes('DATE')) return 'DataTypes.DATE';
819
+ if (type.includes('TIME')) return 'DataTypes.TIME';
820
+ if (type.includes('JSON')) return 'DataTypes.JSON';
821
+ if (type.includes('UUID')) return 'DataTypes.UUID';
822
+ if (type.includes('BLOB')) return 'DataTypes.BLOB';
823
+ if (type.includes('ENUM')) return 'DataTypes.ENUM'; // Needs manual values
824
+ if (type.includes('GEOMETRY')) return 'DataTypes.GEOMETRY';
825
+ // Fallback
826
+ return `DataTypes.STRING`;
827
+ };
828
+ // Looping key fields
829
+ const fields = Object.entries(modelSchema).map(([name, props]) => {
830
+ // Declare line
831
+ const lines = [];
832
+ // Push line and assign space for beautiful
833
+ lines.push(` type: ${mapToSequelizeType(props.type)},`);
834
+ lines.push(` allowNull: ${props.allowNull},`);
835
+ // Check is primary key
836
+ if (props.primaryKey) lines.push(` primaryKey: true,`);
837
+ if (props.autoIncrement) lines.push(` autoIncrement: true,`);
838
+ // Check <noComment> for comment, because some database have noComment comment in field but it's not comment for field, so we need to check it first before assign to comment
839
+ if(!noComment) {
840
+ if (props.comment) lines.push(` comment: "${props.comment}",`);
841
+ }
842
+ // Handle defaultValue
843
+ if (props.defaultValue !== null && props.defaultValue !== undefined) {
844
+ const defaultVal = String(props.defaultValue).toUpperCase();
845
+ if (defaultVal === 'CURRENT_TIMESTAMP' || defaultVal === 'NOW()' || defaultVal.includes('CURRENT_TIMESTAMP')) {
846
+ lines.push(` defaultValue: DataTypes.NOW,`);
847
+ } else if (defaultVal === 'UUID()' || defaultVal === 'uuid()' || defaultVal === 'UUID') {
848
+ lines.push(` defaultValue: DataTypes.UUIDV4,`);
849
+ } else if (typeof props.defaultValue === 'string') {
850
+ lines.push(` defaultValue: "${props.defaultValue}",`);
851
+ } else {
852
+ lines.push(` defaultValue: ${props.defaultValue},`);
853
+ }
854
+ }
855
+ // Finally
856
+ return ` ${name}: {\n${lines.join('\n')}\n }`;
857
+ });
858
+ // Callback
859
+ cb(null, `const ${newModelName} = Schema(sql.${dbNameSelected}).define("${tableName}", {\n${fields.join(',\n')}\n});`);
860
+ } catch (error) {
861
+ cb(error, null);
862
+ }
863
+ }
864
+
865
+ makeHelper() {
866
+ return new Promise((resolve, reject) => {
867
+ try {
868
+ let tmpHelpersPath = __dirname + '/_helpers';
869
+ let helperPath = './src/helpers/';
870
+ // argument join `slash`
871
+ let arg = this.argument.replace(/^\/+|\/+$/g, '');
872
+ arg = arg.split('/');
873
+ let helpers = arg.pop();
874
+ helpers = helpers.charAt(0).toUpperCase() + helpers.slice(1);
875
+ let subFolder = arg.join('/');
876
+ // helpers
877
+ let fullHelpers = helperPath + subFolder.concat('/') + helpers.concat('.js');
878
+ // check file exists
879
+ if (!this.fs.existsSync(fullHelpers)) {
880
+ // generater model
881
+ this.makeFolder(helperPath + subFolder)
882
+ .then(this.copy.bind(this, tmpHelpersPath, fullHelpers))
883
+ .then(logUpdate("\n Processing  The helper `" + helpers + "` it's generating..."))
884
+ .then(logUpdate("\n Passed  The helper `" + helpers + "` it's generated."))
885
+ .catch(err => {
886
+ throw err;
887
+ });
888
+ } else {
889
+ resolve("\n Warning  The helper `" + helpers + "` it's duplicated.");
890
+ }
891
+ } catch (error) {
892
+ reject(error);
893
+ }
894
+ });
895
+ }
896
+
897
+ makeFolder(path) {
898
+ /**
899
+ * @param path path to make
900
+ *
901
+ * @return new full path
902
+ *
903
+ */
904
+ return new Promise((resolve, reject) => {
905
+ try {
906
+ const mkdirp = require("mkdirp");
907
+ mkdirp(path)
908
+ .then(p => resolve(p))
909
+ .catch(err => reject(err));
910
+ } catch (error) {
911
+ reject(error);
912
+ }
913
+ });
914
+ }
915
+
916
+ makePassportInit() {
917
+ return new Promise((resolve, reject) => {
918
+ try {
919
+ let passport_config_file = __dirname + '/../configure/passport.config.js';
920
+ let passport_config_paste_point = "./passport.config.js";
921
+ if (!this.fs.existsSync(passport_config_paste_point)) {
922
+ this.copy(passport_config_file, passport_config_paste_point)
923
+ .then(resolve("\n Passed  The `passport-jwt` is initialized."))
924
+ .catch(err => console.log(err));
925
+ } else {
926
+ resolve("\n Warning  The `passport-jwt` already is initialized.");
927
+ }
928
+ } catch (error) {
929
+ reject(error);
930
+ }
931
+ });
932
+ }
933
+
934
+ makeAddOnInit() {
935
+ return new Promise((resolve, reject) => {
936
+ try {
937
+ let tmpEndpointsPath = __dirname + '/_scheduler';
938
+ let add_on_paste_point = "Scheduler.js";
939
+ let folder_add_on = "./src/";
940
+ if (!this.fs.existsSync(folder_add_on + add_on_paste_point)) {
941
+ this.makeFolder(folder_add_on)
942
+ .then(this.copy.bind(this, tmpEndpointsPath, folder_add_on + add_on_paste_point))
943
+ .then(resolve("\n Passed  The `Scheduler` is initialized."))
944
+ .catch(err => console.log(err));
945
+ } else {
946
+ resolve("\n Warning  The `Scheduler` already is initialized.");
947
+ }
948
+ } catch (error) {
949
+ reject(error);
950
+ }
951
+ });
952
+ }
953
+
954
+ copy(path, to) {
955
+ /**
956
+ * @param path old path file
957
+ * @param to save to new path file
958
+ *
959
+ * @return new path file
960
+ */
961
+ return new Promise((resolve, reject) => {
962
+ try {
963
+ if (!this.fs.existsSync(to)) {
964
+ if (this.fs.ReadStream(path).pipe(this.fs.createWriteStream(to))) {
965
+ resolve(to);
966
+ } else {
967
+ throw err;
968
+ }
969
+ } else {
970
+ resolve(to);
971
+ }
972
+ } catch (error) {
973
+ reject(error);
974
+ }
975
+ });
976
+ }
977
+
978
+ contentReplace(pathFile, textCondition) {
979
+ return new Promise((resolve, reject) => {
980
+ try {
981
+ let endpoint = textCondition.endpoint;
982
+ let endpointName = textCondition.endpointName;
983
+ let rq = textCondition.rq;
984
+ let modelName = textCondition.modelName;
985
+ // delay for generator
986
+ setTimeout(() => {
987
+ this.fs.readFile(pathFile, 'utf8', (err, data) => {
988
+ if (err) {
989
+ throw err;
990
+ } else {
991
+ // content replace
992
+ let text = data.replace(new RegExp('{{endpoint}}', 'g'), endpoint);
993
+ text = text.replace(new RegExp('{{endpointName}}', 'g'), endpointName);
994
+ text = text.replace(new RegExp('{{requireSomething}}', 'g'), rq);
995
+ text = text.replace(new RegExp('{{modelName}}', 'g'), modelName);
996
+ setTimeout(() => {
997
+ // writing the file
998
+ this.fs.writeFile(pathFile, text, 'utf8', (err) => {
999
+ if (err) {
1000
+ throw err;
1001
+ } else {
1002
+ resolve("\n Passed  The endpoint `" + endpointName + "` it's generated.");
1003
+ }
1004
+ });
1005
+ }, 1000);
1006
+ }
1007
+ })
1008
+ }, 1000);
1009
+ } catch (error) {
1010
+ reject(error);
1011
+ }
1012
+ });
1013
+ }
1014
+
1015
+ modelContentReplace(pathFile, textCondition) {
1016
+ return new Promise((resolve, reject) => {
1017
+ try {
1018
+ let modelName = textCondition.modelName;
1019
+ // delay for generator
1020
+ setTimeout(() => {
1021
+ this.fs.readFile(pathFile, 'utf8', (err, data) => {
1022
+ if (err) {
1023
+ throw err;
1024
+ } else {
1025
+ // content replace
1026
+ let text = data.replace(new RegExp('{{modelName}}', 'g'), modelName);
1027
+ text = text.replace(new RegExp('{{dbSelected}}', 'g'), textCondition.dbSelected);
1028
+ // check add model name text uppercase
1029
+ if (Object.keys(textCondition).length > 1) {
1030
+ text = text.replace(new RegExp('{{modelStructure}}', 'g'), textCondition.modelStructure);
1031
+ text = text.replace(new RegExp('{{modelNameUppercase}}', 'g'), textCondition.modelNameUppercase);
1032
+ }
1033
+ // writing the file
1034
+ this.fs.writeFile(pathFile, text, 'utf8', (err) => {
1035
+ if (err) {
1036
+ throw err;
1037
+ } else {
1038
+ resolve("\n Passed  The model `" + modelName + "` it's generated.");
1039
+ }
1040
+ });
1041
+ }
1042
+ })
1043
+ }, 2000);
1044
+ } catch (error) {
1045
+ reject(error);
1046
+ }
1047
+ });
1048
+ }
1049
+
1050
+ appKeyGenerator(length) {
1051
+ return new Promise((resolve, reject) => {
1052
+ try {
1053
+ let md5 = require("md5");
1054
+ let secret = require(__dirname + "/../../../lib/src/salt").salt;
1055
+ let result = '';
1056
+ let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
1057
+ let charactersLength = characters.length;
1058
+ for (var i = 0; i < length; i++) {
1059
+ result += characters.charAt(Math.floor(Math.random() * charactersLength));
1060
+ }
1061
+ resolve(md5(result + secret));
1062
+ } catch (error) {
1063
+ reject(error);
1064
+ }
1065
+ });
1066
+ }
1067
+
1068
+ generateKeyConfigFile() {
1069
+ return new Promise((resolve, reject) => {
1070
+ try {
1071
+ this.fs.readFile("app.config.js", 'utf8', (err, data) => {
1072
+ if (err) {
1073
+ throw err;
1074
+ } else {
1075
+ // edit or add property
1076
+ let buffer = Buffer.from(data);
1077
+ let buf2str = buffer.toString();
1078
+ let buf2json = JSON.parse(JSON.stringify(buf2str));
1079
+ let buf2eval = eval(buf2json);
1080
+ let oldSecret = buf2eval.main_config.app_key;
1081
+ // generate new key secret
1082
+ this.appKeyGenerator(8).then(newAppSecret => {
1083
+ // content replace
1084
+ let text = data.replace(new RegExp(oldSecret, 'g'), newAppSecret);
1085
+ // writing the file
1086
+ this.fs.writeFile("app.config.js", text, 'utf8', (err) => {
1087
+ if (err) {
1088
+ throw err;
1089
+ } else {
1090
+ resolve("\n Passed  App secret it's new generated.");
1091
+ }
1092
+ });
1093
+ });
1094
+ }
1095
+ });
1096
+ } catch (error) {
1097
+ reject(error);
1098
+ }
1099
+ });
1100
+ }
1101
+
1102
+ help() {
1103
+ return new Promise((resolve, reject) => {
1104
+ try {
1105
+ this.fs.readFile(__dirname + "/_help", 'utf8', function (err, data) {
1106
+ if (err) {
1107
+ throw err;
1108
+ }
1109
+ resolve(data);
1110
+ });
1111
+ } catch (error) {
1112
+ reject(err);
1113
+ }
1114
+ });
1115
+ }
1116
+
1117
+ findClosestOption(input, options) {
1118
+ let closest = null;
1119
+ let minDistance = Infinity;
1120
+ options.forEach(opt => {
1121
+ const distance = this.levenshteinDistance(input, opt);
1122
+ if (distance < minDistance) {
1123
+ minDistance = distance;
1124
+ closest = opt;
1125
+ }
1126
+ });
1127
+ return minDistance <= 3 ? closest : null;
1128
+ }
1129
+
1130
+ levenshteinDistance(a, b) {
1131
+ const tmp = [];
1132
+ for (let i = 0; i <= a.length; i++) tmp[i] = [i];
1133
+ for (let j = 0; j <= b.length; j++) tmp[0][j] = j;
1134
+ for (let i = 1; i <= a.length; i++) {
1135
+ for (let j = 1; j <= b.length; j++) {
1136
+ tmp[i][j] = Math.min(
1137
+ tmp[i - 1][j] + 1,
1138
+ tmp[i][j - 1] + 1,
1139
+ tmp[i - 1][j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1)
1140
+ );
1141
+ }
1142
+ }
1143
+ return tmp[a.length][b.length];
1144
+ }
1145
+
1146
+ validateModelName(name, nextArg, cb) {
1147
+ // role:
1148
+ // 1. Only first text with (a-z, A-Z) or _ not number first charecter
1149
+ // 2. Then only text with (a-z, A-Z), number (0-9), - or _ allowed for next characters
1150
+ // 3. No space allowed
1151
+ if (name.includes(' ')) {
1152
+ return cb("Error: Model name cannot contain spaces.", false);
1153
+ }
1154
+ const regex = /^[a-zA-Z_][a-zA-Z0-9-_]*$/;
1155
+ if (!regex.test(name)) {
1156
+ return cb("Error: Invalid characters in model name.", false);
1157
+ } else if (nextArg && !nextArg.startsWith('--')) {
1158
+ cb("Error model name not space allowed.", null);
1159
+ } else {
1160
+ cb(null, true);
1161
+ }
1162
+ }
1163
+
1164
+ embed(argv) {
1165
+ return new Promise((resolve, reject) => {
1166
+ try {
1167
+ this.fs = require('fs');
1168
+ this.cmd = require('node-cmd');
1169
+ this.argv = argv;
1170
+ this.option = argv[ 2 ];
1171
+ this.argument = argv[ 3 ];
1172
+ this.special = argv[ 4 ];
1173
+ // Validate argument with Whitelist `--`
1174
+ const validOptions = ['--model', '--name', '--no-comment', '--inject', '--require', '--version', '--help', '--helper'];
1175
+ const providedOptions = argv.filter(arg => arg.startsWith('--'));
1176
+ for (const opt of providedOptions) {
1177
+ if (!validOptions.includes(opt)) {
1178
+ const suggestion = this.findClosestOption(opt, validOptions);
1179
+ let errorMessage = `\n Error  Unknown option: "${opt}".`;
1180
+ if (suggestion) {
1181
+ errorMessage += ` \nDid you mean to use "${suggestion}" ?`;
1182
+ }
1183
+ return reject(errorMessage);
1184
+ }
1185
+ }
1186
+ // Check for noComment variable with `--no-comment`, `--name`, `--inject` flag
1187
+ this.noComment = argv.includes('--no-comment');
1188
+ this.customModelName = null;
1189
+ this.realTableName = null;
1190
+ // Check for custom model name with `--name` flag
1191
+ const nameIndex = argv.indexOf('--name');
1192
+ if (nameIndex !== -1 && argv[nameIndex + 1]) {
1193
+ const inputName = argv[nameIndex + 1];
1194
+ this.validateModelName(inputName, argv[nameIndex + 2], (err, isValid) => {
1195
+ if (isValid && !err) {
1196
+ this.customModelName = argv[nameIndex + 1];
1197
+ } else {
1198
+ reject("\n Warning  Invalid model name format: '" + inputName + " ...' Model name must start with a letter/underscore and contain only letters, numbers, '-', or '_' & no space.");
1199
+ }
1200
+ });
1201
+ } else if (nameIndex > 2) {
1202
+ reject("\n Warning  Custom model name flag `--name` found but no name provided. Usage: `--name <custom_model_name>`");
1203
+ } else {
1204
+ if(nameIndex !== -1) {
1205
+ reject("\nNote: You can specify a custom model name with the `--name` flag. Example: `--name fruit` to generate `Fruit.js` instead of default model name.");
1206
+ }
1207
+ }
1208
+ // Check for inject table to update schema to latest with `--inject` flag
1209
+ const injectIndex = argv.indexOf('--inject');
1210
+ if (injectIndex !== -1) {
1211
+ if (!argv[injectIndex + 1] || argv[injectIndex + 1].startsWith('--')) {
1212
+ return reject(`\n Warning  Option "--inject" requires a value. Usage: "--inject <table_name>"`);
1213
+ }
1214
+ if (injectIndex !== -1 && argv[injectIndex + 1]) {
1215
+ this.realTableName = argv[injectIndex + 1];
1216
+ } else {
1217
+ if(injectIndex !== -1) {
1218
+ reject("\n Warning  Inject table name flag `--inject` found but no table name provided. Usage: `--inject <table_name>`");
1219
+ }
1220
+ }
1221
+ }
1222
+ resolve(this);
1223
+ } catch (error) {
1224
+ reject(err);
1225
+ }
1226
+ });
1227
+ }
1228
+ }
1229
+
993
1230
  new Generator();