@anthonylzq/simba.js 6.2.2 → 7.0.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.
- package/lib/index.js +48 -17
- package/lib/src/functions/api/database.js +4 -4
- package/lib/src/functions/api/express.js +1989 -0
- package/lib/src/functions/api/fastify.js +2033 -0
- package/lib/src/functions/api/index.js +36 -140
- package/lib/src/functions/api/services.js +5 -5
- package/lib/src/functions/eslint.js +8 -3
- package/lib/src/functions/ghat.js +2 -1
- package/lib/src/functions/tests.js +20 -15
- package/lib/src/functions/tsconfig.js +7 -2
- package/lib/src/index.js +36 -27
- package/lib/src/utils/constants.js +2 -2
- package/package.json +61 -24
- package/lib/src/functions/api/network.js +0 -2444
|
@@ -0,0 +1,1989 @@
|
|
|
1
|
+
const { platform } = require('os')
|
|
2
|
+
const { promisify } = require('util')
|
|
3
|
+
const exec = promisify(require('child_process').exec)
|
|
4
|
+
const writeFile = require('../../utils/writeFile')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {Object} args
|
|
8
|
+
* @param {String} args.projectName
|
|
9
|
+
* @param {Boolean} args.graphQL
|
|
10
|
+
* @param {Boolean} args.dbIsSQL
|
|
11
|
+
*/
|
|
12
|
+
const types = async ({ projectName, graphQL, dbIsSQL }) => {
|
|
13
|
+
const createFoldersCommand = `mkdir ${projectName}/src/@types ${projectName}/src/@types/custom \
|
|
14
|
+
${!dbIsSQL ? ` ${projectName}/src/@types/models` : ''} ${
|
|
15
|
+
graphQL ? ` ${projectName}/src/@types/graphQL` : ''
|
|
16
|
+
}`
|
|
17
|
+
|
|
18
|
+
if (platform() === 'win32')
|
|
19
|
+
await exec(createFoldersCommand.replaceAll('/', '\\'))
|
|
20
|
+
else await exec(createFoldersCommand)
|
|
21
|
+
|
|
22
|
+
const types = {
|
|
23
|
+
index: {
|
|
24
|
+
content: `/* eslint-disable no-var */
|
|
25
|
+
declare global {}
|
|
26
|
+
|
|
27
|
+
export {}
|
|
28
|
+
`,
|
|
29
|
+
file: `${projectName}/src/@types/index.d.ts`
|
|
30
|
+
},
|
|
31
|
+
custom: {
|
|
32
|
+
request: {
|
|
33
|
+
content: `type ExpressRequest = import('express').Request
|
|
34
|
+
|
|
35
|
+
interface CustomRequest extends ExpressRequest {
|
|
36
|
+
log: import('express-pino-logger').HttpLogger['logger']
|
|
37
|
+
body: {
|
|
38
|
+
args?: import('schemas').UserDTO
|
|
39
|
+
}
|
|
40
|
+
// We can add custom headers via intersection, remember that for some reason
|
|
41
|
+
// headers must be in Snake-Pascal-Case
|
|
42
|
+
headers: import('http').IncomingHttpHeaders & {
|
|
43
|
+
'Custom-Header'?: string
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
`,
|
|
47
|
+
file: `${projectName}/src/@types/custom/request.d.ts`
|
|
48
|
+
},
|
|
49
|
+
response: {
|
|
50
|
+
content: `type ExpressResponse = import('express').Response
|
|
51
|
+
|
|
52
|
+
interface CustomResponse extends ExpressResponse {
|
|
53
|
+
newValue?: string
|
|
54
|
+
}
|
|
55
|
+
`,
|
|
56
|
+
file: `${projectName}/src/@types/custom/response.d.ts`
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
...(!dbIsSQL && {
|
|
60
|
+
models: {
|
|
61
|
+
user: {
|
|
62
|
+
content: `interface UserDBO {
|
|
63
|
+
name: string
|
|
64
|
+
lastName: string
|
|
65
|
+
createdAt: Date
|
|
66
|
+
updatedAt: Date
|
|
67
|
+
}
|
|
68
|
+
`,
|
|
69
|
+
file: `${projectName}/src/@types/models/user.d.ts`
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}),
|
|
73
|
+
...(graphQL && {
|
|
74
|
+
graphQL: {
|
|
75
|
+
context: {
|
|
76
|
+
content: `type Context = {
|
|
77
|
+
log: import('express-pino-logger').HttpLogger['logger']
|
|
78
|
+
}
|
|
79
|
+
`,
|
|
80
|
+
file: `${projectName}/src/@types/graphQL/context.d.ts`
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
const processes = [
|
|
86
|
+
writeFile(types.index.file, types.index.content),
|
|
87
|
+
writeFile(types.custom.request.file, types.custom.request.content),
|
|
88
|
+
writeFile(types.custom.response.file, types.custom.response.content)
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
if (!dbIsSQL)
|
|
92
|
+
processes.push(writeFile(types.models.user.file, types.models.user.content))
|
|
93
|
+
|
|
94
|
+
if (graphQL)
|
|
95
|
+
processes.push(
|
|
96
|
+
writeFile(types.graphQL.context.file, types.graphQL.context.content)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
await Promise.all(processes)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @param {Object} args
|
|
104
|
+
* @param {String} args.projectName
|
|
105
|
+
*/
|
|
106
|
+
const mongo = async ({ projectName }) => {
|
|
107
|
+
const createFoldersCommand = `mkdir ${projectName}/src/database \
|
|
108
|
+
${projectName}/src/database/mongo ${projectName}/src/database/mongo/models \
|
|
109
|
+
${projectName}/src/database/mongo/queries`
|
|
110
|
+
|
|
111
|
+
if (platform() === 'win32')
|
|
112
|
+
await exec(createFoldersCommand.replaceAll('/', '\\'))
|
|
113
|
+
else await exec(createFoldersCommand)
|
|
114
|
+
|
|
115
|
+
const database = {
|
|
116
|
+
index: {
|
|
117
|
+
content: "export * from './mongo'\n",
|
|
118
|
+
file: `${projectName}/src/database/index.ts`
|
|
119
|
+
},
|
|
120
|
+
mongo: {
|
|
121
|
+
connection: {
|
|
122
|
+
content: `import { connect, connection } from 'mongoose'
|
|
123
|
+
import { HttpLogger } from 'express-pino-logger'
|
|
124
|
+
|
|
125
|
+
const ENVIRONMENTS_WITHOUT_RECONNECTION = ['ci', 'local']
|
|
126
|
+
const dbConnection = async (
|
|
127
|
+
logger: HttpLogger['logger']
|
|
128
|
+
): Promise<{
|
|
129
|
+
connect: () => Promise<typeof import('mongoose')>
|
|
130
|
+
disconnect: () => Promise<void>
|
|
131
|
+
}> => {
|
|
132
|
+
const connectionConfig = {
|
|
133
|
+
keepAlive: true,
|
|
134
|
+
useNewUrlParser: true,
|
|
135
|
+
useUnifiedTopology: true
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
connection.on('connected', () => {
|
|
139
|
+
logger.info('Mongo connection established.')
|
|
140
|
+
})
|
|
141
|
+
connection.on('reconnected', () => {
|
|
142
|
+
logger.info('Mongo connection reestablished')
|
|
143
|
+
})
|
|
144
|
+
connection.on('disconnected', () => {
|
|
145
|
+
if (
|
|
146
|
+
!ENVIRONMENTS_WITHOUT_RECONNECTION.includes(
|
|
147
|
+
process.env.NODE_ENV as string
|
|
148
|
+
)
|
|
149
|
+
) {
|
|
150
|
+
logger.info(
|
|
151
|
+
'Mongo connection disconnected. Trying to reconnected to Mongo...'
|
|
152
|
+
)
|
|
153
|
+
setTimeout(() => {
|
|
154
|
+
connect(process.env.DB_URI as string, {
|
|
155
|
+
...connection,
|
|
156
|
+
connectTimeoutMS: 3000,
|
|
157
|
+
socketTimeoutMS: 3000
|
|
158
|
+
})
|
|
159
|
+
}, 3000)
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
connection.on('close', () => {
|
|
163
|
+
logger.info('Mongo connection closed')
|
|
164
|
+
})
|
|
165
|
+
connection.on('error', (e: Error) => {
|
|
166
|
+
logger.info('Mongo connection error:')
|
|
167
|
+
logger.error(e)
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
connect: () => connect(process.env.DB_URI as string, connectionConfig),
|
|
172
|
+
disconnect: () => connection.close()
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export { dbConnection }
|
|
177
|
+
`,
|
|
178
|
+
file: `${projectName}/src/database/mongo/connection.ts`
|
|
179
|
+
},
|
|
180
|
+
index: {
|
|
181
|
+
content: `export * from './models'
|
|
182
|
+
export * from './queries'
|
|
183
|
+
export * from './connection'
|
|
184
|
+
`,
|
|
185
|
+
file: `${projectName}/src/database/mongo/index.ts`
|
|
186
|
+
},
|
|
187
|
+
models: {
|
|
188
|
+
index: {
|
|
189
|
+
content: "export * from './user'\n",
|
|
190
|
+
file: `${projectName}/src/database/mongo/models/index.ts`
|
|
191
|
+
},
|
|
192
|
+
user: {
|
|
193
|
+
content: `import { model, Schema } from 'mongoose'
|
|
194
|
+
|
|
195
|
+
const UserSchema = new Schema<UserDBO>(
|
|
196
|
+
{
|
|
197
|
+
lastName: {
|
|
198
|
+
required: true,
|
|
199
|
+
type: String
|
|
200
|
+
},
|
|
201
|
+
name: {
|
|
202
|
+
required: true,
|
|
203
|
+
type: String
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
timestamps: true,
|
|
208
|
+
versionKey: false,
|
|
209
|
+
toObject: {
|
|
210
|
+
transform: (_, ret) => {
|
|
211
|
+
ret.id = ret._id.toString()
|
|
212
|
+
delete ret._id
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
const UserModel = model<UserDBO>('users', UserSchema)
|
|
219
|
+
|
|
220
|
+
export { UserModel }
|
|
221
|
+
`,
|
|
222
|
+
file: `${projectName}/src/database/mongo/models/user.ts`
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
queries: {
|
|
226
|
+
index: {
|
|
227
|
+
content: "export * from './user'\n",
|
|
228
|
+
file: `${projectName}/src/database/mongo/queries/index.ts`
|
|
229
|
+
},
|
|
230
|
+
user: {
|
|
231
|
+
content: `import { Document, MergeType, Types } from 'mongoose'
|
|
232
|
+
|
|
233
|
+
import { UserModel } from '..'
|
|
234
|
+
import { User, UserDTO, UserWithId } from 'schemas'
|
|
235
|
+
|
|
236
|
+
const userDBOtoDTO = (
|
|
237
|
+
userDBO: Document<unknown, unknown, MergeType<UserDBO, UserDBO>> &
|
|
238
|
+
Omit<UserDBO, keyof UserDBO> &
|
|
239
|
+
UserDBO & {
|
|
240
|
+
_id: Types.ObjectId
|
|
241
|
+
}
|
|
242
|
+
): UserDTO => ({
|
|
243
|
+
...userDBO.toObject(),
|
|
244
|
+
createdAt: userDBO.createdAt.toISOString(),
|
|
245
|
+
updatedAt: userDBO.updatedAt.toISOString()
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
const store = async (userData: User): Promise<UserDTO> => {
|
|
249
|
+
const user = new UserModel(userData)
|
|
250
|
+
|
|
251
|
+
await user.save()
|
|
252
|
+
|
|
253
|
+
return userDBOtoDTO(user)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const remove = async (
|
|
257
|
+
id: string | null = null
|
|
258
|
+
): Promise<UserDTO | number | null> => {
|
|
259
|
+
if (id) {
|
|
260
|
+
const removedUser = await UserModel.findByIdAndRemove(id)
|
|
261
|
+
|
|
262
|
+
if (!removedUser) return null
|
|
263
|
+
|
|
264
|
+
return userDBOtoDTO(removedUser)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return (await UserModel.deleteMany({})).deletedCount
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const get = async (
|
|
271
|
+
id: string | null = null
|
|
272
|
+
): Promise<UserDTO[] | UserDTO | null> => {
|
|
273
|
+
if (id) {
|
|
274
|
+
const user = await UserModel.findById(id)
|
|
275
|
+
|
|
276
|
+
return user ? userDBOtoDTO(user) : null
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const users = await UserModel.find({})
|
|
280
|
+
|
|
281
|
+
return users.map(u => userDBOtoDTO(u))
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const update = async (userData: UserWithId): Promise<UserDTO | null> => {
|
|
285
|
+
const { id, ...rest } = userData
|
|
286
|
+
const user = await UserModel.findByIdAndUpdate(id, rest, { new: true })
|
|
287
|
+
|
|
288
|
+
return user ? userDBOtoDTO(user) : null
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export { store, remove, get, update }
|
|
292
|
+
`,
|
|
293
|
+
file: `${projectName}/src/database/mongo/queries/user.ts`
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
await Promise.all([
|
|
300
|
+
writeFile(database.index.file, database.index.content),
|
|
301
|
+
writeFile(
|
|
302
|
+
database.mongo.connection.file,
|
|
303
|
+
database.mongo.connection.content
|
|
304
|
+
),
|
|
305
|
+
writeFile(database.mongo.index.file, database.mongo.index.content),
|
|
306
|
+
writeFile(
|
|
307
|
+
database.mongo.models.index.file,
|
|
308
|
+
database.mongo.models.index.content
|
|
309
|
+
),
|
|
310
|
+
writeFile(
|
|
311
|
+
database.mongo.models.user.file,
|
|
312
|
+
database.mongo.models.user.content
|
|
313
|
+
),
|
|
314
|
+
writeFile(
|
|
315
|
+
database.mongo.queries.index.file,
|
|
316
|
+
database.mongo.queries.index.content
|
|
317
|
+
),
|
|
318
|
+
writeFile(
|
|
319
|
+
database.mongo.queries.user.file,
|
|
320
|
+
database.mongo.queries.user.content
|
|
321
|
+
)
|
|
322
|
+
])
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* @param {Object} args
|
|
327
|
+
* @param {String} args.projectName
|
|
328
|
+
* @param {import('../../../../').Config['database']} args.db
|
|
329
|
+
*/
|
|
330
|
+
const sql = async ({ projectName, db }) => {
|
|
331
|
+
const createFoldersCommand = `mkdir ${projectName}/src/scripts \
|
|
332
|
+
${projectName}/src/database ${projectName}/src/database/${db} \
|
|
333
|
+
${projectName}/src/database/${db}/models ${projectName}/src/database/${db}/queries \
|
|
334
|
+
${projectName}/src/database/migrations`
|
|
335
|
+
|
|
336
|
+
if (platform() === 'win32')
|
|
337
|
+
await exec(createFoldersCommand.replaceAll('/', '\\'))
|
|
338
|
+
else await exec(createFoldersCommand)
|
|
339
|
+
|
|
340
|
+
const database = {
|
|
341
|
+
index: {
|
|
342
|
+
content: `export * from './${db}'\n`,
|
|
343
|
+
file: `${projectName}/src/database/index.ts`
|
|
344
|
+
},
|
|
345
|
+
[db]: {
|
|
346
|
+
config: {
|
|
347
|
+
content: `require('dotenv').config()
|
|
348
|
+
|
|
349
|
+
module.exports = {
|
|
350
|
+
development: {
|
|
351
|
+
url: process.env.DB_URI,
|
|
352
|
+
dialect: 'postgres'
|
|
353
|
+
},
|
|
354
|
+
production: {
|
|
355
|
+
url: process.env.DB_URI,
|
|
356
|
+
dialect: 'postgres'
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
`,
|
|
360
|
+
file: `${projectName}/src/database/${db}/config.js`
|
|
361
|
+
},
|
|
362
|
+
connection: {
|
|
363
|
+
content: `import { join } from 'path'
|
|
364
|
+
import { Sequelize } from 'sequelize-typescript'
|
|
365
|
+
import { HttpLogger } from 'express-pino-logger'
|
|
366
|
+
|
|
367
|
+
import * as models from './models'
|
|
368
|
+
|
|
369
|
+
let sequelize: Sequelize
|
|
370
|
+
|
|
371
|
+
const dbConnection = async (
|
|
372
|
+
logger?: HttpLogger['logger']
|
|
373
|
+
): Promise<{
|
|
374
|
+
connect: () => Promise<Sequelize>
|
|
375
|
+
disconnect: () => Promise<void>
|
|
376
|
+
createMigration: (migrationName: string) => Promise<void>
|
|
377
|
+
}> => {
|
|
378
|
+
return {
|
|
379
|
+
connect: async () => {
|
|
380
|
+
if (!sequelize) {
|
|
381
|
+
sequelize = new Sequelize(process.env.DB_URI as string, {
|
|
382
|
+
models: Object.values(models)
|
|
383
|
+
})
|
|
384
|
+
logger?.info('Postgres connection established.')
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return sequelize
|
|
388
|
+
},
|
|
389
|
+
disconnect: () => {
|
|
390
|
+
logger?.info('Postgres connection closed.')
|
|
391
|
+
|
|
392
|
+
return sequelize?.close()
|
|
393
|
+
},
|
|
394
|
+
createMigration: async (migrationName: string) => {
|
|
395
|
+
const { SequelizeTypescriptMigration } = await import(
|
|
396
|
+
'sequelize-typescript-migration-lts'
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
await SequelizeTypescriptMigration.makeMigration(sequelize, {
|
|
400
|
+
outDir: join(__dirname, './migrations'),
|
|
401
|
+
migrationName,
|
|
402
|
+
preview: false
|
|
403
|
+
})
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export { dbConnection }
|
|
409
|
+
`,
|
|
410
|
+
file: `${projectName}/src/database/${db}/connection.ts`
|
|
411
|
+
},
|
|
412
|
+
index: {
|
|
413
|
+
content: `export * from './connection'
|
|
414
|
+
export * from './models'
|
|
415
|
+
export * from './queries'
|
|
416
|
+
`,
|
|
417
|
+
file: `${projectName}/src/database/${db}/index.ts`
|
|
418
|
+
},
|
|
419
|
+
models: {
|
|
420
|
+
index: {
|
|
421
|
+
content: "export * from './user'\n",
|
|
422
|
+
file: `${projectName}/src/database/${db}/models/index.ts`
|
|
423
|
+
},
|
|
424
|
+
user: {
|
|
425
|
+
content: `import { Model, Column, Table, DataType } from 'sequelize-typescript'
|
|
426
|
+
|
|
427
|
+
@Table({
|
|
428
|
+
paranoid: true,
|
|
429
|
+
tableName: 'users'
|
|
430
|
+
})
|
|
431
|
+
class User extends Model {
|
|
432
|
+
@Column({
|
|
433
|
+
type: DataType.STRING
|
|
434
|
+
})
|
|
435
|
+
name!: string
|
|
436
|
+
|
|
437
|
+
@Column({
|
|
438
|
+
type: DataType.STRING
|
|
439
|
+
})
|
|
440
|
+
lastName!: string
|
|
441
|
+
|
|
442
|
+
@Column({
|
|
443
|
+
type: DataType.STRING
|
|
444
|
+
})
|
|
445
|
+
email!: string
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export { User }
|
|
449
|
+
`,
|
|
450
|
+
file: `${projectName}/src/database/${db}/models/user.ts`
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
queries: {
|
|
454
|
+
index: {
|
|
455
|
+
content: "export * from './user'\n",
|
|
456
|
+
file: `${projectName}/src/database/${db}/queries/index.ts`
|
|
457
|
+
},
|
|
458
|
+
user: {
|
|
459
|
+
content: `import { User } from '..'
|
|
460
|
+
import { User as UserSchema, UserDTO, UserWithId } from 'schemas'
|
|
461
|
+
import { Transaction } from 'sequelize/types'
|
|
462
|
+
|
|
463
|
+
const userDBOtoDTO = (userDBO: User): UserDTO => ({
|
|
464
|
+
...userDBO.get(),
|
|
465
|
+
createdAt: userDBO.createdAt.toISOString(),
|
|
466
|
+
updatedAt: userDBO.updatedAt.toISOString()
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
const store = async (
|
|
470
|
+
userData: UserSchema,
|
|
471
|
+
transaction: Transaction | null = null
|
|
472
|
+
): Promise<UserDTO> => {
|
|
473
|
+
const user = await User.create(userData, {
|
|
474
|
+
transaction
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
return userDBOtoDTO(user)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const remove = async (
|
|
481
|
+
id: number | null = null,
|
|
482
|
+
transaction: Transaction | null = null
|
|
483
|
+
): Promise<number | null> => {
|
|
484
|
+
if (id) {
|
|
485
|
+
const removedUser = await User.destroy({
|
|
486
|
+
where: { id },
|
|
487
|
+
transaction
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
return removedUser
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const w = await User.destroy({ truncate: true, transaction })
|
|
494
|
+
|
|
495
|
+
return w
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const get = async (
|
|
499
|
+
id: number | null = null
|
|
500
|
+
): Promise<UserDTO[] | UserDTO | null> => {
|
|
501
|
+
if (id) {
|
|
502
|
+
const user = await User.findByPk(id)
|
|
503
|
+
|
|
504
|
+
return user ? userDBOtoDTO(user) : null
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const { rows: users } = await User.findAndCountAll()
|
|
508
|
+
|
|
509
|
+
return users.map(u => userDBOtoDTO(u))
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const update = async (userData: UserWithId): Promise<UserDTO | null> => {
|
|
513
|
+
const { id, ...rest } = userData
|
|
514
|
+
const [, user] = await User.update(rest, {
|
|
515
|
+
where: { id },
|
|
516
|
+
returning: true,
|
|
517
|
+
limit: 1
|
|
518
|
+
})
|
|
519
|
+
|
|
520
|
+
return user[0] ? userDBOtoDTO(user[0]) : null
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
export { store, remove, get, update }
|
|
524
|
+
`,
|
|
525
|
+
file: `${projectName}/src/database/${db}/queries/user.ts`
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
},
|
|
529
|
+
scripts: {
|
|
530
|
+
migration: {
|
|
531
|
+
content: `import { dbConnection } from 'database'
|
|
532
|
+
import { promisify } from 'util'
|
|
533
|
+
|
|
534
|
+
const exec = promisify(require('child_process').exec)
|
|
535
|
+
|
|
536
|
+
const migration = async () => {
|
|
537
|
+
const connection = await dbConnection()
|
|
538
|
+
|
|
539
|
+
await connection.connect()
|
|
540
|
+
|
|
541
|
+
console.log('Creating migration')
|
|
542
|
+
|
|
543
|
+
if (process.env.MIGRATION)
|
|
544
|
+
await connection.createMigration(process.env.MIGRATION)
|
|
545
|
+
|
|
546
|
+
console.log('Executing migration')
|
|
547
|
+
|
|
548
|
+
await exec(
|
|
549
|
+
'yarn migrations:run:last && eslint src/database/* --ext .js --fix'
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
console.log('Migration complete')
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
migration()
|
|
556
|
+
`,
|
|
557
|
+
file: `${projectName}/src/scripts/migration.ts`
|
|
558
|
+
}
|
|
559
|
+
},
|
|
560
|
+
sequelizerc: {
|
|
561
|
+
content: `module.exports = {
|
|
562
|
+
config: './src/database/postgres/config.js',
|
|
563
|
+
'migrations-path': './src/database/postgres/migrations/'
|
|
564
|
+
}
|
|
565
|
+
`,
|
|
566
|
+
file: `${projectName}/.sequelizerc`
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
await Promise.all([
|
|
571
|
+
writeFile(database.index.file, database.index.content),
|
|
572
|
+
writeFile(database[db].config.file, database[db].config.content),
|
|
573
|
+
writeFile(database[db].connection.file, database[db].connection.content),
|
|
574
|
+
writeFile(database[db].index.file, database[db].index.content),
|
|
575
|
+
writeFile(
|
|
576
|
+
database[db].models.index.file,
|
|
577
|
+
database[db].models.index.content
|
|
578
|
+
),
|
|
579
|
+
writeFile(database[db].models.user.file, database[db].models.user.content),
|
|
580
|
+
writeFile(
|
|
581
|
+
database[db].queries.index.file,
|
|
582
|
+
database[db].queries.index.content
|
|
583
|
+
),
|
|
584
|
+
writeFile(
|
|
585
|
+
database[db].queries.user.file,
|
|
586
|
+
database[db].queries.user.content
|
|
587
|
+
),
|
|
588
|
+
writeFile(
|
|
589
|
+
database.scripts.migration.file,
|
|
590
|
+
database.scripts.migration.content
|
|
591
|
+
),
|
|
592
|
+
writeFile(database.sequelizerc.file, database.sequelizerc.content)
|
|
593
|
+
])
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* @param {Object} args
|
|
598
|
+
* @param {String} args.projectName
|
|
599
|
+
* @param {String} args.graphQL
|
|
600
|
+
* @param {String} args.dbIsSQL
|
|
601
|
+
*/
|
|
602
|
+
const network = async ({ projectName, graphQL, dbIsSQL }) => {
|
|
603
|
+
const createFoldersCommand = `mkdir ${projectName}/src/network \
|
|
604
|
+
${projectName}/src/network/routes ${projectName}/src/network/routes/utils ${
|
|
605
|
+
graphQL
|
|
606
|
+
? ` ${projectName}/src/graphQL ${projectName}/src/graphQL/models \
|
|
607
|
+
${projectName}/src/graphQL/models/User ${projectName}/src/graphQL/models/utils \
|
|
608
|
+
${projectName}/src/graphQL/models/utils/messages`
|
|
609
|
+
: ''
|
|
610
|
+
}`
|
|
611
|
+
|
|
612
|
+
if (platform() === 'win32')
|
|
613
|
+
await exec(createFoldersCommand.replaceAll('/', '\\'))
|
|
614
|
+
else await exec(createFoldersCommand)
|
|
615
|
+
|
|
616
|
+
const network = {
|
|
617
|
+
index: {
|
|
618
|
+
content: `export * from './routes'
|
|
619
|
+
export * from './server'
|
|
620
|
+
`,
|
|
621
|
+
file: `${projectName}/src/network/index.ts`
|
|
622
|
+
},
|
|
623
|
+
response: {
|
|
624
|
+
content: `interface ResponseProps {
|
|
625
|
+
error: boolean
|
|
626
|
+
message: unknown
|
|
627
|
+
res: CustomResponse
|
|
628
|
+
status: number
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const response = ({ error, message, res, status }: ResponseProps): void => {
|
|
632
|
+
res.status(status).send({ error, message })
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
export { response }
|
|
636
|
+
`,
|
|
637
|
+
file: `${projectName}/src/network/response.ts`
|
|
638
|
+
},
|
|
639
|
+
router: {
|
|
640
|
+
content: `import { Application, Response, Request, Router, NextFunction } from 'express'
|
|
641
|
+
import swaggerUi from 'swagger-ui-express'
|
|
642
|
+
import httpErrors from 'http-errors'
|
|
643
|
+
|
|
644
|
+
import { response } from './response'
|
|
645
|
+
${
|
|
646
|
+
graphQL
|
|
647
|
+
? "import { Home } from './routes'"
|
|
648
|
+
: "import { Home, User } from './routes'"
|
|
649
|
+
}
|
|
650
|
+
import { docs } from 'utils'
|
|
651
|
+
|
|
652
|
+
${graphQL ? 'const routers: Router[] = []' : 'const routers = [User]'}
|
|
653
|
+
const applyRoutes = (app: Application): void => {
|
|
654
|
+
app.use('/', Home)
|
|
655
|
+
app.use('/api/docs', swaggerUi.serve, swaggerUi.setup(docs))
|
|
656
|
+
routers.forEach((router: Router): Application => app.use('/api', router))
|
|
657
|
+
|
|
658
|
+
// Handling 404 error
|
|
659
|
+
app.use((req, res, next) => {
|
|
660
|
+
next(new httpErrors.NotFound('This route does not exists'))
|
|
661
|
+
})
|
|
662
|
+
app.use(
|
|
663
|
+
(
|
|
664
|
+
error: httpErrors.HttpError,
|
|
665
|
+
req: Request,
|
|
666
|
+
res: Response,
|
|
667
|
+
next: NextFunction
|
|
668
|
+
) => {
|
|
669
|
+
response({
|
|
670
|
+
error: true,
|
|
671
|
+
message: error.message,
|
|
672
|
+
res,
|
|
673
|
+
status: error.status
|
|
674
|
+
})
|
|
675
|
+
next()
|
|
676
|
+
}
|
|
677
|
+
)
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
export { applyRoutes }
|
|
681
|
+
`,
|
|
682
|
+
file: `${projectName}/src/network/router.ts`
|
|
683
|
+
},
|
|
684
|
+
server: {
|
|
685
|
+
content: graphQL
|
|
686
|
+
? `import { createServer, Server as HttpServer } from 'http'
|
|
687
|
+
import express from 'express'
|
|
688
|
+
import cors from 'cors'
|
|
689
|
+
import pino, { HttpLogger } from 'express-pino-logger'
|
|
690
|
+
import { ApolloServer } from 'apollo-server-express'
|
|
691
|
+
import {
|
|
692
|
+
ApolloServerPluginDrainHttpServer,
|
|
693
|
+
ApolloServerPluginLandingPageDisabled,
|
|
694
|
+
ApolloServerPluginLandingPageGraphQLPlayground
|
|
695
|
+
} from 'apollo-server-core'
|
|
696
|
+
|
|
697
|
+
import { dbConnection } from 'database'
|
|
698
|
+
import { mergedSchema as schema } from 'graphQL'
|
|
699
|
+
import { applyRoutes } from './router'
|
|
700
|
+
|
|
701
|
+
const PORT = (process.env.PORT as string) || 1996
|
|
702
|
+
const ENVIRONMENTS_WITHOUT_PRETTY_PRINT = ['production', 'ci']
|
|
703
|
+
|
|
704
|
+
class Server {
|
|
705
|
+
#app: express.Application
|
|
706
|
+
#log: HttpLogger
|
|
707
|
+
#server: HttpServer
|
|
708
|
+
#connection: Awaited<ReturnType<typeof dbConnection>> | undefined
|
|
709
|
+
|
|
710
|
+
constructor() {
|
|
711
|
+
this.#app = express()
|
|
712
|
+
this.#server = createServer(this.#app)
|
|
713
|
+
this.#log = pino({
|
|
714
|
+
transport: !ENVIRONMENTS_WITHOUT_PRETTY_PRINT.includes(
|
|
715
|
+
process.env.NODE_ENV as string
|
|
716
|
+
)
|
|
717
|
+
? {
|
|
718
|
+
target: 'pino-pretty',
|
|
719
|
+
options: {
|
|
720
|
+
translateTime: 'HH:MM:ss Z',
|
|
721
|
+
ignore: 'pid,hostname'
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
: undefined
|
|
725
|
+
})
|
|
726
|
+
this.#config()
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
#config() {
|
|
730
|
+
this.#app.use(cors())
|
|
731
|
+
this.#app.use(express.json())
|
|
732
|
+
this.#app.use(express.urlencoded({ extended: false }))
|
|
733
|
+
this.#app.use(this.#log)
|
|
734
|
+
this.#app.use(
|
|
735
|
+
(
|
|
736
|
+
req: express.Request,
|
|
737
|
+
res: express.Response,
|
|
738
|
+
next: express.NextFunction
|
|
739
|
+
) => {
|
|
740
|
+
res.header('Access-Control-Allow-Methods', 'GET, POST, PATCH, DELETE')
|
|
741
|
+
res.header('Access-Control-Allow-Origin', '*')
|
|
742
|
+
res.header(
|
|
743
|
+
'Access-Control-Allow-Headers',
|
|
744
|
+
'Authorization, Content-Type'
|
|
745
|
+
)
|
|
746
|
+
res.header('x-powered-by', 'Simba.js')
|
|
747
|
+
next()
|
|
748
|
+
}
|
|
749
|
+
)
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
async #dbConnection() {
|
|
753
|
+
this.#connection = await dbConnection(this.#log.logger)
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
public async start(): Promise<void> {
|
|
757
|
+
const server = new ApolloServer({
|
|
758
|
+
schema,
|
|
759
|
+
plugins: [
|
|
760
|
+
ApolloServerPluginDrainHttpServer({ httpServer: this.#server }),
|
|
761
|
+
process.env.NODE_ENV === 'production'
|
|
762
|
+
? ApolloServerPluginLandingPageDisabled()
|
|
763
|
+
: ApolloServerPluginLandingPageGraphQLPlayground()
|
|
764
|
+
],
|
|
765
|
+
context: (): Context => ({
|
|
766
|
+
log: this.#log.logger
|
|
767
|
+
})
|
|
768
|
+
})
|
|
769
|
+
|
|
770
|
+
try {
|
|
771
|
+
await server.start()
|
|
772
|
+
server.applyMiddleware({
|
|
773
|
+
app: this.#app,
|
|
774
|
+
path: '/api'
|
|
775
|
+
})
|
|
776
|
+
applyRoutes(this.#app)
|
|
777
|
+
await this.#dbConnection()
|
|
778
|
+
await this.#connection?.connect()
|
|
779
|
+
this.#server.listen(PORT, () => {
|
|
780
|
+
this.#log.logger.info(\`Server listening at port: \${PORT}\`)
|
|
781
|
+
this.#log.logger.info(
|
|
782
|
+
\`GraphQL server listening at: http://localhost:\${PORT}\${server.graphqlPath}\`
|
|
783
|
+
)
|
|
784
|
+
})
|
|
785
|
+
} catch (e) {
|
|
786
|
+
console.error(e)
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
public async stop(): Promise<void> {
|
|
791
|
+
try {
|
|
792
|
+
await this.#connection?.disconnect()
|
|
793
|
+
this.#server?.close()
|
|
794
|
+
} catch (e) {
|
|
795
|
+
this.#log.logger.error(e)
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
const server = new Server()
|
|
801
|
+
|
|
802
|
+
export { server as Server }
|
|
803
|
+
`
|
|
804
|
+
: `import { Server as HttpServer } from 'http'
|
|
805
|
+
import express from 'express'
|
|
806
|
+
import cors from 'cors'
|
|
807
|
+
import pino, { HttpLogger } from 'express-pino-logger'
|
|
808
|
+
|
|
809
|
+
import { dbConnection } from 'database'
|
|
810
|
+
import { applyRoutes } from './router'
|
|
811
|
+
|
|
812
|
+
const PORT = (process.env.PORT as string) || 1996
|
|
813
|
+
const ENVIRONMENTS_WITHOUT_PRETTY_PRINT = ['production', 'ci']
|
|
814
|
+
|
|
815
|
+
class Server {
|
|
816
|
+
#app: express.Application
|
|
817
|
+
#log: HttpLogger
|
|
818
|
+
#server: HttpServer | undefined
|
|
819
|
+
#connection: Awaited<ReturnType<typeof dbConnection>> | undefined
|
|
820
|
+
|
|
821
|
+
constructor() {
|
|
822
|
+
this.#app = express()
|
|
823
|
+
this.#log = pino({
|
|
824
|
+
transport: !ENVIRONMENTS_WITHOUT_PRETTY_PRINT.includes(
|
|
825
|
+
process.env.NODE_ENV as string
|
|
826
|
+
)
|
|
827
|
+
? {
|
|
828
|
+
target: 'pino-pretty',
|
|
829
|
+
options: {
|
|
830
|
+
translateTime: 'HH:MM:ss Z',
|
|
831
|
+
ignore: 'pid,hostname'
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
: undefined
|
|
835
|
+
})
|
|
836
|
+
this.#config()
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
#config() {
|
|
840
|
+
this.#app.use(cors())
|
|
841
|
+
this.#app.use(express.json())
|
|
842
|
+
this.#app.use(express.urlencoded({ extended: false }))
|
|
843
|
+
this.#app.use(this.#log)
|
|
844
|
+
this.#app.use(
|
|
845
|
+
(
|
|
846
|
+
req: express.Request,
|
|
847
|
+
res: express.Response,
|
|
848
|
+
next: express.NextFunction
|
|
849
|
+
) => {
|
|
850
|
+
res.header('Access-Control-Allow-Methods', 'GET, POST, PATCH, DELETE')
|
|
851
|
+
res.header('Access-Control-Allow-Origin', '*')
|
|
852
|
+
res.header(
|
|
853
|
+
'Access-Control-Allow-Headers',
|
|
854
|
+
'Authorization, Content-Type'
|
|
855
|
+
)
|
|
856
|
+
res.header('x-powered-by', 'Simba.js')
|
|
857
|
+
next()
|
|
858
|
+
}
|
|
859
|
+
)
|
|
860
|
+
applyRoutes(this.#app)
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
async #dbConnection() {
|
|
864
|
+
this.#connection = await dbConnection(this.#log.logger)
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
public async start(): Promise<void> {
|
|
868
|
+
try {
|
|
869
|
+
await this.#dbConnection()
|
|
870
|
+
await this.#connection?.connect()
|
|
871
|
+
this.#server = this.#app.listen(PORT, () => {
|
|
872
|
+
this.#log.logger.info(\`Server running at port \${PORT}\`)
|
|
873
|
+
})
|
|
874
|
+
} catch (e) {
|
|
875
|
+
this.#log.logger.error(e)
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
public async stop(): Promise<void> {
|
|
880
|
+
try {
|
|
881
|
+
await this.#connection?.disconnect()
|
|
882
|
+
this.#server?.close()
|
|
883
|
+
} catch (e) {
|
|
884
|
+
this.#log.logger.error(e)
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
const server = new Server()
|
|
890
|
+
|
|
891
|
+
export { server as Server }
|
|
892
|
+
`,
|
|
893
|
+
file: `${projectName}/src/network/server.ts`
|
|
894
|
+
},
|
|
895
|
+
routes: {
|
|
896
|
+
home: {
|
|
897
|
+
content: `import { Response, Request, Router } from 'express'
|
|
898
|
+
|
|
899
|
+
import { response } from 'network/response'
|
|
900
|
+
|
|
901
|
+
const Home = Router()
|
|
902
|
+
|
|
903
|
+
Home.route('').get((req: Request, res: Response) => {
|
|
904
|
+
response({
|
|
905
|
+
error: false,
|
|
906
|
+
message: 'Welcome to your Express Backend!',
|
|
907
|
+
res,
|
|
908
|
+
status: 200
|
|
909
|
+
})
|
|
910
|
+
})
|
|
911
|
+
|
|
912
|
+
export { Home }
|
|
913
|
+
`,
|
|
914
|
+
file: `${projectName}/src/network/routes/home.ts`
|
|
915
|
+
},
|
|
916
|
+
index: {
|
|
917
|
+
content: `export * from './home'
|
|
918
|
+
${graphQL ? '' : "export * from './user'\n"}`,
|
|
919
|
+
file: `${projectName}/src/network/routes/index.ts`
|
|
920
|
+
},
|
|
921
|
+
...(!graphQL && {
|
|
922
|
+
user: {
|
|
923
|
+
content: `import { NextFunction, Router } from 'express'
|
|
924
|
+
|
|
925
|
+
import { response } from 'network/response'
|
|
926
|
+
import { UserService } from 'services'
|
|
927
|
+
import { idSchema, storeUserDto, UserWithId } from 'schemas'
|
|
928
|
+
import { validatorCompiler } from './utils'
|
|
929
|
+
|
|
930
|
+
const User = Router()
|
|
931
|
+
|
|
932
|
+
User.route('/users')
|
|
933
|
+
.post(
|
|
934
|
+
validatorCompiler(storeUserDto, 'body'),
|
|
935
|
+
async (
|
|
936
|
+
req: CustomRequest,
|
|
937
|
+
res: CustomResponse,
|
|
938
|
+
next: NextFunction
|
|
939
|
+
): Promise<void> => {
|
|
940
|
+
try {
|
|
941
|
+
const {
|
|
942
|
+
body: { args: user }
|
|
943
|
+
} = req
|
|
944
|
+
const us = new UserService({ user })
|
|
945
|
+
const result = await us.process({ type: 'store' })
|
|
946
|
+
|
|
947
|
+
response({ error: false, message: result, res, status: 201 })
|
|
948
|
+
} catch (error) {
|
|
949
|
+
next(error)
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
)
|
|
953
|
+
.get(
|
|
954
|
+
async (
|
|
955
|
+
req: CustomRequest,
|
|
956
|
+
res: CustomResponse,
|
|
957
|
+
next: NextFunction
|
|
958
|
+
): Promise<void> => {
|
|
959
|
+
try {
|
|
960
|
+
const us = new UserService()
|
|
961
|
+
const result = await us.process({ type: 'getAll' })
|
|
962
|
+
|
|
963
|
+
response({ error: false, message: result, res, status: 200 })
|
|
964
|
+
} catch (error) {
|
|
965
|
+
next(error)
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
)
|
|
969
|
+
.delete(
|
|
970
|
+
async (
|
|
971
|
+
req: CustomRequest,
|
|
972
|
+
res: CustomResponse,
|
|
973
|
+
next: NextFunction
|
|
974
|
+
): Promise<void> => {
|
|
975
|
+
try {
|
|
976
|
+
const us = new UserService()
|
|
977
|
+
const result = await us.process({ type: 'deleteAll' })
|
|
978
|
+
|
|
979
|
+
response({ error: false, message: result, res, status: 200 })
|
|
980
|
+
} catch (error) {
|
|
981
|
+
next(error)
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
)
|
|
985
|
+
|
|
986
|
+
User.route('/user/:id')
|
|
987
|
+
.get(
|
|
988
|
+
validatorCompiler(idSchema, 'params'),
|
|
989
|
+
async (
|
|
990
|
+
req: CustomRequest,
|
|
991
|
+
res: CustomResponse,
|
|
992
|
+
next: NextFunction
|
|
993
|
+
): Promise<void> => {
|
|
994
|
+
try {
|
|
995
|
+
const {
|
|
996
|
+
params: { id }
|
|
997
|
+
} = req
|
|
998
|
+
${
|
|
999
|
+
dbIsSQL
|
|
1000
|
+
? 'const us = new UserService({ id: parseInt(id) })'
|
|
1001
|
+
: 'const us = new UserService({ id })'
|
|
1002
|
+
}
|
|
1003
|
+
const result = await us.process({ type: 'getOne' })
|
|
1004
|
+
|
|
1005
|
+
response({ error: false, message: result, res, status: 200 })
|
|
1006
|
+
} catch (error) {
|
|
1007
|
+
next(error)
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
)
|
|
1011
|
+
.patch(
|
|
1012
|
+
validatorCompiler(idSchema, 'params'),
|
|
1013
|
+
validatorCompiler(storeUserDto, 'body'),
|
|
1014
|
+
async (
|
|
1015
|
+
req: CustomRequest,
|
|
1016
|
+
res: CustomResponse,
|
|
1017
|
+
next: NextFunction
|
|
1018
|
+
): Promise<void> => {
|
|
1019
|
+
try {
|
|
1020
|
+
const {
|
|
1021
|
+
body: { args },
|
|
1022
|
+
params: { id }
|
|
1023
|
+
} = req
|
|
1024
|
+
const userWithId = {
|
|
1025
|
+
id,
|
|
1026
|
+
...args
|
|
1027
|
+
} as UserWithId
|
|
1028
|
+
const us = new UserService({ userWithId })
|
|
1029
|
+
const result = await us.process({ type: 'update' })
|
|
1030
|
+
|
|
1031
|
+
response({ error: false, message: result, res, status: 200 })
|
|
1032
|
+
} catch (error) {
|
|
1033
|
+
next(error)
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
)
|
|
1037
|
+
.delete(
|
|
1038
|
+
validatorCompiler(idSchema, 'params'),
|
|
1039
|
+
async (
|
|
1040
|
+
req: CustomRequest,
|
|
1041
|
+
res: CustomResponse,
|
|
1042
|
+
next: NextFunction
|
|
1043
|
+
): Promise<void> => {
|
|
1044
|
+
try {
|
|
1045
|
+
const {
|
|
1046
|
+
params: { id }
|
|
1047
|
+
} = req
|
|
1048
|
+
${
|
|
1049
|
+
dbIsSQL
|
|
1050
|
+
? 'const us = new UserService({ id: parseInt(id) })'
|
|
1051
|
+
: 'const us = new UserService({ id })'
|
|
1052
|
+
}
|
|
1053
|
+
const result = await us.process({ type: 'delete' })
|
|
1054
|
+
|
|
1055
|
+
response({ error: false, message: result, res, status: 200 })
|
|
1056
|
+
} catch (error) {
|
|
1057
|
+
next(error)
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
)
|
|
1061
|
+
|
|
1062
|
+
export { User }
|
|
1063
|
+
`,
|
|
1064
|
+
file: `${projectName}/src/network/routes/user.ts`
|
|
1065
|
+
}
|
|
1066
|
+
}),
|
|
1067
|
+
utils: {
|
|
1068
|
+
index: {
|
|
1069
|
+
content: `import { NextFunction } from 'express'
|
|
1070
|
+
import httpErrors from 'http-errors'
|
|
1071
|
+
import { TObject, TProperties } from '@sinclair/typebox'
|
|
1072
|
+
import Ajv from 'ajv'
|
|
1073
|
+
|
|
1074
|
+
const ajv = new Ajv({
|
|
1075
|
+
removeAdditional: true,
|
|
1076
|
+
useDefaults: true,
|
|
1077
|
+
coerceTypes: true,
|
|
1078
|
+
nullable: true
|
|
1079
|
+
})
|
|
1080
|
+
|
|
1081
|
+
type Middleware = (
|
|
1082
|
+
req: CustomRequest,
|
|
1083
|
+
res: CustomResponse,
|
|
1084
|
+
next: NextFunction
|
|
1085
|
+
) => void
|
|
1086
|
+
|
|
1087
|
+
const validatorCompiler = <T extends TProperties>(
|
|
1088
|
+
schema: TObject<T>,
|
|
1089
|
+
value: 'body' | 'params'
|
|
1090
|
+
): Middleware => {
|
|
1091
|
+
return (req: CustomRequest, res: CustomResponse, next: NextFunction) => {
|
|
1092
|
+
const validate = ajv.compile(schema)
|
|
1093
|
+
const ok = validate(req[value])
|
|
1094
|
+
|
|
1095
|
+
if (!ok && validate.errors) {
|
|
1096
|
+
const [error] = validate.errors
|
|
1097
|
+
const errorMessage = \`\${error.dataPath.replace('.', '')} \${error.message}\`
|
|
1098
|
+
|
|
1099
|
+
return next(new httpErrors.UnprocessableEntity(errorMessage))
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
next()
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
export { validatorCompiler }
|
|
1107
|
+
`,
|
|
1108
|
+
file: `${projectName}/src/network/routes/utils/index.ts`
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
},
|
|
1112
|
+
...(graphQL && {
|
|
1113
|
+
graphQL: {
|
|
1114
|
+
index: {
|
|
1115
|
+
content: "export * from './models'\n",
|
|
1116
|
+
file: `${projectName}/src/graphQL/index.ts`
|
|
1117
|
+
},
|
|
1118
|
+
models: {
|
|
1119
|
+
User: {
|
|
1120
|
+
index: {
|
|
1121
|
+
content: `import { makeExecutableSchema } from '@graphql-tools/schema'
|
|
1122
|
+
|
|
1123
|
+
import { User as UserTD } from './typeDefs'
|
|
1124
|
+
import { Query } from './queriesResolver'
|
|
1125
|
+
import { Mutation } from './mutationsResolver'
|
|
1126
|
+
|
|
1127
|
+
const resolvers = {
|
|
1128
|
+
Query,
|
|
1129
|
+
Mutation
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
const User = makeExecutableSchema({
|
|
1133
|
+
typeDefs: UserTD,
|
|
1134
|
+
resolvers
|
|
1135
|
+
})
|
|
1136
|
+
|
|
1137
|
+
export { User }
|
|
1138
|
+
`,
|
|
1139
|
+
file: `${projectName}/src/graphQL/models/User/index.ts`
|
|
1140
|
+
},
|
|
1141
|
+
mutations: {
|
|
1142
|
+
content: `import { ApolloError } from 'apollo-server-core'
|
|
1143
|
+
|
|
1144
|
+
import { store, remove, update } from 'database'
|
|
1145
|
+
import { User, UserDTO, UserWithId } from 'schemas'
|
|
1146
|
+
import { EFU, MFU, GE, errorHandling } from '../utils'
|
|
1147
|
+
|
|
1148
|
+
const storeUser = async (
|
|
1149
|
+
{ user }: { user: User },
|
|
1150
|
+
{ log }: Context
|
|
1151
|
+
): Promise<UserDTO> => {
|
|
1152
|
+
try {
|
|
1153
|
+
const result = await store(user)
|
|
1154
|
+
|
|
1155
|
+
return result
|
|
1156
|
+
} catch (e) {
|
|
1157
|
+
return errorHandling({
|
|
1158
|
+
e,
|
|
1159
|
+
message: GE.INTERNAL_SERVER_ERROR,
|
|
1160
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
1161
|
+
log
|
|
1162
|
+
})
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
const deleteAllUsers = async ({ log }: Context): Promise<string> => {
|
|
1167
|
+
try {
|
|
1168
|
+
const usersDeleted = (await remove()) as number
|
|
1169
|
+
|
|
1170
|
+
if (usersDeleted >= 1) return MFU.ALL_USERS_DELETED
|
|
1171
|
+
|
|
1172
|
+
if (usersDeleted === 0)
|
|
1173
|
+
throw new ApolloError(EFU.NOTHING_TO_DELETE, 'NOTHING_TO_DELETE')
|
|
1174
|
+
|
|
1175
|
+
throw new ApolloError(GE.INTERNAL_SERVER_ERROR, 'INTERNAL_SERVER_ERROR')
|
|
1176
|
+
} catch (e) {
|
|
1177
|
+
return errorHandling({
|
|
1178
|
+
e,
|
|
1179
|
+
message: GE.INTERNAL_SERVER_ERROR,
|
|
1180
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
1181
|
+
log
|
|
1182
|
+
})
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
const updateUser = async (
|
|
1187
|
+
{ user }: { user: UserWithId },
|
|
1188
|
+
{ log }: Context
|
|
1189
|
+
): Promise<UserDTO> => {
|
|
1190
|
+
try {
|
|
1191
|
+
const updatedUser = await update(user)
|
|
1192
|
+
|
|
1193
|
+
if (!updatedUser) throw new ApolloError(EFU.NOT_FOUND, 'NOT_FOUND')
|
|
1194
|
+
|
|
1195
|
+
return updatedUser
|
|
1196
|
+
} catch (e) {
|
|
1197
|
+
return errorHandling({
|
|
1198
|
+
e,
|
|
1199
|
+
message: GE.INTERNAL_SERVER_ERROR,
|
|
1200
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
1201
|
+
log
|
|
1202
|
+
})
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
const deleteUser = async (
|
|
1207
|
+
{ id }: { id: ${dbIsSQL ? 'number' : 'string'} },
|
|
1208
|
+
{ log }: Context
|
|
1209
|
+
): Promise<string> => {
|
|
1210
|
+
try {
|
|
1211
|
+
const deletedUser = await remove(id)
|
|
1212
|
+
|
|
1213
|
+
if (!deletedUser) throw new ApolloError(EFU.NOT_FOUND, 'NOT_FOUND')
|
|
1214
|
+
|
|
1215
|
+
return MFU.USER_DELETED
|
|
1216
|
+
} catch (e) {
|
|
1217
|
+
return errorHandling({
|
|
1218
|
+
e,
|
|
1219
|
+
message: GE.INTERNAL_SERVER_ERROR,
|
|
1220
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
1221
|
+
log
|
|
1222
|
+
})
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
export { storeUser, deleteAllUsers, updateUser, deleteUser }
|
|
1227
|
+
`,
|
|
1228
|
+
file: `${projectName}/src/graphQL/models/User/mutations.ts`
|
|
1229
|
+
},
|
|
1230
|
+
mutationsResolver: {
|
|
1231
|
+
content: `import { DefinedError } from 'ajv'
|
|
1232
|
+
import { ApolloError } from 'apollo-server-core'
|
|
1233
|
+
|
|
1234
|
+
import {
|
|
1235
|
+
ajv,
|
|
1236
|
+
idSchema,
|
|
1237
|
+
User,
|
|
1238
|
+
user as storeUserSchema,
|
|
1239
|
+
UserDTO,
|
|
1240
|
+
UserWithId,
|
|
1241
|
+
userWithId as updateUserSchema
|
|
1242
|
+
} from 'schemas'
|
|
1243
|
+
import { storeUser, updateUser, deleteUser, deleteAllUsers } from './mutations'
|
|
1244
|
+
import { errorHandling, GE } from '../utils'
|
|
1245
|
+
|
|
1246
|
+
const Mutation = {
|
|
1247
|
+
storeUser: async (
|
|
1248
|
+
parent: unknown,
|
|
1249
|
+
{ user }: { user: User },
|
|
1250
|
+
context: Context
|
|
1251
|
+
): Promise<UserDTO> => {
|
|
1252
|
+
const { log } = context
|
|
1253
|
+
const validate = ajv.compile(storeUserSchema)
|
|
1254
|
+
|
|
1255
|
+
try {
|
|
1256
|
+
const ok = validate(user)
|
|
1257
|
+
|
|
1258
|
+
if (!ok)
|
|
1259
|
+
throw new ApolloError(
|
|
1260
|
+
\`\${(validate.errors as DefinedError[])[0].instancePath.replace(
|
|
1261
|
+
'/',
|
|
1262
|
+
''
|
|
1263
|
+
)} \${(validate.errors as DefinedError[])[0].message as string}\`,
|
|
1264
|
+
'UNPROCESSABLE_ENTITY'
|
|
1265
|
+
)
|
|
1266
|
+
|
|
1267
|
+
return await storeUser({ user }, context)
|
|
1268
|
+
} catch (e) {
|
|
1269
|
+
log.error(validate.errors)
|
|
1270
|
+
|
|
1271
|
+
return errorHandling({
|
|
1272
|
+
e,
|
|
1273
|
+
message: GE.INTERNAL_SERVER_ERROR,
|
|
1274
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
1275
|
+
log
|
|
1276
|
+
})
|
|
1277
|
+
}
|
|
1278
|
+
},
|
|
1279
|
+
updateUser: async (
|
|
1280
|
+
parent: unknown,
|
|
1281
|
+
{ user }: { user: UserWithId },
|
|
1282
|
+
context: Context
|
|
1283
|
+
): Promise<UserDTO> => {
|
|
1284
|
+
const validate = ajv.compile(updateUserSchema)
|
|
1285
|
+
const { log } = context
|
|
1286
|
+
|
|
1287
|
+
try {
|
|
1288
|
+
const ok = validate(user)
|
|
1289
|
+
|
|
1290
|
+
if (!ok)
|
|
1291
|
+
throw new ApolloError(
|
|
1292
|
+
\`\${(validate.errors as DefinedError[])[0].instancePath.replace(
|
|
1293
|
+
'/',
|
|
1294
|
+
''
|
|
1295
|
+
)} \${(validate.errors as DefinedError[])[0].message as string}\`,
|
|
1296
|
+
'UNPROCESSABLE_ENTITY'
|
|
1297
|
+
)
|
|
1298
|
+
|
|
1299
|
+
return await updateUser({ user }, context)
|
|
1300
|
+
} catch (e) {
|
|
1301
|
+
log.error(validate.errors)
|
|
1302
|
+
|
|
1303
|
+
return errorHandling({
|
|
1304
|
+
e,
|
|
1305
|
+
message: GE.INTERNAL_SERVER_ERROR,
|
|
1306
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
1307
|
+
log
|
|
1308
|
+
})
|
|
1309
|
+
}
|
|
1310
|
+
},
|
|
1311
|
+
deleteUser: async (
|
|
1312
|
+
parent: unknown,
|
|
1313
|
+
{ id }: { id: ${dbIsSQL ? 'number' : 'string'} },
|
|
1314
|
+
context: Context
|
|
1315
|
+
): Promise<string> => {
|
|
1316
|
+
const validate = ajv.compile(idSchema)
|
|
1317
|
+
const { log } = context
|
|
1318
|
+
|
|
1319
|
+
try {
|
|
1320
|
+
const ok = validate({ id })
|
|
1321
|
+
|
|
1322
|
+
if (!ok)
|
|
1323
|
+
throw new ApolloError(
|
|
1324
|
+
\`\${(validate.errors as DefinedError[])[0].instancePath.replace(
|
|
1325
|
+
'/',
|
|
1326
|
+
''
|
|
1327
|
+
)} \${(validate.errors as DefinedError[])[0].message as string}\`,
|
|
1328
|
+
'UNPROCESSABLE_ENTITY'
|
|
1329
|
+
)
|
|
1330
|
+
|
|
1331
|
+
return await deleteUser({ id }, context)
|
|
1332
|
+
} catch (e) {
|
|
1333
|
+
log.error(validate.errors)
|
|
1334
|
+
|
|
1335
|
+
return errorHandling({
|
|
1336
|
+
e,
|
|
1337
|
+
message: GE.INTERNAL_SERVER_ERROR,
|
|
1338
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
1339
|
+
log
|
|
1340
|
+
})
|
|
1341
|
+
}
|
|
1342
|
+
},
|
|
1343
|
+
deleteAllUsers: async (
|
|
1344
|
+
parent: unknown,
|
|
1345
|
+
args: unknown,
|
|
1346
|
+
context: Context
|
|
1347
|
+
): Promise<string> => {
|
|
1348
|
+
const { log } = context
|
|
1349
|
+
try {
|
|
1350
|
+
return await deleteAllUsers(context)
|
|
1351
|
+
} catch (e) {
|
|
1352
|
+
return errorHandling({
|
|
1353
|
+
e,
|
|
1354
|
+
message: GE.INTERNAL_SERVER_ERROR,
|
|
1355
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
1356
|
+
log
|
|
1357
|
+
})
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
export { Mutation }
|
|
1363
|
+
`,
|
|
1364
|
+
file: `${projectName}/src/graphQL/models/User/mutationsResolver.ts`
|
|
1365
|
+
},
|
|
1366
|
+
queries: {
|
|
1367
|
+
content: `import { ApolloError } from 'apollo-server-core'
|
|
1368
|
+
|
|
1369
|
+
import { get } from 'database'
|
|
1370
|
+
import { UserDTO } from 'schemas'
|
|
1371
|
+
import { EFU, GE, errorHandling } from '../utils'
|
|
1372
|
+
|
|
1373
|
+
const getUsers = async (
|
|
1374
|
+
parent: unknown,
|
|
1375
|
+
args: unknown,
|
|
1376
|
+
{ log }: Context
|
|
1377
|
+
): Promise<UserDTO[]> => {
|
|
1378
|
+
try {
|
|
1379
|
+
const users = (await get()) as UserDTO[]
|
|
1380
|
+
|
|
1381
|
+
return users
|
|
1382
|
+
} catch (e) {
|
|
1383
|
+
return errorHandling({
|
|
1384
|
+
e,
|
|
1385
|
+
message: GE.INTERNAL_SERVER_ERROR,
|
|
1386
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
1387
|
+
log
|
|
1388
|
+
})
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
const getUser = async (
|
|
1393
|
+
{ id }: { id: ${dbIsSQL ? 'number' : 'string'} },
|
|
1394
|
+
{ log }: Context
|
|
1395
|
+
): Promise<UserDTO> => {
|
|
1396
|
+
try {
|
|
1397
|
+
const user = (await get(id${dbIsSQL ? '' : ' as string'})) as UserDTO | null
|
|
1398
|
+
|
|
1399
|
+
if (!user) throw new ApolloError(EFU.NOT_FOUND, 'NOT_FOUND')
|
|
1400
|
+
|
|
1401
|
+
return user
|
|
1402
|
+
} catch (e) {
|
|
1403
|
+
return errorHandling({
|
|
1404
|
+
e,
|
|
1405
|
+
message: GE.INTERNAL_SERVER_ERROR,
|
|
1406
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
1407
|
+
log
|
|
1408
|
+
})
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
export { getUsers, getUser }
|
|
1413
|
+
`,
|
|
1414
|
+
file: `${projectName}/src/graphQL/models/User/queries.ts`
|
|
1415
|
+
},
|
|
1416
|
+
queriesResolver: {
|
|
1417
|
+
content: `import { DefinedError } from 'ajv'
|
|
1418
|
+
import { ApolloError } from 'apollo-server-core'
|
|
1419
|
+
|
|
1420
|
+
import { ajv, idSchema, UserDTO } from 'schemas'
|
|
1421
|
+
import { getUser, getUsers } from './queries'
|
|
1422
|
+
import { errorHandling, GE } from '../utils'
|
|
1423
|
+
|
|
1424
|
+
const Query = {
|
|
1425
|
+
getUser: async (
|
|
1426
|
+
parent: unknown,
|
|
1427
|
+
{ id }: { id: ${dbIsSQL ? 'number' : 'string'} },
|
|
1428
|
+
context: Context
|
|
1429
|
+
): Promise<UserDTO> => {
|
|
1430
|
+
const { log } = context
|
|
1431
|
+
const validate = ajv.compile(idSchema)
|
|
1432
|
+
|
|
1433
|
+
try {
|
|
1434
|
+
const ok = validate({ id })
|
|
1435
|
+
|
|
1436
|
+
if (!ok)
|
|
1437
|
+
throw new ApolloError(
|
|
1438
|
+
\`id \${(validate.errors as DefinedError[])[0].message as string}\`,
|
|
1439
|
+
'UNPROCESSABLE_ENTITY'
|
|
1440
|
+
)
|
|
1441
|
+
|
|
1442
|
+
return await getUser({ id }, context)
|
|
1443
|
+
} catch (e) {
|
|
1444
|
+
log.error(validate.errors)
|
|
1445
|
+
|
|
1446
|
+
return errorHandling({
|
|
1447
|
+
e,
|
|
1448
|
+
message: GE.INTERNAL_SERVER_ERROR,
|
|
1449
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
1450
|
+
log
|
|
1451
|
+
})
|
|
1452
|
+
}
|
|
1453
|
+
},
|
|
1454
|
+
getUsers
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
export { Query }
|
|
1458
|
+
`,
|
|
1459
|
+
file: `${projectName}/src/graphQL/models/User/queriesResolver.ts`
|
|
1460
|
+
},
|
|
1461
|
+
typeDefs: {
|
|
1462
|
+
content: `import { gql } from 'apollo-server-core'
|
|
1463
|
+
|
|
1464
|
+
const User = gql\`
|
|
1465
|
+
type User {
|
|
1466
|
+
id: ${dbIsSQL ? 'Int' : 'ID'}!
|
|
1467
|
+
name: String!
|
|
1468
|
+
lastName: String!
|
|
1469
|
+
createdAt: String!
|
|
1470
|
+
updatedAt: String!
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
type Query {
|
|
1474
|
+
getUsers: [User!]!
|
|
1475
|
+
getUser(id: ${dbIsSQL ? 'Int' : 'ID'}!): User!
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
input StoreUserInput {
|
|
1479
|
+
lastName: String!
|
|
1480
|
+
name: String!
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
input UpdateUserInput {
|
|
1484
|
+
id: ${dbIsSQL ? 'Int' : 'String'}!
|
|
1485
|
+
lastName: String!
|
|
1486
|
+
name: String!
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
type Mutation {
|
|
1490
|
+
storeUser(user: StoreUserInput!): User!
|
|
1491
|
+
deleteAllUsers: String
|
|
1492
|
+
updateUser(user: UpdateUserInput!): User!
|
|
1493
|
+
deleteUser(id: ${dbIsSQL ? 'Int' : 'ID'}!): String
|
|
1494
|
+
}
|
|
1495
|
+
\`
|
|
1496
|
+
|
|
1497
|
+
export { User }
|
|
1498
|
+
`,
|
|
1499
|
+
file: `${projectName}/src/graphQL/models/User/typeDefs.ts`
|
|
1500
|
+
}
|
|
1501
|
+
},
|
|
1502
|
+
utils: {
|
|
1503
|
+
messages: {
|
|
1504
|
+
index: {
|
|
1505
|
+
content: `enum GenericErrors {
|
|
1506
|
+
INTERNAL_SERVER_ERROR = 'Something went wrong'
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
export { GenericErrors as GE }
|
|
1510
|
+
export * from './user'
|
|
1511
|
+
`,
|
|
1512
|
+
file: `${projectName}/src/graphQL/models/utils/messages/index.ts`
|
|
1513
|
+
},
|
|
1514
|
+
user: {
|
|
1515
|
+
content: `enum ErrorForUser {
|
|
1516
|
+
NOT_FOUND = 'The requested user does not exists',
|
|
1517
|
+
NOTHING_TO_DELETE = 'There is no user to be deleted'
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
enum MessageForUser {
|
|
1521
|
+
ALL_USERS_DELETED = 'All the users were deleted successfully',
|
|
1522
|
+
USER_DELETED = 'The requested user was successfully deleted'
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
export { ErrorForUser as EFU, MessageForUser as MFU }
|
|
1526
|
+
`,
|
|
1527
|
+
file: `${projectName}/src/graphQL/models/utils/messages/user.ts`
|
|
1528
|
+
}
|
|
1529
|
+
},
|
|
1530
|
+
index: {
|
|
1531
|
+
content: `import { ApolloError } from 'apollo-server-core'
|
|
1532
|
+
import { HttpLogger } from 'express-pino-logger'
|
|
1533
|
+
|
|
1534
|
+
const errorHandling = ({
|
|
1535
|
+
e,
|
|
1536
|
+
message,
|
|
1537
|
+
code,
|
|
1538
|
+
log
|
|
1539
|
+
}: {
|
|
1540
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1541
|
+
e: any
|
|
1542
|
+
message: string
|
|
1543
|
+
code: string
|
|
1544
|
+
log: HttpLogger['logger']
|
|
1545
|
+
}): never => {
|
|
1546
|
+
log.error(e)
|
|
1547
|
+
|
|
1548
|
+
if (e instanceof ApolloError) throw e
|
|
1549
|
+
|
|
1550
|
+
throw new ApolloError(message ?? e.message, code)
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
export { errorHandling }
|
|
1554
|
+
export * from './messages'
|
|
1555
|
+
`,
|
|
1556
|
+
file: `${projectName}/src/graphQL/models/utils/index.ts`
|
|
1557
|
+
}
|
|
1558
|
+
},
|
|
1559
|
+
index: {
|
|
1560
|
+
content: `import { mergeSchemas } from '@graphql-tools/schema'
|
|
1561
|
+
|
|
1562
|
+
import { User } from './User'
|
|
1563
|
+
|
|
1564
|
+
const mergedSchema = mergeSchemas({
|
|
1565
|
+
schemas: [User]
|
|
1566
|
+
})
|
|
1567
|
+
|
|
1568
|
+
export { mergedSchema }
|
|
1569
|
+
`,
|
|
1570
|
+
file: `${projectName}/src/graphQL/models/index.ts`
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
})
|
|
1575
|
+
}
|
|
1576
|
+
const processes = [
|
|
1577
|
+
writeFile(network.index.file, network.index.content),
|
|
1578
|
+
writeFile(network.response.file, network.response.content),
|
|
1579
|
+
writeFile(network.router.file, network.router.content),
|
|
1580
|
+
writeFile(network.server.file, network.server.content),
|
|
1581
|
+
writeFile(network.routes.home.file, network.routes.home.content),
|
|
1582
|
+
writeFile(network.routes.index.file, network.routes.index.content),
|
|
1583
|
+
writeFile(
|
|
1584
|
+
network.routes.utils.index.file,
|
|
1585
|
+
network.routes.utils.index.content
|
|
1586
|
+
)
|
|
1587
|
+
]
|
|
1588
|
+
|
|
1589
|
+
if (graphQL)
|
|
1590
|
+
processes.concat([
|
|
1591
|
+
writeFile(network.graphQL?.index.file, network.graphQL?.index.content),
|
|
1592
|
+
writeFile(
|
|
1593
|
+
network.graphQL?.models.User.index.file,
|
|
1594
|
+
network.graphQL?.models.User.index.content
|
|
1595
|
+
),
|
|
1596
|
+
writeFile(
|
|
1597
|
+
network.graphQL?.models.User.mutations.file,
|
|
1598
|
+
network.graphQL?.models.User.mutations.content
|
|
1599
|
+
),
|
|
1600
|
+
writeFile(
|
|
1601
|
+
network.graphQL?.models.User.mutationsResolver.file,
|
|
1602
|
+
network.graphQL?.models.User.mutationsResolver.content
|
|
1603
|
+
),
|
|
1604
|
+
writeFile(
|
|
1605
|
+
network.graphQL?.models.User.queries.file,
|
|
1606
|
+
network.graphQL?.models.User.queries.content
|
|
1607
|
+
),
|
|
1608
|
+
writeFile(
|
|
1609
|
+
network.graphQL?.models.User.queriesResolver.file,
|
|
1610
|
+
network.graphQL?.models.User.queriesResolver.content
|
|
1611
|
+
),
|
|
1612
|
+
writeFile(
|
|
1613
|
+
network.graphQL?.models.User.typeDefs.file,
|
|
1614
|
+
network.graphQL?.models.User.typeDefs.content
|
|
1615
|
+
),
|
|
1616
|
+
writeFile(
|
|
1617
|
+
network.graphQL?.models.utils.messages.index.file,
|
|
1618
|
+
network.graphQL?.models.utils.messages.index.content
|
|
1619
|
+
),
|
|
1620
|
+
writeFile(
|
|
1621
|
+
network.graphQL?.models.utils.messages.user.file,
|
|
1622
|
+
network.graphQL?.models.utils.messages.user.content
|
|
1623
|
+
),
|
|
1624
|
+
writeFile(
|
|
1625
|
+
network.graphQL?.models.utils.index.file,
|
|
1626
|
+
network.graphQL?.models.utils.index.content
|
|
1627
|
+
),
|
|
1628
|
+
writeFile(
|
|
1629
|
+
network.graphQL?.models.index.file,
|
|
1630
|
+
network.graphQL?.models.index.content
|
|
1631
|
+
)
|
|
1632
|
+
])
|
|
1633
|
+
else
|
|
1634
|
+
processes.push(
|
|
1635
|
+
writeFile(network.routes.user.file, network.routes.user.content)
|
|
1636
|
+
)
|
|
1637
|
+
|
|
1638
|
+
await Promise.all(processes)
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
/**
|
|
1642
|
+
* @param {Object} args
|
|
1643
|
+
* @param {String} args.projectName
|
|
1644
|
+
* @param {Boolean} args.dbIsSQL
|
|
1645
|
+
* @param {Boolean} args.graphQL
|
|
1646
|
+
*/
|
|
1647
|
+
const schemas = async ({ projectName, dbIsSQL, graphQL }) => {
|
|
1648
|
+
const createFoldersCommand = `mkdir ${projectName}/src/schemas`
|
|
1649
|
+
|
|
1650
|
+
if (platform() === 'win32')
|
|
1651
|
+
await exec(createFoldersCommand.replaceAll('/', '\\'))
|
|
1652
|
+
else await exec(createFoldersCommand)
|
|
1653
|
+
|
|
1654
|
+
const schemas = {
|
|
1655
|
+
index: {
|
|
1656
|
+
content: graphQL
|
|
1657
|
+
? `import { Static, Type } from '@sinclair/typebox'
|
|
1658
|
+
import Ajv from 'ajv/dist/2019.js'
|
|
1659
|
+
import addFormats from 'ajv-formats'
|
|
1660
|
+
|
|
1661
|
+
const id = ${
|
|
1662
|
+
dbIsSQL
|
|
1663
|
+
? `Type.Number()`
|
|
1664
|
+
: `Type.String({ pattern: '^[a-zA-Z0-9]{24,}$' })`
|
|
1665
|
+
}
|
|
1666
|
+
type ID = Static<typeof id>
|
|
1667
|
+
|
|
1668
|
+
const idSchema = Type.Object({ id })
|
|
1669
|
+
|
|
1670
|
+
type IDSchema = Static<typeof idSchema>
|
|
1671
|
+
|
|
1672
|
+
const ajv = addFormats(new Ajv(), ['email'])
|
|
1673
|
+
.addKeyword('kind')
|
|
1674
|
+
.addKeyword('modifier')
|
|
1675
|
+
|
|
1676
|
+
export { id, ID, idSchema, IDSchema, ajv }
|
|
1677
|
+
export * from './user'
|
|
1678
|
+
`
|
|
1679
|
+
: `import { Static, Type } from '@sinclair/typebox'
|
|
1680
|
+
|
|
1681
|
+
const id = ${
|
|
1682
|
+
dbIsSQL
|
|
1683
|
+
? `Type.Number()`
|
|
1684
|
+
: `Type.String({ pattern: '^[a-zA-Z0-9]{24,}$' })`
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
type Id = Static<typeof id>
|
|
1688
|
+
|
|
1689
|
+
const idSchema = Type.Object({ id })
|
|
1690
|
+
|
|
1691
|
+
type IdSchema = Static<typeof idSchema>
|
|
1692
|
+
|
|
1693
|
+
export { id, Id, idSchema, IdSchema }
|
|
1694
|
+
export * from './user'
|
|
1695
|
+
`,
|
|
1696
|
+
file: `${projectName}/src/schemas/index.ts`
|
|
1697
|
+
},
|
|
1698
|
+
user: {
|
|
1699
|
+
content: `import { Static, Type } from '@sinclair/typebox'
|
|
1700
|
+
|
|
1701
|
+
import { id } from '.'
|
|
1702
|
+
|
|
1703
|
+
const user = Type.Object({
|
|
1704
|
+
lastName: Type.String(),
|
|
1705
|
+
name: Type.String()
|
|
1706
|
+
})
|
|
1707
|
+
|
|
1708
|
+
type User = Static<typeof user>
|
|
1709
|
+
|
|
1710
|
+
const userWithId = Type.Intersect([user, Type.Object({ id })])
|
|
1711
|
+
|
|
1712
|
+
type UserWithId = Static<typeof userWithId>
|
|
1713
|
+
|
|
1714
|
+
const userDto = Type.Object({
|
|
1715
|
+
id: Type.Optional(id),
|
|
1716
|
+
lastName: Type.String(),
|
|
1717
|
+
name: Type.String(),
|
|
1718
|
+
createdAt: Type.Optional(Type.String()),
|
|
1719
|
+
updatedAt: Type.Optional(Type.String())
|
|
1720
|
+
})
|
|
1721
|
+
|
|
1722
|
+
type UserDTO = Static<typeof userDto>
|
|
1723
|
+
|
|
1724
|
+
const storeUserDto = Type.Object({
|
|
1725
|
+
args: user
|
|
1726
|
+
})
|
|
1727
|
+
|
|
1728
|
+
type StoreUserDTO = Static<typeof storeUserDto>
|
|
1729
|
+
|
|
1730
|
+
export {
|
|
1731
|
+
userDto,
|
|
1732
|
+
UserDTO,
|
|
1733
|
+
userWithId,
|
|
1734
|
+
UserWithId,
|
|
1735
|
+
user,
|
|
1736
|
+
User,
|
|
1737
|
+
storeUserDto,
|
|
1738
|
+
StoreUserDTO
|
|
1739
|
+
}
|
|
1740
|
+
`,
|
|
1741
|
+
file: `${projectName}/src/schemas/user.ts`
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
await Promise.all([
|
|
1746
|
+
writeFile(schemas.index.file, schemas.index.content),
|
|
1747
|
+
writeFile(schemas.user.file, schemas.user.content)
|
|
1748
|
+
])
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
/**
|
|
1752
|
+
* @param {Object} args
|
|
1753
|
+
* @param {String} args.projectName
|
|
1754
|
+
* @param {Boolean} args.dbIsSQL
|
|
1755
|
+
*/
|
|
1756
|
+
const services = async ({ projectName, dbIsSQL }) => {
|
|
1757
|
+
const createFoldersCommand = `mkdir ${projectName}/src/services \
|
|
1758
|
+
${projectName}/src/services/utils \
|
|
1759
|
+
${projectName}/src/services/utils/messages`
|
|
1760
|
+
|
|
1761
|
+
if (platform() === 'win32')
|
|
1762
|
+
await exec(createFoldersCommand.replaceAll('/', '\\'))
|
|
1763
|
+
else await exec(createFoldersCommand)
|
|
1764
|
+
|
|
1765
|
+
const services = {
|
|
1766
|
+
index: {
|
|
1767
|
+
content: "export * from './user'\n",
|
|
1768
|
+
file: `${projectName}/src/services/index.ts`
|
|
1769
|
+
},
|
|
1770
|
+
user: {
|
|
1771
|
+
content: `import httpErrors from 'http-errors'
|
|
1772
|
+
|
|
1773
|
+
import { store, remove, get, update } from 'database'
|
|
1774
|
+
import { User, UserDTO, UserWithId } from 'schemas'
|
|
1775
|
+
import { EFU, MFU, GE, errorHandling } from './utils'
|
|
1776
|
+
|
|
1777
|
+
type Process = {
|
|
1778
|
+
type: 'store' | 'getAll' | 'deleteAll' | 'getOne' | 'update' | 'delete'
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
type Arguments = {
|
|
1782
|
+
id?: ${dbIsSQL ? 'number' : 'string'}
|
|
1783
|
+
user?: User
|
|
1784
|
+
userWithId?: UserWithId
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
class UserService {
|
|
1788
|
+
#args: Arguments
|
|
1789
|
+
|
|
1790
|
+
constructor(args: Arguments = {}) {
|
|
1791
|
+
this.#args = args
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
public process({ type }: Process): Promise<string | UserDTO | UserDTO[]> {
|
|
1795
|
+
switch (type) {
|
|
1796
|
+
case 'store':
|
|
1797
|
+
return this.#store()
|
|
1798
|
+
case 'getAll':
|
|
1799
|
+
return this.#getAll()
|
|
1800
|
+
case 'deleteAll':
|
|
1801
|
+
return this.#deleteAll()
|
|
1802
|
+
case 'getOne':
|
|
1803
|
+
return this.#getOne()
|
|
1804
|
+
case 'update':
|
|
1805
|
+
return this.#update()
|
|
1806
|
+
case 'delete':
|
|
1807
|
+
return this.#delete()
|
|
1808
|
+
default:
|
|
1809
|
+
throw new httpErrors.InternalServerError(GE.INTERNAL_SERVER_ERROR)
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
async #store(): Promise<UserDTO> {
|
|
1814
|
+
try {
|
|
1815
|
+
if (!this.#args.user)
|
|
1816
|
+
throw new httpErrors.UnprocessableEntity(GE.INTERNAL_SERVER_ERROR)
|
|
1817
|
+
|
|
1818
|
+
const result = await store(this.#args.user)
|
|
1819
|
+
|
|
1820
|
+
return result
|
|
1821
|
+
} catch (e) {
|
|
1822
|
+
return errorHandling(e, GE.INTERNAL_SERVER_ERROR)
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
async #getAll(): Promise<UserDTO[]> {
|
|
1827
|
+
try {
|
|
1828
|
+
const users = (await get()) as UserDTO[]
|
|
1829
|
+
|
|
1830
|
+
return users
|
|
1831
|
+
} catch (e) {
|
|
1832
|
+
return errorHandling(e, GE.INTERNAL_SERVER_ERROR)
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
async #deleteAll(): Promise<string> {
|
|
1837
|
+
try {
|
|
1838
|
+
const usersDeleted = (await remove()) as number
|
|
1839
|
+
|
|
1840
|
+
${
|
|
1841
|
+
dbIsSQL
|
|
1842
|
+
? 'if (usersDeleted !== 0) return MFU.ALL_USERS_DELETED'
|
|
1843
|
+
: `if (usersDeleted >= 1) return MFU.ALL_USERS_DELETED
|
|
1844
|
+
|
|
1845
|
+
if (usersDeleted === 0)
|
|
1846
|
+
throw new httpErrors.Conflict(EFU.NOTHING_TO_DELETE)`
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
throw new httpErrors.InternalServerError(GE.INTERNAL_SERVER_ERROR)
|
|
1850
|
+
} catch (e) {
|
|
1851
|
+
return errorHandling(e, GE.INTERNAL_SERVER_ERROR)
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
async #getOne(): Promise<UserDTO> {
|
|
1856
|
+
try {
|
|
1857
|
+
if (!this.#args.id)
|
|
1858
|
+
throw new httpErrors.UnprocessableEntity(GE.INTERNAL_SERVER_ERROR)
|
|
1859
|
+
|
|
1860
|
+
const { id } = this.#args
|
|
1861
|
+
const user = (await get(id)) as UserDTO | null
|
|
1862
|
+
|
|
1863
|
+
if (!user) throw new httpErrors.NotFound(EFU.NOT_FOUND)
|
|
1864
|
+
|
|
1865
|
+
return user
|
|
1866
|
+
} catch (e) {
|
|
1867
|
+
return errorHandling(e, GE.INTERNAL_SERVER_ERROR)
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
async #update(): Promise<UserDTO> {
|
|
1872
|
+
try {
|
|
1873
|
+
if (!this.#args.userWithId || !this.#args.userWithId.id)
|
|
1874
|
+
throw new httpErrors.UnprocessableEntity(GE.INTERNAL_SERVER_ERROR)
|
|
1875
|
+
|
|
1876
|
+
const updatedUser = await update(this.#args.userWithId)
|
|
1877
|
+
|
|
1878
|
+
if (!updatedUser) throw new httpErrors.NotFound(EFU.NOT_FOUND)
|
|
1879
|
+
|
|
1880
|
+
return updatedUser
|
|
1881
|
+
} catch (e) {
|
|
1882
|
+
return errorHandling(e, GE.INTERNAL_SERVER_ERROR)
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
async #delete(): Promise<string> {
|
|
1887
|
+
try {
|
|
1888
|
+
if (!this.#args.id)
|
|
1889
|
+
throw new httpErrors.UnprocessableEntity(GE.INTERNAL_SERVER_ERROR)
|
|
1890
|
+
|
|
1891
|
+
const { id } = this.#args
|
|
1892
|
+
const deletedUser = await remove(id)
|
|
1893
|
+
|
|
1894
|
+
if (!deletedUser) throw new httpErrors.NotFound(EFU.NOT_FOUND)
|
|
1895
|
+
|
|
1896
|
+
return MFU.USER_DELETED
|
|
1897
|
+
} catch (e) {
|
|
1898
|
+
return errorHandling(e, GE.INTERNAL_SERVER_ERROR)
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
export { UserService }
|
|
1904
|
+
`,
|
|
1905
|
+
file: `${projectName}/src/services/user.ts`
|
|
1906
|
+
},
|
|
1907
|
+
utils: {
|
|
1908
|
+
index: {
|
|
1909
|
+
content: `import httpErrors from 'http-errors'
|
|
1910
|
+
|
|
1911
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1912
|
+
const errorHandling = (e: any, message?: string): never => {
|
|
1913
|
+
console.error(e)
|
|
1914
|
+
|
|
1915
|
+
if (e instanceof httpErrors.HttpError) throw e
|
|
1916
|
+
|
|
1917
|
+
throw new httpErrors.InternalServerError(message ?? e.message)
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
export { errorHandling }
|
|
1921
|
+
export * from './messages'
|
|
1922
|
+
`,
|
|
1923
|
+
file: `${projectName}/src/services/utils/index.ts`
|
|
1924
|
+
}
|
|
1925
|
+
},
|
|
1926
|
+
'utils/messages': {
|
|
1927
|
+
index: {
|
|
1928
|
+
content: `enum GenericErrors {
|
|
1929
|
+
INTERNAL_SERVER_ERROR = 'Something went wrong'
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
export { GenericErrors as GE }
|
|
1933
|
+
export * from './user'
|
|
1934
|
+
`,
|
|
1935
|
+
file: `${projectName}/src/services/utils/messages/index.ts`
|
|
1936
|
+
},
|
|
1937
|
+
user: {
|
|
1938
|
+
content: `enum ErrorForUser {
|
|
1939
|
+
NOT_FOUND = 'The requested user does not exists',
|
|
1940
|
+
NOTHING_TO_DELETE = 'There is no user to be deleted'
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
enum MessageForUser {
|
|
1944
|
+
ALL_USERS_DELETED = 'All the users were deleted successfully',
|
|
1945
|
+
USER_DELETED = 'The requested user was successfully deleted'
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
export { ErrorForUser as EFU, MessageForUser as MFU }
|
|
1949
|
+
`,
|
|
1950
|
+
file: `${projectName}/src/services/utils/messages/user.ts`
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
await Promise.all([
|
|
1956
|
+
writeFile(services.index.file, services.index.content),
|
|
1957
|
+
writeFile(services.user.file, services.user.content),
|
|
1958
|
+
writeFile(services.utils.index.file, services.utils.index.content),
|
|
1959
|
+
writeFile(
|
|
1960
|
+
services['utils/messages'].index.file,
|
|
1961
|
+
services['utils/messages'].index.content
|
|
1962
|
+
),
|
|
1963
|
+
writeFile(
|
|
1964
|
+
services['utils/messages'].user.file,
|
|
1965
|
+
services['utils/messages'].user.content
|
|
1966
|
+
)
|
|
1967
|
+
])
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
/**
|
|
1971
|
+
* @param {Object} args
|
|
1972
|
+
* @param {String} args.projectName
|
|
1973
|
+
* @param {Boolean} args.graphQL
|
|
1974
|
+
* @param {import('../../../../').Config['database']} args.database
|
|
1975
|
+
*/
|
|
1976
|
+
const main = async ({ projectName, graphQL, database }) => {
|
|
1977
|
+
const dbIsSQL = database !== 'mongo'
|
|
1978
|
+
|
|
1979
|
+
await types({ projectName, graphQL, dbIsSQL })
|
|
1980
|
+
await network({ projectName, graphQL, dbIsSQL })
|
|
1981
|
+
await schemas({ projectName, dbIsSQL, graphQL })
|
|
1982
|
+
|
|
1983
|
+
if (!graphQL) await services({ projectName, dbIsSQL })
|
|
1984
|
+
|
|
1985
|
+
if (dbIsSQL) await sql({ projectName, db: database })
|
|
1986
|
+
else await mongo({ projectName })
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
module.exports = main
|