beech-api 3.9.0-beta.9-rc → 3.9.57
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.
package/README.md
CHANGED
|
@@ -146,7 +146,15 @@ The following commands are available:
|
|
|
146
146
|
$ beech make <endpoint> Create a new Endpoints and unit test file,
|
|
147
147
|
You might using [special] `-R, --require`
|
|
148
148
|
for choose Model(s) used to endpoint file.
|
|
149
|
-
|
|
149
|
+
|
|
150
|
+
$ beech make <model> -M, --model Create a new Models file, You might using
|
|
151
|
+
[special] `--no-comment` for ignore comment
|
|
152
|
+
Table Property in your Schema.
|
|
153
|
+
|
|
154
|
+
$ beech update model <model_name> Update new Table Structure for latest, You
|
|
155
|
+
might using [special] `--no-comment` for
|
|
156
|
+
ignore comment Table Property in your Schema.
|
|
157
|
+
|
|
150
158
|
$ beech make <helper> --helper Create a new Helpers file.
|
|
151
159
|
$ beech passport init Initialize authentication with passport-jwt.
|
|
152
160
|
$ beech skd init Initialize Job Scheduler file.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "beech-api",
|
|
3
|
-
"version": "3.9.
|
|
3
|
+
"version": "3.9.57",
|
|
4
4
|
"description": "Command line interface for rapid Beech API development",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"api",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"app-root-path": "^3.0.0",
|
|
35
|
+
"ast-types": "^0.14.2",
|
|
35
36
|
"axios": "^1.8.4",
|
|
36
37
|
"child-process-promise": "^2.2.1",
|
|
37
38
|
"cli-clear": "^1.0.4",
|
|
@@ -56,6 +57,7 @@
|
|
|
56
57
|
"method-override": "^3.0.0",
|
|
57
58
|
"mkdirp": "^2.1.6",
|
|
58
59
|
"module-alias": "^2.2.2",
|
|
60
|
+
"multer": "^2.1.1",
|
|
59
61
|
"mysql": "^2.18.1",
|
|
60
62
|
"mysql2": "^3.14.0",
|
|
61
63
|
"node-cmd": "^3.0.0",
|
|
@@ -68,6 +70,7 @@
|
|
|
68
70
|
"passport-local": "^1.0.0",
|
|
69
71
|
"passport-oauth": "^1.0.0",
|
|
70
72
|
"path": "^0.12.7",
|
|
73
|
+
"recast": "^0.23.11",
|
|
71
74
|
"sequelize": "^6.21.3",
|
|
72
75
|
"tedious": "^18.6.1",
|
|
73
76
|
"walk": "^2.3.14"
|
|
@@ -11,7 +11,15 @@ The following commands are available:
|
|
|
11
11
|
$ beech make <endpoint> Create a new Endpoints and unit test file,
|
|
12
12
|
You might using [special] `-R, --require`
|
|
13
13
|
for choose Model(s) used to endpoint file.
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
$ beech make <model> -M, --model Create a new Models file, You might using
|
|
16
|
+
[special] `--no-comment` for ignore comment
|
|
17
|
+
Table Property in your Schema.
|
|
18
|
+
|
|
19
|
+
$ beech update model <model_name> Update new Table Structure for latest, You
|
|
20
|
+
might using [special] `--no-comment` for
|
|
21
|
+
ignore comment Table Property in your Schema.
|
|
22
|
+
|
|
15
23
|
$ beech make <helper> --helper Create a new Helpers file.
|
|
16
24
|
$ beech passport init Initialize authentication with passport-jwt.
|
|
17
25
|
$ beech skd init Initialize Job Scheduler file.
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
const logUpdate = require("log-update");
|
|
3
3
|
const inquirer = require('inquirer');
|
|
4
4
|
const walk = require("walk");
|
|
5
|
+
const recast = require("recast");
|
|
6
|
+
const { builders } = require("ast-types");
|
|
5
7
|
const { connectForGenerateModel } = require("../databases/test");
|
|
6
8
|
|
|
7
9
|
class Generator {
|
|
@@ -180,16 +182,22 @@ class Generator {
|
|
|
180
182
|
}
|
|
181
183
|
}
|
|
182
184
|
} else if (this.option == 'update') {
|
|
183
|
-
console.log('------------------------------');
|
|
184
185
|
if(this.argument == 'model') {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
186
|
+
if(this.special) {
|
|
187
|
+
if (!this.special) {
|
|
188
|
+
resolve("\n[103m[90m Warning [0m[0m Please specify model name to update.");
|
|
189
|
+
} else {
|
|
190
|
+
this.updateModel()
|
|
191
|
+
.then(res => resolve(res))
|
|
192
|
+
.catch(err => reject(err));
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
resolve("\n[103m[90m Warning [0m[0m Please specify model name to update.");
|
|
196
|
+
}
|
|
197
|
+
} else if(this.argument != 'model') {
|
|
198
|
+
resolve("\n[101m Faltal [0m commnad it's not available.");
|
|
199
|
+
} else {
|
|
200
|
+
resolve("\n[103m[90m Warning [0m[0m Please specify what you want to update.");
|
|
193
201
|
}
|
|
194
202
|
} else if (this.option == 'passport') {
|
|
195
203
|
if (this.argument == "init") {
|
|
@@ -466,60 +474,136 @@ class Generator {
|
|
|
466
474
|
updateModel() {
|
|
467
475
|
return new Promise((resolve, reject) => {
|
|
468
476
|
try {
|
|
469
|
-
// 1. อ่านการตั้งค่าจาก global.config และ app.config เหมือนตอนสร้าง Model
|
|
470
477
|
this.fs.readFile("./global.config.js", 'utf8', (err, globalData) => {
|
|
471
|
-
if (err) return resolve("\n
|
|
472
|
-
|
|
478
|
+
if (err) return resolve("\n[101m Faltal [0m Can't read `global.config.js` file.");
|
|
473
479
|
this.fs.readFile("./app.config.js", 'utf8', (appErr, appData) => {
|
|
474
|
-
if (appErr) return resolve("\n
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
// ตรวจสอบว่ามีไฟล์ Model เดิมอยู่หรือไม่
|
|
480
|
+
if (appErr) return resolve("\n[101m Faltal [0m Can't read `app.config.js` file.");
|
|
481
|
+
const appConfig = eval(appData);
|
|
482
|
+
const modelName = this.special; // <table_name>
|
|
483
|
+
const modelFileName = modelName.charAt(0).toUpperCase() + modelName.slice(1);
|
|
484
|
+
const modelPath = `./src/models/${modelFileName}.js`;
|
|
481
485
|
if (!this.fs.existsSync(modelPath)) {
|
|
482
|
-
return resolve(`\n
|
|
486
|
+
return resolve(`\n[103m[90m Warning [0m[0m Model file \`${modelFileName}\` not found.`);
|
|
483
487
|
}
|
|
484
|
-
|
|
485
|
-
// ให้ผู้ใช้เลือก Connection ที่ต้องการดึง Schema ใหม่
|
|
488
|
+
// Choose connection for new schema
|
|
486
489
|
inquirer.prompt([{
|
|
487
490
|
type: "list",
|
|
488
491
|
name: "selectDbConnect",
|
|
489
492
|
message: " [93mSelect database connection to sync schema: [0m",
|
|
490
|
-
choices:
|
|
493
|
+
choices: appConfig.database_config.map(e => e.name),
|
|
491
494
|
}]).then(dbSelected => {
|
|
492
|
-
|
|
493
|
-
// 2. ดึง Schema ใหม่จาก DB
|
|
494
495
|
const { connectForGenerateModel } = require("../databases/test");
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
if (
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
if (
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
}
|
|
520
|
-
|
|
496
|
+
// pull new schema from database
|
|
497
|
+
connectForGenerateModel(dbSelected.selectDbConnect, modelName, appConfig.database_config, (dbErr, tableSchema, tableName) => {
|
|
498
|
+
if (dbErr) return reject(dbErr);
|
|
499
|
+
// read current model file for AST
|
|
500
|
+
this.fs.readFile(modelPath, 'utf8', (readErr, code) => {
|
|
501
|
+
if (readErr) return reject(readErr);
|
|
502
|
+
const ast = recast.parse(code);
|
|
503
|
+
let isUpdated = false;
|
|
504
|
+
// Function for map raw database type to Sequelize DataTypes AST Node
|
|
505
|
+
const getDataTypeNode = (rawType) => {
|
|
506
|
+
const type = rawType.toUpperCase();
|
|
507
|
+
if (type.includes('INT')) return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('INTEGER'));
|
|
508
|
+
if (type.includes('BIGINT')) return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('BIGINT'));
|
|
509
|
+
if (type.includes('FLOAT')) return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('FLOAT'));
|
|
510
|
+
if (type.includes('DOUBLE')) return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('DOUBLE'));
|
|
511
|
+
if (type.includes('DECIMAL')) return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('DECIMAL'));
|
|
512
|
+
if (type.includes('BOOLEAN') || type === 'TINYINT(1)') return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('BOOLEAN'));
|
|
513
|
+
if (type.includes('CHAR') || type.includes('VARCHAR')) {
|
|
514
|
+
const match = type.match(/\((\d+)\)/);
|
|
515
|
+
const length = match ? match[1] : '255';
|
|
516
|
+
return builders.callExpression(
|
|
517
|
+
builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('STRING')),
|
|
518
|
+
[builders.literal(parseInt(length))]
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
if (type.includes('TEXT')) return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('TEXT'));
|
|
522
|
+
if (type.includes('DATE')) return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('DATE'));
|
|
523
|
+
if (type.includes('TIME')) return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('TIME'));
|
|
524
|
+
if (type.includes('JSON')) return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('JSON'));
|
|
525
|
+
if (type.includes('UUID')) return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('UUID'));
|
|
526
|
+
if (type.includes('BLOB')) return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('BLOB'));
|
|
527
|
+
if (type.includes('ENUM')) return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('ENUM'));
|
|
528
|
+
if (type.includes('GEOMETRY')) return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('GEOMETRY'));
|
|
529
|
+
return builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('STRING'));
|
|
530
|
+
};
|
|
531
|
+
// recast.visit for find position of Schema(...).define() and update fields
|
|
532
|
+
recast.visit(ast, {
|
|
533
|
+
visitCallExpression(path) {
|
|
534
|
+
const node = path.node;
|
|
535
|
+
if (node.callee.property && node.callee.property.name === 'define' && node.arguments[1] && node.arguments[1].type === 'ObjectExpression') {
|
|
536
|
+
const fieldsObject = node.arguments[1];
|
|
537
|
+
const newFieldNames = Object.keys(tableSchema);
|
|
538
|
+
const updatedProperties = [];
|
|
539
|
+
newFieldNames.forEach(dbFieldName => {
|
|
540
|
+
const dbFieldData = tableSchema[dbFieldName];
|
|
541
|
+
let existingProperty = fieldsObject.properties.find(p => {
|
|
542
|
+
const propName = p.key.name || p.key.value;
|
|
543
|
+
if (propName === dbFieldName) return true;
|
|
544
|
+
if (p.value && p.value.properties) {
|
|
545
|
+
const hasFieldMapping = p.value.properties.find(
|
|
546
|
+
innerP => (innerP.key.name === 'field' || innerP.key.value === 'field') &&
|
|
547
|
+
(innerP.value.value === dbFieldName)
|
|
548
|
+
);
|
|
549
|
+
if (hasFieldMapping) return true;
|
|
550
|
+
}
|
|
551
|
+
return false;
|
|
552
|
+
});
|
|
553
|
+
const latestFieldProps = [];
|
|
554
|
+
// Prepare properties from Table DB (Type, PK, AI, Default, etc.)
|
|
555
|
+
latestFieldProps.push(builders.property('init', builders.identifier('type'), getDataTypeNode(dbFieldData.type)));
|
|
556
|
+
latestFieldProps.push(builders.property('init', builders.identifier('allowNull'), builders.literal(dbFieldData.allowNull === 'YES' || dbFieldData.allowNull === true)));
|
|
557
|
+
if (dbFieldData.primaryKey) latestFieldProps.push(builders.property('init', builders.identifier('primaryKey'), builders.literal(true)));
|
|
558
|
+
if (dbFieldData.autoIncrement) latestFieldProps.push(builders.property('init', builders.identifier('autoIncrement'), builders.literal(true)));
|
|
559
|
+
if (dbFieldData.unique) latestFieldProps.push(builders.property('init', builders.identifier('unique'), builders.literal(true)));
|
|
560
|
+
if(this.extra != '--no-comment') {
|
|
561
|
+
if (dbFieldData.comment) latestFieldProps.push(builders.property('init', builders.identifier('comment'), builders.literal(dbFieldData.comment)));
|
|
562
|
+
}
|
|
563
|
+
if (dbFieldData.defaultValue !== null && dbFieldData.defaultValue !== undefined) {
|
|
564
|
+
const dVal = String(dbFieldData.defaultValue).toUpperCase();
|
|
565
|
+
const defaultNode = (dVal === 'CURRENT_TIMESTAMP' || dVal === 'NOW()')
|
|
566
|
+
? builders.memberExpression(builders.identifier('DataTypes'), builders.identifier('NOW'))
|
|
567
|
+
: builders.literal(dbFieldData.defaultValue);
|
|
568
|
+
latestFieldProps.push(builders.property('init', builders.identifier('defaultValue'), defaultNode));
|
|
569
|
+
}
|
|
570
|
+
if (!existingProperty) {
|
|
571
|
+
existingProperty = builders.property('init', builders.identifier(dbFieldName), builders.objectExpression(latestFieldProps));
|
|
572
|
+
isUpdated = true;
|
|
573
|
+
} else {
|
|
574
|
+
const currentProps = existingProperty.value.properties;
|
|
575
|
+
latestFieldProps.forEach(newProp => {
|
|
576
|
+
const oldProp = currentProps.find(p => p.key.name === newProp.key.name);
|
|
577
|
+
if (!oldProp || recast.print(oldProp.value).code !== recast.print(newProp.value).code) {
|
|
578
|
+
isUpdated = true;
|
|
579
|
+
if (!oldProp) currentProps.push(newProp); else oldProp.value = newProp.value;
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
const latestPropNames = latestFieldProps.map(p => p.key.name);
|
|
583
|
+
latestPropNames.push('field');
|
|
584
|
+
existingProperty.value.properties = currentProps.filter(p => latestPropNames.includes(p.key.name));
|
|
585
|
+
}
|
|
586
|
+
updatedProperties.push(existingProperty);
|
|
587
|
+
});
|
|
588
|
+
const currentOrder = fieldsObject.properties.map(p => p.key.name || p.key.value).join(',');
|
|
589
|
+
const newOrder = updatedProperties.map(p => p.key.name || p.key.value).join(',');
|
|
590
|
+
if (currentOrder !== newOrder || fieldsObject.properties.length !== updatedProperties.length) isUpdated = true;
|
|
591
|
+
fieldsObject.properties = updatedProperties;
|
|
592
|
+
}
|
|
593
|
+
return false;
|
|
521
594
|
}
|
|
522
595
|
});
|
|
596
|
+
// Write back updated AST, Shout updated
|
|
597
|
+
if (isUpdated) {
|
|
598
|
+
// Write updated AST back to file
|
|
599
|
+
const output = recast.print(ast).code;
|
|
600
|
+
this.fs.writeFile(modelPath, output, 'utf8', (writeErr) => {
|
|
601
|
+
if (writeErr) return reject(writeErr);
|
|
602
|
+
resolve(`\n[102m[90m Updated [0m[0m The model \`${modelFileName}\` has been updated via AST.`);
|
|
603
|
+
});
|
|
604
|
+
} else {
|
|
605
|
+
resolve(`\n[103m[90m Warning [0m[0m No new fields found to update for \`${modelFileName}\`.`);
|
|
606
|
+
}
|
|
523
607
|
});
|
|
524
608
|
});
|
|
525
609
|
});
|
|
@@ -691,21 +775,19 @@ class Generator {
|
|
|
691
775
|
// Check is primary key
|
|
692
776
|
if (props.primaryKey) lines.push(` primaryKey: true,`);
|
|
693
777
|
if (props.autoIncrement) lines.push(` autoIncrement: true,`);
|
|
694
|
-
//
|
|
695
|
-
|
|
778
|
+
// 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
|
|
779
|
+
if(this.extra != '--no-comment') {
|
|
780
|
+
if (props.comment) lines.push(` comment: "${props.comment}",`);
|
|
781
|
+
}
|
|
696
782
|
// Handle defaultValue
|
|
697
783
|
if (props.defaultValue !== null && props.defaultValue !== undefined) {
|
|
698
784
|
const defaultVal = String(props.defaultValue).toUpperCase();
|
|
699
|
-
if (
|
|
700
|
-
defaultVal === 'CURRENT_TIMESTAMP' ||
|
|
701
|
-
defaultVal === 'NOW()' ||
|
|
702
|
-
defaultVal.includes('CURRENT_TIMESTAMP')
|
|
703
|
-
) {
|
|
785
|
+
if (defaultVal === 'CURRENT_TIMESTAMP' || defaultVal === 'NOW()' || defaultVal.includes('CURRENT_TIMESTAMP')) {
|
|
704
786
|
lines.push(` defaultValue: DataTypes.NOW,`);
|
|
705
787
|
} else if (defaultVal === 'UUID()' || defaultVal === 'uuid()' || defaultVal === 'UUID') {
|
|
706
788
|
lines.push(` defaultValue: DataTypes.UUIDV4,`);
|
|
707
789
|
} else if (typeof props.defaultValue === 'string') {
|
|
708
|
-
lines.push(` defaultValue:
|
|
790
|
+
lines.push(` defaultValue: "${props.defaultValue}",`);
|
|
709
791
|
} else {
|
|
710
792
|
lines.push(` defaultValue: ${props.defaultValue},`);
|
|
711
793
|
}
|
|
@@ -43,7 +43,7 @@ const uploadStrategy = multer({
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
}).any();
|
|
46
|
-
const jsonLimitSize = _beech_?.payload
|
|
46
|
+
const jsonLimitSize = _beech_?.payload?.json?.limit || "100KB"; // json payload
|
|
47
47
|
const urlencodedLimitSize = _beech_?.payload?.urlencoded?.limit || "100KB"; // urlencoded payload (multipart/form-data)
|
|
48
48
|
const urlencodedExtended = !!(_beech_?.payload?.urlencoded?.extended ?? true);
|
|
49
49
|
_app_.use(_express_.urlencoded({ limit: urlencodedLimitSize, extended: urlencodedExtended })); // application/x-www-form-urlencoded payload
|