@anthonylzq/simba.js 3.1.0 → 4.2.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
- }
833
+ } as UserDTO
834
+ const us = new UserService({ userDto })
835
+ const result = await us.process({ type: 'update' })
802
836
 
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))
811
-
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
+ }
869
892
 
870
- export { userSchema, storeUserSchema }
893
+ next()
894
+ }
895
+ }
896
+
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": {
@@ -1368,16 +1395,17 @@ import { applyRoutes } from './router'
1368
1395
  const PORT = process.env.PORT ?? '1996'
1369
1396
 
1370
1397
  class Server {
1371
- private _app: FastifyInstance
1372
- private _connection: mongoose.Connection | undefined
1398
+ #app: FastifyInstance
1399
+ #connection: mongoose.Connection | undefined
1373
1400
 
1374
1401
  constructor() {
1375
- this._app = Fastify({ logger: true })
1376
- this._config()
1402
+ this.#app = Fastify({ logger: true })
1403
+ this.#config()
1377
1404
  }
1378
1405
 
1379
- private _config() {
1380
- this._app.addHook('preHandler', (req, reply, done) => {
1406
+ #config() {
1407
+ this.#app.register(require('fastify-cors'), {})
1408
+ this.#app.addHook('preHandler', (req, reply, done) => {
1381
1409
  reply.header('Access-Control-Allow-Methods', 'GET, POST, PATCH, DELETE')
1382
1410
  reply.header('Access-Control-Allow-Origin', '*')
1383
1411
  reply.header(
@@ -1386,25 +1414,25 @@ class Server {
1386
1414
  )
1387
1415
  done()
1388
1416
  })
1389
- applyRoutes(this._app)
1417
+ applyRoutes(this.#app)
1390
1418
  }
1391
1419
 
1392
- private async _mongo(): Promise<void> {
1393
- this._connection = mongoose.connection
1420
+ async #mongo(): Promise<void> {
1421
+ this.#connection = mongoose.connection
1394
1422
  const connection = {
1395
1423
  keepAlive: true,
1396
1424
  useNewUrlParser: true,
1397
1425
  useUnifiedTopology: true
1398
1426
  }
1399
- this._connection.on('connected', () => {
1400
- this._app.log.info('Mongo connection established.')
1427
+ this.#connection.on('connected', () => {
1428
+ this.#app.log.info('Mongo connection established.')
1401
1429
  })
1402
- this._connection.on('reconnected', () => {
1403
- this._app.log.info('Mongo connection reestablished')
1430
+ this.#connection.on('reconnected', () => {
1431
+ this.#app.log.info('Mongo connection reestablished')
1404
1432
  })
1405
- this._connection.on('disconnected', () => {
1406
- this._app.log.info('Mongo connection disconnected')
1407
- this._app.log.info('Trying to reconnected to Mongo...')
1433
+ this.#connection.on('disconnected', () => {
1434
+ this.#app.log.info('Mongo connection disconnected')
1435
+ this.#app.log.info('Trying to reconnected to Mongo...')
1408
1436
  setTimeout(() => {
1409
1437
  mongoose.connect(process.env.MONGO_URI as string, {
1410
1438
  ...connection,
@@ -1413,20 +1441,20 @@ class Server {
1413
1441
  })
1414
1442
  }, 3000)
1415
1443
  })
1416
- this._connection.on('close', () => {
1417
- this._app.log.info('Mongo connection closed')
1444
+ this.#connection.on('close', () => {
1445
+ this.#app.log.info('Mongo connection closed')
1418
1446
  })
1419
- this._connection.on('error', (e: Error) => {
1420
- this._app.log.info('Mongo connection error:')
1421
- this._app.log.error(e)
1447
+ this.#connection.on('error', (e: Error) => {
1448
+ this.#app.log.info('Mongo connection error:')
1449
+ this.#app.log.error(e)
1422
1450
  })
1423
1451
  await mongoose.connect(process.env.MONGO_URI as string, connection)
1424
1452
  }
1425
1453
 
1426
1454
  public async start(): Promise<void> {
1427
1455
  try {
1428
- await this._app.listen(PORT)
1429
- this._mongo()
1456
+ await this.#app.listen(PORT)
1457
+ this.#mongo()
1430
1458
  } catch (e) {
1431
1459
  console.error(e)
1432
1460
  }
@@ -1479,7 +1507,7 @@ const Docs = (app: FastifyInstance, prefix = '/api'): void => {
1479
1507
  }
1480
1508
 
1481
1509
  export { Docs }
1482
- `,
1510
+ `,
1483
1511
  file: `${projectName}/src/network/routes/docs.ts`
1484
1512
  },
1485
1513
  home: {
@@ -1510,30 +1538,37 @@ export * from './docs'
1510
1538
  },
1511
1539
  user: {
1512
1540
  content: `import { FastifyInstance } from 'fastify'
1541
+ import { Type } from '@sinclair/typebox'
1513
1542
 
1514
1543
  import { response } from 'network/response'
1515
- import { userSchema, idSchema, storeUserSchema } from './schemas'
1544
+ import {
1545
+ userDto,
1546
+ idSchema,
1547
+ IdSchema,
1548
+ storeUserSchema,
1549
+ StoreUser
1550
+ } from 'schemas'
1516
1551
  import { UserService } from 'services'
1552
+ import { validatorCompiler } from './utils'
1517
1553
 
1518
1554
  const User = (app: FastifyInstance, prefix = '/api'): void => {
1519
1555
  app
1520
- .post<{ Body: { args: Omit<DtoUser, 'id'> } }>(
1556
+ .post<{ Body: StoreUser }>(
1521
1557
  \`\${prefix}/users\`,
1522
1558
  {
1523
1559
  schema: {
1524
- body: {
1525
- args: storeUserSchema
1526
- },
1560
+ body: storeUserSchema,
1527
1561
  response: {
1528
1562
  200: {
1529
1563
  error: {
1530
1564
  type: 'boolean'
1531
1565
  },
1532
- message: userSchema
1566
+ message: userDto
1533
1567
  }
1534
1568
  },
1535
1569
  tags: ['user']
1536
- }
1570
+ },
1571
+ validatorCompiler
1537
1572
  },
1538
1573
  async (request, reply) => {
1539
1574
  const {
@@ -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,23 +1645,22 @@ 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']
1630
- }
1662
+ },
1663
+ validatorCompiler
1631
1664
  },
1632
1665
  async (request, reply) => {
1633
1666
  const {
@@ -1644,22 +1677,18 @@ const User = (app: FastifyInstance, prefix = '/api'): void => {
1644
1677
  })
1645
1678
  }
1646
1679
  )
1647
- .patch<{ Body: { args: Omit<DtoUser, 'id'> }; Params: { id: string } }>(
1680
+ .patch<{ Body: StoreUser; Params: IdSchema }>(
1648
1681
  \`\${prefix}/user/:id\`,
1649
1682
  {
1650
1683
  schema: {
1651
- body: {
1652
- args: userSchema
1653
- },
1654
- params: {
1655
- id: idSchema
1656
- },
1684
+ body: storeUserSchema,
1685
+ params: idSchema,
1657
1686
  response: {
1658
1687
  200: {
1659
1688
  error: {
1660
1689
  type: 'boolean'
1661
1690
  },
1662
- message: userSchema
1691
+ message: userDto
1663
1692
  }
1664
1693
  },
1665
1694
  tags: ['user']
@@ -1672,7 +1701,9 @@ const User = (app: FastifyInstance, prefix = '/api'): void => {
1672
1701
  },
1673
1702
  params: { id }
1674
1703
  } = request
1675
- const us = new UserService({ name, lastName, id })
1704
+ const us = new UserService({
1705
+ userDto: { name, lastName, id }
1706
+ })
1676
1707
  const user = await us.process({ type: 'update' })
1677
1708
 
1678
1709
  response({
@@ -1683,13 +1714,11 @@ const User = (app: FastifyInstance, prefix = '/api'): void => {
1683
1714
  })
1684
1715
  }
1685
1716
  )
1686
- .delete<{ Params: { id: string } }>(
1717
+ .delete<{ Params: IdSchema }>(
1687
1718
  \`\${prefix}/user/:id\`,
1688
1719
  {
1689
1720
  schema: {
1690
- params: {
1691
- id: idSchema
1692
- },
1721
+ params: idSchema,
1693
1722
  response: {
1694
1723
  200: {
1695
1724
  error: {
@@ -1725,45 +1754,54 @@ export { User }
1725
1754
  file: `${projectName}/src/network/routes/user.ts`
1726
1755
  }
1727
1756
  },
1728
- 'network/routes/schemas': {
1757
+ 'network/routes/utils': {
1729
1758
  index: {
1730
- content: `import { Type } from '@sinclair/typebox'
1759
+ content: `/* eslint-disable @typescript-eslint/no-explicit-any */
1760
+ import {
1761
+ FastifyRouteSchemaDef,
1762
+ FastifyValidationResult
1763
+ } from 'fastify/types/schema'
1764
+ import httpErrors from 'http-errors'
1765
+ import Ajv from 'ajv'
1731
1766
 
1732
- const idSchema = Type.String({ minLength: 24, maxLength: 24 })
1767
+ const ajv = new Ajv({
1768
+ removeAdditional: true,
1769
+ useDefaults: true,
1770
+ coerceTypes: true,
1771
+ nullable: true
1772
+ })
1733
1773
 
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'
1774
+ const validatorCompiler = ({
1775
+ schema
1776
+ }: FastifyRouteSchemaDef<any>): FastifyValidationResult => {
1777
+ const validate = ajv.compile(schema)
1741
1778
 
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
- })
1779
+ return (data: unknown): boolean => {
1780
+ const ok = validate(data)
1748
1781
 
1749
- const storeUserSchema = Type.Object({
1750
- lastName: Type.String(),
1751
- name: Type.String()
1752
- })
1782
+ if (!ok && validate.errors) {
1783
+ const [error] = validate.errors
1784
+ const errorMessage = \`\${error.dataPath.replace('.', '')} \${error.message}\`
1785
+
1786
+ throw new httpErrors.UnprocessableEntity(errorMessage)
1787
+ }
1788
+
1789
+ return true
1790
+ }
1791
+ }
1753
1792
 
1754
- export { userSchema, storeUserSchema }
1793
+ export { validatorCompiler }
1755
1794
  `,
1756
- file: `${projectName}/src/network/routes/schemas/user.ts`
1795
+ file: `${projectName}/src/network/routes/utils/index.ts`
1757
1796
  }
1758
1797
  }
1759
1798
  }
1760
1799
 
1761
- const expressFolders = `${projectName}/src/utils`
1800
+ const expressFolders = `${projectName}/src/utils \
1801
+ ${projectName}/src/@types/custom`
1762
1802
 
1763
1803
  const createFoldersCommands = `mkdir ${projectName}/src \
1764
1804
  ${projectName}/src/@types \
1765
- ${projectName}/src/@types/dto \
1766
- ${projectName}/src/@types/custom \
1767
1805
  ${projectName}/src/@types/models \
1768
1806
  ${projectName}/src/database \
1769
1807
  ${projectName}/src/database/mongo \
@@ -1771,7 +1809,8 @@ ${projectName}/src/database/mongo/models \
1771
1809
  ${projectName}/src/database/mongo/queries \
1772
1810
  ${projectName}/src/network \
1773
1811
  ${projectName}/src/network/routes \
1774
- ${projectName}/src/network/routes/schemas \
1812
+ ${projectName}/src/network/routes/utils \
1813
+ ${projectName}/src/schemas \
1775
1814
  ${projectName}/src/services \
1776
1815
  ${projectName}/src/services/utils \
1777
1816
  ${projectName}/src/services/utils/messages \
@@ -1785,19 +1824,12 @@ ${fastify ? '' : `${expressFolders}`}
1785
1824
  // /@types
1786
1825
  await writeFile(data['@types'].index.file, data['@types'].index.content)
1787
1826
 
1788
- // /@types/dto
1789
- await writeFile(data['@types/dto'].user.file, data['@types/dto'].user.content)
1790
-
1791
1827
  // /@types/models
1792
1828
  await writeFile(
1793
1829
  data['@types/models'].user.file,
1794
1830
  data['@types/models'].user.content
1795
1831
  )
1796
1832
 
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
1833
  // /database
1802
1834
  await writeFile(data.database.index.file, data.database.index.content)
1803
1835
  await writeFile(
@@ -1821,6 +1853,14 @@ ${fastify ? '' : `${expressFolders}`}
1821
1853
  data['database/mongo/queries'].user.content
1822
1854
  )
1823
1855
 
1856
+ // /schemas
1857
+ await writeFile(data.schemas.user.file, data.schemas.user.content)
1858
+ await writeFile(data.schemas.index.file, data.schemas.index.content)
1859
+
1860
+ // /services
1861
+ await writeFile(data.services.user.file, data.services.user.content)
1862
+ await writeFile(data.services.index.file, data.services.index.content)
1863
+
1824
1864
  // /services/utils
1825
1865
  await writeFile(
1826
1866
  data['services/utils'].index.file,
@@ -1882,16 +1922,22 @@ ${fastify ? '' : `${expressFolders}`}
1882
1922
  fastifyData['network/routes'].index.content
1883
1923
  )
1884
1924
 
1885
- // /network/routes/schemas
1925
+ // /network/routes/utils
1926
+ await writeFile(
1927
+ fastifyData['network/routes/utils'].index.file,
1928
+ fastifyData['network/routes/utils'].index.content
1929
+ )
1930
+ } else {
1931
+ // /@types/custom
1886
1932
  await writeFile(
1887
- fastifyData['network/routes/schemas'].index.file,
1888
- fastifyData['network/routes/schemas'].index.content
1933
+ expressData['@types/custom'].request.file,
1934
+ expressData['@types/custom'].request.content
1889
1935
  )
1890
1936
  await writeFile(
1891
- fastifyData['network/routes/schemas'].user.file,
1892
- fastifyData['network/routes/schemas'].user.content
1937
+ expressData['@types/custom'].response.file,
1938
+ expressData['@types/custom'].response.content
1893
1939
  )
1894
- } else {
1940
+
1895
1941
  // /network
1896
1942
  await writeFile(
1897
1943
  expressData.network.response.file,
@@ -1920,14 +1966,10 @@ ${fastify ? '' : `${expressFolders}`}
1920
1966
  expressData['network/routes'].index.content
1921
1967
  )
1922
1968
 
1923
- // /network/routes/schemas
1924
- await writeFile(
1925
- expressData['network/routes/schemas'].index.file,
1926
- expressData['network/routes/schemas'].index.content
1927
- )
1969
+ // /network/routes/utils
1928
1970
  await writeFile(
1929
- expressData['network/routes/schemas'].user.file,
1930
- expressData['network/routes/schemas'].user.content
1971
+ expressData['network/routes/utils'].index.file,
1972
+ expressData['network/routes/utils'].index.content
1931
1973
  )
1932
1974
 
1933
1975
  // /utils
@@ -1936,15 +1978,5 @@ ${fastify ? '' : `${expressFolders}`}
1936
1978
  expressData.utils.index.file,
1937
1979
  expressData.utils.index.content
1938
1980
  )
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
1981
  }
1950
1982
  }