From e3575caab38f28884574d5d7feadf50ce4a1f25e Mon Sep 17 00:00:00 2001 From: Sergey Yarkov Date: Sat, 11 Dec 2021 23:49:39 +0300 Subject: [PATCH] feat: detaching students list --- .../Http/Api/v1/CoursesController.ts | 32 +++++++ app/Repositories/UserRepository.ts | 5 ++ app/Services/CourseService.ts | 87 +++++++++++++++++++ .../Course/AttachStudentsValitator.ts | 23 +++++ .../Course/DetachStudentstValitator.ts | 12 +++ routes/apis/v1/courses.ts | 8 ++ 6 files changed, 167 insertions(+) create mode 100644 app/Validators/Course/AttachStudentsValitator.ts create mode 100644 app/Validators/Course/DetachStudentstValitator.ts diff --git a/app/Controllers/Http/Api/v1/CoursesController.ts b/app/Controllers/Http/Api/v1/CoursesController.ts index e39c128..5a82a82 100644 --- a/app/Controllers/Http/Api/v1/CoursesController.ts +++ b/app/Controllers/Http/Api/v1/CoursesController.ts @@ -10,6 +10,8 @@ import CourseService from 'App/Services/CourseService'; * Validators */ import AddCourseToUserValidator from 'App/Validators/Course/AddCourseToUserValidator'; +import AttachStudentsValitator from 'App/Validators/Course/AttachStudentsValitator'; +import DetachStudentstValitator from 'App/Validators/Course/DetachStudentstValitator'; import CreateCourseValidator from 'App/Validators/Course/CreateCourseValidator'; import DelCourseFromUserValidator from 'App/Validators/Course/DelCourseFromUserValidator'; import SetCourseStatusValidator from 'App/Validators/Course/SetCourseStatusValidator'; @@ -171,6 +173,21 @@ export default class CoursesController extends BaseController { return this.sendResponse(ctx, result.data, result.message, result.status); } + /** + * Attach list of students to course + * POST /courses/:id/attach-student-list + */ + public async attachStudentList(ctx: HttpContextContract) { + const payload = await ctx.request.validate(AttachStudentsValitator); + const result = await this.courseService.attachStudentList(ctx.params.id, payload.students); + + 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); + } + /** * Detach student from course * DELETE /courses/:id/detach-student @@ -186,6 +203,21 @@ export default class CoursesController extends BaseController { return this.sendResponse(ctx, result.data, result.message, result.status); } + /** + * Detach list of students to course + * POST /courses/:id/detach-student-list + */ + public async detachStudentList(ctx: HttpContextContract) { + const payload = await ctx.request.validate(DetachStudentstValitator); + const result = await this.courseService.detachStudentList(ctx.params.id, payload.students); + + 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); + } + /** * Fetch count of students of course * GET /courses/:id/students/count diff --git a/app/Repositories/UserRepository.ts b/app/Repositories/UserRepository.ts index 650790e..ed9b30e 100644 --- a/app/Repositories/UserRepository.ts +++ b/app/Repositories/UserRepository.ts @@ -30,6 +30,11 @@ export default class UserRepository { return data; } + public async getByIds(ids: Array): Promise> { + const data = await this.User.query().preload('contacts').preload('roles').whereIn('id', ids); + return data; + } + /** * Get user by column * diff --git a/app/Services/CourseService.ts b/app/Services/CourseService.ts index 0b6623d..e650312 100644 --- a/app/Services/CourseService.ts +++ b/app/Services/CourseService.ts @@ -420,6 +420,93 @@ export default class CourseService { }; } + /** + * Attach list of students from course + * + * @param id Course id + * @param ids Student ids + * @returns Response + */ + public async attachStudentList(id: string | number, ids: Array): Promise { + const course = await this.courseRepository.getById(id); + + if (!course) { + return { + success: false, + status: HttpStatusEnum.NOT_FOUND, + message: 'Course not found.', + data: {}, + error: { + code: 'E_NOT_FOUND', + }, + }; + } + + const students = await this.userRepository.getByIds(ids); + + try { + await course.related('students').attach(students.map(s => s.id)); + } catch (error) { + if (error.code === '23505') { + return { + success: false, + status: HttpStatusEnum.BAD_REQUEST, + message: 'Some student already attached to that course.', + data: {}, + error: { + code: 'E_BAD_REQUEST', + }, + }; + } + throw new Error(error); + } + + return { + success: true, + status: HttpStatusEnum.OK, + message: 'Student list successfully attached.', + data: {}, + }; + } + + /** + * Detach list of students from course + * + * @param id Course id + * @param ids Student ids + * @returns Response + */ + public async detachStudentList(id: string | number, ids: Array): Promise { + const course = await this.courseRepository.getById(id); + + if (!course) { + return { + success: false, + status: HttpStatusEnum.NOT_FOUND, + message: 'Course not found.', + data: {}, + error: { + code: 'E_NOT_FOUND', + }, + }; + } + + const students = await this.userRepository.getByIds(ids); + + try { + await course.related('students').detach(students.map(s => s.id)); + } catch (error) { + throw new Error(error); + } + + return { + success: true, + status: HttpStatusEnum.OK, + message: 'Student list successfully detached from course.', + data: {}, + }; + } + public async detachUserCourse(id: string | number, studentId: string | number): Promise { const course = await this.courseRepository.getById(id); diff --git a/app/Validators/Course/AttachStudentsValitator.ts b/app/Validators/Course/AttachStudentsValitator.ts new file mode 100644 index 0000000..3a07d08 --- /dev/null +++ b/app/Validators/Course/AttachStudentsValitator.ts @@ -0,0 +1,23 @@ +import { schema, rules } from '@ioc:Adonis/Core/Validator'; +import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; + +export default class AttachStudentsValitator { + constructor(protected ctx: HttpContextContract) {} + + public schema = schema.create({ + students: schema.array().members( + schema.string({}, [ + rules.exists({ table: 'users', column: 'id' }), + rules.unique({ + table: 'users_courses', + column: 'user_id', + where: { + course_id: this.ctx.params.id, + }, + }), + ]) + ), + }); + + public messages = {}; +} diff --git a/app/Validators/Course/DetachStudentstValitator.ts b/app/Validators/Course/DetachStudentstValitator.ts new file mode 100644 index 0000000..27c89ff --- /dev/null +++ b/app/Validators/Course/DetachStudentstValitator.ts @@ -0,0 +1,12 @@ +import { schema, rules } from '@ioc:Adonis/Core/Validator'; +import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; + +export default class DetachStudentstValitator { + constructor(protected ctx: HttpContextContract) {} + + public schema = schema.create({ + students: schema.array().members(schema.string({}, [rules.exists({ table: 'users', column: 'id' })])), + }); + + public messages = {}; +} diff --git a/routes/apis/v1/courses.ts b/routes/apis/v1/courses.ts index 314d06b..319a394 100644 --- a/routes/apis/v1/courses.ts +++ b/routes/apis/v1/courses.ts @@ -30,6 +30,14 @@ Route.group(() => { .middleware('role:admin,teacher') .as('courses.attach-student'); + Route.post('/:id/attach-student-list', 'Api/v1/CoursesController.attachStudentList') + .middleware('role:admin,teacher') + .as('courses.attach-student-list'); + + Route.patch('/:id/detach-student-list', 'Api/v1/CoursesController.detachStudentList') + .middleware('role:admin,teacher') + .as('courses.detach-student-list'); + Route.delete('/:id/detach-student', 'Api/v1/CoursesController.detachStudent') .middleware('role:admin,teacher') .as('courses.detach-student');