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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
package/expressapi/package.json
CHANGED
|
@@ -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",
|
|
@@ -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 ',
|
|
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
|
|
19
|
+
.expect(201)
|
|
20
20
|
|
|
21
21
|
})
|
|
22
|
-
it('post /login ', (
|
|
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
|
|
32
|
-
|
|
33
|
-
|
|
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 ',
|
|
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
|
|
44
|
+
.expect(200)
|
|
42
45
|
})
|
|
43
46
|
})
|