@anthonylzq/simba.js 7.2.1 → 8.1.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,7 +1,12 @@
1
1
  const { platform } = require('os')
2
2
  const { promisify } = require('util')
3
3
  const exec = promisify(require('child_process').exec)
4
+
5
+ const db = require('./database')
6
+ const schemas = require('./schemas')
7
+ const services = require('./services')
4
8
  const writeFile = require('../../utils/writeFile')
9
+ const utils = require('./utils')
5
10
 
6
11
  /**
7
12
  * @param {Object} args
@@ -10,10 +15,8 @@ const writeFile = require('../../utils/writeFile')
10
15
  * @param {Boolean} args.dbIsSQL
11
16
  */
12
17
  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
- }`
18
+ const createFoldersCommand = `mkdir ${projectName}/src/@types \
19
+ ${!dbIsSQL ? ` ${projectName}/src/@types/models` : ''}`
17
20
 
18
21
  if (platform() === 'win32')
19
22
  await exec(createFoldersCommand.replaceAll('/', '\\'))
@@ -27,570 +30,11 @@ declare global {}
27
30
  export {}
28
31
  `,
29
32
  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
33
  }
568
34
  }
35
+ const processes = [writeFile(types.index.file, types.index.content)]
569
36
 
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
- ])
37
+ await Promise.all(processes)
594
38
  }
595
39
 
596
40
  /**
@@ -603,9 +47,7 @@ const network = async ({ projectName, graphQL, dbIsSQL }) => {
603
47
  const createFoldersCommand = `mkdir ${projectName}/src/network \
604
48
  ${projectName}/src/network/routes ${projectName}/src/network/routes/utils ${
605
49
  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`
50
+ ? ` ${projectName}/src/network/models ${projectName}/src/network/resolvers`
609
51
  : ''
610
52
  }`
611
53
 
@@ -615,25 +57,27 @@ ${projectName}/src/graphQL/models/utils/messages`
615
57
 
616
58
  const network = {
617
59
  index: {
618
- content: `export * from './routes'
619
- export * from './server'
620
- `,
60
+ content: `export * from './routes'\nexport * from './server'\n`,
621
61
  file: `${projectName}/src/network/index.ts`
622
62
  },
623
63
  response: {
624
- content: `interface ResponseProps {
64
+ content: `import { type Response } from 'express'
65
+
66
+ const response = ({
67
+ error,
68
+ message,
69
+ res,
70
+ status
71
+ }: {
625
72
  error: boolean
626
73
  message: unknown
627
- res: CustomResponse
74
+ res: Response
628
75
  status: number
629
- }
630
-
631
- const response = ({ error, message, res, status }: ResponseProps): void => {
76
+ }) => {
632
77
  res.status(status).send({ error, message })
633
78
  }
634
79
 
635
- export { response }
636
- `,
80
+ export { response }\n`,
637
81
  file: `${projectName}/src/network/response.ts`
638
82
  },
639
83
  router: {
@@ -683,54 +127,38 @@ export { applyRoutes }
683
127
  },
684
128
  server: {
685
129
  content: graphQL
686
- ? `import { createServer, Server as HttpServer } from 'http'
130
+ ? `import { Server as HttpServer } from 'http'
687
131
  import express from 'express'
688
132
  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'
133
+ import debug from 'debug'
134
+ import { ApolloServer } from '@apollo/server'
135
+ // eslint-disable-next-line import/extensions
136
+ import { expressMiddleware } from '@apollo/server/express4'
696
137
 
697
138
  import { dbConnection } from 'database'
698
- import { mergedSchema as schema } from 'graphQL'
699
139
  import { applyRoutes } from './router'
140
+ import { buildSchemas } from './resolvers'
141
+ import { Log } from 'utils'
700
142
 
143
+ const d = debug('App:Network:Server')
701
144
  const PORT = (process.env.PORT as string) || 1996
702
- const ENVIRONMENTS_WITHOUT_PRETTY_PRINT = ['production', 'ci']
703
145
 
704
- class Server {
146
+ class Server implements Log {
705
147
  #app: express.Application
706
- #log: HttpLogger
707
- #server: HttpServer
708
- #connection: Awaited<ReturnType<typeof dbConnection>> | undefined
148
+ #server: HttpServer | undefined
149
+ #connection: Awaited<ReturnType<typeof dbConnection>>
150
+ #apolloServer: ApolloServer | undefined
709
151
 
710
152
  constructor() {
711
153
  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()
154
+ this.#connection = dbConnection(d)
727
155
  }
728
156
 
729
- #config() {
157
+ async #config() {
158
+ await this.#apolloConfig()
730
159
  this.#app.use(cors())
731
160
  this.#app.use(express.json())
732
161
  this.#app.use(express.urlencoded({ extended: false }))
733
- this.#app.use(this.#log)
734
162
  this.#app.use(
735
163
  (
736
164
  req: express.Request,
@@ -747,100 +175,98 @@ class Server {
747
175
  next()
748
176
  }
749
177
  )
178
+ applyRoutes(this.#app)
750
179
  }
751
180
 
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
- })
181
+ async #apolloConfig() {
182
+ this.#apolloServer = new ApolloServer({
183
+ schema: await buildSchemas()
768
184
  })
185
+ await this.#apolloServer.start()
186
+ this.#app.use(
187
+ '/graphql',
188
+ cors(),
189
+ express.json(),
190
+ expressMiddleware(this.#apolloServer)
191
+ )
192
+ }
769
193
 
194
+ async start() {
770
195
  try {
771
- await server.start()
772
- server.applyMiddleware({
773
- app: this.#app,
774
- path: '/api'
775
- })
776
- applyRoutes(this.#app)
777
- await this.#dbConnection()
196
+ await this.#config()
778
197
  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
- )
198
+ this.#server = this.#app.listen(PORT, () => {
199
+ d(\`HTTP server listening on port \${PORT}.\`)
784
200
  })
785
201
  } catch (e) {
786
- console.error(e)
202
+ this.log({
203
+ method: this.start.name,
204
+ value: 'error',
205
+ content: e
206
+ })
787
207
  }
788
208
  }
789
209
 
790
- public async stop(): Promise<void> {
210
+ async stop() {
791
211
  try {
792
212
  await this.#connection?.disconnect()
793
213
  this.#server?.close()
214
+ await this.#apolloServer?.stop()
794
215
  } catch (e) {
795
- this.#log.logger.error(e)
216
+ this.log({
217
+ method: this.stop.name,
218
+ value: 'error',
219
+ content: e
220
+ })
796
221
  }
797
222
  }
223
+
224
+ log({
225
+ method,
226
+ value,
227
+ content
228
+ }: {
229
+ method: string
230
+ value: string
231
+ content: unknown
232
+ }) {
233
+ d(
234
+ \`Server invoked -> \${
235
+ this.constructor.name
236
+ } ~ \${method} ~ value: \${value} ~ content: \${content}\`
237
+ )
238
+ }
798
239
  }
799
240
 
800
241
  const server = new Server()
801
242
 
802
- export { server as Server }
803
- `
243
+ export { server as Server }\n`
804
244
  : `import { Server as HttpServer } from 'http'
805
245
  import express from 'express'
806
246
  import cors from 'cors'
807
- import pino, { HttpLogger } from 'express-pino-logger'
247
+ import debug from 'debug'
808
248
 
809
249
  import { dbConnection } from 'database'
810
250
  import { applyRoutes } from './router'
251
+ import { Log } from 'utils'
811
252
 
253
+ const d = debug('App:Network:Server')
812
254
  const PORT = (process.env.PORT as string) || 1996
813
- const ENVIRONMENTS_WITHOUT_PRETTY_PRINT = ['production', 'ci']
814
255
 
815
- class Server {
256
+ class Server implements Log {
816
257
  #app: express.Application
817
- #log: HttpLogger
818
258
  #server: HttpServer | undefined
819
- #connection: Awaited<ReturnType<typeof dbConnection>> | undefined
259
+ #connection: Awaited<ReturnType<typeof dbConnection>>
820
260
 
821
261
  constructor() {
822
262
  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()
263
+ this.#connection = dbConnection(d)
837
264
  }
838
265
 
839
266
  #config() {
840
267
  this.#app.use(cors())
841
268
  this.#app.use(express.json())
842
269
  this.#app.use(express.urlencoded({ extended: false }))
843
- this.#app.use(this.#log)
844
270
  this.#app.use(
845
271
  (
846
272
  req: express.Request,
@@ -860,36 +286,55 @@ class Server {
860
286
  applyRoutes(this.#app)
861
287
  }
862
288
 
863
- async #dbConnection() {
864
- this.#connection = await dbConnection(this.#log.logger)
865
- }
866
-
867
- public async start(): Promise<void> {
289
+ async start(): Promise<void> {
868
290
  try {
869
- await this.#dbConnection()
291
+ this.#config()
870
292
  await this.#connection?.connect()
871
293
  this.#server = this.#app.listen(PORT, () => {
872
- this.#log.logger.info(\`Server running at port \${PORT}\`)
294
+ d(\`HTTP server listening on port \${PORT}.\`)
873
295
  })
874
296
  } catch (e) {
875
- this.#log.logger.error(e)
297
+ this.log({
298
+ method: this.start.name,
299
+ value: 'error',
300
+ content: e
301
+ })
876
302
  }
877
303
  }
878
304
 
879
- public async stop(): Promise<void> {
305
+ async stop(): Promise<void> {
880
306
  try {
881
307
  await this.#connection?.disconnect()
882
308
  this.#server?.close()
883
309
  } catch (e) {
884
- this.#log.logger.error(e)
310
+ this.log({
311
+ method: this.stop.name,
312
+ value: 'error',
313
+ content: e
314
+ })
885
315
  }
886
316
  }
317
+
318
+ log({
319
+ method,
320
+ value,
321
+ content
322
+ }: {
323
+ method: string
324
+ value: string
325
+ content: unknown
326
+ }) {
327
+ d(
328
+ \`Server invoked -> \${
329
+ this.constructor.name
330
+ } ~ \${method} ~ value: \${value} ~ content: \${JSON.stringify(content)}\`
331
+ )
332
+ }
887
333
  }
888
334
 
889
335
  const server = new Server()
890
336
 
891
- export { server as Server }
892
- `,
337
+ export { server as Server }\n`,
893
338
  file: `${projectName}/src/network/server.ts`
894
339
  },
895
340
  routes: {
@@ -920,89 +365,60 @@ ${graphQL ? '' : "export * from './user'\n"}`,
920
365
  },
921
366
  ...(!graphQL && {
922
367
  user: {
923
- content: `import { NextFunction, Router } from 'express'
368
+ content: `import { type NextFunction, type Request, type Response, Router } from 'express'
924
369
 
925
370
  import { response } from 'network/response'
926
371
  import { UserService } from 'services'
927
- import { idSchema, storeUserDto, UserWithId } from 'schemas'
372
+ import { idSchema, storeUserDto, UserDTO } from 'schemas'
928
373
  import { validatorCompiler } from './utils'
929
374
 
930
375
  const User = Router()
931
376
 
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' })
377
+ User.route('/users').post(
378
+ validatorCompiler(storeUserDto, 'body'),
379
+ async (
380
+ req: Request<
381
+ {
382
+ [key: string]: string
383
+ },
384
+ Record<string, unknown>,
385
+ { args: UserDTO }
386
+ >,
387
+ res: Response,
388
+ next: NextFunction
389
+ ): Promise<void> => {
390
+ try {
391
+ const {
392
+ body: {
393
+ args: { lastName, name }
394
+ }
395
+ } = req
396
+ const us = new UserService()
397
+ const user = await us.store({ lastName, name })
978
398
 
979
- response({ error: false, message: result, res, status: 200 })
980
- } catch (error) {
981
- next(error)
982
- }
399
+ response({ error: false, message: user, res, status: 201 })
400
+ } catch (error) {
401
+ next(error)
983
402
  }
984
- )
403
+ }
404
+ )
985
405
 
986
406
  User.route('/user/:id')
987
407
  .get(
988
408
  validatorCompiler(idSchema, 'params'),
989
409
  async (
990
- req: CustomRequest,
991
- res: CustomResponse,
410
+ req: Request<{ id: string }, Record<string, unknown>>,
411
+ res: Response,
992
412
  next: NextFunction
993
413
  ): Promise<void> => {
994
414
  try {
995
415
  const {
996
416
  params: { id }
997
417
  } = 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' })
418
+ const us = new UserService()
419
+ const user = await us.getById(${dbIsSQL ? 'parseInt(id)' : 'id'})
1004
420
 
1005
- response({ error: false, message: result, res, status: 200 })
421
+ response({ error: false, message: user, res, status: 200 })
1006
422
  } catch (error) {
1007
423
  next(error)
1008
424
  }
@@ -1012,23 +428,23 @@ User.route('/user/:id')
1012
428
  validatorCompiler(idSchema, 'params'),
1013
429
  validatorCompiler(storeUserDto, 'body'),
1014
430
  async (
1015
- req: CustomRequest,
1016
- res: CustomResponse,
431
+ req: Request<{ id: string }, Record<string, unknown>, { args: UserDTO }>,
432
+ res: Response,
1017
433
  next: NextFunction
1018
434
  ): Promise<void> => {
1019
435
  try {
1020
436
  const {
1021
- body: { args },
437
+ body: {
438
+ args: { name, lastName }
439
+ },
1022
440
  params: { id }
1023
441
  } = 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' })
442
+ const us = new UserService()
443
+ const user = await us.update(${
444
+ dbIsSQL ? 'parseInt(id)' : 'id'
445
+ }, { name, lastName })
1030
446
 
1031
- response({ error: false, message: result, res, status: 200 })
447
+ response({ error: false, message: user, res, status: 200 })
1032
448
  } catch (error) {
1033
449
  next(error)
1034
450
  }
@@ -1037,20 +453,16 @@ User.route('/user/:id')
1037
453
  .delete(
1038
454
  validatorCompiler(idSchema, 'params'),
1039
455
  async (
1040
- req: CustomRequest,
1041
- res: CustomResponse,
456
+ req: Request<{ id: string }>,
457
+ res: Response,
1042
458
  next: NextFunction
1043
459
  ): Promise<void> => {
1044
460
  try {
1045
461
  const {
1046
462
  params: { id }
1047
463
  } = 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' })
464
+ const us = new UserService()
465
+ const result = await us.deleteById(${dbIsSQL ? 'parseInt(id)' : 'id'})
1054
466
 
1055
467
  response({ error: false, message: result, res, status: 200 })
1056
468
  } catch (error) {
@@ -1059,516 +471,132 @@ User.route('/user/:id')
1059
471
  }
1060
472
  )
1061
473
 
1062
- export { User }
1063
- `,
474
+ export { User }\n`,
1064
475
  file: `${projectName}/src/network/routes/user.ts`
1065
476
  }
1066
477
  }),
1067
478
  utils: {
1068
479
  index: {
1069
- content: `import { NextFunction } from 'express'
480
+ content: `import { type NextFunction, type Request, type Response } from 'express'
1070
481
  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
- })
482
+ import { ZodType } from 'zod'
1080
483
 
1081
- type Middleware = (
1082
- req: CustomRequest,
1083
- res: CustomResponse,
1084
- next: NextFunction
1085
- ) => void
484
+ type Middleware = (req: Request, res: Response, next: NextFunction) => void
1086
485
 
1087
- const validatorCompiler = <T extends TProperties>(
1088
- schema: TObject<T>,
486
+ const validatorCompiler = (
487
+ schema: ZodType,
1089
488
  value: 'body' | 'params'
1090
489
  ): Middleware => {
1091
- return (req: CustomRequest, res: CustomResponse, next: NextFunction) => {
1092
- const validate = ajv.compile(schema)
1093
- const ok = validate(req[value])
490
+ return (req: Request, res: Response, next: NextFunction) => {
491
+ const result = schema.safeParse(req[value])
1094
492
 
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
- }
493
+ if (result.success) return next()
1101
494
 
1102
- next()
495
+ return next(
496
+ new httpErrors.UnprocessableEntity(JSON.stringify(result.error))
497
+ )
1103
498
  }
1104
499
  }
1105
500
 
1106
- export { validatorCompiler }
1107
- `,
501
+ export { validatorCompiler }\n`,
1108
502
  file: `${projectName}/src/network/routes/utils/index.ts`
1109
503
  }
1110
504
  }
1111
505
  },
1112
506
  ...(graphQL && {
1113
- graphQL: {
507
+ models: {
1114
508
  index: {
1115
- content: "export * from './models'\n",
1116
- file: `${projectName}/src/graphQL/index.ts`
509
+ content: "export * from './User'\n",
510
+ file: `${projectName}/src/network/models/index.ts`
1117
511
  },
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
- })
512
+ User: {
513
+ content: `import 'reflect-metadata'
514
+ import { Field, ${dbIsSQL ? 'Int' : 'ID'}, ObjectType } from 'type-graphql'
1136
515
 
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
- }
516
+ @ObjectType()
517
+ class User {
518
+ @Field(() => ${dbIsSQL ? 'Int' : 'ID'})
519
+ id!: number
1165
520
 
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')
521
+ @Field()
522
+ lastName!: string
1174
523
 
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
- }
524
+ @Field()
525
+ name!: string
1185
526
 
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
- }
527
+ @Field({ nullable: true })
528
+ createdAt?: string
1205
529
 
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
- }
530
+ @Field({ nullable: true })
531
+ updatedAt?: string
1224
532
  }
1225
533
 
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
- }
534
+ export { User }\n`,
535
+ file: `${projectName}/src/network/models/User.ts`
536
+ }
537
+ },
538
+ resolvers: {
539
+ index: {
540
+ content: `import { buildSchema } from 'type-graphql'
541
+ import { UserResolver } from './User'
1361
542
 
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
- }
543
+ const buildSchemas = async () => {
544
+ const schema = await buildSchema({
545
+ resolvers: [UserResolver],
546
+ validate: { forbidUnknownValues: false }
547
+ })
1391
548
 
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
- }
549
+ return schema
1410
550
  }
1411
551
 
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 })
552
+ export { buildSchemas }\n`,
553
+ file: `${projectName}/src/network/resolvers/index.ts`
554
+ },
555
+ User: {
556
+ content: `import 'reflect-metadata'
557
+ import { Arg, Field, InputType, Mutation, Query, Resolver } from 'type-graphql'
1435
558
 
1436
- if (!ok)
1437
- throw new ApolloError(
1438
- \`id \${(validate.errors as DefinedError[])[0].message as string}\`,
1439
- 'UNPROCESSABLE_ENTITY'
1440
- )
559
+ import { User } from 'network/models'
560
+ import { UserService } from 'services'
1441
561
 
1442
- return await getUser({ id }, context)
1443
- } catch (e) {
1444
- log.error(validate.errors)
562
+ @InputType()
563
+ class UserInput {
564
+ @Field()
565
+ name!: string
1445
566
 
1446
- return errorHandling({
1447
- e,
1448
- message: GE.INTERNAL_SERVER_ERROR,
1449
- code: 'INTERNAL_SERVER_ERROR',
1450
- log
1451
- })
1452
- }
1453
- },
1454
- getUsers
567
+ @Field()
568
+ lastName!: string
1455
569
  }
1456
570
 
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
- }
571
+ @Resolver(User)
572
+ class UserResolver {
573
+ readonly #userService = new UserService()
1472
574
 
1473
- type Query {
1474
- getUsers: [User!]!
1475
- getUser(id: ${dbIsSQL ? 'Int' : 'ID'}!): User!
575
+ @Query(() => User)
576
+ async getById(@Arg('id') id: ${dbIsSQL ? 'number' : 'string'}) {
577
+ return this.#userService.getById(id)
1476
578
  }
1477
579
 
1478
- input StoreUserInput {
1479
- lastName: String!
1480
- name: String!
580
+ @Mutation(() => User)
581
+ async store(@Arg('user') user: UserInput) {
582
+ return this.#userService.store(user)
1481
583
  }
1482
584
 
1483
- input UpdateUserInput {
1484
- id: ${dbIsSQL ? 'Int' : 'String'}!
1485
- lastName: String!
1486
- name: String!
585
+ @Mutation(() => User)
586
+ async update(@Arg('id') id: ${
587
+ dbIsSQL ? 'number' : 'string'
588
+ }, @Arg('user') user: UserInput) {
589
+ return this.#userService.update(id, user)
1487
590
  }
1488
591
 
1489
- type Mutation {
1490
- storeUser(user: StoreUserInput!): User!
1491
- deleteAllUsers: String
1492
- updateUser(user: UpdateUserInput!): User!
1493
- deleteUser(id: ${dbIsSQL ? 'Int' : 'ID'}!): String
592
+ @Mutation(() => String)
593
+ async deleteById(@Arg('id') id: ${dbIsSQL ? 'number' : 'string'}) {
594
+ return this.#userService.deleteById(id)
1494
595
  }
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
596
  }
1524
597
 
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
- }
598
+ export { UserResolver }\n`,
599
+ file: `${projectName}/src/network/resolvers/User.ts`
1572
600
  }
1573
601
  }
1574
602
  })
@@ -1588,47 +616,10 @@ export { mergedSchema }
1588
616
 
1589
617
  if (graphQL)
1590
618
  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
- )
619
+ writeFile(network.models.index.file, network.models.index.content),
620
+ writeFile(network.models.User.file, network.models.User.content),
621
+ writeFile(network.resolvers.index.file, network.resolvers.index.content),
622
+ writeFile(network.resolvers.User.file, network.resolvers.User.content)
1632
623
  ])
1633
624
  else
1634
625
  processes.push(
@@ -1641,349 +632,33 @@ export { mergedSchema }
1641
632
  /**
1642
633
  * @param {Object} args
1643
634
  * @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
635
+ * @param {String} args.email
636
+ * @param {String} args.projectVersion
1973
637
  * @param {Boolean} args.graphQL
1974
638
  * @param {import('../../../../').Config['database']} args.database
1975
639
  */
1976
- const main = async ({ projectName, graphQL, database }) => {
640
+ const main = async ({
641
+ projectName,
642
+ email,
643
+ projectVersion,
644
+ graphQL,
645
+ database
646
+ }) => {
1977
647
  const dbIsSQL = database !== 'mongo'
1978
648
 
649
+ await utils({
650
+ fastify: false,
651
+ projectName,
652
+ email,
653
+ projectVersion,
654
+ graphQL,
655
+ dbIsSQL
656
+ })
1979
657
  await types({ projectName, graphQL, dbIsSQL })
1980
658
  await network({ projectName, graphQL, dbIsSQL })
1981
659
  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 })
660
+ await services({ projectName, dbIsSQL })
661
+ await db({ projectName, database })
1987
662
  }
1988
663
 
1989
664
  module.exports = main