diff --git a/app/Controllers/Http/Api/v1/CoursesController.ts b/app/Controllers/Http/Api/v1/CoursesController.ts index ef49b31..c11b878 100644 --- a/app/Controllers/Http/Api/v1/CoursesController.ts +++ b/app/Controllers/Http/Api/v1/CoursesController.ts @@ -170,6 +170,7 @@ export default class CoursesController extends BaseController { /** * Detach student from course + * DELETE /courses/:id/detach-student */ public async detachStudent(ctx: HttpContextContract) { const payload = await ctx.request.validate(DelCourseFromUserValidator); @@ -182,6 +183,10 @@ export default class CoursesController extends BaseController { return this.sendResponse(ctx, result.data, result.message, result.status); } + /** + * Fetch count of students of course + * GET /courses/:id/students/count + */ public async studentsCount(ctx: HttpContextContract) { const result = await this.courseService.fetchStudentsCount(ctx.params.id); @@ -191,6 +196,34 @@ export default class CoursesController extends BaseController { return this.sendResponse(ctx, result.data, result.message, result.status); } + + /** + * Set like on course by authorized user + * PUT /courses/:id/likes + */ + public async setLike(ctx: HttpContextContract) { + const result = await this.courseService.attachUserLike(ctx.params.id, 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); + } + + /** + * Unset like on course by authorized user + * DELETE /courses/:id/likes + */ + public async unsetLike(ctx: HttpContextContract) { + const result = await this.courseService.detachUserLike(ctx.params.id, 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(CoursesController); diff --git a/app/Models/Course.ts b/app/Models/Course.ts index 2e83db5..29a1a14 100644 --- a/app/Models/Course.ts +++ b/app/Models/Course.ts @@ -53,6 +53,16 @@ export default class Course extends BaseModel { }) public students: ManyToMany; + @manyToMany(() => User, { + pivotTable: 'courses_likes', + pivotTimestamps: { + createdAt: 'liked_on', + updatedAt: false, + }, + pivotColumns: ['liked_on'], + }) + public likes: ManyToMany; + @column.dateTime({ autoCreate: true }) public createdAt: DateTime; diff --git a/app/Services/CourseService.ts b/app/Services/CourseService.ts index 4571e67..2de1409 100644 --- a/app/Services/CourseService.ts +++ b/app/Services/CourseService.ts @@ -1,4 +1,5 @@ import { inject, Ioc } from '@adonisjs/core/build/standalone'; +import { AuthContract } from '@ioc:Adonis/Addons/Auth'; import RoleEnum from 'App/Datatypes/Enums/RoleEnum'; /** @@ -7,13 +8,17 @@ import RoleEnum from 'App/Datatypes/Enums/RoleEnum'; import StatusCodeEnum from 'App/Datatypes/Enums/StatusCodeEnum'; import IResponse from 'App/Datatypes/Interfaces/IResponse'; import RoleHelper from 'App/Helpers/RoleHelper'; -import CategoryRepository from 'App/Repositories/CategoryRepository'; /** * Repositories */ import CourseRepository from 'App/Repositories/CourseRepository'; import UserRepository from 'App/Repositories/UserRepository'; +import CategoryRepository from 'App/Repositories/CategoryRepository'; + +/** + * Validators + */ import CreateCourseValidator from 'App/Validators/Course/CreateCourseValidator'; import UpdateCourseValidator from 'App/Validators/Course/UpdateCourseValidator'; @@ -479,6 +484,77 @@ export default class CourseService { data: {}, }; } + + public async attachUserLike(id: string | number, auth: AuthContract): Promise { + const user = await auth.use('api').authenticate(); + const course = await this.courseRepository.getById(id); + + if (!course) { + return { + success: false, + status: StatusCodeEnum.NOT_FOUND, + message: 'Course not found.', + data: {}, + error: { + code: 'E_NOT_FOUND', + }, + }; + } + + try { + await course.related('likes').attach([user.id]); + } catch (error) { + if (error.code === '23505') { + return { + success: false, + status: StatusCodeEnum.BAD_REQUEST, + message: 'You are already liked this course.', + data: {}, + error: { + code: 'E_BAD_REQUEST', + }, + }; + } + throw new Error(error); + } + + return { + success: true, + status: StatusCodeEnum.OK, + message: 'Like attached.', + data: {}, + }; + } + + public async detachUserLike(id: string | number, auth: AuthContract): Promise { + const user = await auth.use('api').authenticate(); + const course = await this.courseRepository.getById(id); + + if (!course) { + return { + success: false, + status: StatusCodeEnum.NOT_FOUND, + message: 'Course not found.', + data: {}, + error: { + code: 'E_NOT_FOUND', + }, + }; + } + + try { + await course.related('likes').detach([user.id]); + } catch (error) { + throw new Error(error); + } + + return { + success: true, + status: StatusCodeEnum.OK, + message: 'Like detached.', + data: {}, + }; + } } new Ioc().make(CourseService); diff --git a/database/migrations/1628622517837_courses_likes.ts b/database/migrations/1628622517837_courses_likes.ts new file mode 100644 index 0000000..51f177a --- /dev/null +++ b/database/migrations/1628622517837_courses_likes.ts @@ -0,0 +1,19 @@ +import BaseSchema from '@ioc:Adonis/Lucid/Schema'; + +export default class CoursesLikes extends BaseSchema { + protected tableName = 'courses_likes'; + + public async up() { + this.schema.createTable(this.tableName, table => { + table.increments('id'); + table.string('user_id', 21).unsigned().references('users.id').onDelete('CASCADE'); + table.string('course_id', 21).unsigned().references('courses.id').onDelete('CASCADE'); + table.timestamp('liked_on', { useTz: true }); + table.unique(['user_id', 'course_id']); + }); + } + + public async down() { + this.schema.dropTable(this.tableName); + } +} diff --git a/routes/apis/v1/courses.ts b/routes/apis/v1/courses.ts index c13479a..43caaee 100644 --- a/routes/apis/v1/courses.ts +++ b/routes/apis/v1/courses.ts @@ -33,4 +33,12 @@ Route.group(() => { Route.delete('/:id/detach-student', 'Api/v1/CoursesController.detachStudent') .middleware('role:admin,teacher') .as('courses.detach-student'); + + Route.put('/:id/likes', 'Api/v1/CoursesController.setLike') + .middleware('role:admin,teacher,student') + .as('courses.set-like'); + + Route.delete('/:id/likes', 'Api/v1/CoursesController.unsetLike') + .middleware('role:admin,teacher,student') + .as('courses.unset-like'); }).prefix('courses');