diff --git a/app/Controllers/Http/MeController.ts b/app/Controllers/Http/MeController.ts new file mode 100644 index 0000000..a108857 --- /dev/null +++ b/app/Controllers/Http/MeController.ts @@ -0,0 +1,73 @@ +import { Exception } from '@adonisjs/core/build/standalone'; +import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; +import Hash from '@ioc:Adonis/Core/Hash'; +import Logger from '@ioc:Adonis/Core/Logger'; +import ChangePasswordValidator from 'App/Validators/ChangePasswordValidator'; +import Contact from 'App/Models/Contact'; + +export default class MeController { + private readonly guard: 'api'; + + private readonly contact: typeof Contact; + + constructor() { + this.contact = Contact; + } + + /** + * Shows user info of the access token resource owner. + * GET /me + */ + public async index({ response, auth }: HttpContextContract) { + const user = await auth.use(this.guard).authenticate(); + + await user.load('contacts'); + await user.load('roles'); + + return response.ok({ + message: 'Fetched data about me.', + data: user, + }); + } + + /** + * Change password of authenticated user + * PATCH /me/password + */ + public async changePassword({ request, response, auth }: HttpContextContract) { + await request.validate(ChangePasswordValidator); + + const user = await auth.use(this.guard).authenticate(); + const oldPassword: string = request.input('oldPassword'); + const newPassword: string = request.input('newPassword'); + + if (oldPassword === newPassword) { + throw new Exception('The new password is the same as the old one.', 400, 'E_PASSWORD_VALUES'); + } + + /** + * Verify old password + */ + if (!(await Hash.verify(user.password, oldPassword))) { + throw new Exception('Invalid credentials.', 403, 'E_INVALID_CREDENTIALS'); + } + + /** + * Update user password + */ + user.password = newPassword; + await user.save(); + + Logger.info(`Password of user with id: "${user.id}" was updated.`); + + return response.ok({ + message: 'The password was successfully changed to the new value.', + }); + } + + /** + * Update contacts of authenticated user + * PATCH /me/contacts + */ + // public async updateContacts({ request, response, auth }: HttpContextContract) {} +} diff --git a/app/Controllers/Http/UsersController.ts b/app/Controllers/Http/UsersController.ts index 8171c7e..c78894c 100644 --- a/app/Controllers/Http/UsersController.ts +++ b/app/Controllers/Http/UsersController.ts @@ -1,17 +1,8 @@ -import { AuthenticationException } from '@adonisjs/auth/build/standalone'; import { Exception } from '@adonisjs/core/build/standalone'; import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; -import Hash from '@ioc:Adonis/Core/Hash'; -import Logger from '@ioc:Adonis/Core/Logger'; - -/* Models */ -import Contact from 'App/Models/Contact'; import Role from 'App/Models/Role'; import User from 'App/Models/User'; - -/* Validators */ import AddRoleToUserValidator from 'App/Validators/AddRoleToUserValidator'; -import ChangePasswordValidator from 'App/Validators/ChangePasswordValidator'; import CreateUserValidator from 'App/Validators/CreateUserValidator'; import DelRoleFromUserValidator from 'App/Validators/DelRoleFromUserValidator'; import UpdateUserValidator from 'App/Validators/UpdateUserValidator'; @@ -21,36 +12,9 @@ export default class UsersController { private readonly role: typeof Role; - private readonly contact: typeof Contact; - constructor() { this.user = User; this.role = Role; - this.contact = Contact; - } - - /** - * Shows user info of the access token resource owner. - * GET /users/me - */ - public async showMe({ response, auth }: HttpContextContract) { - const userId = auth.use('api').token?.userId; - - if (userId) { - const user = await this.user.findOrFail(userId); - await user.load('contacts'); - await user.load('roles'); - - return response.ok({ - message: 'Fetched data about me.', - data: user, - }); - } - - /** - * Unauthorized user - */ - throw new AuthenticationException('Unauthorized access', 'E_UNAUTHORIZED_ACCESS', 'api'); } /** @@ -58,7 +22,7 @@ export default class UsersController { * GET /users */ - public async showAll({ response }: HttpContextContract) { + public async index({ response }: HttpContextContract) { const users = await this.user.query().preload('contacts').preload('roles'); return response.ok({ @@ -194,50 +158,6 @@ export default class UsersController { }); } - /** - * Change password of authenticated user - * PATCH /users/me/password - */ - public async changePassword({ request, response, auth }: HttpContextContract) { - await request.validate(ChangePasswordValidator); - - const userId = auth.use('api').token?.userId; - - if (userId) { - const user = await this.user.findOrFail(userId); - const oldPassword: string = request.input('oldPassword'); - const newPassword: string = request.input('newPassword'); - - if (oldPassword === newPassword) { - throw new Exception('The new password is the same as the old one.', 400, 'E_PASSWORD_VALUES'); - } - - /** - * Verify old password - */ - if (!(await Hash.verify(user.password, oldPassword))) { - throw new Exception('Invalid credentials.', 403, 'E_INVALID_CREDENTIALS'); - } - - /** - * Update user password - */ - user.password = newPassword; - await user.save(); - - Logger.info(`Password of user with id: "${user.id}" was updated.`); - - return response.ok({ - message: 'The password was successfully changed to the new value.', - }); - } - - /** - * Unauthorized user - */ - throw new AuthenticationException('Unauthorized access', 'E_UNAUTHORIZED_ACCESS', 'api'); - } - /** * Finds roles by input value and throw error if role does not exist. * diff --git a/app/Validators/UpdateContactsValidator.ts b/app/Validators/UpdateContactsValidator.ts new file mode 100644 index 0000000..771db23 --- /dev/null +++ b/app/Validators/UpdateContactsValidator.ts @@ -0,0 +1,16 @@ +import { schema, rules } from '@ioc:Adonis/Core/Validator'; +import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; + +export default class UpdateContactsValidator { + constructor(protected ctx: HttpContextContract) {} + + public schema = schema.create({ + email: schema.string.optional({}, [rules.email(), rules.unique({ table: 'contacts', column: 'email' })]), + phone_number: schema.string.optional(), + vk_id: schema.string.optional(), + twitter_id: schema.string.optional(), + telegram_id: schema.string.optional(), + }); + + public messages = {}; +} diff --git a/app/Validators/UpdateUserValidator.ts b/app/Validators/UpdateUserValidator.ts index acce2ae..da58035 100644 --- a/app/Validators/UpdateUserValidator.ts +++ b/app/Validators/UpdateUserValidator.ts @@ -5,15 +5,10 @@ export default class UpdateUserValidator { constructor(protected ctx: HttpContextContract) {} public schema = schema.create({ - first_name: schema.string.optional({}, [ - rules.unique({ table: 'users', column: 'first_name' }), - ]), + first_name: schema.string.optional({}, [rules.unique({ table: 'users', column: 'first_name' })]), last_name: schema.string.optional({}, [rules.unique({ table: 'users', column: 'last_name' })]), login: schema.string.optional({}, [rules.unique({ table: 'users', column: 'login' })]), - email: schema.string.optional({}, [ - rules.email(), - rules.unique({ table: 'contacts', column: 'email' }), - ]), + email: schema.string.optional({}, [rules.email(), rules.unique({ table: 'contacts', column: 'email' })]), password: schema.string.optional({}, [rules.unique({ table: 'users', column: 'password' })]), }); diff --git a/start/routes.ts b/start/routes.ts index 6bff601..a643cda 100644 --- a/start/routes.ts +++ b/start/routes.ts @@ -32,18 +32,22 @@ Route.group(() => { /** * Users controller */ - Route.get('users', 'UsersController.showAll').middleware('role:admin,teacher,student').as('showAllUsers'); - Route.get('users/:id', 'UsersController.show').middleware('role:admin,teacher,student').as('showUserById'); + Route.get('users', 'UsersController.index').middleware('role:admin,teacher,student').as('showAllUsers'); Route.post('users', 'UsersController.create').middleware('role:admin').as('createUser'); + Route.get('users/:id', 'UsersController.show').middleware('role:admin,teacher,student').as('showUserById'); Route.patch('users/:id', 'UsersController.update').middleware('role:admin').as('updateUser'); Route.delete('users/:id', 'UsersController.destroy').middleware('role:admin').as('deleteUser'); Route.post('users/:id/attach-roles', 'UsersController.attachRoles').middleware('role:admin').as('attachUserRole'); Route.delete('users/:id/detach-roles', 'UsersController.detachRoles').middleware('role:admin').as('detachUserRole'); - Route.get('users/me', 'UsersController.showMe').middleware('role:admin,teacher,student').as('showMe'); - Route.patch('users/me/password', 'UsersController.changePassword') + + /** + * Me controller + */ + Route.get('me', 'MeController.index').middleware('role:admin,teacher,student').as('showMe'); + Route.patch('me/password', 'MeController.changePassword') .middleware('role:admin,teacher,student') .as('changePassword'); - Route.patch('users/me/contacts', 'UsersController.updateContacts') + Route.patch('me/contacts', 'MeController.updateContacts') .middleware('role:admin,teacher,student') .as('updateContacts'); }).middleware('auth');