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

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