@anthonylzq/simba.js 4.4.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.
- package/README.md +197 -20
- package/lib/index.js +17 -11
- package/lib/src/functions/api/database.js +20 -18
- package/lib/src/functions/api/index.js +47 -37
- package/lib/src/functions/api/network.js +1727 -97
- package/lib/src/functions/api/schemas.js +53 -3
- package/lib/src/functions/api/services.js +13 -11
- package/lib/src/functions/api/types.js +37 -7
- package/lib/src/functions/api/utils.js +19 -6
- package/lib/src/index.js +21 -7
- package/package.json +9 -2
|
@@ -75,25 +75,33 @@ export { applyRoutes }
|
|
|
75
75
|
server: {
|
|
76
76
|
content: `import express from 'express'
|
|
77
77
|
import mongoose from 'mongoose'
|
|
78
|
-
import morgan from 'morgan'
|
|
79
78
|
import cors from 'cors'
|
|
79
|
+
import pino, { HttpLogger } from 'express-pino-logger'
|
|
80
80
|
|
|
81
81
|
import { applyRoutes } from './router'
|
|
82
82
|
|
|
83
|
-
const PORT = (process.env.PORT as string) ||
|
|
83
|
+
const PORT = (process.env.PORT as string) || 1996
|
|
84
84
|
|
|
85
85
|
class Server {
|
|
86
86
|
#app: express.Application
|
|
87
87
|
#connection: mongoose.Connection | undefined
|
|
88
|
+
#log: HttpLogger
|
|
88
89
|
|
|
89
90
|
constructor() {
|
|
90
91
|
this.#app = express()
|
|
92
|
+
this.#log = pino({
|
|
93
|
+
transport: {
|
|
94
|
+
target: 'pino-pretty',
|
|
95
|
+
options: {
|
|
96
|
+
colorize: true
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
})
|
|
91
100
|
this.#config()
|
|
92
101
|
}
|
|
93
102
|
|
|
94
103
|
#config() {
|
|
95
104
|
this.#app.use(cors())
|
|
96
|
-
this.#app.use(morgan('dev'))
|
|
97
105
|
this.#app.use(express.json())
|
|
98
106
|
this.#app.use(express.urlencoded({ extended: false }))
|
|
99
107
|
this.#app.use(
|
|
@@ -108,10 +116,10 @@ class Server {
|
|
|
108
116
|
'Access-Control-Allow-Headers',
|
|
109
117
|
'Authorization, Content-Type'
|
|
110
118
|
)
|
|
119
|
+
res.header('x-powered-by', 'Simba.js')
|
|
111
120
|
next()
|
|
112
121
|
}
|
|
113
122
|
)
|
|
114
|
-
|
|
115
123
|
applyRoutes(this.#app)
|
|
116
124
|
}
|
|
117
125
|
|
|
@@ -123,14 +131,14 @@ class Server {
|
|
|
123
131
|
useUnifiedTopology: true
|
|
124
132
|
}
|
|
125
133
|
this.#connection.on('connected', () => {
|
|
126
|
-
|
|
134
|
+
this.#log.logger.info('Mongo connection established.')
|
|
127
135
|
})
|
|
128
136
|
this.#connection.on('reconnected', () => {
|
|
129
|
-
|
|
137
|
+
this.#log.logger.info('Mongo connection reestablished')
|
|
130
138
|
})
|
|
131
139
|
this.#connection.on('disconnected', () => {
|
|
132
|
-
|
|
133
|
-
|
|
140
|
+
this.#log.logger.info('Mongo connection disconnected')
|
|
141
|
+
this.#log.logger.info('Trying to reconnected to Mongo...')
|
|
134
142
|
setTimeout(() => {
|
|
135
143
|
mongoose.connect(process.env.MONGO_URI as string, {
|
|
136
144
|
...connection,
|
|
@@ -140,24 +148,24 @@ class Server {
|
|
|
140
148
|
}, 3000)
|
|
141
149
|
})
|
|
142
150
|
this.#connection.on('close', () => {
|
|
143
|
-
|
|
151
|
+
this.#log.logger.info('Mongo connection closed')
|
|
144
152
|
})
|
|
145
153
|
this.#connection.on('error', (e: Error) => {
|
|
146
|
-
|
|
147
|
-
|
|
154
|
+
this.#log.logger.info('Mongo connection error:')
|
|
155
|
+
this.#log.logger.error(e)
|
|
148
156
|
})
|
|
149
157
|
await mongoose.connect(process.env.MONGO_URI as string, connection)
|
|
150
158
|
}
|
|
151
159
|
|
|
152
160
|
public start(): void {
|
|
153
161
|
this.#app.listen(PORT, () => {
|
|
154
|
-
|
|
162
|
+
this.#log.logger.info(\`Server running at port \${PORT}\`)
|
|
155
163
|
})
|
|
156
164
|
|
|
157
165
|
try {
|
|
158
166
|
this.#mongo()
|
|
159
167
|
} catch (e) {
|
|
160
|
-
|
|
168
|
+
this.#log.logger.error(e)
|
|
161
169
|
}
|
|
162
170
|
}
|
|
163
171
|
}
|
|
@@ -378,45 +386,524 @@ export { validatorCompiler }
|
|
|
378
386
|
}
|
|
379
387
|
}
|
|
380
388
|
|
|
381
|
-
await
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
+
])
|
|
391
401
|
}
|
|
392
402
|
|
|
393
403
|
/**
|
|
394
404
|
* @param {Object} args
|
|
395
405
|
* @param {String} args.projectName
|
|
396
406
|
*/
|
|
397
|
-
const
|
|
398
|
-
const createFoldersCommand = `mkdir ${projectName}/src/network/utils
|
|
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`
|
|
399
413
|
|
|
400
414
|
if (platform() === 'win32')
|
|
401
415
|
await exec(createFoldersCommand.replaceAll('/', '\\'))
|
|
402
416
|
else await exec(createFoldersCommand)
|
|
403
417
|
|
|
404
|
-
const
|
|
405
|
-
|
|
406
|
-
content:
|
|
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'
|
|
407
427
|
|
|
408
|
-
|
|
409
|
-
|
|
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,
|
|
410
857
|
message,
|
|
411
|
-
|
|
412
|
-
|
|
858
|
+
code,
|
|
859
|
+
log
|
|
413
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 {
|
|
414
899
|
error: boolean
|
|
415
900
|
message: unknown
|
|
416
|
-
|
|
901
|
+
res: CustomResponse
|
|
417
902
|
status: number
|
|
418
|
-
}
|
|
419
|
-
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
const response = ({ error, message, res, status }: ResponseProps): void => {
|
|
906
|
+
res.status(status).send({ error, message })
|
|
420
907
|
}
|
|
421
908
|
|
|
422
909
|
export { response }
|
|
@@ -424,35 +911,40 @@ export { response }
|
|
|
424
911
|
file: `${projectName}/src/network/response.ts`
|
|
425
912
|
},
|
|
426
913
|
router: {
|
|
427
|
-
content: `import {
|
|
428
|
-
import
|
|
914
|
+
content: `import { Application, Response, Request, Router, NextFunction } from 'express'
|
|
915
|
+
import swaggerUi from 'swagger-ui-express'
|
|
916
|
+
import httpErrors from 'http-errors'
|
|
429
917
|
|
|
430
918
|
import { response } from './response'
|
|
431
|
-
import { Home
|
|
432
|
-
|
|
433
|
-
const routers = [Docs, User]
|
|
919
|
+
import { Home } from './routes'
|
|
920
|
+
import { docs } from 'utils'
|
|
434
921
|
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
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))
|
|
438
927
|
|
|
439
928
|
// Handling 404 error
|
|
440
|
-
app.
|
|
441
|
-
|
|
442
|
-
error: true,
|
|
443
|
-
message: 'This route does not exists',
|
|
444
|
-
reply,
|
|
445
|
-
status: 404
|
|
446
|
-
})
|
|
447
|
-
})
|
|
448
|
-
app.setErrorHandler<HttpError>((error, request, reply) => {
|
|
449
|
-
response({
|
|
450
|
-
error: true,
|
|
451
|
-
message: error.message,
|
|
452
|
-
reply,
|
|
453
|
-
status: error.status ?? 500
|
|
454
|
-
})
|
|
929
|
+
app.use((req, res, next) => {
|
|
930
|
+
next(new httpErrors.NotFound('This route does not exists'))
|
|
455
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
|
+
)
|
|
456
948
|
}
|
|
457
949
|
|
|
458
950
|
export { applyRoutes }
|
|
@@ -460,36 +952,64 @@ export { applyRoutes }
|
|
|
460
952
|
file: `${projectName}/src/network/router.ts`
|
|
461
953
|
},
|
|
462
954
|
server: {
|
|
463
|
-
content: `import
|
|
955
|
+
content: `import http from 'http'
|
|
956
|
+
import express from 'express'
|
|
464
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'
|
|
465
966
|
|
|
967
|
+
import { mergedSchema as schema } from 'graphQL'
|
|
466
968
|
import { applyRoutes } from './router'
|
|
467
|
-
import { validatorCompiler } from './utils'
|
|
468
969
|
|
|
469
|
-
const PORT = process.env.PORT
|
|
970
|
+
const PORT = (process.env.PORT as string) || 1996
|
|
470
971
|
|
|
471
972
|
class Server {
|
|
472
|
-
#app:
|
|
973
|
+
#app: express.Application
|
|
473
974
|
#connection: mongoose.Connection | undefined
|
|
975
|
+
#httpServer: http.Server
|
|
976
|
+
#log: HttpLogger
|
|
474
977
|
|
|
475
978
|
constructor() {
|
|
476
|
-
this.#app =
|
|
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
|
+
})
|
|
477
989
|
this.#config()
|
|
478
990
|
}
|
|
479
991
|
|
|
480
992
|
#config() {
|
|
481
|
-
this.#app.
|
|
482
|
-
this.#app.
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
+
)
|
|
493
1013
|
}
|
|
494
1014
|
|
|
495
1015
|
async #mongo(): Promise<void> {
|
|
@@ -500,10 +1020,313 @@ class Server {
|
|
|
500
1020
|
useUnifiedTopology: true
|
|
501
1021
|
}
|
|
502
1022
|
this.#connection.on('connected', () => {
|
|
503
|
-
this.#
|
|
1023
|
+
this.#log.logger.info('Mongo connection established.')
|
|
504
1024
|
})
|
|
505
1025
|
this.#connection.on('reconnected', () => {
|
|
506
|
-
this.#
|
|
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')
|
|
507
1330
|
})
|
|
508
1331
|
this.#connection.on('disconnected', () => {
|
|
509
1332
|
this.#app.log.info('Mongo connection disconnected')
|
|
@@ -868,39 +1691,846 @@ export { validatorCompiler }
|
|
|
868
1691
|
}
|
|
869
1692
|
}
|
|
870
1693
|
|
|
871
|
-
await
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
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
|
+
])
|
|
879
1704
|
}
|
|
1705
|
+
|
|
880
1706
|
/**
|
|
881
1707
|
* @param {Object} args
|
|
882
|
-
* @param {Boolean} args.fastify
|
|
883
1708
|
* @param {String} args.projectName
|
|
884
1709
|
*/
|
|
885
|
-
|
|
886
|
-
const createFoldersCommand = `mkdir ${projectName}/src/
|
|
887
|
-
${projectName}/src/
|
|
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`
|
|
888
1715
|
|
|
889
1716
|
if (platform() === 'win32')
|
|
890
1717
|
await exec(createFoldersCommand.replaceAll('/', '\\'))
|
|
891
1718
|
else await exec(createFoldersCommand)
|
|
892
1719
|
|
|
893
|
-
const
|
|
1720
|
+
const graphQL = {
|
|
894
1721
|
index: {
|
|
895
|
-
content:
|
|
896
|
-
|
|
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 }
|
|
897
1745
|
`,
|
|
898
|
-
|
|
899
|
-
|
|
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
|
+
})
|
|
900
1770
|
}
|
|
1771
|
+
}
|
|
901
1772
|
|
|
902
|
-
|
|
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 })
|
|
903
2534
|
|
|
904
|
-
|
|
905
|
-
else await expressRestNetwork({ projectName })
|
|
2535
|
+
return await expressRestNetwork({ projectName })
|
|
906
2536
|
}
|