@anthonylzq/simba.js 7.2.0 → 8.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.
@@ -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
@@ -29,47 +34,13 @@ export {}
29
34
  file: `${projectName}/src/@types/index.d.ts`
30
35
  },
31
36
  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`
37
+ params: {
38
+ content: `type Params = {
39
+ [key: string]: string
40
+ }\n`,
41
+ file: `${projectName}/src/@types/custom/params.d.ts`
57
42
  }
58
43
  },
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
44
  ...(graphQL && {
74
45
  graphQL: {
75
46
  context: {
@@ -84,13 +55,9 @@ interface CustomResponse extends ExpressResponse {
84
55
  }
85
56
  const processes = [
86
57
  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)
58
+ writeFile(types.custom.params.file, types.custom.params.content)
89
59
  ]
90
60
 
91
- if (!dbIsSQL)
92
- processes.push(writeFile(types.models.user.file, types.models.user.content))
93
-
94
61
  if (graphQL)
95
62
  processes.push(
96
63
  writeFile(types.graphQL.context.file, types.graphQL.context.content)
@@ -99,500 +66,6 @@ interface CustomResponse extends ExpressResponse {
99
66
  await Promise.all(processes)
100
67
  }
101
68
 
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
69
  /**
597
70
  * @param {Object} args
598
71
  * @param {String} args.projectName
@@ -603,9 +76,7 @@ const network = async ({ projectName, graphQL, dbIsSQL }) => {
603
76
  const createFoldersCommand = `mkdir ${projectName}/src/network \
604
77
  ${projectName}/src/network/routes ${projectName}/src/network/routes/utils ${
605
78
  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`
79
+ ? ` ${projectName}/src/network/models ${projectName}/src/network/resolvers`
609
80
  : ''
610
81
  }`
611
82
 
@@ -615,25 +86,27 @@ ${projectName}/src/graphQL/models/utils/messages`
615
86
 
616
87
  const network = {
617
88
  index: {
618
- content: `export * from './routes'
619
- export * from './server'
620
- `,
89
+ content: `export * from './routes'\nexport * from './server'\n`,
621
90
  file: `${projectName}/src/network/index.ts`
622
91
  },
623
92
  response: {
624
- content: `interface ResponseProps {
93
+ content: `import { type Response } from 'express'
94
+
95
+ const response = ({
96
+ error,
97
+ message,
98
+ res,
99
+ status
100
+ }: {
625
101
  error: boolean
626
102
  message: unknown
627
- res: CustomResponse
103
+ res: Response
628
104
  status: number
629
- }
630
-
631
- const response = ({ error, message, res, status }: ResponseProps): void => {
105
+ }) => {
632
106
  res.status(status).send({ error, message })
633
107
  }
634
108
 
635
- export { response }
636
- `,
109
+ export { response }\n`,
637
110
  file: `${projectName}/src/network/response.ts`
638
111
  },
639
112
  router: {
@@ -683,54 +156,38 @@ export { applyRoutes }
683
156
  },
684
157
  server: {
685
158
  content: graphQL
686
- ? `import { createServer, Server as HttpServer } from 'http'
159
+ ? `import { Server as HttpServer } from 'http'
687
160
  import express from 'express'
688
161
  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'
162
+ import debug from 'debug'
163
+ import { ApolloServer } from '@apollo/server'
164
+ // eslint-disable-next-line import/extensions
165
+ import { expressMiddleware } from '@apollo/server/express4'
696
166
 
697
167
  import { dbConnection } from 'database'
698
- import { mergedSchema as schema } from 'graphQL'
699
168
  import { applyRoutes } from './router'
169
+ import { buildSchemas } from './resolvers'
170
+ import { Log } from 'utils'
700
171
 
172
+ const d = debug('App:Network:Server')
701
173
  const PORT = (process.env.PORT as string) || 1996
702
- const ENVIRONMENTS_WITHOUT_PRETTY_PRINT = ['production', 'ci']
703
174
 
704
- class Server {
175
+ class Server implements Log {
705
176
  #app: express.Application
706
- #log: HttpLogger
707
- #server: HttpServer
708
- #connection: Awaited<ReturnType<typeof dbConnection>> | undefined
177
+ #server: HttpServer | undefined
178
+ #connection: Awaited<ReturnType<typeof dbConnection>>
179
+ #apolloServer: ApolloServer | undefined
709
180
 
710
181
  constructor() {
711
182
  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()
183
+ this.#connection = dbConnection(d)
727
184
  }
728
185
 
729
- #config() {
186
+ async #config() {
187
+ await this.#apolloConfig()
730
188
  this.#app.use(cors())
731
189
  this.#app.use(express.json())
732
190
  this.#app.use(express.urlencoded({ extended: false }))
733
- this.#app.use(this.#log)
734
191
  this.#app.use(
735
192
  (
736
193
  req: express.Request,
@@ -747,100 +204,98 @@ class Server {
747
204
  next()
748
205
  }
749
206
  )
207
+ applyRoutes(this.#app)
750
208
  }
751
209
 
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
- })
210
+ async #apolloConfig() {
211
+ this.#apolloServer = new ApolloServer({
212
+ schema: await buildSchemas()
768
213
  })
214
+ await this.#apolloServer.start()
215
+ this.#app.use(
216
+ '/graphql',
217
+ cors(),
218
+ express.json(),
219
+ expressMiddleware(this.#apolloServer)
220
+ )
221
+ }
769
222
 
223
+ async start() {
770
224
  try {
771
- await server.start()
772
- server.applyMiddleware({
773
- app: this.#app,
774
- path: '/api'
775
- })
776
- applyRoutes(this.#app)
777
- await this.#dbConnection()
225
+ await this.#config()
778
226
  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
- )
227
+ this.#server = this.#app.listen(PORT, () => {
228
+ d(\`HTTP server listening on port \${PORT}.\`)
784
229
  })
785
230
  } catch (e) {
786
- console.error(e)
231
+ this.log({
232
+ method: this.start.name,
233
+ value: 'error',
234
+ content: e
235
+ })
787
236
  }
788
237
  }
789
238
 
790
- public async stop(): Promise<void> {
239
+ async stop() {
791
240
  try {
792
241
  await this.#connection?.disconnect()
793
242
  this.#server?.close()
243
+ await this.#apolloServer?.stop()
794
244
  } catch (e) {
795
- this.#log.logger.error(e)
245
+ this.log({
246
+ method: this.stop.name,
247
+ value: 'error',
248
+ content: e
249
+ })
796
250
  }
797
251
  }
252
+
253
+ log({
254
+ method,
255
+ value,
256
+ content
257
+ }: {
258
+ method: string
259
+ value: string
260
+ content: unknown
261
+ }) {
262
+ d(
263
+ \`Server invoked -> \${
264
+ this.constructor.name
265
+ } ~ \${method} ~ value: \${value} ~ content: \${content}\`
266
+ )
267
+ }
798
268
  }
799
269
 
800
270
  const server = new Server()
801
271
 
802
- export { server as Server }
803
- `
272
+ export { server as Server }\n`
804
273
  : `import { Server as HttpServer } from 'http'
805
274
  import express from 'express'
806
275
  import cors from 'cors'
807
- import pino, { HttpLogger } from 'express-pino-logger'
276
+ import debug from 'debug'
808
277
 
809
278
  import { dbConnection } from 'database'
810
279
  import { applyRoutes } from './router'
280
+ import { Log } from 'utils'
811
281
 
282
+ const d = debug('App:Network:Server')
812
283
  const PORT = (process.env.PORT as string) || 1996
813
- const ENVIRONMENTS_WITHOUT_PRETTY_PRINT = ['production', 'ci']
814
284
 
815
- class Server {
285
+ class Server implements Log {
816
286
  #app: express.Application
817
- #log: HttpLogger
818
287
  #server: HttpServer | undefined
819
- #connection: Awaited<ReturnType<typeof dbConnection>> | undefined
288
+ #connection: Awaited<ReturnType<typeof dbConnection>>
820
289
 
821
290
  constructor() {
822
291
  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()
292
+ this.#connection = dbConnection(d)
837
293
  }
838
294
 
839
295
  #config() {
840
296
  this.#app.use(cors())
841
297
  this.#app.use(express.json())
842
298
  this.#app.use(express.urlencoded({ extended: false }))
843
- this.#app.use(this.#log)
844
299
  this.#app.use(
845
300
  (
846
301
  req: express.Request,
@@ -860,36 +315,55 @@ class Server {
860
315
  applyRoutes(this.#app)
861
316
  }
862
317
 
863
- async #dbConnection() {
864
- this.#connection = await dbConnection(this.#log.logger)
865
- }
866
-
867
- public async start(): Promise<void> {
318
+ async start(): Promise<void> {
868
319
  try {
869
- await this.#dbConnection()
320
+ this.#config()
870
321
  await this.#connection?.connect()
871
322
  this.#server = this.#app.listen(PORT, () => {
872
- this.#log.logger.info(\`Server running at port \${PORT}\`)
323
+ d(\`HTTP server listening on port \${PORT}.\`)
873
324
  })
874
325
  } catch (e) {
875
- this.#log.logger.error(e)
326
+ this.log({
327
+ method: this.start.name,
328
+ value: 'error',
329
+ content: e
330
+ })
876
331
  }
877
332
  }
878
333
 
879
- public async stop(): Promise<void> {
334
+ async stop(): Promise<void> {
880
335
  try {
881
336
  await this.#connection?.disconnect()
882
337
  this.#server?.close()
883
338
  } catch (e) {
884
- this.#log.logger.error(e)
339
+ this.log({
340
+ method: this.stop.name,
341
+ value: 'error',
342
+ content: e
343
+ })
885
344
  }
886
345
  }
346
+
347
+ log({
348
+ method,
349
+ value,
350
+ content
351
+ }: {
352
+ method: string
353
+ value: string
354
+ content: unknown
355
+ }) {
356
+ d(
357
+ \`Server invoked -> \${
358
+ this.constructor.name
359
+ } ~ \${method} ~ value: \${value} ~ content: \${JSON.stringify(content)}\`
360
+ )
361
+ }
887
362
  }
888
363
 
889
364
  const server = new Server()
890
365
 
891
- export { server as Server }
892
- `,
366
+ export { server as Server }\n`,
893
367
  file: `${projectName}/src/network/server.ts`
894
368
  },
895
369
  routes: {
@@ -920,89 +394,54 @@ ${graphQL ? '' : "export * from './user'\n"}`,
920
394
  },
921
395
  ...(!graphQL && {
922
396
  user: {
923
- content: `import { NextFunction, Router } from 'express'
397
+ content: `import { type NextFunction, type Request, type Response, Router } from 'express'
924
398
 
925
399
  import { response } from 'network/response'
926
400
  import { UserService } from 'services'
927
- import { idSchema, storeUserDto, UserWithId } from 'schemas'
401
+ import { idSchema, storeUserDto, UserDTO } from 'schemas'
928
402
  import { validatorCompiler } from './utils'
929
403
 
930
404
  const User = Router()
931
405
 
932
- User.route('/users')
933
- .post(
934
- validatorCompiler(storeUserDto, 'body'),
406
+ User.route('/users').post(
407
+ validatorCompiler(storeUserDto, 'body'),
408
+ async (
409
+ req: Request<Params, Record<string, unknown>, { args: UserDTO }>,
410
+ res: Response,
411
+ next: NextFunction
412
+ ): Promise<void> => {
413
+ try {
414
+ const {
415
+ body: {
416
+ args: { lastName, name }
417
+ }
418
+ } = req
419
+ const us = new UserService()
420
+ const user = await us.store({ lastName, name })
421
+
422
+ response({ error: false, message: user, res, status: 201 })
423
+ } catch (error) {
424
+ next(error)
425
+ }
426
+ }
427
+ )
428
+
429
+ User.route('/user/:id')
430
+ .get(
431
+ validatorCompiler(idSchema, 'params'),
935
432
  async (
936
- req: CustomRequest,
937
- res: CustomResponse,
433
+ req: Request<{ id: string }, Record<string, unknown>>,
434
+ res: Response,
938
435
  next: NextFunction
939
436
  ): Promise<void> => {
940
437
  try {
941
438
  const {
942
- body: { args: user }
439
+ params: { id }
943
440
  } = req
944
- const us = new UserService({ user })
945
- const result = await us.process({ type: 'store' })
441
+ const us = new UserService()
442
+ const user = await us.getById(${dbIsSQL ? 'parseInt(id)' : 'id'})
946
443
 
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 })
444
+ response({ error: false, message: user, res, status: 200 })
1006
445
  } catch (error) {
1007
446
  next(error)
1008
447
  }
@@ -1012,23 +451,23 @@ User.route('/user/:id')
1012
451
  validatorCompiler(idSchema, 'params'),
1013
452
  validatorCompiler(storeUserDto, 'body'),
1014
453
  async (
1015
- req: CustomRequest,
1016
- res: CustomResponse,
454
+ req: Request<{ id: string }, Record<string, unknown>, { args: UserDTO }>,
455
+ res: Response,
1017
456
  next: NextFunction
1018
457
  ): Promise<void> => {
1019
458
  try {
1020
459
  const {
1021
- body: { args },
460
+ body: {
461
+ args: { name, lastName }
462
+ },
1022
463
  params: { id }
1023
464
  } = 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' })
465
+ const us = new UserService()
466
+ const user = await us.update(${
467
+ dbIsSQL ? 'parseInt(id)' : 'id'
468
+ }, { name, lastName })
1030
469
 
1031
- response({ error: false, message: result, res, status: 200 })
470
+ response({ error: false, message: user, res, status: 200 })
1032
471
  } catch (error) {
1033
472
  next(error)
1034
473
  }
@@ -1037,20 +476,16 @@ User.route('/user/:id')
1037
476
  .delete(
1038
477
  validatorCompiler(idSchema, 'params'),
1039
478
  async (
1040
- req: CustomRequest,
1041
- res: CustomResponse,
479
+ req: Request<{ id: string }>,
480
+ res: Response,
1042
481
  next: NextFunction
1043
482
  ): Promise<void> => {
1044
483
  try {
1045
484
  const {
1046
485
  params: { id }
1047
486
  } = 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' })
487
+ const us = new UserService()
488
+ const result = await us.deleteById(${dbIsSQL ? 'parseInt(id)' : 'id'})
1054
489
 
1055
490
  response({ error: false, message: result, res, status: 200 })
1056
491
  } catch (error) {
@@ -1059,516 +494,132 @@ User.route('/user/:id')
1059
494
  }
1060
495
  )
1061
496
 
1062
- export { User }
1063
- `,
497
+ export { User }\n`,
1064
498
  file: `${projectName}/src/network/routes/user.ts`
1065
499
  }
1066
500
  }),
1067
501
  utils: {
1068
502
  index: {
1069
- content: `import { NextFunction } from 'express'
503
+ content: `import { type NextFunction, type Request, type Response } from 'express'
1070
504
  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
- })
505
+ import { ZodType } from 'zod'
1080
506
 
1081
- type Middleware = (
1082
- req: CustomRequest,
1083
- res: CustomResponse,
1084
- next: NextFunction
1085
- ) => void
507
+ type Middleware = (req: Request, res: Response, next: NextFunction) => void
1086
508
 
1087
- const validatorCompiler = <T extends TProperties>(
1088
- schema: TObject<T>,
509
+ const validatorCompiler = (
510
+ schema: ZodType,
1089
511
  value: 'body' | 'params'
1090
512
  ): Middleware => {
1091
- return (req: CustomRequest, res: CustomResponse, next: NextFunction) => {
1092
- const validate = ajv.compile(schema)
1093
- const ok = validate(req[value])
513
+ return (req: Request, res: Response, next: NextFunction) => {
514
+ const result = schema.safeParse(req[value])
1094
515
 
1095
- if (!ok && validate.errors) {
1096
- const [error] = validate.errors
1097
- const errorMessage = \`\${error.dataPath.replace('.', '')} \${error.message}\`
516
+ if (result.success) return next()
1098
517
 
1099
- return next(new httpErrors.UnprocessableEntity(errorMessage))
1100
- }
1101
-
1102
- next()
518
+ return next(
519
+ new httpErrors.UnprocessableEntity(JSON.stringify(result.error))
520
+ )
1103
521
  }
1104
522
  }
1105
523
 
1106
- export { validatorCompiler }
1107
- `,
524
+ export { validatorCompiler }\n`,
1108
525
  file: `${projectName}/src/network/routes/utils/index.ts`
1109
526
  }
1110
527
  }
1111
528
  },
1112
529
  ...(graphQL && {
1113
- graphQL: {
530
+ models: {
1114
531
  index: {
1115
- content: "export * from './models'\n",
1116
- file: `${projectName}/src/graphQL/index.ts`
532
+ content: "export * from './User'\n",
533
+ file: `${projectName}/src/network/models/index.ts`
1117
534
  },
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
- }
535
+ User: {
536
+ content: `import 'reflect-metadata'
537
+ import { Field, ${dbIsSQL ? 'Int' : 'ID'}, ObjectType } from 'type-graphql'
1165
538
 
1166
- const deleteAllUsers = async ({ log }: Context): Promise<string> => {
1167
- try {
1168
- const usersDeleted = (await remove()) as number
539
+ @ObjectType()
540
+ class User {
541
+ @Field(() => ${dbIsSQL ? 'Int' : 'ID'})
542
+ id!: number
1169
543
 
1170
- if (usersDeleted >= 1) return MFU.ALL_USERS_DELETED
544
+ @Field()
545
+ lastName!: string
1171
546
 
1172
- if (usersDeleted === 0)
1173
- throw new ApolloError(EFU.NOTHING_TO_DELETE, 'NOTHING_TO_DELETE')
547
+ @Field()
548
+ name!: string
1174
549
 
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
- }
550
+ @Field({ nullable: true })
551
+ createdAt?: string
1185
552
 
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
- }
553
+ @Field({ nullable: true })
554
+ updatedAt?: string
1204
555
  }
1205
556
 
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
- }
557
+ export { User }\n`,
558
+ file: `${projectName}/src/network/models/User.ts`
559
+ }
560
+ },
561
+ resolvers: {
562
+ index: {
563
+ content: `import { buildSchema } from 'type-graphql'
564
+ import { UserResolver } from './User'
1361
565
 
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
- }
566
+ const buildSchemas = async () => {
567
+ const schema = await buildSchema({
568
+ resolvers: [UserResolver],
569
+ validate: { forbidUnknownValues: false }
570
+ })
1391
571
 
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
- }
572
+ return schema
1410
573
  }
1411
574
 
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 })
575
+ export { buildSchemas }\n`,
576
+ file: `${projectName}/src/network/resolvers/index.ts`
577
+ },
578
+ User: {
579
+ content: `import 'reflect-metadata'
580
+ import { Arg, Field, InputType, Mutation, Query, Resolver } from 'type-graphql'
1435
581
 
1436
- if (!ok)
1437
- throw new ApolloError(
1438
- \`id \${(validate.errors as DefinedError[])[0].message as string}\`,
1439
- 'UNPROCESSABLE_ENTITY'
1440
- )
582
+ import { User } from 'network/models'
583
+ import { UserService } from 'services'
1441
584
 
1442
- return await getUser({ id }, context)
1443
- } catch (e) {
1444
- log.error(validate.errors)
585
+ @InputType()
586
+ class UserInput {
587
+ @Field()
588
+ name!: string
1445
589
 
1446
- return errorHandling({
1447
- e,
1448
- message: GE.INTERNAL_SERVER_ERROR,
1449
- code: 'INTERNAL_SERVER_ERROR',
1450
- log
1451
- })
1452
- }
1453
- },
1454
- getUsers
590
+ @Field()
591
+ lastName!: string
1455
592
  }
1456
593
 
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
- }
594
+ @Resolver(User)
595
+ class UserResolver {
596
+ readonly #userService = new UserService()
1472
597
 
1473
- type Query {
1474
- getUsers: [User!]!
1475
- getUser(id: ${dbIsSQL ? 'Int' : 'ID'}!): User!
598
+ @Query(() => User)
599
+ async getById(@Arg('id') id: ${dbIsSQL ? 'number' : 'string'}) {
600
+ return this.#userService.getById(id)
1476
601
  }
1477
602
 
1478
- input StoreUserInput {
1479
- lastName: String!
1480
- name: String!
603
+ @Mutation(() => User)
604
+ async store(@Arg('user') user: UserInput) {
605
+ return this.#userService.store(user)
1481
606
  }
1482
607
 
1483
- input UpdateUserInput {
1484
- id: ${dbIsSQL ? 'Int' : 'String'}!
1485
- lastName: String!
1486
- name: String!
608
+ @Mutation(() => User)
609
+ async update(@Arg('id') id: ${
610
+ dbIsSQL ? 'number' : 'string'
611
+ }, @Arg('user') user: UserInput) {
612
+ return this.#userService.update(id, user)
1487
613
  }
1488
614
 
1489
- type Mutation {
1490
- storeUser(user: StoreUserInput!): User!
1491
- deleteAllUsers: String
1492
- updateUser(user: UpdateUserInput!): User!
1493
- deleteUser(id: ${dbIsSQL ? 'Int' : 'ID'}!): String
615
+ @Mutation(() => String)
616
+ async deleteById(@Arg('id') id: ${dbIsSQL ? 'number' : 'string'}) {
617
+ return this.#userService.deleteById(id)
1494
618
  }
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
619
  }
1552
620
 
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
- }
621
+ export { UserResolver }\n`,
622
+ file: `${projectName}/src/network/resolvers/User.ts`
1572
623
  }
1573
624
  }
1574
625
  })
@@ -1588,47 +639,10 @@ export { mergedSchema }
1588
639
 
1589
640
  if (graphQL)
1590
641
  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
- )
642
+ writeFile(network.models.index.file, network.models.index.content),
643
+ writeFile(network.models.User.file, network.models.User.content),
644
+ writeFile(network.resolvers.index.file, network.resolvers.index.content),
645
+ writeFile(network.resolvers.User.file, network.resolvers.User.content)
1632
646
  ])
1633
647
  else
1634
648
  processes.push(
@@ -1641,349 +655,33 @@ export { mergedSchema }
1641
655
  /**
1642
656
  * @param {Object} args
1643
657
  * @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
658
+ * @param {String} args.email
659
+ * @param {String} args.projectVersion
1973
660
  * @param {Boolean} args.graphQL
1974
661
  * @param {import('../../../../').Config['database']} args.database
1975
662
  */
1976
- const main = async ({ projectName, graphQL, database }) => {
663
+ const main = async ({
664
+ projectName,
665
+ email,
666
+ projectVersion,
667
+ graphQL,
668
+ database
669
+ }) => {
1977
670
  const dbIsSQL = database !== 'mongo'
1978
671
 
672
+ await utils({
673
+ fastify: false,
674
+ projectName,
675
+ email,
676
+ projectVersion,
677
+ graphQL,
678
+ dbIsSQL
679
+ })
1979
680
  await types({ projectName, graphQL, dbIsSQL })
1980
681
  await network({ projectName, graphQL, dbIsSQL })
1981
682
  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 })
683
+ await services({ projectName, dbIsSQL })
684
+ await db({ projectName, database })
1987
685
  }
1988
686
 
1989
687
  module.exports = main