From 4c406ed4a46246973a746fdbaf1528a846bc4fad Mon Sep 17 00:00:00 2001 From: Sergey Yarkov Date: Mon, 20 Sep 2021 23:18:00 +0300 Subject: [PATCH] feat: debug sql queries & added role filed when creating a new user --- .adonisrc.json | 9 +-- .../Http/Api/v1/UsersController.ts | 2 +- app/Helpers/RoleHelper.ts | 4 ++ app/Repositories/UserRepository.ts | 26 +++++--- app/Services/UserService.ts | 63 ++++++++++++++++++- app/Validators/User/CreateUserValidator.ts | 2 + routes/apis/v1/users.ts | 6 +- start/event.ts | 12 ++++ 8 files changed, 101 insertions(+), 23 deletions(-) create mode 100644 start/event.ts diff --git a/.adonisrc.json b/.adonisrc.json index f5cf765..262e656 100644 --- a/.adonisrc.json +++ b/.adonisrc.json @@ -23,10 +23,7 @@ "Database": "database", "Contracts": "contracts" }, - "preloads": [ - "./routes/index", - "./start/kernel" - ], + "preloads": ["./routes/index", "./start/kernel", "./start/event"], "providers": [ "./providers/AppProvider", "./providers/CloudinaryProvider", @@ -37,9 +34,7 @@ "@adonisjs/mail", "@adonisjs/view" ], - "aceProviders": [ - "@adonisjs/repl" - ], + "aceProviders": ["@adonisjs/repl"], "metaFiles": [ { "pattern": "resources/views/**/*.edge", diff --git a/app/Controllers/Http/Api/v1/UsersController.ts b/app/Controllers/Http/Api/v1/UsersController.ts index b7b5d94..6dce72a 100644 --- a/app/Controllers/Http/Api/v1/UsersController.ts +++ b/app/Controllers/Http/Api/v1/UsersController.ts @@ -62,7 +62,7 @@ export default class UsersController extends BaseController { public async create(ctx: HttpContextContract) { const payload = await ctx.request.validate(CreateUserValidator); - const result = await this.userService.createUser(payload); + const result = await this.userService.createUser(payload, ctx); if (!result.success && result.error) { throw new Exception(result.message, result.status, result.error.code); diff --git a/app/Helpers/RoleHelper.ts b/app/Helpers/RoleHelper.ts index 5a0c56f..61493a8 100644 --- a/app/Helpers/RoleHelper.ts +++ b/app/Helpers/RoleHelper.ts @@ -21,4 +21,8 @@ export default class RoleHelper { } return true; } + + public static userContainRoles(userRoles: Role[], roles: RoleEnum[]): boolean { + return userRoles.map(r => r.slug as RoleEnum).some(r => roles.includes(r)); + } } diff --git a/app/Repositories/UserRepository.ts b/app/Repositories/UserRepository.ts index 8183deb..8cadd04 100644 --- a/app/Repositories/UserRepository.ts +++ b/app/Repositories/UserRepository.ts @@ -68,13 +68,6 @@ export default class UserRepository { const query = this.User.query(); - if (search) { - query - .where('first_name', 'ilike', `%${search}%`) - .orWhere('last_name', 'ilike', `%${search}%`) - .orWhere('email', 'ilike', `%${search}%`); - } - if (email) { query.preload('contacts').whereHas('contacts', q => q.where('email', 'like', `%${email}%`)); } @@ -92,7 +85,16 @@ export default class UserRepository { } if (role) { - query.preload('roles').whereHas('roles', q => q.where('slug', role)); + query.whereHas('roles', q => q.where('slug', role)); + } + + // TODO fix search when gets params "serach" and "role" + if (search && role) { + query + .where('first_name', 'like', `%${search}%`) + .orWhere('last_name', 'like', `%${search}%`) + .orWhere('email', 'like', `%${search}%`) + .andWhereHas('roles', q => q.where('slug', role)); } const data = await query.preload('contacts').preload('roles').orderBy('created_at', 'desc').paginate(page, limit); @@ -106,7 +108,13 @@ export default class UserRepository { * @returns Created user */ public async create(data: CreateUserValidator['schema']['props']): Promise { - const user = await this.User.create(data); + const user = await this.User.create({ + first_name: data.first_name, + last_name: data.last_name, + login: data.login, + email: data.email, + password: data.password, + }); await user.load('contacts'); await user.load('roles'); diff --git a/app/Services/UserService.ts b/app/Services/UserService.ts index 70e5816..d907185 100644 --- a/app/Services/UserService.ts +++ b/app/Services/UserService.ts @@ -1,4 +1,5 @@ import { inject, Ioc } from '@adonisjs/core/build/standalone'; +import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; /** * Interfaces @@ -26,6 +27,8 @@ import UserRepository from 'App/Repositories/UserRepository'; */ import CreateUserValidator from 'App/Validators/User/CreateUserValidator'; import UpdateUserValidator from 'App/Validators/User/UpdateUserValidator'; +import RoleEnum from 'App/Datatypes/Enums/RoleEnum'; +import RoleHelper from 'App/Helpers/RoleHelper'; @inject() export default class UserService { @@ -93,14 +96,68 @@ export default class UserService { * @param data Input for create new user * @returns Response */ - public async createUser(data: CreateUserValidator['schema']['props']): Promise { - const user = await this.userRepository.create(data); + public async createUser(data: CreateUserValidator['schema']['props'], ctx: HttpContextContract): Promise { + const user = await ctx.auth.use('api').authenticate(); + + /** + * Load current user roles + */ + await user.load('roles'); + + const role = await this.roleRepository.getBySlug(data.role); + + /** + * Cannot find role + */ + if (role === null) { + return { + success: false, + status: HttpStatusEnum.NOT_FOUND, + message: 'Role not found.', + data: {}, + error: { + code: 'E_NOT_FOUND', + }, + }; + } + + /* TODO need to check the role of the authenticated user before creating a new user, + so we can may be move this into a separate middleware + */ + /** + * Check for authenticaded user roles + */ + if ( + (role.slug === RoleEnum.ADMIN || role.slug === RoleEnum.TEACHER) && + !RoleHelper.userContainRoles(user.roles, [RoleEnum.ADMIN]) + ) { + return { + success: false, + status: HttpStatusEnum.FORBIDDEN, + message: 'You dont have permissions to permorm that action', + data: {}, + error: { + code: 'E_FORBIDDEN', + }, + }; + } + + /** + * Create new user + */ + const createdUser = await this.userRepository.create(data); + + /** + * Attach role + */ + await createdUser.related('roles').attach([role.id]); + await createdUser.load('roles'); return { success: true, status: HttpStatusEnum.CREATED, message: 'User created.', - data: user, + data: createdUser, }; } diff --git a/app/Validators/User/CreateUserValidator.ts b/app/Validators/User/CreateUserValidator.ts index 2c2ed56..3b7c1dc 100644 --- a/app/Validators/User/CreateUserValidator.ts +++ b/app/Validators/User/CreateUserValidator.ts @@ -1,5 +1,6 @@ import { schema, rules } from '@ioc:Adonis/Core/Validator'; import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; +import RoleEnum from 'App/Datatypes/Enums/RoleEnum'; export default class CreateUserValidator { constructor(protected ctx: HttpContextContract) {} @@ -29,6 +30,7 @@ export default class CreateUserValidator { login: schema.string({}, [rules.maxLength(128), rules.unique({ table: 'users', column: 'login' })]), email: schema.string({}, [rules.email(), rules.unique({ table: 'users', column: 'email' })]), password: schema.string({}, [rules.minLength(6), rules.maxLength(128)]), + role: schema.enum(Object.values(RoleEnum)), }); /** diff --git a/routes/apis/v1/users.ts b/routes/apis/v1/users.ts index 1b25437..263e623 100644 --- a/routes/apis/v1/users.ts +++ b/routes/apis/v1/users.ts @@ -2,12 +2,12 @@ import Route from '@ioc:Adonis/Core/Route'; Route.group(() => { Route.get('/', 'Api/v1/UsersController.list').middleware('role:admin,teacher,student').as('users.list'); - Route.post('/', 'Api/v1/UsersController.create').middleware('role:admin,student').as('users.create'); + Route.post('/', 'Api/v1/UsersController.create').middleware('role:admin,teacher').as('users.create'); Route.get('/:id', 'Api/v1/UsersController.show').middleware('role:admin,teacher,student').as('users.show'); Route.put('/:id', 'Api/v1/UsersController.update').middleware('role:admin').as('users.update'); - Route.delete('/:id', 'Api/v1/UsersController.delete').middleware('role:admin').as('users.delete'); + Route.delete('/:id', 'Api/v1/UsersController.delete').middleware('role:admin,teacher').as('users.delete'); Route.post('/:id/attach-roles', 'Api/v1/UsersController.attachRoles') - .middleware('role:admin') + .middleware('role:admin,teacher') .as('users.attach-role'); Route.delete('/:id/detach-roles', 'Api/v1/UsersController.detachRoles') .middleware('role:admin') diff --git a/start/event.ts b/start/event.ts new file mode 100644 index 0000000..21a8fed --- /dev/null +++ b/start/event.ts @@ -0,0 +1,12 @@ +import Event from '@ioc:Adonis/Core/Event'; +import Database from '@ioc:Adonis/Lucid/Database'; +import Application from '@ioc:Adonis/Core/Application'; + +/** + * Debug sql queries + */ +Event.on('db:query', query => { + if (Application.inDev) { + Database.prettyPrint(query); + } +});