create-sip 1.1.2 → 1.2.0

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.
@@ -89,12 +89,12 @@ The next command generates the default config file:
89
89
  node op conf:generate
90
90
  ```
91
91
 
92
- ## Database seed
92
+ ## Database import
93
93
 
94
94
  The database can be seeded with the following command:
95
95
 
96
96
  ```bash
97
- node op db:seed <model_name> <file_path>
97
+ node op db:import <model_name> <file_path>
98
98
  ```
99
99
 
100
100
  The model name must be given in the singular and lowercase. The file extension must be:
@@ -106,6 +106,50 @@ The keys in the JSON file and the field names in the CSV file must match the mod
106
106
 
107
107
  If the CSV file contains quotation marks, they are automatically removed.
108
108
 
109
+ ## Migration
110
+
111
+ Generate a migration:
112
+
113
+ ```bash
114
+ node op make/migration thing
115
+ ```
116
+
117
+ Run all migration:
118
+
119
+ ```bash
120
+ node op migration:run
121
+ ```
122
+
123
+ Run a migration:
124
+
125
+ ```bash
126
+ node op migration:run <migration_name>
127
+ ```
128
+
129
+ Rollback a migration:
130
+
131
+ ```bash
132
+ node op migration:rollback
133
+ ```
134
+
135
+ Rollback two migrations:
136
+
137
+ ```bash
138
+ node op migration:rollback 2
139
+ ```
140
+
141
+ Reset the database:
142
+
143
+ ```bash
144
+ node op migration:reset
145
+ ```
146
+
147
+ Reset the database and run all migrations:
148
+
149
+ ```bash
150
+ node op migration:fresh
151
+ ```
152
+
109
153
  ## Licence
110
154
 
111
155
  May be freely distributed under the MIT license.
@@ -62,3 +62,36 @@ Send the bearer token.
62
62
  node op create model thing
63
63
  node op create controller thing
64
64
  ```
65
+
66
+ ## Key generation
67
+
68
+ ```cmd
69
+ node op key:generate
70
+ ```
71
+
72
+ ## Generate admin user
73
+
74
+ ```cmd
75
+ node op admin:generate
76
+ ```
77
+
78
+ ## Database import
79
+
80
+ ```cmd
81
+ node op db:import thing things.json
82
+ node op db:import thing things.csv
83
+ node op db:import thing things.csv ,
84
+ node op db:import thing things.csv ";"
85
+ node op db:import thing things.csv :
86
+ ```
87
+
88
+ ## Migration
89
+
90
+ ```cmd
91
+ node op make:migration thing
92
+ node op migrate:run
93
+ node op migrate:fresh
94
+ node op migrate:rollback
95
+ node op migrate:rollback --step 2
96
+ node op migrate:reset
97
+ ```
package/expressapi/op CHANGED
@@ -1,3 +1,5 @@
1
+ import yargs from 'yargs';
2
+ import { hideBin } from 'yargs/helpers';
1
3
  import replace from 'replace';
2
4
  import fse from 'fs-extra';
3
5
  import fsp from 'fs/promises';
@@ -6,78 +8,162 @@ import bcrypt from 'bcryptjs';
6
8
  import path from 'path';
7
9
  import { read } from 'read';
8
10
 
9
- if(process.argv.length == 3 && process.argv[2] == 'help') {
10
- console.log('Usable commands:');
11
- console.log(' node op make:model <name>');
12
- console.log(' node op make:controller <name>');
13
- console.log(' node op key:generate');
14
- console.log(' node op conf:generate');
15
- console.log(' node op admin:generate');
16
- console.log(' node op db:seed <model> <filePath> [sep]');
17
- process.exit(0);
18
- } else if(process.argv.length == 3 && process.argv[2] == 'key:generate') {
19
- startGenerateKey();
20
- } else if(process.argv.length == 3 && process.argv[2] == 'conf:generate') {
21
- startGenerateConf();
22
- } else if(process.argv.length == 3 && process.argv[2] == 'admin:generate') {
23
- startGenerateAdmin();
24
- } else if(process.argv[2] === 'db:seed') {
25
- if(process.argv.length < 5) {
26
- console.log('Usage:');
27
- console.log('node op db:seed <model> <filePath> [sep]');
28
- console.log('Examples:');
29
- console.log('node op db:seed thing somethings.json');
30
- console.log('node op db:seed thing somethings.csv');
31
- console.log('node op db:seed thing somethings.csv ,');
32
- console.log('node op db:seed thing somethings.csv :');
33
- console.log('node op db:seed thing somethings.csv ";"');
11
+ const { argv } = yargs(hideBin(process.argv))
12
+ .version()
13
+ .usage('Használat: node op <command> [name]')
14
+ .help()
15
+ .demandCommand(1, 'Not enough arguments!')
16
+ .command('make:model <name>',
17
+ 'Generates a new Sequelize model',
18
+ (yargs) => {
19
+ return yargs
20
+ .positional('name', {
21
+ type: 'string',
22
+ description: 'Name of the model'
23
+ })
24
+ },
25
+ async (argv) => {
26
+ const { name } = argv;
27
+ await copyModel(name);
34
28
  }
35
- const model = process.argv[3];
36
- const filePath = process.argv[4];
37
- if(process.argv.length == 6) {
38
- const sep = process.argv[5];
39
- runSeed(model, filePath, sep);
40
- }else {
41
- runSeed(model, filePath);
29
+ )
30
+ .command('make:controller <name>',
31
+ 'Generates a new controller',
32
+ (yargs) => {
33
+ return yargs
34
+ .positional('name', {
35
+ type: 'string',
36
+ description: 'Name of the controller'
37
+ })
38
+ },
39
+ async (argv) => {
40
+ const { name } = argv;
41
+ await copyController(name);
42
42
  }
43
-
44
- } else if(process.argv[2] === 'make:model') {
45
- if(process.argv.length < 4) {
46
- console.log('Usage:');
47
- console.log('node op make:model <name>');
48
- console.log('Examples:');
49
- console.log('node op make:model something');
43
+ )
44
+ .command('key:generate',
45
+ 'Generates a new API key',
46
+ (yargs) => {
47
+ return yargs
48
+ },
49
+ async (argv) => {
50
+ startGenerateKey();
50
51
  }
51
- copyModel(process.argv[3]);
52
-
53
- } else if(process.argv[2] === 'make:controller') {
54
- if(process.argv.length < 4) {
55
- console.log('Usage:');
56
- console.log('node op make:controller <name>');
57
- console.log('Examples:');
58
- console.log('node op make:controller something');
52
+ )
53
+ .command('conf:generate',
54
+ 'Generates a new config file',
55
+ (yargs) => {
56
+ return yargs
57
+ },
58
+ async (argv) => {
59
+ startGenerateConf();
59
60
  }
60
- copyController(process.argv[3]);
61
-
62
- } else if(process.argv.length < 4) {
63
- console.log('Usage:');
64
- console.log('node op <command> <type> <name>');
65
- console.log('Example:');
66
- console.log('node op create model something');
67
- console.log('node op create controller something');
68
- console.log('node op key:generate');
69
- process.exit(1);
70
- } else {
71
- if(process.argv[2] === 'create') {
72
- console.log('Create a new ' + process.argv[3] + '...');
73
- if(process.argv[3] === 'model') {
74
- copyModel(process.argv[4]);
75
- }
76
- if(process.argv[3] === 'controller') {
77
- copyController(process.argv[4]);
78
- }
61
+ )
62
+ .command('admin:generate',
63
+ 'Generates a new admin user',
64
+ (yargs) => {
65
+ return yargs
66
+ },
67
+ async (argv) => {
68
+ startGenerateAdmin();
79
69
  }
80
- }
70
+ )
71
+ .command('db:import <model> <filePath> [sep]',
72
+ `Import CSV or JSON file to database table
73
+
74
+ Examples:
75
+ node op db:import thing somethings.json
76
+ node op db:import thing somethings.csv
77
+ node op db:import thing somethings.csv ,
78
+ node op db:import thing somethings.csv :
79
+ node op db:import thing somethings.csv ";"
80
+
81
+ In CSV file the field names must match the model fields.
82
+ `,
83
+ (yargs) => {
84
+ return yargs
85
+ .positional('model', {
86
+ type: 'string',
87
+ description: 'Name of the model'
88
+ })
89
+ .positional('filePath', {
90
+ type: 'string',
91
+ description: 'Path to the file'
92
+ })
93
+ .positional('sep', {
94
+ type: 'string',
95
+ description: 'Separator for the CSV file'
96
+ })
97
+ },
98
+ async (argv) => {
99
+ const { model, filePath, sep } = argv;
100
+ await runImportData(model, filePath, sep);
101
+ }
102
+ )
103
+ .command('make:migration <name>',
104
+ 'Generates a new migration file',
105
+ (yargs) => {
106
+ return yargs
107
+ .positional('name', {
108
+ type: 'string',
109
+ description: 'Name of the migration'
110
+ })
111
+ },
112
+ async (argv) => {
113
+ const { name } = argv;
114
+ await createMigration(name);
115
+ }
116
+ )
117
+ .command('migration:run [migrationName]',
118
+ 'Runs migrations',
119
+ (yargs) => {
120
+ return yargs
121
+ .positional('migrationName', {
122
+ type: 'string',
123
+ description: 'Name of the migration'
124
+ })
125
+ },
126
+ async (argv) => {
127
+ const { migrationName } = argv;
128
+ await startMigrations(migrationName);
129
+ }
130
+ )
131
+ .command('migration:rollback',
132
+ 'Rolls back migrations',
133
+ (yargs) => {
134
+ return yargs
135
+ .options({
136
+ step: {
137
+ type: 'number',
138
+ description: 'Number of migrations to rollback',
139
+ default: 1
140
+ }
141
+ })
142
+ },
143
+ async (argv) => {
144
+ const { step } = argv;
145
+ await rollbackMigrations(step);
146
+ }
147
+ )
148
+ .command('migration:fresh',
149
+ 'Rolls back all migrations',
150
+ (yargs) => {
151
+ return yargs
152
+ },
153
+ async (argv) => {
154
+ await freshMigrations();
155
+ }
156
+ )
157
+ .command('migration:reset',
158
+ 'Rolls back all migrations',
159
+ (yargs) => {
160
+ return yargs
161
+ },
162
+ async (argv) => {
163
+ await resetMigrations();
164
+ }
165
+ )
166
+ .parse();
81
167
 
82
168
  async function copyController(className) {
83
169
  const lowerName = className.toLowerCase()
@@ -232,17 +318,17 @@ async function startGenerateAdmin() {
232
318
  }
233
319
  }
234
320
 
235
- const seedFromJson = async (model, filePath) => {
321
+ const importFromJson = async (model, filePath) => {
236
322
  try {
237
323
  const data = JSON.parse(await fsp.readFile(filePath, 'utf8'))
238
324
  await model.bulkCreate(data)
239
- console.log(`Data seeded successfully! ${model.name}`)
325
+ console.log(`Data imported successfully! ${model.name}`)
240
326
  } catch (error) {
241
327
  console.error(error)
242
328
  }
243
329
  }
244
330
 
245
- const seedFromCsv = async (model, filePath, sep) => {
331
+ const importFromCsv = async (model, filePath, sep) => {
246
332
  try {
247
333
  const data = await fsp.readFile(filePath, 'utf8')
248
334
  const clearData = data.replace(/"/g, '').trim()
@@ -261,16 +347,16 @@ const seedFromCsv = async (model, filePath, sep) => {
261
347
  })
262
348
 
263
349
  await model.bulkCreate(dataToInsert)
264
- console.log(`Data seeded successfully! ${model.name}`)
350
+ console.log(`Data imported successfully! ${model.name}`)
265
351
  } catch (error) {
266
352
  console.error(error)
267
353
  }
268
354
  }
269
355
 
270
- async function runSeed(model, filePath, sep=',') {
356
+ async function runImportData(model, filePath, sep=',') {
271
357
 
272
358
  if(!filePath || !model) {
273
- console.log('Usage: node seed.js <modelName> <filePath> [sep]')
359
+ console.log('Usage: node db:import <modelName> <filePath> [sep]')
274
360
  process.exit(1)
275
361
  }
276
362
 
@@ -306,12 +392,107 @@ async function runSeed(model, filePath, sep=',') {
306
392
  await sequelize.sync({ force: true })
307
393
  await sequelize.authenticate()
308
394
  if(ext === '.csv') {
309
- await seedFromCsv(modelObject, filePath, sep)
395
+ await importFromCsv(modelObject, filePath, sep)
310
396
  }else {
311
- await seedFromJson(modelObject, filePath)
397
+ await importFromJson(modelObject, filePath)
312
398
  }
313
399
  } catch (error) {
314
400
  console.error(error)
315
401
  }
316
402
 
317
403
  }
404
+
405
+ async function createMigration(name) {
406
+ console.log('Create a new migration...', name)
407
+
408
+ const lowerName = name.toLowerCase()
409
+ const date = new Date()
410
+ const year = date.getFullYear()
411
+ const month = String(date.getMonth() + 1).padStart(2, '0')
412
+ const day = String(date.getDate()).padStart(2, '0')
413
+ const hours = String(date.getHours()).padStart(2, '0')
414
+ const minutes = String(date.getMinutes()).padStart(2, '0')
415
+ const seconds = String(date.getSeconds()).padStart(2, '0')
416
+ const timestamp = `${year}_${month}_${day}_${hours}${minutes}${seconds}`
417
+
418
+ const migrationName = `${timestamp}_${lowerName}`
419
+
420
+ const src = 'templates/migrationTemplate.js'
421
+ const dest = `database/migrations/${migrationName}.js`
422
+
423
+ if(await startCheckIfFileExists(dest)) {
424
+ process.exit(1);
425
+ }
426
+
427
+ await fse.copy(src, dest)
428
+
429
+ replace({
430
+ regex: 'thing',
431
+ replacement: name,
432
+ paths: [dest]
433
+ })
434
+
435
+ }
436
+
437
+ async function getUmzug() {
438
+ const { default: sequelize } = await import('./app/database/database.js')
439
+ const { Umzug, SequelizeStorage } = await import('umzug')
440
+
441
+ const umzug = new Umzug({
442
+ migrations: { glob: './database/migrations/*.js' },
443
+ context: sequelize.getQueryInterface(),
444
+ storage: new SequelizeStorage({ sequelize }),
445
+ logger: console
446
+ })
447
+
448
+ return umzug
449
+ }
450
+
451
+ async function startMigrations(name) {
452
+ if(name) {
453
+ await runOneMigration(name)
454
+ } else {
455
+ await runMigrations()
456
+ }
457
+ }
458
+
459
+ async function runOneMigration(name) {
460
+ console.log('Run one migration...', name)
461
+
462
+ if(!name.endsWith('.js')) {
463
+ name += '.js'
464
+ }
465
+
466
+ const migrationPath = `database/migrations/${name}`
467
+ if(!await startCheckIfFileExists(migrationPath)) {
468
+ console.log(`The migration file ${migrationPath} not already exists.`)
469
+ process.exit(1)
470
+ }
471
+
472
+ const umzug = await getUmzug()
473
+ await umzug.up()
474
+ }
475
+
476
+ async function runMigrations() {
477
+ console.log('Run migrations...')
478
+ const umzug = await getUmzug()
479
+ await umzug.up()
480
+ }
481
+
482
+ async function resetMigrations() {
483
+ console.log('Reset migrations...')
484
+ const umzug = await getUmzug()
485
+ await umzug.down({ to: 0 })
486
+ }
487
+
488
+ async function freshMigrations() {
489
+ console.log('Fresh migrations...')
490
+ await resetMigrations()
491
+ await runMigrations()
492
+ }
493
+
494
+ async function rollbackMigrations(step = 1) {
495
+ console.log('Rollback migrations...')
496
+ const umzug = await getUmzug()
497
+ await umzug.down({ step: step })
498
+ }
@@ -5,14 +5,14 @@
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "test": "mocha",
8
- "dev": "nodemon app",
8
+ "dev": "nodemon app --watch app --watch config",
9
9
  "start": "node app"
10
10
  },
11
11
  "keywords": [
12
12
  "express",
13
13
  "api"
14
14
  ],
15
- "author": "Sallai András",
15
+ "author": "your name",
16
16
  "license": "ISC",
17
17
  "dependencies": {
18
18
  "bcryptjs": "^2.4.3",
@@ -25,10 +25,13 @@
25
25
  "read": "^4.1.0",
26
26
  "replace": "^1.2.2",
27
27
  "sequelize": "^6.32.0",
28
- "sqlite3": "^5.1.6"
28
+ "sqlite3": "^5.1.6",
29
+ "umzug": "^3.8.2",
30
+ "yargs": "^18.0.0"
29
31
  },
30
32
  "devDependencies": {
31
- "fs-extra": "^11.3.0",
33
+ "@types/bcryptjs": "^2.4.6",
34
+ "fs-extra": "^11.3.0",
32
35
  "nodemon": "^2.0.22",
33
36
  "supertest": "^6.3.3"
34
37
  },
@@ -41,9 +41,9 @@ const ThingController = {
41
41
  data: thing
42
42
  })
43
43
  },
44
- async create(req, res) {
44
+ async store(req, res) {
45
45
  try {
46
- await ThingController.tryCreate(req, res)
46
+ await ThingController.tryStore(req, res)
47
47
  }catch(error) {
48
48
  res.status(500)
49
49
  res.json({
@@ -53,7 +53,7 @@ const ThingController = {
53
53
  })
54
54
  }
55
55
  },
56
- async tryCreate(req, res) {
56
+ async tryStore(req, res) {
57
57
  const thing = await Thing.create(req.body)
58
58
  res.status(201)
59
59
  res.json({
@@ -0,0 +1,21 @@
1
+ import { DataTypes } from 'sequelize';
2
+
3
+ async function up({context: QueryInterface}) {
4
+ await QueryInterface.createTable('things', {
5
+ id: {
6
+ allowNull: false,
7
+ autoIncrement: true,
8
+ primaryKey: true,
9
+ type: DataTypes.INTEGER
10
+ },
11
+ name: {
12
+ type: DataTypes.STRING
13
+ }
14
+ });
15
+ }
16
+
17
+ async function down({context: QueryInterface}) {
18
+ await QueryInterface.dropTable('things');
19
+ }
20
+
21
+ export { up, down }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-sip",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "main": "index.js",
5
5
  "bin": {
6
6
  "create-sip": "create-sip.js"
@@ -1,15 +0,0 @@
1
- {
2
- "app": {
3
- "port": 8000,
4
- "key": "",
5
- "log": "console.log"
6
- },
7
- "db": {
8
- "dialect": "sqlite",
9
- "host": "127.0.0.1",
10
- "name": "",
11
- "user": "",
12
- "pass": "",
13
- "path": ":memory:"
14
- }
15
- }