mirror of
https://github.com/sergeyyarkov/educt-server.git
synced 2026-10-11 10:19:20 +03:00
refactor: UsersController
This commit is contained in:
parent
419495d39c
commit
f8e3417f1f
@ -4,7 +4,7 @@ import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||
/**
|
||||
* Datatype
|
||||
*/
|
||||
import Role from 'App/Datatype/Role';
|
||||
import Role from 'App/Datatypes/Enums/RoleEnum';
|
||||
|
||||
/**
|
||||
* Models
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import { Exception } from '@adonisjs/core/build/standalone';
|
||||
import { Exception, inject, Ioc } from '@adonisjs/core/build/standalone';
|
||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||
|
||||
/**
|
||||
* Models
|
||||
* Services
|
||||
*/
|
||||
import Role from 'App/Models/Role';
|
||||
import User from 'App/Models/User';
|
||||
import UserService from 'App/Services/UserService';
|
||||
|
||||
/**
|
||||
* Validators
|
||||
@ -17,15 +16,13 @@ import UpdateUserValidator from 'App/Validators/User/UpdateUserValidator';
|
||||
|
||||
import BaseController from '../../BaseController';
|
||||
|
||||
@inject()
|
||||
export default class UsersController extends BaseController {
|
||||
private readonly User: typeof User;
|
||||
private userService: UserService;
|
||||
|
||||
private readonly Role: typeof Role;
|
||||
|
||||
constructor() {
|
||||
constructor(userService: UserService) {
|
||||
super();
|
||||
this.User = User;
|
||||
this.Role = Role;
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -33,13 +30,14 @@ export default class UsersController extends BaseController {
|
||||
* GET /users
|
||||
*/
|
||||
|
||||
public async list({ response }: HttpContextContract) {
|
||||
const users = await this.User.query().preload('contacts').preload('roles');
|
||||
public async list(ctx: HttpContextContract) {
|
||||
const result = await this.userService.fetchUsers(ctx.request.qs());
|
||||
|
||||
return response.ok({
|
||||
message: 'Fetched all users.',
|
||||
data: users,
|
||||
});
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -47,16 +45,14 @@ export default class UsersController extends BaseController {
|
||||
* GET /users/:id
|
||||
*/
|
||||
|
||||
public async show({ response, params }: HttpContextContract) {
|
||||
const user = await this.User.findOrFail(params.id);
|
||||
public async show(ctx: HttpContextContract) {
|
||||
const result = await this.userService.fetchUser(ctx.params.id);
|
||||
|
||||
await user.load('contacts');
|
||||
await user.load('roles');
|
||||
if (!result.success && result.error) {
|
||||
throw new Exception(result.message, result.status, result.error.code);
|
||||
}
|
||||
|
||||
return response.ok({
|
||||
message: `Fetched user with id: "${user.id}"`,
|
||||
data: user,
|
||||
});
|
||||
return this.sendResponse(ctx, result.data, result.message, result.status);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -64,20 +60,15 @@ export default class UsersController extends BaseController {
|
||||
* POST /users
|
||||
*/
|
||||
|
||||
public async create({ response, request }: HttpContextContract) {
|
||||
await request.validate(CreateUserValidator);
|
||||
public async create(ctx: HttpContextContract) {
|
||||
const payload = await ctx.request.validate(CreateUserValidator);
|
||||
const result = await this.userService.createUser(payload);
|
||||
|
||||
const user = await this.User.create({
|
||||
first_name: request.input('first_name'),
|
||||
last_name: request.input('last_name'),
|
||||
login: request.input('login'),
|
||||
password: request.input('password'),
|
||||
});
|
||||
if (!result.success && result.error) {
|
||||
throw new Exception(result.message, result.status, result.error.code);
|
||||
}
|
||||
|
||||
await user.related('contacts').create({ email: request.input('email') });
|
||||
await user.load('contacts');
|
||||
|
||||
return response.created({ message: 'Successfully created.', data: user });
|
||||
return this.sendResponse(ctx, result.data, result.message, result.status);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -85,34 +76,15 @@ export default class UsersController extends BaseController {
|
||||
* PATCH /users/:id
|
||||
*/
|
||||
|
||||
public async update({ request, response, params }: HttpContextContract) {
|
||||
await request.validate(UpdateUserValidator);
|
||||
public async update(ctx: HttpContextContract) {
|
||||
const payload = await ctx.request.validate(UpdateUserValidator);
|
||||
const result = await this.userService.updateUser(ctx.params.id, payload);
|
||||
|
||||
const user = await this.User.findOrFail(params.id);
|
||||
const input: Pick<User, 'first_name' | 'last_name' | 'login' | 'password'> = {
|
||||
first_name: request.input('first_name'),
|
||||
last_name: request.input('last_name'),
|
||||
login: request.input('login'),
|
||||
password: request.input('password'),
|
||||
};
|
||||
if (!result.success && result.error) {
|
||||
throw new Exception(result.message, result.status, result.error.code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update roles
|
||||
*/
|
||||
Object.keys(input).forEach(k => {
|
||||
if (input[k] !== undefined) {
|
||||
user[k] = input[k];
|
||||
}
|
||||
});
|
||||
|
||||
await user.save();
|
||||
await user.load('roles');
|
||||
await user.load('contacts');
|
||||
|
||||
return response.ok({
|
||||
message: `User with id: "${user.id}" was successfully updated.`,
|
||||
data: user,
|
||||
});
|
||||
return this.sendResponse(ctx, result.data, result.message, result.status);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -120,74 +92,45 @@ export default class UsersController extends BaseController {
|
||||
* DELETE /users/:id
|
||||
*/
|
||||
|
||||
public async delete({ response, params }: HttpContextContract) {
|
||||
const user = await this.User.query().preload('contacts').preload('roles').where('id', params.id).firstOrFail();
|
||||
public async delete(ctx: HttpContextContract) {
|
||||
const result = await this.userService.deleteUser(ctx.params.id);
|
||||
|
||||
await user.delete();
|
||||
if (!result.success && result.error) {
|
||||
throw new Exception(result.message, result.status, result.error.code);
|
||||
}
|
||||
|
||||
return response.ok({
|
||||
message: `Used with id: "${user.id}" was successfully deleted.`,
|
||||
data: user,
|
||||
});
|
||||
return this.sendResponse(ctx, result.data, result.message, result.status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach array of roles to user
|
||||
* POST /users/:id/attach-roles
|
||||
*/
|
||||
public async attachRoles({ request, response, params }: HttpContextContract) {
|
||||
await request.validate(AddRoleToUserValidator);
|
||||
public async attachRoles(ctx: HttpContextContract) {
|
||||
const payload = await ctx.request.validate(AddRoleToUserValidator);
|
||||
const result = await this.userService.attachRolesToUser(ctx.params.id, payload.roles);
|
||||
|
||||
const input: string[] | undefined = request.input('roles');
|
||||
const user = await this.User.query().preload('roles').where('id', params.id).firstOrFail();
|
||||
const roles = await this.findRolesBySlug(input);
|
||||
if (!result.success && result.error) {
|
||||
throw new Exception(result.message, result.status, result.error.code);
|
||||
}
|
||||
|
||||
await this.User.attachRoles(user, roles);
|
||||
|
||||
return response.ok({
|
||||
message: 'Roles attached',
|
||||
data: user.roles,
|
||||
});
|
||||
return this.sendResponse(ctx, result.data, result.message, result.status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach array of roles from user
|
||||
* DELETE /users/:id/detach-roles
|
||||
*/
|
||||
public async detachRoles({ request, response, params }: HttpContextContract) {
|
||||
await request.validate(DelRoleFromUserValidator);
|
||||
public async detachRoles(ctx: HttpContextContract) {
|
||||
const payload = await ctx.request.validate(DelRoleFromUserValidator);
|
||||
const result = await this.userService.detachRolesFromUser(ctx.params.id, payload.roles);
|
||||
|
||||
const input: string[] | undefined = request.input('roles');
|
||||
const user = await this.User.query().preload('roles').where('id', params.id).firstOrFail();
|
||||
const roles = await this.findRolesBySlug(input);
|
||||
|
||||
await this.User.detachRoles(user, roles);
|
||||
|
||||
return response.ok({
|
||||
message: 'Roles detached',
|
||||
data: user.roles,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds roles by input value and throw error if role does not exist.
|
||||
*
|
||||
* @param input Array of roles to find them
|
||||
* @returns Finded roles
|
||||
*/
|
||||
private async findRolesBySlug(input: string[] | undefined): Promise<Role[]> {
|
||||
if (!input) {
|
||||
throw new Exception('Unable to find "roles" field in input.', 400, 'E_ROLES_FIELD_NOT_PROVIDED');
|
||||
if (!result.success && result.error) {
|
||||
throw new Exception(result.message, result.status, result.error.code);
|
||||
}
|
||||
|
||||
const roles = await this.Role.query().whereIn('slug', input);
|
||||
|
||||
input.forEach(val => {
|
||||
if (!roles.map(r => r.slug).includes(val)) {
|
||||
throw new Exception(`Unable to find role: "${val}" using input value "roles"`, 404, 'E_ROLE_NOT_FOUND');
|
||||
}
|
||||
});
|
||||
|
||||
return roles;
|
||||
return this.sendResponse(ctx, result.data, result.message, result.status);
|
||||
}
|
||||
}
|
||||
|
||||
new Ioc().make(UsersController);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
enum Role {
|
||||
enum RoleEnum {
|
||||
ADMIN = 'admin',
|
||||
TEACHER = 'teacher',
|
||||
STUDENT = 'student',
|
||||
}
|
||||
|
||||
export default Role;
|
||||
export default RoleEnum;
|
||||
9
app/Datatypes/Interfaces/IResponse.ts
Normal file
9
app/Datatypes/Interfaces/IResponse.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export default interface IResponse {
|
||||
success: boolean;
|
||||
status: number;
|
||||
message: string;
|
||||
data: object;
|
||||
error?: {
|
||||
code: string;
|
||||
};
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import { BaseModel, column } from '@ioc:Adonis/Lucid/Orm';
|
||||
import { BaseModel, BelongsTo, belongsTo, column } from '@ioc:Adonis/Lucid/Orm';
|
||||
import User from './User';
|
||||
|
||||
export default class Contact extends BaseModel {
|
||||
@column({ isPrimary: true })
|
||||
@ -23,6 +24,11 @@ export default class Contact extends BaseModel {
|
||||
@column()
|
||||
public telegram_id: string | null;
|
||||
|
||||
@belongsTo(() => User, {
|
||||
foreignKey: 'user_id',
|
||||
})
|
||||
public user: BelongsTo<typeof User>;
|
||||
|
||||
@column.dateTime({ autoCreate: true, serializeAs: null })
|
||||
public createdAt: DateTime;
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
/* eslint-disable import/no-cycle */
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { DateTime } from 'luxon';
|
||||
import { Exception } from '@adonisjs/core/build/standalone';
|
||||
import {
|
||||
BaseModel,
|
||||
beforeCreate,
|
||||
@ -55,45 +54,6 @@ export default class User extends BaseModel {
|
||||
@column.dateTime({ autoCreate: true, autoUpdate: true, serializeAs: null })
|
||||
public updatedAt: DateTime;
|
||||
|
||||
/**
|
||||
* Checks if the input role is already attached, and if so, throws an error,
|
||||
* otherwise it attaches the role to the user.
|
||||
*
|
||||
* @param user User
|
||||
* @param inputRoles Array of roles to attach
|
||||
*/
|
||||
public static async attachRoles(user: User, inputRoles: Role[]): Promise<void> {
|
||||
const currentRolesSlugs: string[] = user.roles.map(r => r.slug);
|
||||
const inputRolesSlugs: string[] = inputRoles.map(r => r.slug);
|
||||
|
||||
currentRolesSlugs.forEach(s => {
|
||||
if (inputRolesSlugs.includes(s)) {
|
||||
throw new Exception(`Role "${s}" already attached to that user.`, 400, 'E_ROLE_ATTACHED');
|
||||
}
|
||||
});
|
||||
|
||||
await user.related('roles').attach(inputRoles.map(r => r.id));
|
||||
await user.load('roles');
|
||||
}
|
||||
|
||||
/**
|
||||
* Before detach, checks if input role is attached to that user and
|
||||
* if not, throws an error.
|
||||
*
|
||||
* @param user User
|
||||
* @param inputRoles Array of roles to detach
|
||||
*/
|
||||
public static async detachRoles(user: User, inputRoles: Role[]): Promise<void> {
|
||||
inputRoles.forEach(role => {
|
||||
if (!user.roles.map(r => r.id).includes(role.id)) {
|
||||
throw new Exception(`Role "${role.slug}" not attached to that user.`, 400, 'E_ROLE_NOT_ATTACHED');
|
||||
}
|
||||
});
|
||||
|
||||
user.related('roles').detach(inputRoles.map(r => r.id));
|
||||
await user.load('roles');
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign id to user by nanoid.
|
||||
* @param user User
|
||||
|
||||
56
app/Repositories/ContactRepository.ts
Normal file
56
app/Repositories/ContactRepository.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import Contact from 'App/Models/Contact';
|
||||
import User from 'App/Models/User';
|
||||
import CreateUserValidator from 'App/Validators/User/CreateUserValidator';
|
||||
import UpdateUserValidator from 'App/Validators/User/UpdateUserValidator';
|
||||
|
||||
export default class ContactRepository {
|
||||
private Contact: typeof Contact;
|
||||
|
||||
constructor() {
|
||||
this.Contact = Contact;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates user contacts in database
|
||||
*
|
||||
* @param user User for associate the contacts
|
||||
* @param data Data contacts
|
||||
* @returns Created contacts
|
||||
*/
|
||||
public async create(user: User, data: Pick<CreateUserValidator['schema']['props'], 'email'>) {
|
||||
const contacts = await this.Contact.create(data);
|
||||
|
||||
await contacts.related('user').associate(user);
|
||||
await user.load('contacts');
|
||||
await user.load('roles');
|
||||
|
||||
return contacts;
|
||||
}
|
||||
|
||||
public async update(user: User, data: Pick<UpdateUserValidator['schema']['props'], 'email'>) {
|
||||
const contacts = await this.Contact.query().where('user_id', user.id).first();
|
||||
|
||||
/**
|
||||
* Update user contacts
|
||||
*/
|
||||
if (contacts) {
|
||||
contacts.email = data.email;
|
||||
|
||||
await contacts.save();
|
||||
await user.load('contacts');
|
||||
|
||||
return contacts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new user contacts if contacts doesn't exist
|
||||
*/
|
||||
await user.related('contacts').create({
|
||||
email: data.email,
|
||||
});
|
||||
|
||||
await user.load('contacts');
|
||||
|
||||
return user.contacts;
|
||||
}
|
||||
}
|
||||
26
app/Repositories/RoleRepository.ts
Normal file
26
app/Repositories/RoleRepository.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import Role from 'App/Models/Role';
|
||||
|
||||
export default class RoleRepository {
|
||||
private Role: typeof Role;
|
||||
|
||||
constructor() {
|
||||
this.Role = Role;
|
||||
}
|
||||
|
||||
public async getAll(params?: any) {
|
||||
const { roles }: any = params || {};
|
||||
const query = this.Role.query();
|
||||
|
||||
if (roles) {
|
||||
query.whereIn('slug', roles);
|
||||
}
|
||||
|
||||
const data = await query;
|
||||
return data;
|
||||
}
|
||||
|
||||
public async getBySlug(slug: string) {
|
||||
const role = await this.Role.query().where('slug', slug);
|
||||
return role;
|
||||
}
|
||||
}
|
||||
106
app/Repositories/UserRepository.ts
Normal file
106
app/Repositories/UserRepository.ts
Normal file
@ -0,0 +1,106 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import User from 'App/Models/User';
|
||||
import CreateUserValidator from 'App/Validators/User/CreateUserValidator';
|
||||
import UpdateUserValidator from 'App/Validators/User/UpdateUserValidator';
|
||||
|
||||
export default class UserRepository {
|
||||
private User: typeof User;
|
||||
|
||||
constructor() {
|
||||
this.User = User;
|
||||
}
|
||||
|
||||
public async getById(id: string | number) {
|
||||
const data = await this.User.query().preload('contacts').preload('roles').where('id', id).first();
|
||||
return data;
|
||||
}
|
||||
|
||||
public async getAllByRole(role: string) {
|
||||
const data = await this.User.query()
|
||||
.preload('contacts')
|
||||
.preload('roles')
|
||||
.whereHas('roles', q => q.where('slug', role));
|
||||
return data;
|
||||
}
|
||||
|
||||
public async getAll(params?: any) {
|
||||
const { email, login, first_name, last_name, role }: any = params || {};
|
||||
const query = this.User.query();
|
||||
|
||||
if (email) {
|
||||
query.preload('contacts').whereHas('contacts', q => q.where('email', 'like', `%${email}%`));
|
||||
}
|
||||
|
||||
if (login) {
|
||||
query.where('login', 'like', `%${login}%`);
|
||||
}
|
||||
|
||||
if (first_name) {
|
||||
query.where('first_name', 'like', `%${first_name}%`);
|
||||
}
|
||||
|
||||
if (last_name) {
|
||||
query.where('last_name', 'like', `%${last_name}%`);
|
||||
}
|
||||
|
||||
if (role) {
|
||||
query.preload('roles').whereHas('roles', q => q.where('slug', role));
|
||||
}
|
||||
|
||||
const data = await query.preload('contacts').preload('roles');
|
||||
return data;
|
||||
}
|
||||
|
||||
public async create(data: CreateUserValidator['schema']['props']) {
|
||||
const createdUser = await this.User.create({
|
||||
first_name: data.first_name,
|
||||
last_name: data.last_name,
|
||||
login: data.login,
|
||||
password: data.password,
|
||||
});
|
||||
|
||||
return createdUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user
|
||||
*
|
||||
* @param id User id
|
||||
* @param data Data to update
|
||||
* @returns User or null if user is not found
|
||||
*/
|
||||
public async update(id: number | string, data: UpdateUserValidator['schema']['props']) {
|
||||
const user = await this.User.query().preload('contacts').preload('roles').where('id', id).first();
|
||||
|
||||
if (user) {
|
||||
await user
|
||||
.merge({
|
||||
first_name: data.first_name,
|
||||
last_name: data.last_name,
|
||||
login: data.login,
|
||||
password: data.password,
|
||||
})
|
||||
.save();
|
||||
return user;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete user
|
||||
*
|
||||
* @param id User id
|
||||
* @returns Deleted user or null if user is not found
|
||||
*/
|
||||
public async delete(id: number | string) {
|
||||
const user = await this.User.query().preload('contacts').preload('roles').where('id', id).first();
|
||||
|
||||
if (user) {
|
||||
await user.delete();
|
||||
return user;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
261
app/Services/UserService.ts
Normal file
261
app/Services/UserService.ts
Normal file
@ -0,0 +1,261 @@
|
||||
import { inject, Ioc } from '@adonisjs/core/build/standalone';
|
||||
import IResponse from 'App/Datatypes/Interfaces/IResponse';
|
||||
import Role from 'App/Models/Role';
|
||||
import ContactRepository from 'App/Repositories/ContactRepository';
|
||||
import RoleRepository from 'App/Repositories/RoleRepository';
|
||||
import UserRepository from 'App/Repositories/UserRepository';
|
||||
import CreateUserValidator from 'App/Validators/User/CreateUserValidator';
|
||||
import UpdateUserValidator from 'App/Validators/User/UpdateUserValidator';
|
||||
|
||||
@inject()
|
||||
export default class UserService {
|
||||
private userRepository: UserRepository;
|
||||
|
||||
private contactRepository: ContactRepository;
|
||||
|
||||
private roleRepository: RoleRepository;
|
||||
|
||||
constructor(userRepository: UserRepository, contactRepository: ContactRepository, roleRepository: RoleRepository) {
|
||||
this.userRepository = userRepository;
|
||||
this.contactRepository = contactRepository;
|
||||
this.roleRepository = roleRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch one user by id
|
||||
*
|
||||
* @param id Id of user
|
||||
* @returns Response data
|
||||
*/
|
||||
public async fetchUser(id: string | number): Promise<IResponse> {
|
||||
const user = await this.userRepository.getById(id);
|
||||
|
||||
if (!user) {
|
||||
return {
|
||||
success: false,
|
||||
status: 404,
|
||||
message: 'User not found.',
|
||||
data: {},
|
||||
error: {
|
||||
code: 'USER_NOT_FOUND',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
status: 200,
|
||||
message: 'Fetched user.',
|
||||
data: user,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch list of users
|
||||
*
|
||||
* @param params Params for finding user
|
||||
* @returns Response data
|
||||
*/
|
||||
public async fetchUsers(params?: any): Promise<IResponse> {
|
||||
const users = await this.userRepository.getAll(params);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
status: 200,
|
||||
message: 'Fetched all users.',
|
||||
data: users,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new user
|
||||
*
|
||||
* @param data Input for create new user
|
||||
* @returns Response data
|
||||
*/
|
||||
public async createUser(data: CreateUserValidator['schema']['props']): Promise<IResponse> {
|
||||
const user = await this.userRepository.create(data);
|
||||
|
||||
/* Associate user contacts */
|
||||
await this.contactRepository.create(user, { email: data.email });
|
||||
|
||||
return {
|
||||
success: true,
|
||||
status: 201,
|
||||
message: 'User created.',
|
||||
data: user,
|
||||
};
|
||||
}
|
||||
|
||||
public async updateUser(id: string | number, data: UpdateUserValidator['schema']['props']): Promise<IResponse> {
|
||||
const user = await this.userRepository.update(id, data);
|
||||
|
||||
if (!user) {
|
||||
return {
|
||||
success: false,
|
||||
status: 404,
|
||||
message: 'User not found.',
|
||||
data: {},
|
||||
error: {
|
||||
code: 'E_USER_NOT_FOUND',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user contacts
|
||||
*/
|
||||
await this.contactRepository.update(user, data);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
status: 200,
|
||||
message: 'User updated.',
|
||||
data: user,
|
||||
};
|
||||
}
|
||||
|
||||
public async deleteUser(id: string | number): Promise<IResponse> {
|
||||
const user = await this.userRepository.delete(id);
|
||||
|
||||
if (!user) {
|
||||
return {
|
||||
success: false,
|
||||
status: 404,
|
||||
message: 'User not found.',
|
||||
data: {},
|
||||
error: {
|
||||
code: 'E_USER_NOT_FOUND',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
status: 200,
|
||||
message: 'User deleted.',
|
||||
data: user,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach array of roles to user
|
||||
*
|
||||
* @param id User id
|
||||
* @param input Roles
|
||||
* @returns Result
|
||||
*/
|
||||
public async attachRolesToUser(id: string | number, input: string[]): Promise<IResponse> {
|
||||
const user = await this.userRepository.getById(id);
|
||||
|
||||
if (!user) {
|
||||
return {
|
||||
success: false,
|
||||
status: 404,
|
||||
message: 'User not found.',
|
||||
data: {},
|
||||
error: {
|
||||
code: 'E_USER_NOT_FOUND',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const roles = await this.roleRepository.getAll({ roles: input });
|
||||
let attachedRole: Role | undefined;
|
||||
|
||||
/**
|
||||
* Set attachedRole variable if some role already
|
||||
* attached to user.
|
||||
*/
|
||||
roles.some(inputRole => {
|
||||
const existed = user.roles.find(role => role.slug === inputRole.slug);
|
||||
if (existed) {
|
||||
attachedRole = inputRole;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (attachedRole) {
|
||||
return {
|
||||
success: false,
|
||||
status: 404,
|
||||
message: `Role "${attachedRole.name}" already attached to that user.`,
|
||||
data: {},
|
||||
error: {
|
||||
code: 'E_ROLE_ATTACHED',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
await user.related('roles').attach([...roles.map(r => r.id)]);
|
||||
await user.load('roles');
|
||||
|
||||
return {
|
||||
success: true,
|
||||
status: 200,
|
||||
message: 'Roles attached.',
|
||||
data: user.roles,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach array of role from user
|
||||
*
|
||||
* @param id User id
|
||||
* @param input Roles
|
||||
* @returns Result
|
||||
*/
|
||||
public async detachRolesFromUser(id: string | number, input: string[]): Promise<IResponse> {
|
||||
const user = await this.userRepository.getById(id);
|
||||
|
||||
if (!user) {
|
||||
return {
|
||||
success: false,
|
||||
status: 404,
|
||||
message: 'User not found.',
|
||||
data: {},
|
||||
error: {
|
||||
code: 'E_USER_NOT_FOUND',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const roles = await this.roleRepository.getAll({ roles: input });
|
||||
let notAttachedRole: Role | undefined;
|
||||
|
||||
/**
|
||||
* Set notAttachedRole variable if some role
|
||||
* not attached to user.
|
||||
*/
|
||||
roles.forEach(role => {
|
||||
if (!user.roles.map(r => r.id).includes(role.id)) {
|
||||
notAttachedRole = role;
|
||||
}
|
||||
});
|
||||
|
||||
if (notAttachedRole) {
|
||||
return {
|
||||
success: false,
|
||||
status: 404,
|
||||
message: `Role "${notAttachedRole.name}" not attached to that user.`,
|
||||
data: {},
|
||||
error: {
|
||||
code: 'E_ROLE_NOT_ATTACHED',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
await user.related('roles').detach([...roles.map(r => r.id)]);
|
||||
await user.load('roles');
|
||||
|
||||
return {
|
||||
success: true,
|
||||
status: 200,
|
||||
message: 'Roles detached.',
|
||||
data: user.roles,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
new Ioc().make(UserService);
|
||||
@ -1,11 +1,12 @@
|
||||
import { schema } from '@ioc:Adonis/Core/Validator';
|
||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||
import RoleEnum from 'App/Datatypes/Enums/RoleEnum';
|
||||
|
||||
export default class AddRoleToUserValidator {
|
||||
constructor(protected ctx: HttpContextContract) {}
|
||||
|
||||
public schema = schema.create({
|
||||
roles: schema.array().members(schema.string()),
|
||||
roles: schema.array().members(schema.enum(Object.values(RoleEnum))),
|
||||
});
|
||||
|
||||
public messages = {};
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import { schema } from '@ioc:Adonis/Core/Validator';
|
||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||
import RoleEnum from 'App/Datatypes/Enums/RoleEnum';
|
||||
|
||||
export default class DelRoleFromUserValidator {
|
||||
constructor(protected ctx: HttpContextContract) {}
|
||||
|
||||
public schema = schema.create({
|
||||
roles: schema.array().members(schema.string()),
|
||||
roles: schema.array().members(schema.enum(Object.values(RoleEnum))),
|
||||
});
|
||||
|
||||
public messages = {};
|
||||
|
||||
@ -5,11 +5,11 @@ export default class UpdateUserValidator {
|
||||
constructor(protected ctx: HttpContextContract) {}
|
||||
|
||||
public schema = schema.create({
|
||||
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' })]),
|
||||
password: schema.string.optional({}, [rules.unique({ table: 'users', column: 'password' })]),
|
||||
first_name: schema.string(),
|
||||
last_name: schema.string(),
|
||||
login: schema.string({}, [rules.unique({ table: 'users', column: 'login' })]),
|
||||
email: schema.string({}, [rules.email(), rules.unique({ table: 'contacts', column: 'email' })]),
|
||||
password: schema.string(),
|
||||
});
|
||||
|
||||
public messages = {};
|
||||
|
||||
@ -17,6 +17,4 @@ import Application from '@ioc:Adonis/Core/Application';
|
||||
|
|
||||
*/
|
||||
|
||||
export default listDirectoryFiles(__dirname, Application.appRoot, [
|
||||
'./commands/index',
|
||||
]);
|
||||
export default listDirectoryFiles(__dirname, Application.appRoot, ['./commands/index']);
|
||||
|
||||
@ -32,12 +32,7 @@ const bodyParserConfig: BodyParserConfig = {
|
||||
encoding: 'utf-8',
|
||||
limit: '1mb',
|
||||
strict: true,
|
||||
types: [
|
||||
'application/json',
|
||||
'application/json-patch+json',
|
||||
'application/vnd.api+json',
|
||||
'application/csp-report',
|
||||
],
|
||||
types: ['application/json', 'application/json-patch+json', 'application/vnd.api+json', 'application/csp-report'],
|
||||
},
|
||||
|
||||
/*
|
||||
|
||||
@ -41,6 +41,7 @@ const redisConfig: RedisConfig = {
|
||||
password: Env.get('REDIS_PASSWORD', ''),
|
||||
db: 0,
|
||||
keyPrefix: '',
|
||||
healthCheck: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
* file.
|
||||
*/
|
||||
|
||||
import User from 'App/Models/User'
|
||||
import User from 'App/Models/User';
|
||||
|
||||
declare module '@ioc:Adonis/Addons/Auth' {
|
||||
/*
|
||||
@ -34,9 +34,9 @@ declare module '@ioc:Adonis/Addons/Auth' {
|
||||
|
|
||||
*/
|
||||
user: {
|
||||
implementation: LucidProviderContract<typeof User>,
|
||||
config: LucidProviderConfig<typeof User>,
|
||||
},
|
||||
implementation: LucidProviderContract<typeof User>;
|
||||
config: LucidProviderConfig<typeof User>;
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
@ -65,8 +65,8 @@ declare module '@ioc:Adonis/Addons/Auth' {
|
||||
|
|
||||
*/
|
||||
api: {
|
||||
implementation: OATGuardContract<'user', 'api'>,
|
||||
config: OATGuardConfig<'user'>,
|
||||
},
|
||||
implementation: OATGuardContract<'user', 'api'>;
|
||||
config: OATGuardConfig<'user'>;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
38
package-lock.json
generated
38
package-lock.json
generated
@ -145,11 +145,41 @@
|
||||
}
|
||||
},
|
||||
"@adonisjs/fold": {
|
||||
"version": "8.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@adonisjs/fold/-/fold-8.1.4.tgz",
|
||||
"integrity": "sha512-+B56Lxb19TZ2DkK2deZH1kTfqyXVUKwrfNirVZ3JfGeO6RbXjW3IYeUTJ7MWzcbiSTqmHXw2HC+TTIpE/EEDpg==",
|
||||
"version": "8.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@adonisjs/fold/-/fold-8.1.5.tgz",
|
||||
"integrity": "sha512-eqbq0zQd0TV7AoN/RP4vUUg442BP4GSc0U1CsNYGKJ3YBhxfgThVLDlgn1K4/nadC1lv4/Gf3ZSnLj1Rx5OzFA==",
|
||||
"requires": {
|
||||
"@poppinss/utils": "^3.1.3"
|
||||
"@poppinss/utils": "^3.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@poppinss/utils": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@poppinss/utils/-/utils-3.2.0.tgz",
|
||||
"integrity": "sha512-gnqlrxE4zMvqW+LpviyCWCg4+RShCYCFyBdVTyg9ZZEEgPBV9bEY+YB5r/2klFrKx30k9nfClHVI2n8ZKmXgsw==",
|
||||
"requires": {
|
||||
"@types/bytes": "^3.1.1",
|
||||
"@types/he": "^1.1.2",
|
||||
"buffer-alloc": "^1.2.0",
|
||||
"bytes": "^3.1.0",
|
||||
"change-case": "^4.1.2",
|
||||
"cuid": "^2.1.8",
|
||||
"fs-readdir-recursive": "^1.1.0",
|
||||
"he": "^1.2.0",
|
||||
"kind-of": "^6.0.3",
|
||||
"lodash": "^4.17.21",
|
||||
"ms": "^2.1.3",
|
||||
"pluralize": "^8.0.0",
|
||||
"require-all": "^3.0.0",
|
||||
"resolve-from": "^5.0.0",
|
||||
"slugify": "^1.6.0",
|
||||
"truncatise": "0.0.8"
|
||||
}
|
||||
},
|
||||
"slugify": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.0.tgz",
|
||||
"integrity": "sha512-FkMq+MQc5hzYgM86nLuHI98Acwi3p4wX+a5BO9Hhw4JdK4L7WueIiZ4tXEobImPqBz2sVcV0+Mu3GRB30IGang=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@adonisjs/hash": {
|
||||
|
||||
@ -3,19 +3,11 @@ import { ApplicationContract } from '@ioc:Adonis/Core/Application';
|
||||
export default class AppProvider {
|
||||
constructor(protected app: ApplicationContract) {}
|
||||
|
||||
public register() {
|
||||
// Register your own bindings
|
||||
}
|
||||
public register() {}
|
||||
|
||||
public async boot() {
|
||||
// IoC container is ready
|
||||
}
|
||||
public async boot() {}
|
||||
|
||||
public async ready() {
|
||||
// App is ready
|
||||
}
|
||||
public async ready() {}
|
||||
|
||||
public async shutdown() {
|
||||
// Cleanup, since app is going down
|
||||
}
|
||||
public async shutdown() {}
|
||||
}
|
||||
|
||||
11
routes/apis/v1/health.ts
Normal file
11
routes/apis/v1/health.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import Route from '@ioc:Adonis/Core/Route';
|
||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
|
||||
import HealthCheck from '@ioc:Adonis/Core/HealthCheck';
|
||||
|
||||
Route.group(() => {
|
||||
Route.get('/', async (ctx: HttpContextContract) => {
|
||||
const report = await HealthCheck.getReport();
|
||||
|
||||
return report.healthy ? ctx.response.ok(report) : ctx.response.badRequest(report);
|
||||
}).as('health');
|
||||
}).prefix('health');
|
||||
@ -12,6 +12,13 @@ Route.group(() => {
|
||||
require('./auth');
|
||||
|
||||
Route.group(() => {
|
||||
/**
|
||||
*
|
||||
* Health check
|
||||
*
|
||||
*/
|
||||
require('./health');
|
||||
|
||||
/**
|
||||
*
|
||||
* Users module
|
||||
|
||||
@ -2,14 +2,14 @@ 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').as('users.create');
|
||||
Route.post('/', 'Api/v1/UsersController.create').middleware('role:admin,student').as('users.create');
|
||||
Route.get('/:id', 'Api/v1/UsersController.show').middleware('role:admin,teacher,student').as('users.show');
|
||||
Route.patch('/: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.put('/:id', 'Api/v1/UsersController.update').middleware('role:admin,student,teacher').as('users.update');
|
||||
Route.delete('/:id', 'Api/v1/UsersController.delete').middleware('role:admin,student,teacher').as('users.delete');
|
||||
Route.post('/:id/attach-roles', 'Api/v1/UsersController.attachRoles')
|
||||
.middleware('role:admin')
|
||||
.middleware('role:admin,student,teacher')
|
||||
.as('users.attach-role');
|
||||
Route.delete('/:id/detach-roles', 'Api/v1/UsersController.detachRoles')
|
||||
.middleware('role:admin')
|
||||
.middleware('role:admin,student,teacher')
|
||||
.as('users.detach-role');
|
||||
}).prefix('users');
|
||||
|
||||
@ -1,36 +1,17 @@
|
||||
{
|
||||
"extends": "./node_modules/adonis-preset-ts/tsconfig",
|
||||
"include": [
|
||||
"**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"build"
|
||||
],
|
||||
"include": ["**/*"],
|
||||
"exclude": ["node_modules", "build"],
|
||||
"compilerOptions": {
|
||||
"outDir": "build",
|
||||
"rootDir": "./",
|
||||
"sourceMap": true,
|
||||
"paths": {
|
||||
"App/*": [
|
||||
"./app/*"
|
||||
],
|
||||
"Config/*": [
|
||||
"./config/*"
|
||||
],
|
||||
"Contracts/*": [
|
||||
"./contracts/*"
|
||||
],
|
||||
"Database/*": [
|
||||
"./database/*"
|
||||
]
|
||||
"App/*": ["./app/*"],
|
||||
"Config/*": ["./config/*"],
|
||||
"Contracts/*": ["./contracts/*"],
|
||||
"Database/*": ["./database/*"]
|
||||
},
|
||||
"types": [
|
||||
"@adonisjs/core",
|
||||
"@adonisjs/repl",
|
||||
"@adonisjs/lucid",
|
||||
"@adonisjs/auth",
|
||||
"@adonisjs/redis"
|
||||
]
|
||||
"types": ["@adonisjs/core", "@adonisjs/repl", "@adonisjs/lucid", "@adonisjs/auth", "@adonisjs/redis"]
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user