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