@anthonylzq/simba.js 3.2.0 → 4.3.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.
@@ -121,23 +121,13 @@ export {}
121
121
  file: `${projectName}/src/@types/index.d.ts`
122
122
  }
123
123
  },
124
- '@types/dto': {
125
- user: {
126
- content: `interface DtoUser {
127
- id: string
128
- lastName: string
129
- name: string
130
- }
131
- `,
132
- file: `${projectName}/src/@types/dto/user.d.ts`
133
- }
134
- },
135
124
  '@types/models': {
136
125
  user: {
137
- content: `interface IUser {
126
+ content: `interface UserDBO {
138
127
  id: string
139
128
  name: string
140
129
  lastName: string
130
+ createdAt: Date
141
131
  updatedAt: Date
142
132
  }
143
133
  `,
@@ -166,7 +156,7 @@ export * from './queries'
166
156
  user: {
167
157
  content: `import { model, Schema } from 'mongoose'
168
158
 
169
- const User = new Schema<IUser>(
159
+ const UserSchema = new Schema<UserDBO>(
170
160
  {
171
161
  lastName: {
172
162
  required: true,
@@ -178,24 +168,18 @@ const User = new Schema<IUser>(
178
168
  }
179
169
  },
180
170
  {
181
- timestamps: {
182
- createdAt: false,
183
- updatedAt: true
184
- },
171
+ timestamps: true,
185
172
  versionKey: false,
186
- toJSON: {
187
- transform(_, ret) {
173
+ toObject: {
174
+ transform: (_, ret) => {
188
175
  ret.id = ret._id.toString()
189
- ret.updatedAt = ret.updatedAt.toISOString()
190
176
  delete ret._id
191
- delete ret.__v
192
- },
193
- virtuals: true
177
+ }
194
178
  }
195
179
  }
196
180
  )
197
181
 
198
- const UserModel = model<IUser>('users', User)
182
+ const UserModel = model<UserDBO>('users', UserSchema)
199
183
 
200
184
  export { UserModel }
201
185
  `,
@@ -208,42 +192,63 @@ export { UserModel }
208
192
  file: `${projectName}/src/database/mongo/queries/index.ts`
209
193
  },
210
194
  user: {
211
- content: `import { UserModel } from '..'
195
+ content: `import { Document, Types } from 'mongoose'
196
+
197
+ import { UserModel } from '..'
198
+ import { UserDTO } from 'schemas'
199
+
200
+ const userDBOtoDTO = (
201
+ userDBO: Document<unknown, unknown, UserDBO> &
202
+ UserDBO & {
203
+ _id: Types.ObjectId
204
+ }
205
+ ): UserDTO => ({
206
+ ...userDBO.toObject(),
207
+ createdAt: userDBO.createdAt.toISOString(),
208
+ updatedAt: userDBO.updatedAt.toISOString()
209
+ })
212
210
 
213
- const store = async (userData: DtoUser): Promise<IUser> => {
211
+ const store = async (userData: UserDTO): Promise<UserDTO> => {
214
212
  const user = new UserModel(userData)
213
+
215
214
  await user.save()
216
215
 
217
- return user.toJSON()
216
+ return userDBOtoDTO(user)
218
217
  }
219
218
 
220
219
  const remove = async (
221
220
  id: string | null = null
222
- ): Promise<IUser | number | null> => {
223
- if (id) return await UserModel.findByIdAndRemove(id)
221
+ ): Promise<UserDTO | number | null> => {
222
+ if (id) {
223
+ const removedUser = await UserModel.findByIdAndRemove(id)
224
+
225
+ if (!removedUser) return null
226
+
227
+ return userDBOtoDTO(removedUser)
228
+ }
224
229
 
225
230
  return (await UserModel.deleteMany({})).deletedCount
226
231
  }
227
232
 
228
233
  const get = async (
229
234
  id: string | null = null
230
- ): Promise<IUser[] | IUser | null> => {
235
+ ): Promise<UserDTO[] | UserDTO | null> => {
231
236
  if (id) {
232
237
  const user = await UserModel.findById(id)
233
238
 
234
- return user ? user.toJSON() : null
239
+ return user ? userDBOtoDTO(user) : null
235
240
  }
236
241
 
237
242
  const users = await UserModel.find({})
238
243
 
239
- return users.map(u => u.toJSON())
244
+ return users.map(u => userDBOtoDTO(u))
240
245
  }
241
246
 
242
- const update = async (userData: DtoUser): Promise<IUser | null> => {
247
+ const update = async (userData: UserDTO): Promise<UserDTO | null> => {
243
248
  const { id, ...rest } = userData
244
249
  const user = await UserModel.findByIdAndUpdate(id, rest, { new: true })
245
250
 
246
- return user ? user.toJSON() : null
251
+ return user ? userDBOtoDTO(user) : null
247
252
  }
248
253
 
249
254
  export { store, remove, get, update }
@@ -259,6 +264,58 @@ export * from './server'
259
264
  file: `${projectName}/src/network/index.ts`
260
265
  }
261
266
  },
267
+ schemas: {
268
+ index: {
269
+ content: `import { Static, Type } from '@sinclair/typebox'
270
+
271
+ const id = Type.String({
272
+ pattern: '^[a-zA-Z0-9]{24,}$'
273
+ })
274
+
275
+ type Id = Static<typeof id>
276
+
277
+ const idSchema = Type.Object({ id })
278
+
279
+ type IdSchema = Static<typeof idSchema>
280
+
281
+ export { id, Id, idSchema, IdSchema }
282
+ export * from './user'
283
+ `,
284
+ file: `${projectName}/src/schemas/index.ts`
285
+ },
286
+ user: {
287
+ content: `import { Static, Type } from '@sinclair/typebox'
288
+
289
+ import { id } from '.'
290
+
291
+ const user = Type.Object({
292
+ lastName: Type.String(),
293
+ name: Type.String()
294
+ })
295
+
296
+ type User = Static<typeof user>
297
+
298
+ const userDto = Type.Object({
299
+ id: Type.Optional(id),
300
+ lastName: Type.String(),
301
+ name: Type.String(),
302
+ createdAt: Type.Optional(Type.String()),
303
+ updatedAt: Type.Optional(Type.String())
304
+ })
305
+
306
+ type UserDTO = Static<typeof userDto>
307
+
308
+ const storeUserSchema = Type.Object({
309
+ args: user
310
+ })
311
+
312
+ type StoreUser = Static<typeof storeUserSchema>
313
+
314
+ export { userDto, UserDTO, user, User, storeUserSchema, StoreUser }
315
+ `,
316
+ file: `${projectName}/src/schemas/user.ts`
317
+ }
318
+ },
262
319
  services: {
263
320
  index: {
264
321
  content: "export * from './user'\n",
@@ -268,41 +325,53 @@ export * from './server'
268
325
  content: `import httpErrors from 'http-errors'
269
326
 
270
327
  import { store, remove, get, update } from 'database'
328
+ import { UserDTO } from 'schemas'
271
329
  import { EFU, MFU, GE, errorHandling } from './utils'
272
330
 
273
331
  type Process = {
274
332
  type: 'store' | 'getAll' | 'deleteAll' | 'getOne' | 'update' | 'delete'
275
333
  }
276
334
 
335
+ type Arguments = {
336
+ id?: string
337
+ userDto?: UserDTO
338
+ userDtoWithoutId?: Omit<UserDTO, 'id'>
339
+ }
340
+
277
341
  class UserService {
278
- private _args: Partial<DtoUser> | null
342
+ #args: Arguments
279
343
 
280
- constructor(args: Partial<DtoUser> | null = null) {
281
- this._args = args
344
+ constructor(args: Arguments = {}) {
345
+ this.#args = args
282
346
  }
283
347
 
284
- public process({ type }: Process): Promise<string | IUser[] | IUser> {
348
+ public process({ type }: Process): Promise<string | UserDTO | UserDTO[]> {
285
349
  switch (type) {
286
350
  case 'store':
287
- return this._store()
351
+ return this.#store()
288
352
  case 'getAll':
289
- return this._getAll()
353
+ return this.#getAll()
290
354
  case 'deleteAll':
291
- return this._deleteAll()
355
+ return this.#deleteAll()
292
356
  case 'getOne':
293
- return this._getOne()
357
+ return this.#getOne()
294
358
  case 'update':
295
- return this._update()
359
+ return this.#update()
296
360
  case 'delete':
297
- return this._delete()
361
+ return this.#delete()
298
362
  default:
299
363
  throw new httpErrors.InternalServerError(GE.INTERNAL_SERVER_ERROR)
300
364
  }
301
365
  }
302
366
 
303
- private async _store(): Promise<IUser> {
367
+ async #store(): Promise<UserDTO> {
304
368
  try {
305
- const result = await store(this._args as DtoUser)
369
+ if (!this.#args.userDtoWithoutId)
370
+ throw new httpErrors.UnprocessableEntity(GE.INTERNAL_SERVER_ERROR)
371
+
372
+ const result = await store({
373
+ ...this.#args.userDtoWithoutId
374
+ })
306
375
 
307
376
  return result
308
377
  } catch (e) {
@@ -310,9 +379,9 @@ class UserService {
310
379
  }
311
380
  }
312
381
 
313
- private async _getAll(): Promise<IUser[]> {
382
+ async #getAll(): Promise<UserDTO[]> {
314
383
  try {
315
- const users = (await get()) as IUser[]
384
+ const users = (await get()) as UserDTO[]
316
385
 
317
386
  return users
318
387
  } catch (e) {
@@ -320,7 +389,7 @@ class UserService {
320
389
  }
321
390
  }
322
391
 
323
- private async _deleteAll(): Promise<string> {
392
+ async #deleteAll(): Promise<string> {
324
393
  try {
325
394
  const usersDeleted = (await remove()) as number
326
395
 
@@ -335,11 +404,13 @@ class UserService {
335
404
  }
336
405
  }
337
406
 
338
- private async _getOne(): Promise<IUser> {
339
- const { id } = this._args as DtoUser
340
-
407
+ async #getOne(): Promise<UserDTO> {
341
408
  try {
342
- const user = (await get(id as string)) as IUser | null
409
+ if (!this.#args.id)
410
+ throw new httpErrors.UnprocessableEntity(GE.INTERNAL_SERVER_ERROR)
411
+
412
+ const { id } = this.#args
413
+ const user = (await get(id)) as UserDTO | null
343
414
 
344
415
  if (!user) throw new httpErrors.NotFound(EFU.NOT_FOUND)
345
416
 
@@ -349,9 +420,12 @@ class UserService {
349
420
  }
350
421
  }
351
422
 
352
- private async _update(): Promise<IUser> {
423
+ async #update(): Promise<UserDTO> {
353
424
  try {
354
- const updatedUser = await update(this._args as DtoUser)
425
+ if (!this.#args.userDto || !this.#args.userDto.id)
426
+ throw new httpErrors.UnprocessableEntity(GE.INTERNAL_SERVER_ERROR)
427
+
428
+ const updatedUser = await update(this.#args.userDto)
355
429
 
356
430
  if (!updatedUser) throw new httpErrors.NotFound(EFU.NOT_FOUND)
357
431
 
@@ -361,10 +435,12 @@ class UserService {
361
435
  }
362
436
  }
363
437
 
364
- private async _delete(): Promise<string> {
365
- const { id } = this._args as DtoUser
366
-
438
+ async #delete(): Promise<string> {
367
439
  try {
440
+ if (!this.#args.id)
441
+ throw new httpErrors.UnprocessableEntity(GE.INTERNAL_SERVER_ERROR)
442
+
443
+ const { id } = this.#args
368
444
  const deletedUser = await remove(id)
369
445
 
370
446
  if (!deletedUser) throw new httpErrors.NotFound(EFU.NOT_FOUND)
@@ -484,6 +560,33 @@ Server.start()
484
560
  }
485
561
 
486
562
  const expressData = {
563
+ '@types/custom': {
564
+ request: {
565
+ content: `type ExpressRequest = import('express').Request
566
+
567
+ interface CustomRequest extends ExpressRequest {
568
+ body: {
569
+ args?: import('schemas').UserDTO
570
+ }
571
+ // We can add custom headers via intersection, remember that for some reason
572
+ // headers must be in Snake-Pascal-Case
573
+ headers: import('http').IncomingHttpHeaders & {
574
+ 'Custom-Header'?: string
575
+ }
576
+ }
577
+ `,
578
+ file: `${projectName}/src/@types/custom/request.d.ts`
579
+ },
580
+ response: {
581
+ content: `type ExpressResponse = import('express').Response
582
+
583
+ interface CustomResponse extends ExpressResponse {
584
+ newValue?: string
585
+ }
586
+ `,
587
+ file: `${projectName}/src/@types/custom/response.d.ts`
588
+ }
589
+ },
487
590
  network: {
488
591
  response: {
489
592
  content: `interface ResponseProps {
@@ -547,26 +650,27 @@ export { applyRoutes }
547
650
  content: `import express from 'express'
548
651
  import mongoose from 'mongoose'
549
652
  import morgan from 'morgan'
653
+ import cors from 'cors'
550
654
 
551
655
  import { applyRoutes } from './router'
552
656
 
553
657
  const PORT = (process.env.PORT as string) || '1996'
554
658
 
555
659
  class Server {
556
- private _app: express.Application
557
- private _connection: mongoose.Connection | undefined
660
+ #app: express.Application
661
+ #connection: mongoose.Connection | undefined
558
662
 
559
663
  constructor() {
560
- this._app = express()
561
- this._config()
664
+ this.#app = express()
665
+ this.#config()
562
666
  }
563
667
 
564
- private _config() {
565
- this._app.set('port', PORT)
566
- this._app.use(morgan('dev'))
567
- this._app.use(express.json())
568
- this._app.use(express.urlencoded({ extended: false }))
569
- this._app.use(
668
+ #config() {
669
+ this.#app.use(cors())
670
+ this.#app.use(morgan('dev'))
671
+ this.#app.use(express.json())
672
+ this.#app.use(express.urlencoded({ extended: false }))
673
+ this.#app.use(
570
674
  (
571
675
  req: express.Request,
572
676
  res: express.Response,
@@ -582,23 +686,23 @@ class Server {
582
686
  }
583
687
  )
584
688
 
585
- applyRoutes(this._app)
689
+ applyRoutes(this.#app)
586
690
  }
587
691
 
588
- private async _mongo(): Promise<void> {
589
- this._connection = mongoose.connection
692
+ async #mongo(): Promise<void> {
693
+ this.#connection = mongoose.connection
590
694
  const connection = {
591
695
  keepAlive: true,
592
696
  useNewUrlParser: true,
593
697
  useUnifiedTopology: true
594
698
  }
595
- this._connection.on('connected', () => {
699
+ this.#connection.on('connected', () => {
596
700
  console.log('Mongo connection established.')
597
701
  })
598
- this._connection.on('reconnected', () => {
702
+ this.#connection.on('reconnected', () => {
599
703
  console.log('Mongo connection reestablished')
600
704
  })
601
- this._connection.on('disconnected', () => {
705
+ this.#connection.on('disconnected', () => {
602
706
  console.log('Mongo connection disconnected')
603
707
  console.log('Trying to reconnected to Mongo...')
604
708
  setTimeout(() => {
@@ -609,10 +713,10 @@ class Server {
609
713
  })
610
714
  }, 3000)
611
715
  })
612
- this._connection.on('close', () => {
716
+ this.#connection.on('close', () => {
613
717
  console.log('Mongo connection closed')
614
718
  })
615
- this._connection.on('error', (e: Error) => {
719
+ this.#connection.on('error', (e: Error) => {
616
720
  console.log('Mongo connection error:')
617
721
  console.error(e)
618
722
  })
@@ -620,12 +724,12 @@ class Server {
620
724
  }
621
725
 
622
726
  public start(): void {
623
- this._app.listen(PORT, () => {
727
+ this.#app.listen(PORT, () => {
624
728
  console.log(\`Server running at port \${PORT}\`)
625
729
  })
626
730
 
627
731
  try {
628
- this._mongo()
732
+ this.#mongo()
629
733
  } catch (e) {
630
734
  console.error(e)
631
735
  }
@@ -639,33 +743,6 @@ export { server as Server }
639
743
  file: `${projectName}/src/network/server.ts`
640
744
  }
641
745
  },
642
- '@types/custom': {
643
- request: {
644
- content: `type ExpressRequest = import('express').Request
645
-
646
- interface CustomRequest extends ExpressRequest {
647
- body: {
648
- args?: DtoUser
649
- }
650
- // We can add custom headers via intersection, remember that for some reason
651
- // headers must be in Snake-Pascal-Case
652
- headers: import('http').IncomingHttpHeaders & {
653
- 'Custom-Header'?: string
654
- }
655
- }
656
- `,
657
- file: `${projectName}/src/@types/custom/request.d.ts`
658
- },
659
- response: {
660
- content: `type ExpressResponse = import('express').Response
661
-
662
- interface CustomResponse extends ExpressResponse {
663
- newValue?: string
664
- }
665
- `,
666
- file: `${projectName}/src/@types/custom/response.d.ts`
667
- }
668
- },
669
746
  'network/routes': {
670
747
  home: {
671
748
  content: `import { Response, Request, Router } from 'express'
@@ -694,146 +771,82 @@ export * from './user'
694
771
  file: `${projectName}/src/network/routes/index.ts`
695
772
  },
696
773
  user: {
697
- content: `import { Router, NextFunction } from 'express'
698
- import httpErrors from 'http-errors'
699
- import { ValidationError } from 'joi'
774
+ content: `import { Router } from 'express'
700
775
 
701
776
  import { response } from 'network/response'
702
- import { UserService } from 'services/user'
703
- import { idSchema, userSchema, storeUserSchema } from './schemas'
777
+ import { UserService } from 'services'
778
+ import { idSchema, storeUserSchema, UserDTO } from 'schemas'
779
+ import { validatorCompiler } from './utils'
704
780
 
705
781
  const User = Router()
706
782
 
707
783
  User.route('/users')
708
784
  .post(
709
- async (
710
- req: CustomRequest,
711
- res: CustomResponse,
712
- next: NextFunction
713
- ): Promise<void> => {
785
+ validatorCompiler(storeUserSchema, 'body'),
786
+ async (req: CustomRequest, res: CustomResponse): Promise<void> => {
714
787
  const {
715
788
  body: { args }
716
789
  } = req
790
+ const us = new UserService({ userDtoWithoutId: args })
791
+ const result = await us.process({ type: 'store' })
717
792
 
718
- try {
719
- await storeUserSchema.validateAsync(args)
720
- const us = new UserService(args)
721
- const result = await us.process({ type: 'store' })
722
- response({ error: false, message: result, res, status: 201 })
723
- } catch (e) {
724
- if (e instanceof ValidationError)
725
- return next(new httpErrors.UnprocessableEntity(e.message))
726
-
727
- next(e)
728
- }
729
- }
730
- )
731
- .get(
732
- async (
733
- req: CustomRequest,
734
- res: CustomResponse,
735
- next: NextFunction
736
- ): Promise<void> => {
737
- const us = new UserService()
738
-
739
- try {
740
- const result = await us.process({ type: 'getAll' })
741
- response({ error: false, message: result, res, status: 200 })
742
- } catch (e) {
743
- next(e)
744
- }
793
+ response({ error: false, message: result, res, status: 201 })
745
794
  }
746
795
  )
747
- .delete(
748
- async (
749
- req: CustomRequest,
750
- res: CustomResponse,
751
- next: NextFunction
752
- ): Promise<void> => {
753
- const us = new UserService()
796
+ .get(async (req: CustomRequest, res: CustomResponse): Promise<void> => {
797
+ const us = new UserService()
798
+ const result = await us.process({ type: 'getAll' })
754
799
 
755
- try {
756
- const result = await us.process({ type: 'deleteAll' })
757
- response({ error: false, message: result, res, status: 200 })
758
- } catch (e) {
759
- next(e)
760
- }
761
- }
762
- )
800
+ response({ error: false, message: result, res, status: 200 })
801
+ })
802
+ .delete(async (req: CustomRequest, res: CustomResponse): Promise<void> => {
803
+ const us = new UserService()
804
+ const result = await us.process({ type: 'deleteAll' })
805
+
806
+ response({ error: false, message: result, res, status: 200 })
807
+ })
763
808
 
764
809
  User.route('/user/:id')
765
810
  .get(
766
- async (
767
- req: CustomRequest,
768
- res: CustomResponse,
769
- next: NextFunction
770
- ): Promise<void> => {
811
+ validatorCompiler(idSchema, 'params'),
812
+ async (req: CustomRequest, res: CustomResponse): Promise<void> => {
771
813
  const {
772
814
  params: { id }
773
815
  } = req
816
+ const us = new UserService({ id })
817
+ const result = await us.process({ type: 'getOne' })
774
818
 
775
- try {
776
- await idSchema.validateAsync(id)
777
- const us = new UserService({ id })
778
- const result = await us.process({ type: 'getOne' })
779
- response({ error: false, message: result, res, status: 200 })
780
- } catch (e) {
781
- if (e instanceof ValidationError)
782
- return next(new httpErrors.UnprocessableEntity(e.message))
783
-
784
- next(e)
785
- }
819
+ response({ error: false, message: result, res, status: 200 })
786
820
  }
787
821
  )
788
822
  .patch(
789
- async (
790
- req: CustomRequest,
791
- res: CustomResponse,
792
- next: NextFunction
793
- ): Promise<void> => {
823
+ validatorCompiler(idSchema, 'params'),
824
+ validatorCompiler(storeUserSchema, 'body'),
825
+ async (req: CustomRequest, res: CustomResponse): Promise<void> => {
794
826
  const {
795
827
  body: { args },
796
828
  params: { id }
797
829
  } = req
798
- const user = {
830
+ const userDto = {
799
831
  id,
800
832
  ...args
801
- }
802
-
803
- try {
804
- await userSchema.validateAsync(user)
805
- const us = new UserService(user)
806
- const result = await us.process({ type: 'update' })
807
- response({ error: false, message: result, res, status: 200 })
808
- } catch (e) {
809
- if (e instanceof ValidationError)
810
- return next(new httpErrors.UnprocessableEntity(e.message))
833
+ } as UserDTO
834
+ const us = new UserService({ userDto })
835
+ const result = await us.process({ type: 'update' })
811
836
 
812
- next(e)
813
- }
837
+ response({ error: false, message: result, res, status: 200 })
814
838
  }
815
839
  )
816
840
  .delete(
817
- async (
818
- req: CustomRequest,
819
- res: CustomResponse,
820
- next: NextFunction
821
- ): Promise<void> => {
841
+ validatorCompiler(idSchema, 'params'),
842
+ async (req: CustomRequest, res: CustomResponse): Promise<void> => {
822
843
  const {
823
844
  params: { id }
824
845
  } = req
846
+ const us = new UserService({ id })
847
+ const result = await us.process({ type: 'delete' })
825
848
 
826
- try {
827
- await idSchema.validateAsync(id)
828
- const us = new UserService({ id })
829
- const result = await us.process({ type: 'delete' })
830
- response({ error: false, message: result, res, status: 200 })
831
- } catch (e) {
832
- if (e instanceof ValidationError)
833
- return next(new httpErrors.UnprocessableEntity(e.message))
834
-
835
- next(e)
836
- }
849
+ response({ error: false, message: result, res, status: 200 })
837
850
  }
838
851
  )
839
852
 
@@ -842,34 +855,48 @@ export { User }
842
855
  file: `${projectName}/src/network/routes/user.ts`
843
856
  }
844
857
  },
845
- 'network/routes/schemas': {
858
+ 'network/routes/utils': {
846
859
  index: {
847
- content: `import Joi from 'joi'
848
-
849
- const idSchema = Joi.string().length(24).required()
850
-
851
- export { idSchema }
852
- export * from './user'
853
- `,
854
- file: `${projectName}/src/network/routes/schemas/index.ts`
855
- },
856
- user: {
857
- content: `import Joi from 'joi'
858
-
859
- const userSchema = Joi.object().keys({
860
- id: Joi.string().length(24).required(),
861
- lastName: Joi.string().required(),
862
- name: Joi.string().required()
860
+ content: `import { NextFunction } from 'express'
861
+ import httpErrors from 'http-errors'
862
+ import { TObject, TProperties } from '@sinclair/typebox'
863
+ import Ajv from 'ajv'
864
+
865
+ const ajv = new Ajv({
866
+ removeAdditional: true,
867
+ useDefaults: true,
868
+ coerceTypes: true,
869
+ nullable: true
863
870
  })
864
871
 
865
- const storeUserSchema = Joi.object().keys({
866
- lastName: Joi.string().required(),
867
- name: Joi.string().required()
868
- })
872
+ type Middleware = (
873
+ req: CustomRequest,
874
+ res: CustomResponse,
875
+ next: NextFunction
876
+ ) => void
877
+
878
+ const validatorCompiler = <T extends TProperties>(
879
+ schema: TObject<T>,
880
+ value: 'body' | 'params'
881
+ ): Middleware => {
882
+ return (req: CustomRequest, res: CustomResponse, next: NextFunction) => {
883
+ const validate = ajv.compile(schema)
884
+ const ok = validate(req[value])
885
+
886
+ if (!ok && validate.errors) {
887
+ const [error] = validate.errors
888
+ const errorMessage = \`\${error.dataPath.replace('.', '')} \${error.message}\`
889
+
890
+ return next(new httpErrors.UnprocessableEntity(errorMessage))
891
+ }
892
+
893
+ next()
894
+ }
895
+ }
869
896
 
870
- export { userSchema, storeUserSchema }
897
+ export { validatorCompiler }
871
898
  `,
872
- file: `${projectName}/src/network/routes/schemas/user.ts`
899
+ file: `${projectName}/src/network/routes/utils/index.ts`
873
900
  }
874
901
  },
875
902
  utils: {
@@ -909,7 +936,7 @@ export { userSchema, storeUserSchema }
909
936
  "summary": "Save a user in the database",
910
937
  "operationId": "store",
911
938
  "requestBody": {
912
- "$ref": "#/components/requestBodies/DtoUser"
939
+ "$ref": "#/components/requestBodies/UserDTO"
913
940
  },
914
941
  "responses": {
915
942
  "201": {
@@ -1103,7 +1130,7 @@ export { userSchema, storeUserSchema }
1103
1130
  }
1104
1131
  ],
1105
1132
  "requestBody": {
1106
- "$ref": "#/components/requestBodies/DtoUser"
1133
+ "$ref": "#/components/requestBodies/UserDTO"
1107
1134
  },
1108
1135
  "responses": {
1109
1136
  "200": {
@@ -1264,7 +1291,7 @@ export { userSchema, storeUserSchema }
1264
1291
  }
1265
1292
  },
1266
1293
  "requestBodies": {
1267
- "DtoUser": {
1294
+ "UserDTO": {
1268
1295
  "description": "User name and last name",
1269
1296
  "content": {
1270
1297
  "application/json": {
@@ -1360,24 +1387,26 @@ export { applyRoutes }
1360
1387
  file: `${projectName}/src/network/router.ts`
1361
1388
  },
1362
1389
  server: {
1363
- content: `import Fastify, { FastifyInstance } from 'fastify'
1390
+ content: `import fastify, { FastifyInstance } from 'fastify'
1364
1391
  import mongoose from 'mongoose'
1365
1392
 
1366
1393
  import { applyRoutes } from './router'
1394
+ import { validatorCompiler } from './utils'
1367
1395
 
1368
1396
  const PORT = process.env.PORT ?? '1996'
1369
1397
 
1370
1398
  class Server {
1371
- private _app: FastifyInstance
1372
- private _connection: mongoose.Connection | undefined
1399
+ #app: FastifyInstance
1400
+ #connection: mongoose.Connection | undefined
1373
1401
 
1374
1402
  constructor() {
1375
- this._app = Fastify({ logger: true })
1376
- this._config()
1403
+ this.#app = fastify({ logger: { prettyPrint: true } })
1404
+ this.#config()
1377
1405
  }
1378
1406
 
1379
- private _config() {
1380
- this._app.addHook('preHandler', (req, reply, done) => {
1407
+ #config() {
1408
+ this.#app.register(require('fastify-cors'), {})
1409
+ this.#app.addHook('preHandler', (req, reply, done) => {
1381
1410
  reply.header('Access-Control-Allow-Methods', 'GET, POST, PATCH, DELETE')
1382
1411
  reply.header('Access-Control-Allow-Origin', '*')
1383
1412
  reply.header(
@@ -1386,25 +1415,26 @@ class Server {
1386
1415
  )
1387
1416
  done()
1388
1417
  })
1389
- applyRoutes(this._app)
1418
+ this.#app.setValidatorCompiler(validatorCompiler)
1419
+ applyRoutes(this.#app)
1390
1420
  }
1391
1421
 
1392
- private async _mongo(): Promise<void> {
1393
- this._connection = mongoose.connection
1422
+ async #mongo(): Promise<void> {
1423
+ this.#connection = mongoose.connection
1394
1424
  const connection = {
1395
1425
  keepAlive: true,
1396
1426
  useNewUrlParser: true,
1397
1427
  useUnifiedTopology: true
1398
1428
  }
1399
- this._connection.on('connected', () => {
1400
- this._app.log.info('Mongo connection established.')
1429
+ this.#connection.on('connected', () => {
1430
+ this.#app.log.info('Mongo connection established.')
1401
1431
  })
1402
- this._connection.on('reconnected', () => {
1403
- this._app.log.info('Mongo connection reestablished')
1432
+ this.#connection.on('reconnected', () => {
1433
+ this.#app.log.info('Mongo connection reestablished')
1404
1434
  })
1405
- this._connection.on('disconnected', () => {
1406
- this._app.log.info('Mongo connection disconnected')
1407
- this._app.log.info('Trying to reconnected to Mongo...')
1435
+ this.#connection.on('disconnected', () => {
1436
+ this.#app.log.info('Mongo connection disconnected')
1437
+ this.#app.log.info('Trying to reconnected to Mongo...')
1408
1438
  setTimeout(() => {
1409
1439
  mongoose.connect(process.env.MONGO_URI as string, {
1410
1440
  ...connection,
@@ -1413,20 +1443,20 @@ class Server {
1413
1443
  })
1414
1444
  }, 3000)
1415
1445
  })
1416
- this._connection.on('close', () => {
1417
- this._app.log.info('Mongo connection closed')
1446
+ this.#connection.on('close', () => {
1447
+ this.#app.log.info('Mongo connection closed')
1418
1448
  })
1419
- this._connection.on('error', (e: Error) => {
1420
- this._app.log.info('Mongo connection error:')
1421
- this._app.log.error(e)
1449
+ this.#connection.on('error', (e: Error) => {
1450
+ this.#app.log.info('Mongo connection error:')
1451
+ this.#app.log.error(e)
1422
1452
  })
1423
1453
  await mongoose.connect(process.env.MONGO_URI as string, connection)
1424
1454
  }
1425
1455
 
1426
1456
  public async start(): Promise<void> {
1427
1457
  try {
1428
- await this._app.listen(PORT)
1429
- this._mongo()
1458
+ await this.#app.listen(PORT)
1459
+ this.#mongo()
1430
1460
  } catch (e) {
1431
1461
  console.error(e)
1432
1462
  }
@@ -1510,26 +1540,31 @@ export * from './docs'
1510
1540
  },
1511
1541
  user: {
1512
1542
  content: `import { FastifyInstance } from 'fastify'
1543
+ import { Type } from '@sinclair/typebox'
1513
1544
 
1514
1545
  import { response } from 'network/response'
1515
- import { userSchema, idSchema, storeUserSchema } from './schemas'
1546
+ import {
1547
+ userDto,
1548
+ idSchema,
1549
+ IdSchema,
1550
+ storeUserSchema,
1551
+ StoreUser
1552
+ } from 'schemas'
1516
1553
  import { UserService } from 'services'
1517
1554
 
1518
1555
  const User = (app: FastifyInstance, prefix = '/api'): void => {
1519
1556
  app
1520
- .post<{ Body: { args: Omit<DtoUser, 'id'> } }>(
1557
+ .post<{ Body: StoreUser }>(
1521
1558
  \`\${prefix}/users\`,
1522
1559
  {
1523
1560
  schema: {
1524
- body: {
1525
- args: storeUserSchema
1526
- },
1561
+ body: storeUserSchema,
1527
1562
  response: {
1528
1563
  200: {
1529
1564
  error: {
1530
1565
  type: 'boolean'
1531
1566
  },
1532
- message: userSchema
1567
+ message: userDto
1533
1568
  }
1534
1569
  },
1535
1570
  tags: ['user']
@@ -1541,14 +1576,16 @@ const User = (app: FastifyInstance, prefix = '/api'): void => {
1541
1576
  args: { lastName, name }
1542
1577
  }
1543
1578
  } = request
1544
- const us = new UserService({ lastName, name })
1579
+ const us = new UserService({
1580
+ userDtoWithoutId: { lastName, name }
1581
+ })
1545
1582
  const user = await us.process({ type: 'store' })
1546
1583
 
1547
1584
  response({
1548
1585
  error: false,
1549
1586
  message: user,
1550
1587
  reply,
1551
- status: 200
1588
+ status: 201
1552
1589
  })
1553
1590
  }
1554
1591
  )
@@ -1561,10 +1598,7 @@ const User = (app: FastifyInstance, prefix = '/api'): void => {
1561
1598
  error: {
1562
1599
  type: 'boolean'
1563
1600
  },
1564
- message: {
1565
- type: 'array',
1566
- items: userSchema
1567
- }
1601
+ message: Type.Array(userDto)
1568
1602
  }
1569
1603
  },
1570
1604
  tags: ['user']
@@ -1611,19 +1645,17 @@ const User = (app: FastifyInstance, prefix = '/api'): void => {
1611
1645
  })
1612
1646
  }
1613
1647
  )
1614
- .get<{ Params: { id: string } }>(
1648
+ .get<{ Params: IdSchema }>(
1615
1649
  \`\${prefix}/user/:id\`,
1616
1650
  {
1617
1651
  schema: {
1618
- params: {
1619
- id: idSchema
1620
- },
1652
+ params: idSchema,
1621
1653
  response: {
1622
1654
  200: {
1623
1655
  error: {
1624
1656
  type: 'boolean'
1625
1657
  },
1626
- message: userSchema
1658
+ message: userDto
1627
1659
  }
1628
1660
  },
1629
1661
  tags: ['user']
@@ -1644,22 +1676,18 @@ const User = (app: FastifyInstance, prefix = '/api'): void => {
1644
1676
  })
1645
1677
  }
1646
1678
  )
1647
- .patch<{ Body: { args: Omit<DtoUser, 'id'> }; Params: { id: string } }>(
1679
+ .patch<{ Body: StoreUser; Params: IdSchema }>(
1648
1680
  \`\${prefix}/user/:id\`,
1649
1681
  {
1650
1682
  schema: {
1651
- body: {
1652
- args: userSchema
1653
- },
1654
- params: {
1655
- id: idSchema
1656
- },
1683
+ body: storeUserSchema,
1684
+ params: idSchema,
1657
1685
  response: {
1658
1686
  200: {
1659
1687
  error: {
1660
1688
  type: 'boolean'
1661
1689
  },
1662
- message: userSchema
1690
+ message: userDto
1663
1691
  }
1664
1692
  },
1665
1693
  tags: ['user']
@@ -1672,7 +1700,9 @@ const User = (app: FastifyInstance, prefix = '/api'): void => {
1672
1700
  },
1673
1701
  params: { id }
1674
1702
  } = request
1675
- const us = new UserService({ name, lastName, id })
1703
+ const us = new UserService({
1704
+ userDto: { name, lastName, id }
1705
+ })
1676
1706
  const user = await us.process({ type: 'update' })
1677
1707
 
1678
1708
  response({
@@ -1683,13 +1713,11 @@ const User = (app: FastifyInstance, prefix = '/api'): void => {
1683
1713
  })
1684
1714
  }
1685
1715
  )
1686
- .delete<{ Params: { id: string } }>(
1716
+ .delete<{ Params: IdSchema }>(
1687
1717
  \`\${prefix}/user/:id\`,
1688
1718
  {
1689
1719
  schema: {
1690
- params: {
1691
- id: idSchema
1692
- },
1720
+ params: idSchema,
1693
1721
  response: {
1694
1722
  200: {
1695
1723
  error: {
@@ -1725,45 +1753,57 @@ export { User }
1725
1753
  file: `${projectName}/src/network/routes/user.ts`
1726
1754
  }
1727
1755
  },
1728
- 'network/routes/schemas': {
1756
+ 'network/utils': {
1729
1757
  index: {
1730
- content: `import { Type } from '@sinclair/typebox'
1758
+ content: `/* eslint-disable @typescript-eslint/no-explicit-any */
1759
+ import {
1760
+ FastifyRouteSchemaDef,
1761
+ FastifyValidationResult
1762
+ } from 'fastify/types/schema'
1763
+ import httpErrors from 'http-errors'
1764
+ import Ajv from 'ajv'
1731
1765
 
1732
- const idSchema = Type.String({ minLength: 24, maxLength: 24 })
1766
+ const ajv = new Ajv({
1767
+ removeAdditional: true,
1768
+ useDefaults: true,
1769
+ coerceTypes: true,
1770
+ nullable: true
1771
+ })
1733
1772
 
1734
- export { idSchema }
1735
- export * from './user'
1736
- `,
1737
- file: `${projectName}/src/network/routes/schemas/index.ts`
1738
- },
1739
- user: {
1740
- content: `import { Type } from '@sinclair/typebox'
1773
+ const validatorCompiler = ({
1774
+ schema
1775
+ }: FastifyRouteSchemaDef<any>): FastifyValidationResult => {
1776
+ const validate = ajv.compile(schema)
1741
1777
 
1742
- const userSchema = Type.Object({
1743
- id: Type.Optional(Type.String({ minLength: 24, maxLength: 24 })),
1744
- lastName: Type.Optional(Type.String()),
1745
- name: Type.Optional(Type.String()),
1746
- updatedAt: Type.Optional(Type.String())
1747
- })
1778
+ return (data: unknown): boolean => {
1779
+ const ok = validate(data)
1748
1780
 
1749
- const storeUserSchema = Type.Object({
1750
- lastName: Type.String(),
1751
- name: Type.String()
1752
- })
1781
+ if (!ok && validate.errors) {
1782
+ const [error] = validate.errors
1783
+ const errorMessage = \`\${error.dataPath.replace('.', '')} \${error.message}\`
1784
+
1785
+ throw new httpErrors.UnprocessableEntity(errorMessage)
1786
+ }
1753
1787
 
1754
- export { userSchema, storeUserSchema }
1788
+ return true
1789
+ }
1790
+ }
1791
+
1792
+ export { validatorCompiler }
1755
1793
  `,
1756
- file: `${projectName}/src/network/routes/schemas/user.ts`
1794
+ file: `${projectName}/src/network/utils/index.ts`
1757
1795
  }
1758
1796
  }
1759
1797
  }
1760
1798
 
1761
- const expressFolders = `${projectName}/src/utils`
1799
+ const expressFolders = `${projectName}/src/utils \
1800
+ ${projectName}/src/@types/custom \
1801
+ ${projectName}/src/network/routes/utils`
1802
+
1803
+ const fastifyFolders = `${projectName}/src/network/utils`
1762
1804
 
1763
1805
  const createFoldersCommands = `mkdir ${projectName}/src \
1764
1806
  ${projectName}/src/@types \
1765
- ${projectName}/src/@types/dto \
1766
- ${projectName}/src/@types/custom \
1767
1807
  ${projectName}/src/@types/models \
1768
1808
  ${projectName}/src/database \
1769
1809
  ${projectName}/src/database/mongo \
@@ -1771,11 +1811,11 @@ ${projectName}/src/database/mongo/models \
1771
1811
  ${projectName}/src/database/mongo/queries \
1772
1812
  ${projectName}/src/network \
1773
1813
  ${projectName}/src/network/routes \
1774
- ${projectName}/src/network/routes/schemas \
1814
+ ${projectName}/src/schemas \
1775
1815
  ${projectName}/src/services \
1776
1816
  ${projectName}/src/services/utils \
1777
1817
  ${projectName}/src/services/utils/messages \
1778
- ${fastify ? '' : `${expressFolders}`}
1818
+ ${fastify ? `${fastifyFolders}` : `${expressFolders}`}
1779
1819
  `
1780
1820
 
1781
1821
  if (os.platform() === 'win32')
@@ -1785,19 +1825,12 @@ ${fastify ? '' : `${expressFolders}`}
1785
1825
  // /@types
1786
1826
  await writeFile(data['@types'].index.file, data['@types'].index.content)
1787
1827
 
1788
- // /@types/dto
1789
- await writeFile(data['@types/dto'].user.file, data['@types/dto'].user.content)
1790
-
1791
1828
  // /@types/models
1792
1829
  await writeFile(
1793
1830
  data['@types/models'].user.file,
1794
1831
  data['@types/models'].user.content
1795
1832
  )
1796
1833
 
1797
- // /services
1798
- await writeFile(data.services.user.file, data.services.user.content)
1799
- await writeFile(data.services.index.file, data.services.index.content)
1800
-
1801
1834
  // /database
1802
1835
  await writeFile(data.database.index.file, data.database.index.content)
1803
1836
  await writeFile(
@@ -1821,6 +1854,14 @@ ${fastify ? '' : `${expressFolders}`}
1821
1854
  data['database/mongo/queries'].user.content
1822
1855
  )
1823
1856
 
1857
+ // /schemas
1858
+ await writeFile(data.schemas.user.file, data.schemas.user.content)
1859
+ await writeFile(data.schemas.index.file, data.schemas.index.content)
1860
+
1861
+ // /services
1862
+ await writeFile(data.services.user.file, data.services.user.content)
1863
+ await writeFile(data.services.index.file, data.services.index.content)
1864
+
1824
1865
  // /services/utils
1825
1866
  await writeFile(
1826
1867
  data['services/utils'].index.file,
@@ -1882,16 +1923,22 @@ ${fastify ? '' : `${expressFolders}`}
1882
1923
  fastifyData['network/routes'].index.content
1883
1924
  )
1884
1925
 
1885
- // /network/routes/schemas
1926
+ // /network/routes/utils
1927
+ await writeFile(
1928
+ fastifyData['network/utils'].index.file,
1929
+ fastifyData['network/utils'].index.content
1930
+ )
1931
+ } else {
1932
+ // /@types/custom
1886
1933
  await writeFile(
1887
- fastifyData['network/routes/schemas'].index.file,
1888
- fastifyData['network/routes/schemas'].index.content
1934
+ expressData['@types/custom'].request.file,
1935
+ expressData['@types/custom'].request.content
1889
1936
  )
1890
1937
  await writeFile(
1891
- fastifyData['network/routes/schemas'].user.file,
1892
- fastifyData['network/routes/schemas'].user.content
1938
+ expressData['@types/custom'].response.file,
1939
+ expressData['@types/custom'].response.content
1893
1940
  )
1894
- } else {
1941
+
1895
1942
  // /network
1896
1943
  await writeFile(
1897
1944
  expressData.network.response.file,
@@ -1920,14 +1967,10 @@ ${fastify ? '' : `${expressFolders}`}
1920
1967
  expressData['network/routes'].index.content
1921
1968
  )
1922
1969
 
1923
- // /network/routes/schemas
1924
- await writeFile(
1925
- expressData['network/routes/schemas'].index.file,
1926
- expressData['network/routes/schemas'].index.content
1927
- )
1970
+ // /network/routes/utils
1928
1971
  await writeFile(
1929
- expressData['network/routes/schemas'].user.file,
1930
- expressData['network/routes/schemas'].user.content
1972
+ expressData['network/routes/utils'].index.file,
1973
+ expressData['network/routes/utils'].index.content
1931
1974
  )
1932
1975
 
1933
1976
  // /utils
@@ -1936,15 +1979,5 @@ ${fastify ? '' : `${expressFolders}`}
1936
1979
  expressData.utils.index.file,
1937
1980
  expressData.utils.index.content
1938
1981
  )
1939
-
1940
- // /@types/custom
1941
- await writeFile(
1942
- expressData['@types/custom'].request.file,
1943
- expressData['@types/custom'].request.content
1944
- )
1945
- await writeFile(
1946
- expressData['@types/custom'].response.file,
1947
- expressData['@types/custom'].response.content
1948
- )
1949
1982
  }
1950
1983
  }