From 335d46f09e06c56265e41fa5d115b59b1ffbbec9 Mon Sep 17 00:00:00 2001 From: jlenon7 Date: Wed, 8 May 2024 15:29:11 +0100 Subject: [PATCH] feat(validator): add validation to update route --- package.json | 1 + src/providers/validator.provider.ts | 14 +++++++++++++ src/routes/http.ts | 4 +++- src/services/user.service.ts | 2 -- src/validators/update.validator.ts | 31 +++++++++++++++++++++++++++++ storage/queues.json | 2 +- tests/e2e/user.controller.test.ts | 9 +++++++-- 7 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 src/validators/update.validator.ts diff --git a/package.json b/package.json index 3d57b5e..7d87083 100644 --- a/package.json +++ b/package.json @@ -280,6 +280,7 @@ "#src/middlewares/auth.middleware", "#src/validators/login.validator", "#src/validators/register.validator", + "#src/validators/update.validator", "#src/interceptors/response.interceptor", "#src/middlewares/pagination.middleware" ] diff --git a/src/providers/validator.provider.ts b/src/providers/validator.provider.ts index e65dbfb..72333bf 100644 --- a/src/providers/validator.provider.ts +++ b/src/providers/validator.provider.ts @@ -9,6 +9,7 @@ import { ValidationException } from '#src/exceptions/validation.exception' type UniqueOptions = { table: string column?: string + max?: number } declare module '@vinejs/vine' { @@ -43,6 +44,19 @@ export default class ValidatorProvider extends ServiceProvider { options.column = field.name as string } + if (options.max) { + const rows = await Database.table(options.table) + .select(options.column) + .where(options.column, value) + .findMany() + + if (rows.length > options.max) { + field.report('The {{ field }} field is not unique', 'unique', field) + } + + return + } + const existsRow = await Database.table(options.table) .select(options.column) .where(options.column, value) diff --git a/src/routes/http.ts b/src/routes/http.ts index a86003a..baadfc7 100644 --- a/src/routes/http.ts +++ b/src/routes/http.ts @@ -11,7 +11,9 @@ Route.group(() => { Route.group(() => { Route.get('users', 'UserController.index').middleware('pagination') Route.get('users/:id', 'UserController.show') - Route.put('users/:id', 'UserController.update') + Route.put('users/:id', 'UserController.update').middleware( + 'update:validator' + ) Route.delete('users/:id', 'UserController.delete') }).name('users') }).middleware('auth') diff --git a/src/services/user.service.ts b/src/services/user.service.ts index a5b54ee..148c852 100644 --- a/src/services/user.service.ts +++ b/src/services/user.service.ts @@ -62,7 +62,6 @@ export class UserService { switch (`${isEmailEqual}:${isPasswordEqual}`) { case 'false:true': - // TODO Validate that email isn't already registered. await Queue.queue('user:email').then(q => q.add({ user, token, email: data.email }) ) @@ -75,7 +74,6 @@ export class UserService { ) break case 'false:false': - // TODO Validate that email isn't already registered. data.password = await bcrypt.hash(data.password, 10) await Queue.queue('user:email:password').then(q => diff --git a/src/validators/update.validator.ts b/src/validators/update.validator.ts new file mode 100644 index 0000000..770caad --- /dev/null +++ b/src/validators/update.validator.ts @@ -0,0 +1,31 @@ +import { Middleware, type Context } from '@athenna/http' +import { BaseValidator } from '#src/validators/base.validator' + +@Middleware({ name: 'update:validator' }) +export class UpdateValidator extends BaseValidator { + public schema = this.validator.object({ + name: this.validator.string().optional(), + email: this.validator + .string() + .email() + .unique({ table: 'users', max: 1 }) + .optional(), + password: this.validator + .string() + .minLength(8) + .maxLength(32) + .confirmed() + .optional() + }) + + public async handle({ request }: Context) { + const data = request.only([ + 'name', + 'email', + 'password', + 'password_confirmation' + ]) + + await this.validate(data) + } +} diff --git a/storage/queues.json b/storage/queues.json index 5fd945d..9ee3082 100644 --- a/storage/queues.json +++ b/storage/queues.json @@ -1 +1 @@ -{"default":[],"deadletter":[],"user:email":[],"user:password":[{"user":{"name":"João Lenon","email":"Darlene.Wilkinson@gmail.com"},"password":"$2b$10$gTXEZsXmkLJhOvvge3t/IO99RUw1AD6NmddfQ3mPByHweP3hWJdoO"}],"user:email:password":[],"user:confirm":[]} \ No newline at end of file +{"default":[],"deadletter":[],"user:email":[],"user:password":[{"user":{"name":"João Lenon","email":"Monique_Johnson@yahoo.com"},"password":"$2b$10$utsMOdfjDFKeEdD6UKgXieeGgzh/KtoLPq0Q7il4Qlsvr7/IC23Qi"}],"user:email:password":[],"user:confirm":[]} \ No newline at end of file diff --git a/tests/e2e/user.controller.test.ts b/tests/e2e/user.controller.test.ts index c1f8c1f..7120862 100644 --- a/tests/e2e/user.controller.test.ts +++ b/tests/e2e/user.controller.test.ts @@ -170,7 +170,7 @@ export default class UserControllerTest extends BaseHttpTest { const user = await User.find({ email: 'customer@athenna.io' }) const token = await ioc.use('authService').login('admin@athenna.io', '12345') const response = await request.put(`/api/v1/users/${user.id}`, { - body: { name: 'Customer Updated', password: '123456' }, + body: { name: 'Customer Updated', password: '12345678', password_confirmation: '12345678' }, headers: { authorization: token } }) @@ -192,7 +192,12 @@ export default class UserControllerTest extends BaseHttpTest { const user = await User.find({ email: 'customer@athenna.io' }) const token = await ioc.use('authService').login('admin@athenna.io', '12345') const response = await request.put(`/api/v1/users/${user.id}`, { - body: { name: 'Customer Updated', email: 'customer-updated@athenna.io', password: '123456' }, + body: { + name: 'Customer Updated', + email: 'customer-updated@athenna.io', + password: '12345678', + password_confirmation: '12345678' + }, headers: { authorization: token } })