create-sip 1.4.6 → 1.5.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.
@@ -1,11 +1,7 @@
1
1
  import Sequelize from 'sequelize'
2
2
  import dotenvFlow from 'dotenv-flow'
3
3
 
4
- if (process.env.NODE_ENV === 'test') {
5
- dotenvFlow.config({ path: '.env.test' })
6
- }else {
7
- dotenvFlow.config()
8
- }
4
+ dotenvFlow.config()
9
5
 
10
6
  const sequelize = new Sequelize(
11
7
  process.env.DB_NAME,
package/expressapi/op CHANGED
@@ -1,650 +1,5 @@
1
- import yargs from 'yargs';
2
- import { hideBin } from 'yargs/helpers';
3
- import replace from 'replace';
4
- import fse from 'fs-extra';
5
- import fsp from 'fs/promises';
6
- import { generateApiKey } from 'generate-api-key';
7
- import bcrypt from 'bcryptjs';
8
- import path from 'path';
9
- import { read } from 'read';
1
+ #!/usr/bin/env node
10
2
 
11
- yargs(hideBin(process.argv))
12
- .version()
13
- .strict()
14
- .usage('Használat: node op <command> [name]')
15
- .help()
16
- .demandCommand(2, 'Not enough arguments!')
17
- .command({
18
- command: 'make:model <name>',
19
- description: 'Generates a new Sequelize model',
20
- builder: (yargs) => {
21
- return yargs
22
- .positional('name', {
23
- type: 'string',
24
- description: 'Name of the model'
25
- })
26
- },
27
- handler: async (argv) => {
28
- const { name } = argv;
29
- await copyModel(name);
30
- }
31
- })
32
- .command({
33
- command: 'make:controller <name>',
34
- description: 'Generates a new controller',
35
- builder: (yargs) => {
36
- return yargs
37
- .positional('name', {
38
- type: 'string',
39
- description: 'Name of the controller'
40
- })
41
- },
42
- handler: async (argv) => {
43
- const { name } = argv;
44
- await copyController(name);
45
- }
46
- })
47
- .command({
48
- command: 'key:generate',
49
- description:'Generates a new API key',
50
- builder: (yargs) => {
51
- return yargs
52
- },
53
- handler: async (argv) => {
54
- startGenerateKey();
55
- }
56
- })
57
- .command({
58
- command: 'conf:generate',
59
- description:'Generate a new config file: .env',
60
- builder: (yargs) => {
61
- return yargs
62
- },
63
- handler: async (argv) => {
64
- startGenerateConf();
65
- }
66
- })
67
- .command({
68
- command: 'testconf:generate',
69
- description:'Generate a new config file: .env.test',
70
- builder: (yargs) => {
71
- return yargs
72
- },
73
- handler: async (argv) => {
74
- startGenerateTestConf();
75
- }
76
- })
77
- .command({
78
- command: 'admin:generate',
79
- description:'Generates a new admin user in database table: users',
80
- builder: (yargs) => {
81
- return yargs
82
- },
83
- handler: async (argv) => {
84
- startGenerateAdmin();
85
- }
86
- })
87
- .command({
88
- command: 'db:import <model> <filePath> [sep]',
89
- description: `Import CSV or JSON file to database table
3
+ import { opCommander } from '@sipere/op-cli/commander.js';
90
4
 
91
- Examples:
92
- node op db:import thing somethings.json
93
- node op db:import thing somethings.csv
94
- node op db:import thing somethings.csv ,
95
- node op db:import thing somethings.csv :
96
- node op db:import thing somethings.csv ";"
97
-
98
- In CSV file the field names must match the model fields.
99
- `,
100
- builder: (yargs) => {
101
- return yargs
102
- .positional('model', {
103
- type: 'string',
104
- description: 'Name of the model'
105
- })
106
- .positional('filePath', {
107
- type: 'string',
108
- description: 'Path to the file'
109
- })
110
- .positional('sep', {
111
- type: 'string',
112
- description: 'Separator for the CSV file'
113
- })
114
- },
115
- handler: async (argv) => {
116
- const { model, filePath, sep } = argv;
117
- await runImportData(model, filePath, sep);
118
- }
119
- })
120
- .command({
121
- command: 'make:migration <name>',
122
- description:'Generates a new migration file',
123
- builder: (yargs) => {
124
- return yargs
125
- .positional('name', {
126
- type: 'string',
127
- description: 'Name of the migration'
128
- })
129
- },
130
- handler: async (argv) => {
131
- const { name } = argv;
132
- await createMigration(name);
133
- }
134
- })
135
- .command({
136
- command: 'migrate [migrationName]',
137
- description:'Runs migrations',
138
- aliases: ['migration:run'],
139
- builder: (yargs) => {
140
- return yargs
141
- .positional('migrationName', {
142
- type: 'string',
143
- description: 'Name of the migration'
144
- })
145
- },
146
- handler: async (argv) => {
147
- const { migrationName } = argv;
148
- await startMigrations(migrationName);
149
- }
150
- })
151
- .command({
152
- command: 'migrate:rollback',
153
- description:'Rolls back migrations',
154
- aliases: ['migration:rollback'],
155
- builder: (yargs) => {
156
- return yargs
157
- .options({
158
- step: {
159
- type: 'number',
160
- description: 'Number of migrations to rollback',
161
- default: 1
162
- }
163
- })
164
- },
165
- handler: async (argv) => {
166
- const { step } = argv;
167
- await rollbackMigrations(step);
168
- }
169
- })
170
- .command({
171
- command: 'migrate:fresh',
172
- description:'Rolls back all migrations',
173
- aliases: ['migration:fresh'],
174
- builder: (yargs) => {
175
- return yargs
176
- },
177
- handler: async (argv) => {
178
- await freshMigrations();
179
- }
180
- })
181
- .command({
182
- command: 'migrate:reset',
183
- description:'Rolls back all migrations',
184
- aliases: ['migration:reset'],
185
- builder: (yargs) => {
186
- return yargs
187
- },
188
- handler: async (argv) => {
189
- await resetMigrations();
190
- }
191
- })
192
- .command({
193
- command: 'make:seeder <seederName>',
194
- description:'Make seeder...',
195
- builder: (yargs) => {
196
- return yargs
197
- .positional('seederName', {
198
- type: 'string',
199
- description: 'Name of the seeder'
200
- })
201
- },
202
- handler: async (argv) => {
203
- const { seederName } = argv;
204
- await createSeeder(seederName);
205
- }
206
- })
207
- .command({
208
- command: 'db:seed [seederName]',
209
- description:'Runs seeders',
210
- aliases: ['seeder:run'],
211
- builder: (yargs) => {
212
- return yargs
213
- .positional('seederName', {
214
- type: 'string',
215
- description: 'Name of the seeder'
216
- })
217
- },
218
- handler: async (argv) => {
219
- const { seederName } = argv;
220
- await startSeeders(seederName);
221
- }
222
- })
223
- .parse();
224
-
225
- async function copyController(className) {
226
- const lowerName = className.toLowerCase()
227
-
228
- const src = 'templates/controllerTemplate.js'
229
- const dest = `app/controllers/${lowerName}Controller.js`
230
-
231
- if(await startCheckIfFileExists(dest)) {
232
- process.exit(1);
233
- }
234
-
235
- await fse.copy(src, dest)
236
-
237
- replace({
238
- regex: 'Thing',
239
- replacement: capitalizeFirstLetter(className),
240
- paths: [dest]
241
- })
242
- replace({
243
- regex: 'thing',
244
- replacement: className,
245
- paths: [dest]
246
- })
247
- replace({
248
- regex: 'things',
249
- replacement: className + 's',
250
- paths: [dest]
251
- })
252
- }
253
-
254
- async function copyModel(className) {
255
- const lowerName = className.toLowerCase()
256
- const src = 'templates/modelTemplate.js'
257
- const dest = `app/models/${lowerName}.js`
258
-
259
- if(await startCheckIfFileExists(dest)) {
260
- process.exit(1);
261
- }
262
-
263
- await fse.copy(src, dest)
264
-
265
- replace({
266
- regex: 'Thing',
267
- replacement: capitalizeFirstLetter(className),
268
- paths: [dest]
269
- })
270
- replace({
271
- regex: 'thing',
272
- replacement: className,
273
- paths: [dest]
274
- })
275
- }
276
-
277
- async function checkIfFileExists(filePath) {
278
- return await fsp.access(filePath)
279
- .then(() => true)
280
- .catch(() => false);
281
- }
282
-
283
- async function startCheckIfFileExists(filePath) {
284
- return await checkIfFileExists(filePath)
285
- .then(exists => {
286
- if (exists) {
287
- console.log(`The file ${filePath} exists!`);
288
- return true;
289
- } else {
290
- return false;
291
- }
292
- })
293
- .catch(error => console.log(error));
294
- }
295
-
296
- function capitalizeFirstLetter(word) {
297
- return word.charAt(0).toUpperCase() + word.slice(1);
298
- }
299
-
300
- async function startGenerateKey() {
301
- try {
302
- const envFile = '.env'
303
- let content = await fse.readFile(envFile, 'utf8')
304
- const key = generateApiKey({method: 'bytes', length: 32})
305
-
306
- const regex = /^APP_KEY *=.*$/m
307
- if(regex.test(content)) {
308
- content = content.replace(regex, `APP_KEY=${key}`)
309
- }else {
310
- if(content.trim() !== '') {
311
- content += `\nAPP_KEY=${key}\n`
312
- }else {
313
- content = `APP_KEY=${key}\n`
314
- }
315
- }
316
- fse.writeFile(envFile, content, 'utf8')
317
- } catch (error) {
318
- console.error(error)
319
- }
320
- }
321
-
322
- async function startGenerateConf() {
323
- const content = `
324
- APP_PORT=8000
325
- APP_KEY=
326
- APP_LOG=console.log
327
-
328
- DB_DIALECT=sqlite
329
- DB_HOST=127.0.0.1
330
- DB_NAME=
331
- DB_USER=
332
- DB_PASS=
333
- DB_STORAGE=database.sqlite
334
- `
335
- const destinationFileName = '.env'
336
- if(await startCheckIfFileExists(destinationFileName)) {
337
- process.exit(1);
338
- }
339
-
340
- await fse.writeFile(destinationFileName, content, 'utf8')
341
- }
342
-
343
- async function startGenerateTestConf() {
344
- const content = `
345
- APP_PORT=8000
346
- APP_KEY=my_secret_key
347
- APP_LOG=console.log
348
-
349
- DB_DIALECT=sqlite
350
- DB_STORAGE=:memory:
351
- `
352
- const destinationFileName = '.env.test'
353
- if(await startCheckIfFileExists(destinationFileName)) {
354
- process.exit(1);
355
- }
356
-
357
- await fse.writeFile(destinationFileName, content, 'utf8')
358
- }
359
-
360
- async function inputPassword() {
361
- const password = await read({
362
- prompt: 'Password: ',
363
- silent: true,
364
- replace: '*'
365
- })
366
- return password
367
- }
368
-
369
- async function startGenerateAdmin() {
370
- try {
371
- const { default: User } = await import('./app/models/user.js')
372
- await User.sync()
373
-
374
- const isUserExist = await User.findOne({ where: { name: 'admin' } })
375
- if (isUserExist) {
376
- console.log('Admin already exists!')
377
- return;
378
- }
379
- const password = await inputPassword()
380
- const hashedPassword = await bcrypt.hash(password, 10);
381
- await User.create({
382
- name: 'admin',
383
- email: 'admin',
384
- password: hashedPassword
385
- })
386
- console.log('Admin created!')
387
- } catch (error) {
388
- console.error('Error creating admin!')
389
- console.error(error)
390
- }
391
- }
392
-
393
- const importFromJson = async (model, filePath) => {
394
- try {
395
- const data = JSON.parse(await fsp.readFile(filePath, 'utf8'))
396
- await model.bulkCreate(data)
397
- console.log(`Data imported successfully! ${model.name}`)
398
- } catch (error) {
399
- console.error(error)
400
- }
401
- }
402
-
403
- const importFromCsv = async (model, filePath, sep) => {
404
- try {
405
- const data = await fsp.readFile(filePath, 'utf8')
406
- const clearData = data.replace(/"/g, '').trim()
407
- const rows = clearData.split('\n')
408
- const headerColumns = rows.shift().split(sep)
409
-
410
- const dataToInsert = rows.map(row => {
411
- const columns = row.split(sep).map(item => {
412
- const number = Number(item)
413
- return Number.isNaN(number) ? `${item}` : number
414
- })
415
- return headerColumns.reduce((obj, header, index) => {
416
- obj[header] = columns[index]
417
- return obj
418
- }, {})
419
- })
420
-
421
- await model.bulkCreate(dataToInsert)
422
- console.log(`Data imported successfully! ${model.name}`)
423
- } catch (error) {
424
- console.error(error)
425
- }
426
- }
427
-
428
- async function runImportData(model, filePath, sep=',') {
429
-
430
- if(!filePath || !model) {
431
- console.log('Usage: node db:import <modelName> <filePath> [sep]')
432
- process.exit(1)
433
- }
434
-
435
- try {
436
- await import(`./app/models/${model}.js`)
437
- } catch (error) {
438
- console.log(`The ${model} model file does not exist!`)
439
- process.exit(1)
440
- }
441
-
442
- try {
443
- await fsp.stat(filePath)
444
- } catch (error) {
445
- if (error.code === 'ENOENT') {
446
- console.log(`The file ${filePath} does not exist!`)
447
- process.exit(1)
448
- } else {
449
- console.error(error)
450
- process.exit(1)
451
- }
452
- }
453
-
454
- const modelInstance = await import(`./app/models/${model}.js`)
455
- const modelObject = modelInstance.default
456
-
457
- const ext = path.extname(filePath).toLowerCase()
458
- if(ext !== '.json' && ext !== '.csv') {
459
- console.log('The file must have .json or .csv extension!')
460
- process.exit(1)
461
- }
462
- const { default: sequelize } = await import('./app/database/database.js')
463
- try {
464
- await sequelize.sync({ force: true })
465
- await sequelize.authenticate()
466
- if(ext === '.csv') {
467
- await importFromCsv(modelObject, filePath, sep)
468
- }else {
469
- await importFromJson(modelObject, filePath)
470
- }
471
- } catch (error) {
472
- console.error(error)
473
- }
474
-
475
- }
476
-
477
- async function createMigration(name) {
478
- console.log('Create a new migration...', name)
479
-
480
- const lowerName = name.toLowerCase()
481
- const date = new Date()
482
- const year = date.getFullYear()
483
- const month = String(date.getMonth() + 1).padStart(2, '0')
484
- const day = String(date.getDate()).padStart(2, '0')
485
- const hours = String(date.getHours()).padStart(2, '0')
486
- const minutes = String(date.getMinutes()).padStart(2, '0')
487
- const seconds = String(date.getSeconds()).padStart(2, '0')
488
- const timestamp = `${year}_${month}_${day}_${hours}${minutes}${seconds}`
489
-
490
- const migrationName = `${timestamp}_${lowerName}`
491
-
492
- const src = 'templates/migrationTemplate.js'
493
- const dest = `database/migrations/${migrationName}.js`
494
-
495
- if(await startCheckIfFileExists(dest)) {
496
- process.exit(1);
497
- }
498
-
499
- await fse.copy(src, dest)
500
-
501
- replace({
502
- regex: 'thing',
503
- replacement: name,
504
- paths: [dest]
505
- })
506
-
507
- }
508
-
509
- async function getUmzug(directory) {
510
- const { default: sequelize } = await import('./app/database/database.js')
511
- const { Umzug, SequelizeStorage } = await import('umzug')
512
-
513
- const umzug = new Umzug({
514
- migrations: { glob: directory },
515
- context: sequelize.getQueryInterface(),
516
- storage: new SequelizeStorage({ sequelize }),
517
- logger: console
518
- })
519
-
520
- return umzug
521
- }
522
-
523
- async function startMigrations(name) {
524
- if(name) {
525
- await runOneMigration(name)
526
- } else {
527
- await runMigrations()
528
- }
529
- }
530
-
531
- async function runOneMigration(name) {
532
- console.log('Run one migration...', name)
533
-
534
- if(!name.endsWith('.js')) {
535
- name += '.js'
536
- }
537
- let migrationPath = '';
538
- if(!name.startsWith('database/migrations/')) {
539
- migrationPath = `database/migrations/${name}`
540
- }else {
541
- migrationPath = name
542
- }
543
-
544
- if(!await startCheckIfFileExists(migrationPath)) {
545
- console.log(`The migration file ${migrationPath} not already exists.`)
546
- process.exit(1)
547
- }
548
-
549
- const umzug = await getUmzug(migrationPath)
550
- await umzug.up()
551
- }
552
-
553
- async function runMigrations() {
554
- console.log('Run migrations...')
555
- const umzug = await getUmzug('./database/migrations/*.js')
556
- await umzug.up()
557
- }
558
-
559
- async function resetMigrations() {
560
- console.log('Reset migrations...')
561
- const umzugSeeder = await getUmzug('./database/seeders/*.js')
562
- await umzugSeeder.down({ to: 0 })
563
- const umzug = await getUmzug('./database/migrations/*.js')
564
- await umzug.down({ to: 0 })
565
- }
566
-
567
- async function freshMigrations() {
568
- console.log('Fresh migrations...')
569
- await resetMigrations()
570
- await runMigrations()
571
- }
572
-
573
- async function rollbackMigrations(step = 1) {
574
- console.log('Rollback migrations...')
575
- const umzug = await getUmzug('./database/migrations/*.js')
576
- await umzug.down({ step: step })
577
- }
578
-
579
- async function createSeeder(name) {
580
- console.log('Create a new seeder...', name)
581
-
582
- const lowerName = name.toLowerCase()
583
- const date = new Date()
584
- const year = date.getFullYear()
585
- const month = String(date.getMonth() + 1).padStart(2, '0')
586
- const day = String(date.getDate()).padStart(2, '0')
587
- const hours = String(date.getHours()).padStart(2, '0')
588
- const minutes = String(date.getMinutes()).padStart(2, '0')
589
- const seconds = String(date.getSeconds()).padStart(2, '0')
590
- const timestamp = `${year}_${month}_${day}_${hours}${minutes}${seconds}`
591
-
592
- const migrationName = `${timestamp}_${lowerName}`
593
-
594
- const src = 'templates/seederTemplate.js'
595
- const dest = `database/seeders/${migrationName}.js`
596
-
597
- if(await startCheckIfFileExists(dest)) {
598
- process.exit(1);
599
- }
600
-
601
- await fse.copy(src, dest)
602
-
603
- replace({
604
- regex: 'Thing',
605
- replacement: capitalizeFirstLetter(name),
606
- paths: [dest]
607
- })
608
- replace({
609
- regex: 'thing',
610
- replacement: name,
611
- paths: [dest]
612
- })
613
-
614
- }
615
-
616
- async function startSeeders(name) {
617
- if(name) {
618
- await runOneSeeder(name)
619
- } else {
620
- await runSeeders()
621
- }
622
- }
623
-
624
- async function runOneSeeder(name) {
625
- console.log('Run one seeder...', name)
626
-
627
- if(!name.endsWith('.js')) {
628
- name += '.js'
629
- }
630
- let seederPath = ''
631
- if(!name.startsWith('database/seeders/')) {
632
- seederPath = `database/seeders/${name}`
633
- }else {
634
- seederPath = name
635
- }
636
-
637
- if(!await startCheckIfFileExists(seederPath)) {
638
- console.log(`The seeder file ${seederPath} not already exists.`)
639
- process.exit(1)
640
- }
641
-
642
- const umzug = await getUmzug(seederPath)
643
- await umzug.up()
644
- }
645
-
646
- async function runSeeders() {
647
- console.log('Run seeders...')
648
- const umzug = await getUmzug('./database/seeders/*.js')
649
- await umzug.up()
650
- }
5
+ opCommander.parse(process.argv)
@@ -4,7 +4,7 @@
4
4
  "description": "Express API with simple Sequelize",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
- "test": "NODE_ENV=test mocha",
7
+ "test": "NODE_ENV=test mocha --file test/mocha.setup.js 'test/**/*.spec.js'",
8
8
  "dev": "nodemon app",
9
9
  "start": "node app"
10
10
  },
@@ -16,6 +16,7 @@
16
16
  "license": "ISC",
17
17
  "dependencies": {
18
18
  "@dotenvx/dotenvx": "^1.51.0",
19
+ "@sipere/op-cli": "^0.9.2",
19
20
  "bcryptjs": "^2.4.3",
20
21
  "cors": "^2.8.5",
21
22
  "dotenv-flow": "^4.1.0",
@@ -0,0 +1,8 @@
1
+ import sequelize from '../app/database/database.js'
2
+
3
+ before(async() => {
4
+ await sequelize.sync({ force: true })
5
+ })
6
+ after(async() => {
7
+ await sequelize.close()
8
+ })
@@ -5,8 +5,8 @@ describe('/api/register and /api/login', () => {
5
5
  const restype= 'application/json; charset=utf-8'
6
6
  var token = null
7
7
 
8
- it('post /register ', function(done) {
9
- supertest(app)
8
+ it('post /register ', async () => {
9
+ await supertest(app)
10
10
  .post('/api/register')
11
11
  .set('Accept', 'application/json')
12
12
  .send({
@@ -16,11 +16,11 @@ describe('/api/register and /api/login', () => {
16
16
  password_confirmation: 'titok'
17
17
  })
18
18
  .expect('Content-Type', restype)
19
- .expect(201, done)
19
+ .expect(201)
20
20
 
21
21
  })
22
- it('post /login ', (done) => {
23
- supertest(app)
22
+ it('post /login ', async () => {
23
+ const res = await supertest(app)
24
24
  .post('/api/login')
25
25
  .set('Accept', 'application/json')
26
26
  .send({
@@ -28,16 +28,19 @@ describe('/api/register and /api/login', () => {
28
28
  password: 'titok'
29
29
  })
30
30
  .expect('Content-Type', restype)
31
- .expect(200, done)
32
- .expect(res => {
33
- token = res.body.accessToken
34
- })
31
+ .expect(200)
32
+
33
+ token = res.body.accessToken
34
+
35
+ if(!token) {
36
+ throw new Error('No token')
37
+ }
35
38
  })
36
- it('get /users ', function(done) {
37
- supertest(app)
39
+ it('get /users ', async () => {
40
+ await supertest(app)
38
41
  .get('/api/users')
39
42
  .set('Accept', 'application/json')
40
43
  .set('Authorization', 'Bearer ' + token)
41
- .expect(200, done)
44
+ .expect(200)
42
45
  })
43
46
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-sip",
3
- "version": "1.4.6",
3
+ "version": "1.5.0",
4
4
  "main": "index.js",
5
5
  "bin": {
6
6
  "create-sip": "create-sip.js"