@anthonylzq/simba.js 4.3.0 → 5.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.
@@ -0,0 +1,2536 @@
1
+ const { platform } = require('os')
2
+ const { promisify } = require('util')
3
+ const exec = promisify(require('child_process').exec)
4
+ const writeFile = require('../../utils/writeFile')
5
+
6
+ /**
7
+ * @param {Object} args
8
+ * @param {String} args.projectName
9
+ */
10
+ const expressRestNetwork = async ({ projectName }) => {
11
+ const createFoldersCommand = `mkdir ${projectName}/src/network/routes/utils`
12
+
13
+ if (platform() === 'win32')
14
+ await exec(createFoldersCommand.replaceAll('/', '\\'))
15
+ else await exec(createFoldersCommand)
16
+
17
+ const network = {
18
+ response: {
19
+ content: `interface ResponseProps {
20
+ error: boolean
21
+ message: unknown
22
+ res: CustomResponse
23
+ status: number
24
+ }
25
+
26
+ const response = ({ error, message, res, status }: ResponseProps): void => {
27
+ res.status(status).send({ error, message })
28
+ }
29
+
30
+ export { response }
31
+ `,
32
+ file: `${projectName}/src/network/response.ts`
33
+ },
34
+ router: {
35
+ content: `import { Application, Response, Request, Router, NextFunction } from 'express'
36
+ import swaggerUi from 'swagger-ui-express'
37
+ import httpErrors from 'http-errors'
38
+
39
+ import { response } from './response'
40
+ import { Home, User } from './routes'
41
+ import { docs } from 'utils'
42
+
43
+ const routers = [User]
44
+ const applyRoutes = (app: Application): void => {
45
+ app.use('/', Home)
46
+ app.use('/api/docs', swaggerUi.serve, swaggerUi.setup(docs))
47
+ routers.forEach((router: Router): Application => app.use('/api', router))
48
+
49
+ // Handling 404 error
50
+ app.use((req, res, next) => {
51
+ next(new httpErrors.NotFound('This route does not exists'))
52
+ })
53
+ app.use(
54
+ (
55
+ error: httpErrors.HttpError,
56
+ req: Request,
57
+ res: Response,
58
+ next: NextFunction
59
+ ) => {
60
+ response({
61
+ error: true,
62
+ message: error.message,
63
+ res,
64
+ status: error.status
65
+ })
66
+ next()
67
+ }
68
+ )
69
+ }
70
+
71
+ export { applyRoutes }
72
+ `,
73
+ file: `${projectName}/src/network/router.ts`
74
+ },
75
+ server: {
76
+ content: `import express from 'express'
77
+ import mongoose from 'mongoose'
78
+ import cors from 'cors'
79
+ import pino, { HttpLogger } from 'express-pino-logger'
80
+
81
+ import { applyRoutes } from './router'
82
+
83
+ const PORT = (process.env.PORT as string) || 1996
84
+
85
+ class Server {
86
+ #app: express.Application
87
+ #connection: mongoose.Connection | undefined
88
+ #log: HttpLogger
89
+
90
+ constructor() {
91
+ this.#app = express()
92
+ this.#log = pino({
93
+ transport: {
94
+ target: 'pino-pretty',
95
+ options: {
96
+ colorize: true
97
+ }
98
+ }
99
+ })
100
+ this.#config()
101
+ }
102
+
103
+ #config() {
104
+ this.#app.use(cors())
105
+ this.#app.use(express.json())
106
+ this.#app.use(express.urlencoded({ extended: false }))
107
+ this.#app.use(
108
+ (
109
+ req: express.Request,
110
+ res: express.Response,
111
+ next: express.NextFunction
112
+ ) => {
113
+ res.header('Access-Control-Allow-Methods', 'GET, POST, PATCH, DELETE')
114
+ res.header('Access-Control-Allow-Origin', '*')
115
+ res.header(
116
+ 'Access-Control-Allow-Headers',
117
+ 'Authorization, Content-Type'
118
+ )
119
+ res.header('x-powered-by', 'Simba.js')
120
+ next()
121
+ }
122
+ )
123
+ applyRoutes(this.#app)
124
+ }
125
+
126
+ async #mongo(): Promise<void> {
127
+ this.#connection = mongoose.connection
128
+ const connection = {
129
+ keepAlive: true,
130
+ useNewUrlParser: true,
131
+ useUnifiedTopology: true
132
+ }
133
+ this.#connection.on('connected', () => {
134
+ this.#log.logger.info('Mongo connection established.')
135
+ })
136
+ this.#connection.on('reconnected', () => {
137
+ this.#log.logger.info('Mongo connection reestablished')
138
+ })
139
+ this.#connection.on('disconnected', () => {
140
+ this.#log.logger.info('Mongo connection disconnected')
141
+ this.#log.logger.info('Trying to reconnected to Mongo...')
142
+ setTimeout(() => {
143
+ mongoose.connect(process.env.MONGO_URI as string, {
144
+ ...connection,
145
+ connectTimeoutMS: 3000,
146
+ socketTimeoutMS: 3000
147
+ })
148
+ }, 3000)
149
+ })
150
+ this.#connection.on('close', () => {
151
+ this.#log.logger.info('Mongo connection closed')
152
+ })
153
+ this.#connection.on('error', (e: Error) => {
154
+ this.#log.logger.info('Mongo connection error:')
155
+ this.#log.logger.error(e)
156
+ })
157
+ await mongoose.connect(process.env.MONGO_URI as string, connection)
158
+ }
159
+
160
+ public start(): void {
161
+ this.#app.listen(PORT, () => {
162
+ this.#log.logger.info(\`Server running at port \${PORT}\`)
163
+ })
164
+
165
+ try {
166
+ this.#mongo()
167
+ } catch (e) {
168
+ this.#log.logger.error(e)
169
+ }
170
+ }
171
+ }
172
+
173
+ const server = new Server()
174
+
175
+ export { server as Server }
176
+ `,
177
+ file: `${projectName}/src/network/server.ts`
178
+ },
179
+ routes: {
180
+ home: {
181
+ content: `import { Response, Request, Router } from 'express'
182
+
183
+ import { response } from 'network/response'
184
+
185
+ const Home = Router()
186
+
187
+ Home.route('').get((req: Request, res: Response) => {
188
+ response({
189
+ error: false,
190
+ message: 'Welcome to your Express Backend!',
191
+ res,
192
+ status: 200
193
+ })
194
+ })
195
+
196
+ export { Home }
197
+ `,
198
+ file: `${projectName}/src/network/routes/home.ts`
199
+ },
200
+ index: {
201
+ content: `export * from './home'
202
+ export * from './user'
203
+ `,
204
+ file: `${projectName}/src/network/routes/index.ts`
205
+ },
206
+ user: {
207
+ content: `import { NextFunction, Router } from 'express'
208
+
209
+ import { response } from 'network/response'
210
+ import { UserService } from 'services'
211
+ import { idSchema, storeUserSchema, UserDTO } from 'schemas'
212
+ import { validatorCompiler } from './utils'
213
+
214
+ const User = Router()
215
+
216
+ User.route('/users')
217
+ .post(
218
+ validatorCompiler(storeUserSchema, 'body'),
219
+ async (
220
+ req: CustomRequest,
221
+ res: CustomResponse,
222
+ next: NextFunction
223
+ ): Promise<void> => {
224
+ try {
225
+ const {
226
+ body: { args }
227
+ } = req
228
+ const us = new UserService({ userDtoWithoutId: args })
229
+ const result = await us.process({ type: 'store' })
230
+
231
+ response({ error: false, message: result, res, status: 201 })
232
+ } catch (error) {
233
+ next(error)
234
+ }
235
+ }
236
+ )
237
+ .get(
238
+ async (
239
+ req: CustomRequest,
240
+ res: CustomResponse,
241
+ next: NextFunction
242
+ ): Promise<void> => {
243
+ try {
244
+ const us = new UserService()
245
+ const result = await us.process({ type: 'getAll' })
246
+
247
+ response({ error: false, message: result, res, status: 200 })
248
+ } catch (error) {
249
+ next(error)
250
+ }
251
+ }
252
+ )
253
+ .delete(
254
+ async (
255
+ req: CustomRequest,
256
+ res: CustomResponse,
257
+ next: NextFunction
258
+ ): Promise<void> => {
259
+ try {
260
+ const us = new UserService()
261
+ const result = await us.process({ type: 'deleteAll' })
262
+
263
+ response({ error: false, message: result, res, status: 200 })
264
+ } catch (error) {
265
+ next(error)
266
+ }
267
+ }
268
+ )
269
+
270
+ User.route('/user/:id')
271
+ .get(
272
+ validatorCompiler(idSchema, 'params'),
273
+ async (
274
+ req: CustomRequest,
275
+ res: CustomResponse,
276
+ next: NextFunction
277
+ ): Promise<void> => {
278
+ try {
279
+ const {
280
+ params: { id }
281
+ } = req
282
+ const us = new UserService({ id })
283
+ const result = await us.process({ type: 'getOne' })
284
+
285
+ response({ error: false, message: result, res, status: 200 })
286
+ } catch (error) {
287
+ next(error)
288
+ }
289
+ }
290
+ )
291
+ .patch(
292
+ validatorCompiler(idSchema, 'params'),
293
+ validatorCompiler(storeUserSchema, 'body'),
294
+ async (
295
+ req: CustomRequest,
296
+ res: CustomResponse,
297
+ next: NextFunction
298
+ ): Promise<void> => {
299
+ try {
300
+ const {
301
+ body: { args },
302
+ params: { id }
303
+ } = req
304
+ const userDto = {
305
+ id,
306
+ ...args
307
+ } as UserDTO
308
+ const us = new UserService({ userDto })
309
+ const result = await us.process({ type: 'update' })
310
+
311
+ response({ error: false, message: result, res, status: 200 })
312
+ } catch (error) {
313
+ next(error)
314
+ }
315
+ }
316
+ )
317
+ .delete(
318
+ validatorCompiler(idSchema, 'params'),
319
+ async (
320
+ req: CustomRequest,
321
+ res: CustomResponse,
322
+ next: NextFunction
323
+ ): Promise<void> => {
324
+ try {
325
+ const {
326
+ params: { id }
327
+ } = req
328
+ const us = new UserService({ id })
329
+ const result = await us.process({ type: 'delete' })
330
+
331
+ response({ error: false, message: result, res, status: 200 })
332
+ } catch (error) {
333
+ next(error)
334
+ }
335
+ }
336
+ )
337
+
338
+ export { User }
339
+ `,
340
+ file: `${projectName}/src/network/routes/user.ts`
341
+ },
342
+ utils: {
343
+ index: {
344
+ content: `import { NextFunction } from 'express'
345
+ import httpErrors from 'http-errors'
346
+ import { TObject, TProperties } from '@sinclair/typebox'
347
+ import Ajv from 'ajv'
348
+
349
+ const ajv = new Ajv({
350
+ removeAdditional: true,
351
+ useDefaults: true,
352
+ coerceTypes: true,
353
+ nullable: true
354
+ })
355
+
356
+ type Middleware = (
357
+ req: CustomRequest,
358
+ res: CustomResponse,
359
+ next: NextFunction
360
+ ) => void
361
+
362
+ const validatorCompiler = <T extends TProperties>(
363
+ schema: TObject<T>,
364
+ value: 'body' | 'params'
365
+ ): Middleware => {
366
+ return (req: CustomRequest, res: CustomResponse, next: NextFunction) => {
367
+ const validate = ajv.compile(schema)
368
+ const ok = validate(req[value])
369
+
370
+ if (!ok && validate.errors) {
371
+ const [error] = validate.errors
372
+ const errorMessage = \`\${error.dataPath.replace('.', '')} \${error.message}\`
373
+
374
+ return next(new httpErrors.UnprocessableEntity(errorMessage))
375
+ }
376
+
377
+ next()
378
+ }
379
+ }
380
+
381
+ export { validatorCompiler }
382
+ `,
383
+ file: `${projectName}/src/network/routes/utils/index.ts`
384
+ }
385
+ }
386
+ }
387
+ }
388
+
389
+ await Promise.all([
390
+ writeFile(network.response.file, network.response.content),
391
+ writeFile(network.router.file, network.router.content),
392
+ writeFile(network.server.file, network.server.content),
393
+ writeFile(network.routes.home.file, network.routes.home.content),
394
+ writeFile(network.routes.index.file, network.routes.index.content),
395
+ writeFile(network.routes.user.file, network.routes.user.content),
396
+ writeFile(
397
+ network.routes.utils.index.file,
398
+ network.routes.utils.index.content
399
+ )
400
+ ])
401
+ }
402
+
403
+ /**
404
+ * @param {Object} args
405
+ * @param {String} args.projectName
406
+ */
407
+ const expressGraphQLNetwork = async ({ projectName }) => {
408
+ const createFoldersCommand = `mkdir ${projectName}/src/network/routes/utils \
409
+ ${projectName}/src/graphQL/models \
410
+ ${projectName}/src/graphQL/models/User \
411
+ ${projectName}/src/graphQL/models/utils \
412
+ ${projectName}/src/graphQL/models/utils/messages`
413
+
414
+ if (platform() === 'win32')
415
+ await exec(createFoldersCommand.replaceAll('/', '\\'))
416
+ else await exec(createFoldersCommand)
417
+
418
+ const graphQL = {
419
+ index: {
420
+ content: "export * from './models'\n",
421
+ file: `${projectName}/src/graphQL/index.ts`
422
+ },
423
+ models: {
424
+ User: {
425
+ index: {
426
+ content: `import { makeExecutableSchema } from '@graphql-tools/schema'
427
+
428
+ import { User as UserTD } from './typeDefs'
429
+ import { Query } from './queriesResolver'
430
+ import { Mutation } from './mutationsResolver'
431
+
432
+ const resolvers = {
433
+ Query,
434
+ Mutation
435
+ }
436
+
437
+ const User = makeExecutableSchema({
438
+ typeDefs: UserTD,
439
+ resolvers
440
+ })
441
+
442
+ export { User }
443
+ `,
444
+ file: `${projectName}/src/graphQL/models/User/index.ts`
445
+ },
446
+ mutations: {
447
+ content: `import { ApolloError } from 'apollo-server-core'
448
+
449
+ import { store, remove, update } from 'database'
450
+ import { UserDTO } from 'schemas'
451
+ import { EFU, MFU, GE, errorHandling } from '../utils'
452
+
453
+ const storeUser = async (
454
+ { user }: { user: UserDTO },
455
+ { log }: Context
456
+ ): Promise<UserDTO> => {
457
+ try {
458
+ const result = await store(user)
459
+
460
+ return result
461
+ } catch (e) {
462
+ return errorHandling({
463
+ e,
464
+ message: GE.INTERNAL_SERVER_ERROR,
465
+ code: 'INTERNAL_SERVER_ERROR',
466
+ log
467
+ })
468
+ }
469
+ }
470
+
471
+ const deleteAllUsers = async ({ log }: Context): Promise<string> => {
472
+ try {
473
+ const usersDeleted = (await remove()) as number
474
+
475
+ if (usersDeleted >= 1) return MFU.ALL_USERS_DELETED
476
+
477
+ if (usersDeleted === 0)
478
+ throw new ApolloError(EFU.NOTHING_TO_DELETE, 'NOTHING_TO_DELETE')
479
+
480
+ throw new ApolloError(GE.INTERNAL_SERVER_ERROR, 'INTERNAL_SERVER_ERROR')
481
+ } catch (e) {
482
+ return errorHandling({
483
+ e,
484
+ message: GE.INTERNAL_SERVER_ERROR,
485
+ code: 'INTERNAL_SERVER_ERROR',
486
+ log
487
+ })
488
+ }
489
+ }
490
+
491
+ const updateUser = async (
492
+ { user }: { user: UserDTO },
493
+ { log }: Context
494
+ ): Promise<UserDTO> => {
495
+ try {
496
+ const updatedUser = await update(user)
497
+
498
+ if (!updatedUser) throw new ApolloError(EFU.NOT_FOUND, 'NOT_FOUND')
499
+
500
+ return updatedUser
501
+ } catch (e) {
502
+ return errorHandling({
503
+ e,
504
+ message: GE.INTERNAL_SERVER_ERROR,
505
+ code: 'INTERNAL_SERVER_ERROR',
506
+ log
507
+ })
508
+ }
509
+ }
510
+
511
+ const deleteUser = async (
512
+ { id }: { id: string },
513
+ { log }: Context
514
+ ): Promise<string> => {
515
+ try {
516
+ const deletedUser = await remove(id)
517
+
518
+ if (!deletedUser) throw new ApolloError(EFU.NOT_FOUND, 'NOT_FOUND')
519
+
520
+ return MFU.USER_DELETED
521
+ } catch (e) {
522
+ return errorHandling({
523
+ e,
524
+ message: GE.INTERNAL_SERVER_ERROR,
525
+ code: 'INTERNAL_SERVER_ERROR',
526
+ log
527
+ })
528
+ }
529
+ }
530
+
531
+ export { storeUser, deleteAllUsers, updateUser, deleteUser }
532
+ `,
533
+ file: `${projectName}/src/graphQL/models/User/mutations.ts`
534
+ },
535
+ mutationsResolver: {
536
+ content: `import { DefinedError } from 'ajv'
537
+ import { ApolloError } from 'apollo-server-core'
538
+
539
+ import { ajv, idSchema, UserDTO } from 'schemas'
540
+ import { storeUserSchema, updateUserSchema } from './schemas'
541
+ import { storeUser, updateUser, deleteUser, deleteAllUsers } from './mutations'
542
+ import { errorHandling, GE } from '../utils'
543
+
544
+ const Mutation = {
545
+ storeUser: async (
546
+ parent: unknown,
547
+ { user }: { user: UserDTO },
548
+ context: Context
549
+ ): Promise<UserDTO> => {
550
+ const { log } = context
551
+ const validate = ajv.compile(storeUserSchema)
552
+
553
+ try {
554
+ const ok = validate(user)
555
+
556
+ if (!ok)
557
+ throw new ApolloError(
558
+ \`\${(validate.errors as DefinedError[])[0].instancePath.replace(
559
+ '/',
560
+ ''
561
+ )} \${(validate.errors as DefinedError[])[0].message as string}\`,
562
+ 'UNPROCESSABLE_ENTITY'
563
+ )
564
+
565
+ return await storeUser({ user }, context)
566
+ } catch (e) {
567
+ log.error(validate.errors)
568
+
569
+ return errorHandling({
570
+ e,
571
+ message: GE.INTERNAL_SERVER_ERROR,
572
+ code: 'INTERNAL_SERVER_ERROR',
573
+ log
574
+ })
575
+ }
576
+ },
577
+ updateUser: async (
578
+ parent: unknown,
579
+ { user }: { user: UserDTO },
580
+ context: Context
581
+ ): Promise<UserDTO> => {
582
+ const validate = ajv.compile(updateUserSchema)
583
+ const { log } = context
584
+
585
+ try {
586
+ const ok = validate(user)
587
+
588
+ if (!ok)
589
+ throw new ApolloError(
590
+ \`\${(validate.errors as DefinedError[])[0].instancePath.replace(
591
+ '/',
592
+ ''
593
+ )} \${(validate.errors as DefinedError[])[0].message as string}\`,
594
+ 'UNPROCESSABLE_ENTITY'
595
+ )
596
+
597
+ return await updateUser({ user }, context)
598
+ } catch (e) {
599
+ log.error(validate.errors)
600
+
601
+ return errorHandling({
602
+ e,
603
+ message: GE.INTERNAL_SERVER_ERROR,
604
+ code: 'INTERNAL_SERVER_ERROR',
605
+ log
606
+ })
607
+ }
608
+ },
609
+ deleteUser: async (
610
+ parent: unknown,
611
+ { id }: { id: string },
612
+ context: Context
613
+ ): Promise<string> => {
614
+ const validate = ajv.compile(idSchema)
615
+ const { log } = context
616
+
617
+ try {
618
+ const ok = validate({ id })
619
+
620
+ if (!ok)
621
+ throw new ApolloError(
622
+ \`\${(validate.errors as DefinedError[])[0].instancePath.replace(
623
+ '/',
624
+ ''
625
+ )} \${(validate.errors as DefinedError[])[0].message as string}\`,
626
+ 'UNPROCESSABLE_ENTITY'
627
+ )
628
+
629
+ return await deleteUser({ id }, context)
630
+ } catch (e) {
631
+ log.error(validate.errors)
632
+
633
+ return errorHandling({
634
+ e,
635
+ message: GE.INTERNAL_SERVER_ERROR,
636
+ code: 'INTERNAL_SERVER_ERROR',
637
+ log
638
+ })
639
+ }
640
+ },
641
+ deleteAllUsers: async (
642
+ parent: unknown,
643
+ args: unknown,
644
+ context: Context
645
+ ): Promise<string> => {
646
+ const { log } = context
647
+ try {
648
+ return await deleteAllUsers(context)
649
+ } catch (e) {
650
+ return errorHandling({
651
+ e,
652
+ message: GE.INTERNAL_SERVER_ERROR,
653
+ code: 'INTERNAL_SERVER_ERROR',
654
+ log
655
+ })
656
+ }
657
+ }
658
+ }
659
+
660
+ export { Mutation }
661
+ `,
662
+ file: `${projectName}/src/graphQL/models/User/mutationsResolver.ts`
663
+ },
664
+ queries: {
665
+ content: `import { ApolloError } from 'apollo-server-core'
666
+
667
+ import { get } from 'database'
668
+ import { UserDTO } from 'schemas'
669
+ import { EFU, GE, errorHandling } from '../utils'
670
+
671
+ const getUsers = async (
672
+ parent: unknown,
673
+ args: unknown,
674
+ { log }: Context
675
+ ): Promise<UserDTO[]> => {
676
+ try {
677
+ const users = (await get()) as UserDTO[]
678
+
679
+ return users
680
+ } catch (e) {
681
+ return errorHandling({
682
+ e,
683
+ message: GE.INTERNAL_SERVER_ERROR,
684
+ code: 'INTERNAL_SERVER_ERROR',
685
+ log
686
+ })
687
+ }
688
+ }
689
+
690
+ const getUser = async (
691
+ { id }: { id: string },
692
+ { log }: Context
693
+ ): Promise<UserDTO> => {
694
+ try {
695
+ const user = (await get(id as string)) as UserDTO | null
696
+
697
+ if (!user) throw new ApolloError(EFU.NOT_FOUND, 'NOT_FOUND')
698
+
699
+ return user
700
+ } catch (e) {
701
+ return errorHandling({
702
+ e,
703
+ message: GE.INTERNAL_SERVER_ERROR,
704
+ code: 'INTERNAL_SERVER_ERROR',
705
+ log
706
+ })
707
+ }
708
+ }
709
+
710
+ export { getUsers, getUser }
711
+ `,
712
+ file: `${projectName}/src/graphQL/models/User/queries.ts`
713
+ },
714
+ queriesResolver: {
715
+ content: `import { DefinedError } from 'ajv'
716
+ import { ApolloError } from 'apollo-server-core'
717
+
718
+ import { ajv, idSchema, UserDTO } from 'schemas'
719
+ import { getUser, getUsers } from './queries'
720
+ import { errorHandling, GE } from '../utils'
721
+
722
+ const Query = {
723
+ getUser: async (
724
+ parent: unknown,
725
+ { id }: { id: string },
726
+ context: Context
727
+ ): Promise<UserDTO> => {
728
+ const { log } = context
729
+ const validate = ajv.compile(idSchema)
730
+
731
+ try {
732
+ const ok = validate({ id })
733
+
734
+ if (!ok)
735
+ throw new ApolloError(
736
+ \`id \${(validate.errors as DefinedError[])[0].message as string}\`,
737
+ 'UNPROCESSABLE_ENTITY'
738
+ )
739
+
740
+ return await getUser({ id }, context)
741
+ } catch (e) {
742
+ log.error(validate.errors)
743
+
744
+ return errorHandling({
745
+ e,
746
+ message: GE.INTERNAL_SERVER_ERROR,
747
+ code: 'INTERNAL_SERVER_ERROR',
748
+ log
749
+ })
750
+ }
751
+ },
752
+ getUsers
753
+ }
754
+
755
+ export { Query }
756
+ `,
757
+ file: `${projectName}/src/graphQL/models/User/queriesResolver.ts`
758
+ },
759
+ schemas: {
760
+ content: `import { Type } from '@sinclair/typebox'
761
+
762
+ const updateUserSchema = Type.Object(
763
+ {
764
+ id: Type.String({ minLength: 24, maxLength: 24 }),
765
+ lastName: Type.String(),
766
+ name: Type.String()
767
+ },
768
+ { additionalProperties: false }
769
+ )
770
+
771
+ const storeUserSchema = Type.Object(
772
+ {
773
+ lastName: Type.String({ minLength: 1 }),
774
+ name: Type.String({ minLength: 1 })
775
+ },
776
+ { additionalProperties: false }
777
+ )
778
+
779
+ export { updateUserSchema, storeUserSchema }
780
+ `,
781
+ file: `${projectName}/src/graphQL/models/User/schemas.ts`
782
+ },
783
+ typeDefs: {
784
+ content: `import { gql } from 'apollo-server-core'
785
+
786
+ const User = gql\`
787
+ type User {
788
+ id: ID!
789
+ name: String!
790
+ lastName: String!
791
+ updatedAt: String!
792
+ }
793
+
794
+ type Query {
795
+ getUsers: [User!]!
796
+ getUser(id: ID!): User!
797
+ }
798
+
799
+ input StoreUserInput {
800
+ lastName: String!
801
+ name: String!
802
+ }
803
+
804
+ input UpdateUserInput {
805
+ id: String!
806
+ lastName: String!
807
+ name: String!
808
+ }
809
+
810
+ type Mutation {
811
+ storeUser(user: StoreUserInput!): User!
812
+ deleteAllUsers: String
813
+ updateUser(user: UpdateUserInput!): User!
814
+ deleteUser(id: ID!): String
815
+ }
816
+ \`
817
+
818
+ export { User }
819
+ `,
820
+ file: `${projectName}/src/graphQL/models/User/typeDefs.ts`
821
+ }
822
+ },
823
+ utils: {
824
+ messages: {
825
+ index: {
826
+ content: `enum GenericErrors {
827
+ INTERNAL_SERVER_ERROR = 'Something went wrong'
828
+ }
829
+
830
+ export { GenericErrors as GE }
831
+ export * from './user'
832
+ `,
833
+ file: `${projectName}/src/graphQL/models/utils/messages/index.ts`
834
+ },
835
+ user: {
836
+ content: `enum ErrorForUser {
837
+ NOT_FOUND = 'The requested user does not exists',
838
+ NOTHING_TO_DELETE = 'There is no user to be deleted'
839
+ }
840
+
841
+ enum MessageForUser {
842
+ ALL_USERS_DELETED = 'All the users were deleted successfully',
843
+ USER_DELETED = 'The requested user was successfully deleted'
844
+ }
845
+
846
+ export { ErrorForUser as EFU, MessageForUser as MFU }
847
+ `,
848
+ file: `${projectName}/src/graphQL/models/utils/messages/user.ts`
849
+ }
850
+ },
851
+ index: {
852
+ content: `import { ApolloError } from 'apollo-server-core'
853
+ import { FastifyLoggerInstance } from 'fastify'
854
+
855
+ const errorHandling = ({
856
+ e,
857
+ message,
858
+ code,
859
+ log
860
+ }: {
861
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
862
+ e: any
863
+ message: string
864
+ code: string
865
+ log: FastifyLoggerInstance
866
+ }): never => {
867
+ log.error(e)
868
+
869
+ if (e instanceof ApolloError) throw e
870
+
871
+ throw new ApolloError(message ?? e.message, code)
872
+ }
873
+
874
+ export { errorHandling }
875
+ export * from './messages'
876
+ `,
877
+ file: `${projectName}/src/graphQL/models/utils/index.ts`
878
+ }
879
+ },
880
+ index: {
881
+ content: `import { mergeSchemas } from '@graphql-tools/schema'
882
+
883
+ import { User } from './User'
884
+
885
+ const mergedSchema = mergeSchemas({
886
+ schemas: [User]
887
+ })
888
+
889
+ export { mergedSchema }
890
+ `,
891
+ file: `${projectName}/src/graphQL/models/index.ts`
892
+ }
893
+ }
894
+ }
895
+
896
+ const network = {
897
+ response: {
898
+ content: `interface ResponseProps {
899
+ error: boolean
900
+ message: unknown
901
+ res: CustomResponse
902
+ status: number
903
+ }
904
+
905
+ const response = ({ error, message, res, status }: ResponseProps): void => {
906
+ res.status(status).send({ error, message })
907
+ }
908
+
909
+ export { response }
910
+ `,
911
+ file: `${projectName}/src/network/response.ts`
912
+ },
913
+ router: {
914
+ content: `import { Application, Response, Request, Router, NextFunction } from 'express'
915
+ import swaggerUi from 'swagger-ui-express'
916
+ import httpErrors from 'http-errors'
917
+
918
+ import { response } from './response'
919
+ import { Home } from './routes'
920
+ import { docs } from 'utils'
921
+
922
+ const routers: Router[] = []
923
+ const applyRoutes = (app: Application): void => {
924
+ app.use('/', Home)
925
+ app.use('/api/docs', swaggerUi.serve, swaggerUi.setup(docs))
926
+ routers.forEach((router: Router): Application => app.use('/api', router))
927
+
928
+ // Handling 404 error
929
+ app.use((req, res, next) => {
930
+ next(new httpErrors.NotFound('This route does not exists'))
931
+ })
932
+ app.use(
933
+ (
934
+ error: httpErrors.HttpError,
935
+ req: Request,
936
+ res: Response,
937
+ next: NextFunction
938
+ ) => {
939
+ response({
940
+ error: true,
941
+ message: error.message,
942
+ res,
943
+ status: error.status
944
+ })
945
+ next()
946
+ }
947
+ )
948
+ }
949
+
950
+ export { applyRoutes }
951
+ `,
952
+ file: `${projectName}/src/network/router.ts`
953
+ },
954
+ server: {
955
+ content: `import http from 'http'
956
+ import express from 'express'
957
+ import mongoose from 'mongoose'
958
+ import cors from 'cors'
959
+ import pino, { HttpLogger } from 'express-pino-logger'
960
+ import { ApolloServer } from 'apollo-server-express'
961
+ import {
962
+ ApolloServerPluginDrainHttpServer,
963
+ ApolloServerPluginLandingPageDisabled,
964
+ ApolloServerPluginLandingPageGraphQLPlayground
965
+ } from 'apollo-server-core'
966
+
967
+ import { mergedSchema as schema } from 'graphQL'
968
+ import { applyRoutes } from './router'
969
+
970
+ const PORT = (process.env.PORT as string) || 1996
971
+
972
+ class Server {
973
+ #app: express.Application
974
+ #connection: mongoose.Connection | undefined
975
+ #httpServer: http.Server
976
+ #log: HttpLogger
977
+
978
+ constructor() {
979
+ this.#app = express()
980
+ this.#httpServer = http.createServer(this.#app)
981
+ this.#log = pino({
982
+ transport: {
983
+ target: 'pino-pretty',
984
+ options: {
985
+ colorize: true
986
+ }
987
+ }
988
+ })
989
+ this.#config()
990
+ }
991
+
992
+ #config() {
993
+ this.#app.use(cors())
994
+ this.#app.use(this.#log)
995
+ this.#app.use(express.json())
996
+ this.#app.use(express.urlencoded({ extended: false }))
997
+ this.#app.use(
998
+ (
999
+ req: express.Request,
1000
+ res: express.Response,
1001
+ next: express.NextFunction
1002
+ ) => {
1003
+ res.header('Access-Control-Allow-Methods', 'GET, POST, PATCH, DELETE')
1004
+ res.header('Access-Control-Allow-Origin', '*')
1005
+ res.header(
1006
+ 'Access-Control-Allow-Headers',
1007
+ 'Authorization, Content-Type'
1008
+ )
1009
+ res.header('x-powered-by', 'Simba.js')
1010
+ next()
1011
+ }
1012
+ )
1013
+ }
1014
+
1015
+ async #mongo(): Promise<void> {
1016
+ this.#connection = mongoose.connection
1017
+ const connection = {
1018
+ keepAlive: true,
1019
+ useNewUrlParser: true,
1020
+ useUnifiedTopology: true
1021
+ }
1022
+ this.#connection.on('connected', () => {
1023
+ this.#log.logger.info('Mongo connection established.')
1024
+ })
1025
+ this.#connection.on('reconnected', () => {
1026
+ this.#log.logger.info('Mongo connection reestablished')
1027
+ })
1028
+ this.#connection.on('disconnected', () => {
1029
+ this.#log.logger.info('Mongo connection disconnected')
1030
+ this.#log.logger.info('Trying to reconnected to Mongo...')
1031
+ setTimeout(() => {
1032
+ mongoose.connect(process.env.MONGO_URI as string, {
1033
+ ...connection,
1034
+ connectTimeoutMS: 3000,
1035
+ socketTimeoutMS: 3000
1036
+ })
1037
+ }, 3000)
1038
+ })
1039
+ this.#connection.on('close', () => {
1040
+ this.#log.logger.info('Mongo connection closed')
1041
+ })
1042
+ this.#connection.on('error', (e: Error) => {
1043
+ this.#log.logger.info('Mongo connection error:')
1044
+ this.#log.logger.error(e)
1045
+ })
1046
+ await mongoose.connect(process.env.MONGO_URI as string, connection)
1047
+ }
1048
+
1049
+ public async start(): Promise<void> {
1050
+ const server = new ApolloServer({
1051
+ schema,
1052
+ plugins: [
1053
+ ApolloServerPluginDrainHttpServer({ httpServer: this.#httpServer }),
1054
+ process.env.NODE_ENV === 'production'
1055
+ ? ApolloServerPluginLandingPageDisabled()
1056
+ : ApolloServerPluginLandingPageGraphQLPlayground()
1057
+ ],
1058
+ context: (): Context => ({
1059
+ log: this.#log.logger
1060
+ })
1061
+ })
1062
+
1063
+ try {
1064
+ await server.start()
1065
+ server.applyMiddleware({
1066
+ app: this.#app,
1067
+ path: '/api'
1068
+ })
1069
+ applyRoutes(this.#app)
1070
+ await this.#mongo()
1071
+ this.#httpServer.listen(PORT, () => {
1072
+ this.#log.logger.info(\`Server listening at port: \${PORT}\`)
1073
+ this.#log.logger.info(
1074
+ \`GraphQL server listening at: http://localhost:\${PORT}\${server.graphqlPath}\`
1075
+ )
1076
+ })
1077
+ } catch (e) {
1078
+ console.error(e)
1079
+ }
1080
+ }
1081
+ }
1082
+
1083
+ const server = new Server()
1084
+
1085
+ export { server as Server }
1086
+ `,
1087
+ file: `${projectName}/src/network/server.ts`
1088
+ },
1089
+ routes: {
1090
+ home: {
1091
+ content: `import { Response, Request, Router } from 'express'
1092
+
1093
+ import { response } from 'network/response'
1094
+
1095
+ const Home = Router()
1096
+
1097
+ Home.route('').get((req: Request, res: Response) => {
1098
+ response({
1099
+ error: false,
1100
+ message: 'Welcome to your GraphQL Express Backend!',
1101
+ res,
1102
+ status: 200
1103
+ })
1104
+ })
1105
+
1106
+ export { Home }
1107
+ `,
1108
+ file: `${projectName}/src/network/routes/home.ts`
1109
+ },
1110
+ index: {
1111
+ content: "export * from './home'\n",
1112
+ file: `${projectName}/src/network/routes/index.ts`
1113
+ },
1114
+ utils: {
1115
+ index: {
1116
+ content: `import { NextFunction } from 'express'
1117
+ import httpErrors from 'http-errors'
1118
+ import { TObject, TProperties } from '@sinclair/typebox'
1119
+ import Ajv from 'ajv'
1120
+
1121
+ const ajv = new Ajv({
1122
+ removeAdditional: true,
1123
+ useDefaults: true,
1124
+ coerceTypes: true,
1125
+ nullable: true
1126
+ })
1127
+
1128
+ type Middleware = (
1129
+ req: CustomRequest,
1130
+ res: CustomResponse,
1131
+ next: NextFunction
1132
+ ) => void
1133
+
1134
+ const validatorCompiler = <T extends TProperties>(
1135
+ schema: TObject<T>,
1136
+ value: 'body' | 'params'
1137
+ ): Middleware => {
1138
+ return (req: CustomRequest, res: CustomResponse, next: NextFunction) => {
1139
+ const validate = ajv.compile(schema)
1140
+ const ok = validate(req[value])
1141
+
1142
+ if (!ok && validate.errors) {
1143
+ const [error] = validate.errors
1144
+ const errorMessage = \`\${error.dataPath.replace('.', '')} \${error.message}\`
1145
+
1146
+ return next(new httpErrors.UnprocessableEntity(errorMessage))
1147
+ }
1148
+
1149
+ next()
1150
+ }
1151
+ }
1152
+
1153
+ export { validatorCompiler }
1154
+ `,
1155
+ file: `${projectName}/src/network/routes/utils/index.ts`
1156
+ }
1157
+ }
1158
+ }
1159
+ }
1160
+
1161
+ await Promise.all([
1162
+ writeFile(graphQL.index.file, graphQL.index.content),
1163
+ writeFile(
1164
+ graphQL.models.User.index.file,
1165
+ graphQL.models.User.index.content
1166
+ ),
1167
+ writeFile(
1168
+ graphQL.models.User.mutations.file,
1169
+ graphQL.models.User.mutations.content
1170
+ ),
1171
+ writeFile(
1172
+ graphQL.models.User.mutationsResolver.file,
1173
+ graphQL.models.User.mutationsResolver.content
1174
+ ),
1175
+ writeFile(
1176
+ graphQL.models.User.queries.file,
1177
+ graphQL.models.User.queries.content
1178
+ ),
1179
+ writeFile(
1180
+ graphQL.models.User.queriesResolver.file,
1181
+ graphQL.models.User.queriesResolver.content
1182
+ ),
1183
+ writeFile(
1184
+ graphQL.models.User.schemas.file,
1185
+ graphQL.models.User.schemas.content
1186
+ ),
1187
+ writeFile(
1188
+ graphQL.models.User.typeDefs.file,
1189
+ graphQL.models.User.typeDefs.content
1190
+ ),
1191
+ writeFile(
1192
+ graphQL.models.utils.messages.index.file,
1193
+ graphQL.models.utils.messages.index.content
1194
+ ),
1195
+ writeFile(
1196
+ graphQL.models.utils.messages.user.file,
1197
+ graphQL.models.utils.messages.user.content
1198
+ ),
1199
+ writeFile(
1200
+ graphQL.models.utils.index.file,
1201
+ graphQL.models.utils.index.content
1202
+ ),
1203
+ writeFile(graphQL.models.index.file, graphQL.models.index.content),
1204
+ writeFile(network.response.file, network.response.content),
1205
+ writeFile(network.router.file, network.router.content),
1206
+ writeFile(network.server.file, network.server.content),
1207
+ writeFile(network.routes.home.file, network.routes.home.content),
1208
+ writeFile(network.routes.index.file, network.routes.index.content),
1209
+ writeFile(
1210
+ network.routes.utils.index.file,
1211
+ network.routes.utils.index.content
1212
+ )
1213
+ ])
1214
+ }
1215
+
1216
+ /**
1217
+ * @param {Object} args
1218
+ * @param {String} args.projectName
1219
+ */
1220
+ const fastifyRestNetwork = async ({ projectName }) => {
1221
+ const createFoldersCommand = `mkdir ${projectName}/src/network/utils`
1222
+
1223
+ if (platform() === 'win32')
1224
+ await exec(createFoldersCommand.replaceAll('/', '\\'))
1225
+ else await exec(createFoldersCommand)
1226
+
1227
+ const network = {
1228
+ response: {
1229
+ content: `import { FastifyReply } from 'fastify'
1230
+
1231
+ const response = ({
1232
+ error,
1233
+ message,
1234
+ reply,
1235
+ status
1236
+ }: {
1237
+ error: boolean
1238
+ message: unknown
1239
+ reply: FastifyReply
1240
+ status: number
1241
+ }): void => {
1242
+ reply.code(status).send({ error, message })
1243
+ }
1244
+
1245
+ export { response }
1246
+ `,
1247
+ file: `${projectName}/src/network/response.ts`
1248
+ },
1249
+ router: {
1250
+ content: `import { FastifyInstance } from 'fastify'
1251
+ import { HttpError } from 'http-errors'
1252
+
1253
+ import { response } from './response'
1254
+ import { Home, User, Docs } from './routes'
1255
+
1256
+ const routers = [Docs, User]
1257
+ const applyRoutes = (app: FastifyInstance): void => {
1258
+ Home(app)
1259
+ routers.forEach(router => router(app))
1260
+
1261
+ // Handling 404 error
1262
+ app.setNotFoundHandler((request, reply) => {
1263
+ response({
1264
+ error: true,
1265
+ message: 'This route does not exists',
1266
+ reply,
1267
+ status: 404
1268
+ })
1269
+ })
1270
+ app.setErrorHandler<HttpError>((error, request, reply) => {
1271
+ response({
1272
+ error: true,
1273
+ message: error.message,
1274
+ reply,
1275
+ status: error.status ?? 500
1276
+ })
1277
+ })
1278
+ }
1279
+
1280
+ export { applyRoutes }
1281
+ `,
1282
+ file: `${projectName}/src/network/router.ts`
1283
+ },
1284
+ server: {
1285
+ content: `import fastify, { FastifyInstance } from 'fastify'
1286
+ import mongoose from 'mongoose'
1287
+
1288
+ import { applyRoutes } from './router'
1289
+ import { validatorCompiler } from './utils'
1290
+
1291
+ const PORT = process.env.PORT ?? 1996
1292
+
1293
+ class Server {
1294
+ #app: FastifyInstance
1295
+ #connection: mongoose.Connection | undefined
1296
+
1297
+ constructor() {
1298
+ this.#app = fastify({ logger: { prettyPrint: true } })
1299
+ this.#config()
1300
+ }
1301
+
1302
+ #config() {
1303
+ this.#app.register(require('fastify-cors'), {})
1304
+ this.#app.addHook('preHandler', (req, reply, done) => {
1305
+ reply.header('Access-Control-Allow-Methods', 'GET, POST, PATCH, DELETE')
1306
+ reply.header('Access-Control-Allow-Origin', '*')
1307
+ reply.header(
1308
+ 'Access-Control-Allow-Headers',
1309
+ 'Authorization, Content-Type'
1310
+ )
1311
+ reply.header('x-powered-by', 'Simba.js')
1312
+ done()
1313
+ })
1314
+ this.#app.setValidatorCompiler(validatorCompiler)
1315
+ applyRoutes(this.#app)
1316
+ }
1317
+
1318
+ async #mongo(): Promise<void> {
1319
+ this.#connection = mongoose.connection
1320
+ const connection = {
1321
+ keepAlive: true,
1322
+ useNewUrlParser: true,
1323
+ useUnifiedTopology: true
1324
+ }
1325
+ this.#connection.on('connected', () => {
1326
+ this.#app.log.info('Mongo connection established.')
1327
+ })
1328
+ this.#connection.on('reconnected', () => {
1329
+ this.#app.log.info('Mongo connection reestablished')
1330
+ })
1331
+ this.#connection.on('disconnected', () => {
1332
+ this.#app.log.info('Mongo connection disconnected')
1333
+ this.#app.log.info('Trying to reconnected to Mongo...')
1334
+ setTimeout(() => {
1335
+ mongoose.connect(process.env.MONGO_URI as string, {
1336
+ ...connection,
1337
+ connectTimeoutMS: 3000,
1338
+ socketTimeoutMS: 3000
1339
+ })
1340
+ }, 3000)
1341
+ })
1342
+ this.#connection.on('close', () => {
1343
+ this.#app.log.info('Mongo connection closed')
1344
+ })
1345
+ this.#connection.on('error', (e: Error) => {
1346
+ this.#app.log.info('Mongo connection error:')
1347
+ this.#app.log.error(e)
1348
+ })
1349
+ await mongoose.connect(process.env.MONGO_URI as string, connection)
1350
+ }
1351
+
1352
+ public async start(): Promise<void> {
1353
+ try {
1354
+ await this.#app.listen(PORT)
1355
+ this.#mongo()
1356
+ } catch (e) {
1357
+ console.error(e)
1358
+ }
1359
+ }
1360
+ }
1361
+
1362
+ const server = new Server()
1363
+
1364
+ export { server as Server }
1365
+ `,
1366
+ file: `${projectName}/src/network/server.ts`
1367
+ },
1368
+ routes: {
1369
+ docs: {
1370
+ content: `import { FastifyInstance } from 'fastify'
1371
+ import fastifySwagger from 'fastify-swagger'
1372
+
1373
+ const Docs = (app: FastifyInstance, prefix = '/api'): void => {
1374
+ app.register(fastifySwagger, {
1375
+ routePrefix: \`\${prefix}/docs\`,
1376
+ openapi: {
1377
+ info: {
1378
+ title: 'Test swagger',
1379
+ description: 'Testing the Fastify swagger API',
1380
+ version: '0.1.0',
1381
+ contact: {
1382
+ email: 'sluzquinosa@uni.pe'
1383
+ },
1384
+ license: {
1385
+ name: 'MIT',
1386
+ url: 'https://opensource.org/licenses/MIT'
1387
+ }
1388
+ },
1389
+ servers: [
1390
+ {
1391
+ url: 'http://localhost:1996/api',
1392
+ description: 'test-fastify local API'
1393
+ }
1394
+ ],
1395
+ tags: [
1396
+ {
1397
+ name: 'user',
1398
+ description: 'User related endpoints'
1399
+ }
1400
+ ]
1401
+ },
1402
+ exposeRoute: true
1403
+ })
1404
+ }
1405
+
1406
+ export { Docs }
1407
+ `,
1408
+ file: `${projectName}/src/network/routes/docs.ts`
1409
+ },
1410
+ home: {
1411
+ content: `import { FastifyInstance } from 'fastify'
1412
+ import { response } from 'network/response'
1413
+
1414
+ const Home = (app: FastifyInstance, prefix = '/'): void => {
1415
+ app.get(\`\${prefix}\`, (request, reply) => {
1416
+ response({
1417
+ error: false,
1418
+ message: 'Welcome to your Fastify Backend!',
1419
+ reply,
1420
+ status: 200
1421
+ })
1422
+ })
1423
+ }
1424
+
1425
+ export { Home }
1426
+ `,
1427
+ file: `${projectName}/src/network/routes/home.ts`
1428
+ },
1429
+ index: {
1430
+ content: `export * from './home'
1431
+ export * from './user'
1432
+ export * from './docs'
1433
+ `,
1434
+ file: `${projectName}/src/network/routes/index.ts`
1435
+ },
1436
+ user: {
1437
+ content: `import { FastifyInstance } from 'fastify'
1438
+ import { Type } from '@sinclair/typebox'
1439
+
1440
+ import { response } from 'network/response'
1441
+ import {
1442
+ userDto,
1443
+ idSchema,
1444
+ IdSchema,
1445
+ storeUserSchema,
1446
+ StoreUser
1447
+ } from 'schemas'
1448
+ import { UserService } from 'services'
1449
+
1450
+ const User = (app: FastifyInstance, prefix = '/api'): void => {
1451
+ app
1452
+ .post<{ Body: StoreUser }>(
1453
+ \`\${prefix}/users\`,
1454
+ {
1455
+ schema: {
1456
+ body: storeUserSchema,
1457
+ response: {
1458
+ 200: {
1459
+ error: {
1460
+ type: 'boolean'
1461
+ },
1462
+ message: userDto
1463
+ }
1464
+ },
1465
+ tags: ['user']
1466
+ }
1467
+ },
1468
+ async (request, reply) => {
1469
+ const {
1470
+ body: {
1471
+ args: { lastName, name }
1472
+ }
1473
+ } = request
1474
+ const us = new UserService({
1475
+ userDtoWithoutId: { lastName, name }
1476
+ })
1477
+ const user = await us.process({ type: 'store' })
1478
+
1479
+ response({
1480
+ error: false,
1481
+ message: user,
1482
+ reply,
1483
+ status: 201
1484
+ })
1485
+ }
1486
+ )
1487
+ .get(
1488
+ \`\${prefix}/users\`,
1489
+ {
1490
+ schema: {
1491
+ response: {
1492
+ 200: {
1493
+ error: {
1494
+ type: 'boolean'
1495
+ },
1496
+ message: Type.Array(userDto)
1497
+ }
1498
+ },
1499
+ tags: ['user']
1500
+ }
1501
+ },
1502
+ async (request, reply) => {
1503
+ const us = new UserService()
1504
+ const users = await us.process({ type: 'getAll' })
1505
+
1506
+ response({
1507
+ error: false,
1508
+ message: users,
1509
+ reply,
1510
+ status: 200
1511
+ })
1512
+ }
1513
+ )
1514
+ .delete(
1515
+ \`\${prefix}/users\`,
1516
+ {
1517
+ schema: {
1518
+ response: {
1519
+ 200: {
1520
+ error: {
1521
+ type: 'boolean'
1522
+ },
1523
+ message: {
1524
+ type: 'string'
1525
+ }
1526
+ }
1527
+ },
1528
+ tags: ['user']
1529
+ }
1530
+ },
1531
+ async (request, reply) => {
1532
+ const us = new UserService()
1533
+ const result = await us.process({ type: 'deleteAll' })
1534
+
1535
+ response({
1536
+ error: false,
1537
+ message: result,
1538
+ reply,
1539
+ status: 200
1540
+ })
1541
+ }
1542
+ )
1543
+ .get<{ Params: IdSchema }>(
1544
+ \`\${prefix}/user/:id\`,
1545
+ {
1546
+ schema: {
1547
+ params: idSchema,
1548
+ response: {
1549
+ 200: {
1550
+ error: {
1551
+ type: 'boolean'
1552
+ },
1553
+ message: userDto
1554
+ }
1555
+ },
1556
+ tags: ['user']
1557
+ }
1558
+ },
1559
+ async (request, reply) => {
1560
+ const {
1561
+ params: { id }
1562
+ } = request
1563
+ const us = new UserService({ id })
1564
+ const user = await us.process({ type: 'getOne' })
1565
+
1566
+ response({
1567
+ error: false,
1568
+ message: user,
1569
+ reply,
1570
+ status: 200
1571
+ })
1572
+ }
1573
+ )
1574
+ .patch<{ Body: StoreUser; Params: IdSchema }>(
1575
+ \`\${prefix}/user/:id\`,
1576
+ {
1577
+ schema: {
1578
+ body: storeUserSchema,
1579
+ params: idSchema,
1580
+ response: {
1581
+ 200: {
1582
+ error: {
1583
+ type: 'boolean'
1584
+ },
1585
+ message: userDto
1586
+ }
1587
+ },
1588
+ tags: ['user']
1589
+ }
1590
+ },
1591
+ async (request, reply) => {
1592
+ const {
1593
+ body: {
1594
+ args: { name, lastName }
1595
+ },
1596
+ params: { id }
1597
+ } = request
1598
+ const us = new UserService({
1599
+ userDto: { name, lastName, id }
1600
+ })
1601
+ const user = await us.process({ type: 'update' })
1602
+
1603
+ response({
1604
+ error: false,
1605
+ message: user,
1606
+ reply,
1607
+ status: 200
1608
+ })
1609
+ }
1610
+ )
1611
+ .delete<{ Params: IdSchema }>(
1612
+ \`\${prefix}/user/:id\`,
1613
+ {
1614
+ schema: {
1615
+ params: idSchema,
1616
+ response: {
1617
+ 200: {
1618
+ error: {
1619
+ type: 'boolean'
1620
+ },
1621
+ message: {
1622
+ type: 'string'
1623
+ }
1624
+ }
1625
+ },
1626
+ tags: ['user']
1627
+ }
1628
+ },
1629
+ async (request, reply) => {
1630
+ const {
1631
+ params: { id }
1632
+ } = request
1633
+ const us = new UserService({ id })
1634
+ const result = await us.process({ type: 'delete' })
1635
+
1636
+ response({
1637
+ error: false,
1638
+ message: result,
1639
+ reply,
1640
+ status: 200
1641
+ })
1642
+ }
1643
+ )
1644
+ }
1645
+
1646
+ export { User }
1647
+ `,
1648
+ file: `${projectName}/src/network/routes/user.ts`
1649
+ }
1650
+ },
1651
+ utils: {
1652
+ index: {
1653
+ content: `/* eslint-disable @typescript-eslint/no-explicit-any */
1654
+ import {
1655
+ FastifyRouteSchemaDef,
1656
+ FastifyValidationResult
1657
+ } from 'fastify/types/schema'
1658
+ import httpErrors from 'http-errors'
1659
+ import Ajv from 'ajv'
1660
+
1661
+ const ajv = new Ajv({
1662
+ removeAdditional: true,
1663
+ useDefaults: true,
1664
+ coerceTypes: true,
1665
+ nullable: true
1666
+ })
1667
+
1668
+ const validatorCompiler = ({
1669
+ schema
1670
+ }: FastifyRouteSchemaDef<any>): FastifyValidationResult => {
1671
+ const validate = ajv.compile(schema)
1672
+
1673
+ return (data: unknown): boolean => {
1674
+ const ok = validate(data)
1675
+
1676
+ if (!ok && validate.errors) {
1677
+ const [error] = validate.errors
1678
+ const errorMessage = \`\${error.dataPath.replace('.', '')} \${error.message}\`
1679
+
1680
+ throw new httpErrors.UnprocessableEntity(errorMessage)
1681
+ }
1682
+
1683
+ return true
1684
+ }
1685
+ }
1686
+
1687
+ export { validatorCompiler }
1688
+ `,
1689
+ file: `${projectName}/src/network/utils/index.ts`
1690
+ }
1691
+ }
1692
+ }
1693
+
1694
+ await Promise.all([
1695
+ writeFile(network.response.file, network.response.content),
1696
+ writeFile(network.router.file, network.router.content),
1697
+ writeFile(network.server.file, network.server.content),
1698
+ writeFile(network.routes.docs.file, network.routes.docs.content),
1699
+ writeFile(network.routes.home.file, network.routes.home.content),
1700
+ writeFile(network.routes.index.file, network.routes.index.content),
1701
+ writeFile(network.routes.user.file, network.routes.user.content),
1702
+ writeFile(network.utils.index.file, network.utils.index.content)
1703
+ ])
1704
+ }
1705
+
1706
+ /**
1707
+ * @param {Object} args
1708
+ * @param {String} args.projectName
1709
+ */
1710
+ const fastifyGraphQLNetwork = async ({ projectName }) => {
1711
+ const createFoldersCommand = `mkdir ${projectName}/src/graphQL/models \
1712
+ ${projectName}/src/graphQL/models/User \
1713
+ ${projectName}/src/graphQL/models/utils \
1714
+ ${projectName}/src/graphQL/models/utils/messages`
1715
+
1716
+ if (platform() === 'win32')
1717
+ await exec(createFoldersCommand.replaceAll('/', '\\'))
1718
+ else await exec(createFoldersCommand)
1719
+
1720
+ const graphQL = {
1721
+ index: {
1722
+ content: "export * from './models'\n",
1723
+ file: `${projectName}/src/graphQL/index.ts`
1724
+ },
1725
+ models: {
1726
+ User: {
1727
+ index: {
1728
+ content: `import { makeExecutableSchema } from '@graphql-tools/schema'
1729
+
1730
+ import { User as UserTD } from './typeDefs'
1731
+ import { Query } from './queriesResolver'
1732
+ import { Mutation } from './mutationsResolver'
1733
+
1734
+ const resolvers = {
1735
+ Query,
1736
+ Mutation
1737
+ }
1738
+
1739
+ const User = makeExecutableSchema({
1740
+ typeDefs: UserTD,
1741
+ resolvers
1742
+ })
1743
+
1744
+ export { User }
1745
+ `,
1746
+ file: `${projectName}/src/graphQL/models/User/index.ts`
1747
+ },
1748
+ mutations: {
1749
+ content: `import { ApolloError } from 'apollo-server-core'
1750
+
1751
+ import { store, remove, update } from 'database'
1752
+ import { UserDTO } from 'schemas'
1753
+ import { EFU, MFU, GE, errorHandling } from '../utils'
1754
+
1755
+ const storeUser = async (
1756
+ { user }: { user: UserDTO },
1757
+ { log }: Context
1758
+ ): Promise<UserDTO> => {
1759
+ try {
1760
+ const result = await store(user)
1761
+
1762
+ return result
1763
+ } catch (e) {
1764
+ return errorHandling({
1765
+ e,
1766
+ message: GE.INTERNAL_SERVER_ERROR,
1767
+ code: 'INTERNAL_SERVER_ERROR',
1768
+ log
1769
+ })
1770
+ }
1771
+ }
1772
+
1773
+ const deleteAllUsers = async ({ log }: Context): Promise<string> => {
1774
+ try {
1775
+ const usersDeleted = (await remove()) as number
1776
+
1777
+ if (usersDeleted >= 1) return MFU.ALL_USERS_DELETED
1778
+
1779
+ if (usersDeleted === 0)
1780
+ throw new ApolloError(EFU.NOTHING_TO_DELETE, 'NOTHING_TO_DELETE')
1781
+
1782
+ throw new ApolloError(GE.INTERNAL_SERVER_ERROR, 'INTERNAL_SERVER_ERROR')
1783
+ } catch (e) {
1784
+ return errorHandling({
1785
+ e,
1786
+ message: GE.INTERNAL_SERVER_ERROR,
1787
+ code: 'INTERNAL_SERVER_ERROR',
1788
+ log
1789
+ })
1790
+ }
1791
+ }
1792
+
1793
+ const updateUser = async (
1794
+ { user }: { user: UserDTO },
1795
+ { log }: Context
1796
+ ): Promise<UserDTO> => {
1797
+ try {
1798
+ const updatedUser = await update(user)
1799
+
1800
+ if (!updatedUser) throw new ApolloError(EFU.NOT_FOUND, 'NOT_FOUND')
1801
+
1802
+ return updatedUser
1803
+ } catch (e) {
1804
+ return errorHandling({
1805
+ e,
1806
+ message: GE.INTERNAL_SERVER_ERROR,
1807
+ code: 'INTERNAL_SERVER_ERROR',
1808
+ log
1809
+ })
1810
+ }
1811
+ }
1812
+
1813
+ const deleteUser = async (
1814
+ { id }: { id: string },
1815
+ { log }: Context
1816
+ ): Promise<string> => {
1817
+ try {
1818
+ const deletedUser = await remove(id)
1819
+
1820
+ if (!deletedUser) throw new ApolloError(EFU.NOT_FOUND, 'NOT_FOUND')
1821
+
1822
+ return MFU.USER_DELETED
1823
+ } catch (e) {
1824
+ return errorHandling({
1825
+ e,
1826
+ message: GE.INTERNAL_SERVER_ERROR,
1827
+ code: 'INTERNAL_SERVER_ERROR',
1828
+ log
1829
+ })
1830
+ }
1831
+ }
1832
+
1833
+ export { storeUser, deleteAllUsers, updateUser, deleteUser }
1834
+ `,
1835
+ file: `${projectName}/src/graphQL/models/User/mutations.ts`
1836
+ },
1837
+ mutationsResolver: {
1838
+ content: `import { DefinedError } from 'ajv'
1839
+ import { ApolloError } from 'apollo-server-core'
1840
+
1841
+ import { ajv, idSchema, UserDTO } from 'schemas'
1842
+ import { storeUserSchema, updateUserSchema } from './schemas'
1843
+ import { storeUser, updateUser, deleteUser, deleteAllUsers } from './mutations'
1844
+ import { errorHandling, GE } from '../utils'
1845
+
1846
+ const Mutation = {
1847
+ storeUser: async (
1848
+ parent: unknown,
1849
+ { user }: { user: UserDTO },
1850
+ context: Context
1851
+ ): Promise<UserDTO> => {
1852
+ const { log } = context
1853
+ const validate = ajv.compile(storeUserSchema)
1854
+
1855
+ try {
1856
+ const ok = validate(user)
1857
+
1858
+ if (!ok)
1859
+ throw new ApolloError(
1860
+ \`\${(validate.errors as DefinedError[])[0].instancePath.replace(
1861
+ '/',
1862
+ ''
1863
+ )} \${(validate.errors as DefinedError[])[0].message as string}\`,
1864
+ 'UNPROCESSABLE_ENTITY'
1865
+ )
1866
+
1867
+ return await storeUser({ user }, context)
1868
+ } catch (e) {
1869
+ log.error(validate.errors)
1870
+
1871
+ return errorHandling({
1872
+ e,
1873
+ message: GE.INTERNAL_SERVER_ERROR,
1874
+ code: 'INTERNAL_SERVER_ERROR',
1875
+ log
1876
+ })
1877
+ }
1878
+ },
1879
+ updateUser: async (
1880
+ parent: unknown,
1881
+ { user }: { user: UserDTO },
1882
+ context: Context
1883
+ ): Promise<UserDTO> => {
1884
+ const validate = ajv.compile(updateUserSchema)
1885
+ const { log } = context
1886
+
1887
+ try {
1888
+ const ok = validate(user)
1889
+
1890
+ if (!ok)
1891
+ throw new ApolloError(
1892
+ \`\${(validate.errors as DefinedError[])[0].instancePath.replace(
1893
+ '/',
1894
+ ''
1895
+ )} \${(validate.errors as DefinedError[])[0].message as string}\`,
1896
+ 'UNPROCESSABLE_ENTITY'
1897
+ )
1898
+
1899
+ return await updateUser({ user }, context)
1900
+ } catch (e) {
1901
+ log.error(validate.errors)
1902
+
1903
+ return errorHandling({
1904
+ e,
1905
+ message: GE.INTERNAL_SERVER_ERROR,
1906
+ code: 'INTERNAL_SERVER_ERROR',
1907
+ log
1908
+ })
1909
+ }
1910
+ },
1911
+ deleteUser: async (
1912
+ parent: unknown,
1913
+ { id }: { id: string },
1914
+ context: Context
1915
+ ): Promise<string> => {
1916
+ const validate = ajv.compile(idSchema)
1917
+ const { log } = context
1918
+
1919
+ try {
1920
+ const ok = validate({ id })
1921
+
1922
+ if (!ok)
1923
+ throw new ApolloError(
1924
+ \`\${(validate.errors as DefinedError[])[0].instancePath.replace(
1925
+ '/',
1926
+ ''
1927
+ )} \${(validate.errors as DefinedError[])[0].message as string}\`,
1928
+ 'UNPROCESSABLE_ENTITY'
1929
+ )
1930
+
1931
+ return await deleteUser({ id }, context)
1932
+ } catch (e) {
1933
+ log.error(validate.errors)
1934
+
1935
+ return errorHandling({
1936
+ e,
1937
+ message: GE.INTERNAL_SERVER_ERROR,
1938
+ code: 'INTERNAL_SERVER_ERROR',
1939
+ log
1940
+ })
1941
+ }
1942
+ },
1943
+ deleteAllUsers: async (
1944
+ parent: unknown,
1945
+ args: unknown,
1946
+ context: Context
1947
+ ): Promise<string> => {
1948
+ const { log } = context
1949
+ try {
1950
+ return await deleteAllUsers(context)
1951
+ } catch (e) {
1952
+ return errorHandling({
1953
+ e,
1954
+ message: GE.INTERNAL_SERVER_ERROR,
1955
+ code: 'INTERNAL_SERVER_ERROR',
1956
+ log
1957
+ })
1958
+ }
1959
+ }
1960
+ }
1961
+
1962
+ export { Mutation }
1963
+ `,
1964
+ file: `${projectName}/src/graphQL/models/User/mutationsResolver.ts`
1965
+ },
1966
+ queries: {
1967
+ content: `import { ApolloError } from 'apollo-server-core'
1968
+
1969
+ import { get } from 'database'
1970
+ import { UserDTO } from 'schemas'
1971
+ import { EFU, GE, errorHandling } from '../utils'
1972
+
1973
+ const getUsers = async (
1974
+ parent: unknown,
1975
+ args: unknown,
1976
+ { log }: Context
1977
+ ): Promise<UserDTO[]> => {
1978
+ try {
1979
+ const users = (await get()) as UserDTO[]
1980
+
1981
+ return users
1982
+ } catch (e) {
1983
+ return errorHandling({
1984
+ e,
1985
+ message: GE.INTERNAL_SERVER_ERROR,
1986
+ code: 'INTERNAL_SERVER_ERROR',
1987
+ log
1988
+ })
1989
+ }
1990
+ }
1991
+
1992
+ const getUser = async (
1993
+ { id }: { id: string },
1994
+ { log }: Context
1995
+ ): Promise<UserDTO> => {
1996
+ try {
1997
+ const user = (await get(id as string)) as UserDTO | null
1998
+
1999
+ if (!user) throw new ApolloError(EFU.NOT_FOUND, 'NOT_FOUND')
2000
+
2001
+ return user
2002
+ } catch (e) {
2003
+ return errorHandling({
2004
+ e,
2005
+ message: GE.INTERNAL_SERVER_ERROR,
2006
+ code: 'INTERNAL_SERVER_ERROR',
2007
+ log
2008
+ })
2009
+ }
2010
+ }
2011
+
2012
+ export { getUsers, getUser }
2013
+ `,
2014
+ file: `${projectName}/src/graphQL/models/User/queries.ts`
2015
+ },
2016
+ queriesResolver: {
2017
+ content: `import { DefinedError } from 'ajv'
2018
+ import { ApolloError } from 'apollo-server-core'
2019
+
2020
+ import { ajv, idSchema, UserDTO } from 'schemas'
2021
+ import { getUser, getUsers } from './queries'
2022
+ import { errorHandling, GE } from '../utils'
2023
+
2024
+ const Query = {
2025
+ getUser: async (
2026
+ parent: unknown,
2027
+ { id }: { id: string },
2028
+ context: Context
2029
+ ): Promise<UserDTO> => {
2030
+ const { log } = context
2031
+ const validate = ajv.compile(idSchema)
2032
+
2033
+ try {
2034
+ const ok = validate({ id })
2035
+
2036
+ if (!ok)
2037
+ throw new ApolloError(
2038
+ \`id \${(validate.errors as DefinedError[])[0].message as string}\`,
2039
+ 'UNPROCESSABLE_ENTITY'
2040
+ )
2041
+
2042
+ return await getUser({ id }, context)
2043
+ } catch (e) {
2044
+ log.error(validate.errors)
2045
+
2046
+ return errorHandling({
2047
+ e,
2048
+ message: GE.INTERNAL_SERVER_ERROR,
2049
+ code: 'INTERNAL_SERVER_ERROR',
2050
+ log
2051
+ })
2052
+ }
2053
+ },
2054
+ getUsers
2055
+ }
2056
+
2057
+ export { Query }
2058
+ `,
2059
+ file: `${projectName}/src/graphQL/models/User/queriesResolver.ts`
2060
+ },
2061
+ schemas: {
2062
+ content: `import { Type } from '@sinclair/typebox'
2063
+
2064
+ const updateUserSchema = Type.Object(
2065
+ {
2066
+ id: Type.String({ minLength: 24, maxLength: 24 }),
2067
+ lastName: Type.String(),
2068
+ name: Type.String()
2069
+ },
2070
+ { additionalProperties: false }
2071
+ )
2072
+
2073
+ const storeUserSchema = Type.Object(
2074
+ {
2075
+ lastName: Type.String({ minLength: 1 }),
2076
+ name: Type.String({ minLength: 1 })
2077
+ },
2078
+ { additionalProperties: false }
2079
+ )
2080
+
2081
+ export { updateUserSchema, storeUserSchema }
2082
+ `,
2083
+ file: `${projectName}/src/graphQL/models/User/schemas.ts`
2084
+ },
2085
+ typeDefs: {
2086
+ content: `import { gql } from 'apollo-server-core'
2087
+
2088
+ const User = gql\`
2089
+ type User {
2090
+ id: ID!
2091
+ name: String!
2092
+ lastName: String!
2093
+ updatedAt: String!
2094
+ }
2095
+
2096
+ type Query {
2097
+ getUsers: [User!]!
2098
+ getUser(id: ID!): User!
2099
+ }
2100
+
2101
+ input StoreUserInput {
2102
+ lastName: String!
2103
+ name: String!
2104
+ }
2105
+
2106
+ input UpdateUserInput {
2107
+ id: String!
2108
+ lastName: String!
2109
+ name: String!
2110
+ }
2111
+
2112
+ type Mutation {
2113
+ storeUser(user: StoreUserInput!): User!
2114
+ deleteAllUsers: String
2115
+ updateUser(user: UpdateUserInput!): User!
2116
+ deleteUser(id: ID!): String
2117
+ }
2118
+ \`
2119
+
2120
+ export { User }
2121
+ `,
2122
+ file: `${projectName}/src/graphQL/models/User/typeDefs.ts`
2123
+ }
2124
+ },
2125
+ utils: {
2126
+ messages: {
2127
+ index: {
2128
+ content: `enum GenericErrors {
2129
+ INTERNAL_SERVER_ERROR = 'Something went wrong'
2130
+ }
2131
+
2132
+ export { GenericErrors as GE }
2133
+ export * from './user'
2134
+ `,
2135
+ file: `${projectName}/src/graphQL/models/utils/messages/index.ts`
2136
+ },
2137
+ user: {
2138
+ content: `enum ErrorForUser {
2139
+ NOT_FOUND = 'The requested user does not exists',
2140
+ NOTHING_TO_DELETE = 'There is no user to be deleted'
2141
+ }
2142
+
2143
+ enum MessageForUser {
2144
+ ALL_USERS_DELETED = 'All the users were deleted successfully',
2145
+ USER_DELETED = 'The requested user was successfully deleted'
2146
+ }
2147
+
2148
+ export { ErrorForUser as EFU, MessageForUser as MFU }
2149
+ `,
2150
+ file: `${projectName}/src/graphQL/models/utils/messages/user.ts`
2151
+ }
2152
+ },
2153
+ index: {
2154
+ content: `import { ApolloError } from 'apollo-server-core'
2155
+ import { FastifyLoggerInstance } from 'fastify'
2156
+
2157
+ const errorHandling = ({
2158
+ e,
2159
+ message,
2160
+ code,
2161
+ log
2162
+ }: {
2163
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2164
+ e: any
2165
+ message: string
2166
+ code: string
2167
+ log: FastifyLoggerInstance
2168
+ }): never => {
2169
+ log.error(e)
2170
+
2171
+ if (e instanceof ApolloError) throw e
2172
+
2173
+ throw new ApolloError(message ?? e.message, code)
2174
+ }
2175
+
2176
+ export { errorHandling }
2177
+ export * from './messages'
2178
+ `,
2179
+ file: `${projectName}/src/graphQL/models/utils/index.ts`
2180
+ }
2181
+ },
2182
+ index: {
2183
+ content: `import { mergeSchemas } from '@graphql-tools/schema'
2184
+
2185
+ import { User } from './User'
2186
+
2187
+ const mergedSchema = mergeSchemas({
2188
+ schemas: [User]
2189
+ })
2190
+
2191
+ export { mergedSchema }
2192
+ `,
2193
+ file: `${projectName}/src/graphQL/models/index.ts`
2194
+ }
2195
+ }
2196
+ }
2197
+ const network = {
2198
+ response: {
2199
+ content: `import { FastifyReply } from 'fastify'
2200
+
2201
+ const response = ({
2202
+ error,
2203
+ message,
2204
+ reply,
2205
+ status
2206
+ }: {
2207
+ error: boolean
2208
+ message: unknown
2209
+ reply: FastifyReply
2210
+ status: number
2211
+ }): void => {
2212
+ reply.code(status).send({ error, message })
2213
+ }
2214
+
2215
+ export { response }
2216
+ `,
2217
+ file: `${projectName}/src/network/response.ts`
2218
+ },
2219
+ router: {
2220
+ content: `import { FastifyInstance } from 'fastify'
2221
+ import { HttpError } from 'http-errors'
2222
+
2223
+ import { response } from './response'
2224
+ import { Home, Docs } from './routes'
2225
+
2226
+ const routers = [Docs]
2227
+ const applyRoutes = (app: FastifyInstance): void => {
2228
+ Home(app)
2229
+ routers.forEach(router => router(app))
2230
+
2231
+ // Handling 404 error
2232
+ app.setNotFoundHandler((request, reply) => {
2233
+ response({
2234
+ error: true,
2235
+ message: 'This route does not exists',
2236
+ reply,
2237
+ status: 404
2238
+ })
2239
+ })
2240
+ app.setErrorHandler<HttpError>((error, request, reply) => {
2241
+ response({
2242
+ error: true,
2243
+ message: error.message,
2244
+ reply,
2245
+ status: error.status ?? 500
2246
+ })
2247
+ })
2248
+ }
2249
+
2250
+ export { applyRoutes }
2251
+ `,
2252
+ file: `${projectName}/src/network/router.ts`
2253
+ },
2254
+ server: {
2255
+ content: `import fastify, { FastifyInstance } from 'fastify'
2256
+ import { ApolloServer } from 'apollo-server-fastify'
2257
+ import {
2258
+ ApolloServerPluginDrainHttpServer,
2259
+ ApolloServerPluginLandingPageGraphQLPlayground,
2260
+ ApolloServerPluginLandingPageDisabled
2261
+ } from 'apollo-server-core'
2262
+ import { ApolloServerPlugin } from 'apollo-server-plugin-base'
2263
+ import mongoose from 'mongoose'
2264
+
2265
+ import { mergedSchema as schema } from 'graphQL'
2266
+ import { applyRoutes } from './router'
2267
+
2268
+ const PORT = process.env.PORT ?? 1996
2269
+
2270
+ class Server {
2271
+ #app: FastifyInstance
2272
+ #connection: mongoose.Connection | undefined
2273
+
2274
+ constructor() {
2275
+ this.#app = fastify({ logger: { prettyPrint: true } })
2276
+ this.#config()
2277
+ }
2278
+
2279
+ #config() {
2280
+ this.#app.addHook('preHandler', (req, reply, done) => {
2281
+ reply.header('Access-Control-Allow-Methods', 'GET, POST, PATCH, DELETE')
2282
+ reply.header('Access-Control-Allow-Origin', '*')
2283
+ reply.header(
2284
+ 'Access-Control-Allow-Headers',
2285
+ 'Authorization, Content-Type'
2286
+ )
2287
+ reply.header('x-powered-by', 'Simba.js')
2288
+ done()
2289
+ })
2290
+ applyRoutes(this.#app)
2291
+ }
2292
+
2293
+ async #mongo(): Promise<void> {
2294
+ this.#connection = mongoose.connection
2295
+ const connection = {
2296
+ keepAlive: true,
2297
+ useNewUrlParser: true,
2298
+ useUnifiedTopology: true
2299
+ }
2300
+ this.#connection.on('connected', () => {
2301
+ this.#app.log.info('Mongo connection established.')
2302
+ })
2303
+ this.#connection.on('reconnected', () => {
2304
+ this.#app.log.info('Mongo connection reestablished')
2305
+ })
2306
+ this.#connection.on('disconnected', () => {
2307
+ this.#app.log.info('Mongo connection disconnected')
2308
+ this.#app.log.info('Trying to reconnected to Mongo...')
2309
+ setTimeout(() => {
2310
+ mongoose.connect(process.env.MONGO_URI as string, {
2311
+ ...connection,
2312
+ connectTimeoutMS: 3000,
2313
+ socketTimeoutMS: 3000
2314
+ })
2315
+ }, 3000)
2316
+ })
2317
+ this.#connection.on('close', () => {
2318
+ this.#app.log.info('Mongo connection closed')
2319
+ })
2320
+ this.#connection.on('error', (e: Error) => {
2321
+ this.#app.log.info('Mongo connection error:')
2322
+ this.#app.log.error(e)
2323
+ })
2324
+ await mongoose.connect(process.env.MONGO_URI as string, connection)
2325
+ }
2326
+
2327
+ #fastifyAppClosePlugin(): ApolloServerPlugin {
2328
+ const app = this.#app
2329
+
2330
+ return {
2331
+ async serverWillStart() {
2332
+ return {
2333
+ async drainServer() {
2334
+ await app.close()
2335
+ }
2336
+ }
2337
+ }
2338
+ }
2339
+ }
2340
+
2341
+ public async start(): Promise<void> {
2342
+ const server = new ApolloServer({
2343
+ schema,
2344
+ plugins: [
2345
+ this.#fastifyAppClosePlugin(),
2346
+ ApolloServerPluginDrainHttpServer({ httpServer: this.#app.server }),
2347
+ process.env.NODE_ENV === 'production'
2348
+ ? ApolloServerPluginLandingPageDisabled()
2349
+ : ApolloServerPluginLandingPageGraphQLPlayground()
2350
+ ],
2351
+ context: (): Context => ({
2352
+ log: this.#app.log
2353
+ })
2354
+ })
2355
+
2356
+ try {
2357
+ await server.start()
2358
+ this.#app.register(
2359
+ server.createHandler({
2360
+ path: '/api'
2361
+ })
2362
+ )
2363
+ await this.#mongo()
2364
+ await this.#app.listen(PORT)
2365
+ this.#app.log.info(
2366
+ \`GraphQL server listening at: http://localhost:\${PORT}\${server.graphqlPath}\`
2367
+ )
2368
+ } catch (e) {
2369
+ console.error(e)
2370
+ }
2371
+ }
2372
+ }
2373
+
2374
+ const server = new Server()
2375
+
2376
+ export { server as Server }
2377
+ `,
2378
+ file: `${projectName}/src/network/server.ts`
2379
+ },
2380
+ routes: {
2381
+ docs: {
2382
+ content: `import { FastifyInstance } from 'fastify'
2383
+ import fastifySwagger from 'fastify-swagger'
2384
+
2385
+ const Docs = (app: FastifyInstance, prefix = '/api'): void => {
2386
+ app.register(fastifySwagger, {
2387
+ routePrefix: \`\${prefix}/docs\`,
2388
+ openapi: {
2389
+ info: {
2390
+ title: 'Test swagger',
2391
+ description: 'Testing the Fastify swagger API',
2392
+ version: '0.1.0',
2393
+ contact: {
2394
+ email: 'sluzquinosa@uni.pe'
2395
+ },
2396
+ license: {
2397
+ name: 'MIT',
2398
+ url: 'https://opensource.org/licenses/MIT'
2399
+ }
2400
+ },
2401
+ servers: [
2402
+ {
2403
+ url: 'http://localhost:1996/api',
2404
+ description: 'test-fastify local API'
2405
+ }
2406
+ ],
2407
+ tags: [
2408
+ {
2409
+ name: 'user',
2410
+ description: 'User related endpoints'
2411
+ }
2412
+ ]
2413
+ },
2414
+ exposeRoute: true
2415
+ })
2416
+ }
2417
+
2418
+ export { Docs }
2419
+ `,
2420
+ file: `${projectName}/src/network/routes/docs.ts`
2421
+ },
2422
+ home: {
2423
+ content: `import { FastifyInstance } from 'fastify'
2424
+ import { response } from 'network/response'
2425
+
2426
+ const Home = (app: FastifyInstance, prefix = '/'): void => {
2427
+ app.get(\`\${prefix}\`, (request, reply) => {
2428
+ response({
2429
+ error: false,
2430
+ message: 'Welcome to your Fastify GraphQL Backend!',
2431
+ reply,
2432
+ status: 200
2433
+ })
2434
+ })
2435
+ }
2436
+
2437
+ export { Home }
2438
+ `,
2439
+ file: `${projectName}/src/network/routes/home.ts`
2440
+ },
2441
+ index: {
2442
+ content: `export * from './home'
2443
+ export * from './docs'
2444
+ `,
2445
+ file: `${projectName}/src/network/routes/index.ts`
2446
+ }
2447
+ }
2448
+ }
2449
+
2450
+ await Promise.all([
2451
+ writeFile(graphQL.index.file, graphQL.index.content),
2452
+ writeFile(
2453
+ graphQL.models.User.index.file,
2454
+ graphQL.models.User.index.content
2455
+ ),
2456
+ writeFile(
2457
+ graphQL.models.User.mutations.file,
2458
+ graphQL.models.User.mutations.content
2459
+ ),
2460
+ writeFile(
2461
+ graphQL.models.User.mutationsResolver.file,
2462
+ graphQL.models.User.mutationsResolver.content
2463
+ ),
2464
+ writeFile(
2465
+ graphQL.models.User.queries.file,
2466
+ graphQL.models.User.queries.content
2467
+ ),
2468
+ writeFile(
2469
+ graphQL.models.User.queriesResolver.file,
2470
+ graphQL.models.User.queriesResolver.content
2471
+ ),
2472
+ writeFile(
2473
+ graphQL.models.User.schemas.file,
2474
+ graphQL.models.User.schemas.content
2475
+ ),
2476
+ writeFile(
2477
+ graphQL.models.User.typeDefs.file,
2478
+ graphQL.models.User.typeDefs.content
2479
+ ),
2480
+ writeFile(
2481
+ graphQL.models.utils.messages.index.file,
2482
+ graphQL.models.utils.messages.index.content
2483
+ ),
2484
+ writeFile(
2485
+ graphQL.models.utils.messages.user.file,
2486
+ graphQL.models.utils.messages.user.content
2487
+ ),
2488
+ writeFile(
2489
+ graphQL.models.utils.index.file,
2490
+ graphQL.models.utils.index.content
2491
+ ),
2492
+ writeFile(graphQL.models.index.file, graphQL.models.index.content),
2493
+ writeFile(network.response.file, network.response.content),
2494
+ writeFile(network.router.file, network.router.content),
2495
+ writeFile(network.server.file, network.server.content),
2496
+ writeFile(network.routes.docs.file, network.routes.docs.content),
2497
+ writeFile(network.routes.home.file, network.routes.home.content),
2498
+ writeFile(network.routes.index.file, network.routes.index.content)
2499
+ ])
2500
+ }
2501
+
2502
+ /**
2503
+ * @param {Object} args
2504
+ * @param {Boolean} args.fastify
2505
+ * @param {String} args.projectName
2506
+ * @param {Boolean} args.graphql
2507
+ */
2508
+ module.exports = async ({ fastify, projectName, graphql }) => {
2509
+ const createFoldersCommand = `mkdir ${projectName}/src/network \
2510
+ ${projectName}/src/network/routes ${
2511
+ graphql ? `${projectName}/src/graphQL` : ''
2512
+ }`
2513
+
2514
+ if (platform() === 'win32')
2515
+ await exec(createFoldersCommand.replaceAll('/', '\\'))
2516
+ else await exec(createFoldersCommand)
2517
+
2518
+ const network = {
2519
+ index: {
2520
+ content: `export * from './routes'
2521
+ export * from './server'
2522
+ `,
2523
+ file: `${projectName}/src/network/index.ts`
2524
+ }
2525
+ }
2526
+
2527
+ await writeFile(network.index.file, network.index.content)
2528
+
2529
+ if (fastify && graphql) return await fastifyGraphQLNetwork({ projectName })
2530
+
2531
+ if (fastify) return await fastifyRestNetwork({ projectName })
2532
+
2533
+ if (graphql) return await expressGraphQLNetwork({ projectName })
2534
+
2535
+ return await expressRestNetwork({ projectName })
2536
+ }