From a0cba27c48440349df1dd8653b97b6acaac9c97d Mon Sep 17 00:00:00 2001 From: Sergey Yarkov Date: Wed, 2 Feb 2022 17:14:52 +0300 Subject: [PATCH] feat: added last login column & about column to user model --- app/Controllers/Http/Api/v1/MeController.ts | 16 ++++++++++ app/Models/User.ts | 6 ++++ app/Repositories/UserRepository.ts | 27 ++++++++++++++++ app/Services/AuthService.ts | 8 +++++ app/Services/MeService.ts | 32 +++++++++++++++++++ .../User/UpdateUserInfoValidator.ts | 12 +++++++ app/Validators/User/UpdateUserValidator.ts | 1 + database/migrations/1626545059989_users.ts | 2 ++ routes/apis/v1/me.ts | 1 + 9 files changed, 105 insertions(+) create mode 100644 app/Validators/User/UpdateUserInfoValidator.ts diff --git a/app/Controllers/Http/Api/v1/MeController.ts b/app/Controllers/Http/Api/v1/MeController.ts index c9db38e..21ce2c9 100644 --- a/app/Controllers/Http/Api/v1/MeController.ts +++ b/app/Controllers/Http/Api/v1/MeController.ts @@ -12,6 +12,7 @@ import MeService from 'App/Services/MeService'; */ import ChangePasswordValidator from 'App/Validators/Password/ChangePasswordValidator'; import UpdateContactsValidator from 'App/Validators/Contacts/UpdateContactsValidator'; +import UpdateUserInfoValidator from 'App/Validators/User/UpdateUserInfoValidator'; import BaseController from '../../BaseController'; @@ -108,6 +109,21 @@ export default class MeController extends BaseController { return this.sendResponse(ctx, result.data, result.message, result.status); } + + /** + * Update personal info of user + * PATCH /me/info + */ + public async updateInfo(ctx: HttpContextContract) { + const payload = await ctx.request.validate(UpdateUserInfoValidator); + const result = await this.meService.updateUserInfo(payload, ctx.auth); + + if (!result.success && result.error) { + throw new Exception(result.message, result.status, result.error.code); + } + + return this.sendResponse(ctx, result.data, result.message, result.status); + } } new Ioc().make(MeController); diff --git a/app/Models/User.ts b/app/Models/User.ts index 695fa6b..5d9cc55 100644 --- a/app/Models/User.ts +++ b/app/Models/User.ts @@ -70,6 +70,12 @@ export default class User extends BaseModel { }) public lessons_progress: HasMany; + @column() + public about: string | null; + + @column.dateTime() + public last_login: DateTime | null; + @column.dateTime({ autoCreate: true }) public createdAt: DateTime; diff --git a/app/Repositories/UserRepository.ts b/app/Repositories/UserRepository.ts index ed9b30e..a0651eb 100644 --- a/app/Repositories/UserRepository.ts +++ b/app/Repositories/UserRepository.ts @@ -1,4 +1,5 @@ import Hash from '@ioc:Adonis/Core/Hash'; +import { DateTime } from 'luxon'; /** * Models @@ -11,6 +12,7 @@ import Role from 'App/Models/Role'; */ import CreateUserValidator from 'App/Validators/User/CreateUserValidator'; import UpdateUserValidator from 'App/Validators/User/UpdateUserValidator'; +import UpdateUserInfoValidator from 'App/Validators/User/UpdateUserInfoValidator'; export default class UserRepository { private User: typeof User; @@ -159,6 +161,31 @@ export default class UserRepository { return null; } + /** + * Updates the time of last login column in database for user + * + * @param id User id + */ + public async updateLastLogin(id: number | string) { + await this.User.query().where('id', id).update({ last_login: DateTime.now() }).first(); + } + + /** + * Update personal info of user + * + * @param data New fields + */ + public async updateInfo(id: string | number, data: UpdateUserInfoValidator['schema']['props']) { + const user = await this.User.query().preload('contacts').preload('roles').where('id', id).first(); + + if (user) { + await user.merge(data).save(); + return { about: user.about }; + } + + return null; + } + /** * Rewrite all user roles * diff --git a/app/Services/AuthService.ts b/app/Services/AuthService.ts index 6b6e47d..bf0e212 100644 --- a/app/Services/AuthService.ts +++ b/app/Services/AuthService.ts @@ -70,8 +70,16 @@ export default class AuthService { userName: user.fullname, }); + /** + * Set cookie toke to response + */ ctx.response.cookie('token', token.token); + /** + * Save last login date + */ + await this.userRepository.updateLastLogin(user.id); + return { success: true, status: HttpStatusEnum.OK, diff --git a/app/Services/MeService.ts b/app/Services/MeService.ts index 5fb3854..15183e8 100644 --- a/app/Services/MeService.ts +++ b/app/Services/MeService.ts @@ -13,6 +13,7 @@ import UpdateContactsValidator from 'App/Validators/Contacts/UpdateContactsValid import CodeErrorEnum from 'App/Datatypes/Enums/CodeErrorEnum'; import RoleHelper from 'App/Helpers/RoleHelper'; import RoleEnum from 'App/Datatypes/Enums/RoleEnum'; +import UpdateUserInfoValidator from 'App/Validators/User/UpdateUserInfoValidator'; @inject() export default class MeService { @@ -279,6 +280,37 @@ export default class MeService { data: user.contacts, }; } + + public async updateUserInfo( + data: UpdateUserInfoValidator['schema']['props'], + auth: AuthContract + ): Promise { + const user = await auth.use(this.authGuard).authenticate(); + const info = await this.userRepository.updateInfo(user.id, data); + + if (info) { + const { about } = info; + + return { + success: true, + status: HttpStatusEnum.OK, + message: 'User info updated.', + data: { + about, + }, + }; + } + + return { + success: false, + status: HttpStatusEnum.SERVICE_UNAVAILABLE, + message: 'Cannot update personal info.', + data: {}, + error: { + code: 'E_SERVICE_UNAVAILABLE', + }, + }; + } } new Ioc().make(MeService); diff --git a/app/Validators/User/UpdateUserInfoValidator.ts b/app/Validators/User/UpdateUserInfoValidator.ts new file mode 100644 index 0000000..f9f1034 --- /dev/null +++ b/app/Validators/User/UpdateUserInfoValidator.ts @@ -0,0 +1,12 @@ +import { schema } from '@ioc:Adonis/Core/Validator'; +import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; + +export default class UpdateUserInfoValidator { + constructor(protected ctx: HttpContextContract) {} + + public schema = schema.create({ + about: schema.string.optional(), + }); + + public messages = {}; +} diff --git a/app/Validators/User/UpdateUserValidator.ts b/app/Validators/User/UpdateUserValidator.ts index ca9d91f..5d60734 100644 --- a/app/Validators/User/UpdateUserValidator.ts +++ b/app/Validators/User/UpdateUserValidator.ts @@ -8,6 +8,7 @@ export default class UpdateUserValidator { public schema = schema.create({ first_name: schema.string.optional(), last_name: schema.string.optional(), + about: schema.string.optional(), login: schema.string.optional({}, [rules.unique({ table: 'users', column: 'login' })]), email: schema.string.optional({}, [rules.email(), rules.unique({ table: 'users', column: 'email' })]), password: schema.string.optional(), diff --git a/database/migrations/1626545059989_users.ts b/database/migrations/1626545059989_users.ts index bda7219..8ec846e 100644 --- a/database/migrations/1626545059989_users.ts +++ b/database/migrations/1626545059989_users.ts @@ -8,9 +8,11 @@ export default class Users extends BaseSchema { table.string('id', 21).primary(); table.string('first_name', 24).notNullable(); table.string('last_name', 24).notNullable(); + table.text('about'); table.string('login', 128).notNullable().unique(); table.string('email').notNullable().unique(); table.string('password').notNullable(); + table.timestamp('last_login', { useTz: true }); table.timestamp('created_at', { useTz: true }); table.timestamp('updated_at', { useTz: true }); }); diff --git a/routes/apis/v1/me.ts b/routes/apis/v1/me.ts index 840c6d8..38e79e3 100644 --- a/routes/apis/v1/me.ts +++ b/routes/apis/v1/me.ts @@ -2,6 +2,7 @@ import Route from '@ioc:Adonis/Core/Route'; Route.group(() => { Route.get('/', 'Api/v1/MeController.show').middleware('role:admin,teacher,student').as('me.show'); + Route.patch('/info', 'Api/v1/MeController.updateInfo').middleware('role:admin,teacher,student').as('me.update-info'); Route.patch('/email', 'Api/v1/MeController.changeEmail') .middleware('role:admin,teacher,student') .as('me.change-email');