feat: likes system in courses

This commit is contained in:
Sergey Yarkov 2021-08-11 01:34:40 +03:00
parent d96a54351e
commit 4c1572a9d8
5 changed files with 147 additions and 1 deletions

View File

@ -170,6 +170,7 @@ export default class CoursesController extends BaseController {
/** /**
* Detach student from course * Detach student from course
* DELETE /courses/:id/detach-student
*/ */
public async detachStudent(ctx: HttpContextContract) { public async detachStudent(ctx: HttpContextContract) {
const payload = await ctx.request.validate(DelCourseFromUserValidator); 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); 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) { public async studentsCount(ctx: HttpContextContract) {
const result = await this.courseService.fetchStudentsCount(ctx.params.id); 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); 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); new Ioc().make(CoursesController);

View File

@ -53,6 +53,16 @@ export default class Course extends BaseModel {
}) })
public students: ManyToMany<typeof User>; public students: ManyToMany<typeof User>;
@manyToMany(() => User, {
pivotTable: 'courses_likes',
pivotTimestamps: {
createdAt: 'liked_on',
updatedAt: false,
},
pivotColumns: ['liked_on'],
})
public likes: ManyToMany<typeof User>;
@column.dateTime({ autoCreate: true }) @column.dateTime({ autoCreate: true })
public createdAt: DateTime; public createdAt: DateTime;

View File

@ -1,4 +1,5 @@
import { inject, Ioc } from '@adonisjs/core/build/standalone'; import { inject, Ioc } from '@adonisjs/core/build/standalone';
import { AuthContract } from '@ioc:Adonis/Addons/Auth';
import RoleEnum from 'App/Datatypes/Enums/RoleEnum'; 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 StatusCodeEnum from 'App/Datatypes/Enums/StatusCodeEnum';
import IResponse from 'App/Datatypes/Interfaces/IResponse'; import IResponse from 'App/Datatypes/Interfaces/IResponse';
import RoleHelper from 'App/Helpers/RoleHelper'; import RoleHelper from 'App/Helpers/RoleHelper';
import CategoryRepository from 'App/Repositories/CategoryRepository';
/** /**
* Repositories * Repositories
*/ */
import CourseRepository from 'App/Repositories/CourseRepository'; import CourseRepository from 'App/Repositories/CourseRepository';
import UserRepository from 'App/Repositories/UserRepository'; import UserRepository from 'App/Repositories/UserRepository';
import CategoryRepository from 'App/Repositories/CategoryRepository';
/**
* Validators
*/
import CreateCourseValidator from 'App/Validators/Course/CreateCourseValidator'; import CreateCourseValidator from 'App/Validators/Course/CreateCourseValidator';
import UpdateCourseValidator from 'App/Validators/Course/UpdateCourseValidator'; import UpdateCourseValidator from 'App/Validators/Course/UpdateCourseValidator';
@ -479,6 +484,77 @@ export default class CourseService {
data: {}, data: {},
}; };
} }
public async attachUserLike(id: string | number, auth: AuthContract): Promise<any> {
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<any> {
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); new Ioc().make(CourseService);

View File

@ -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);
}
}

View File

@ -33,4 +33,12 @@ Route.group(() => {
Route.delete('/:id/detach-student', 'Api/v1/CoursesController.detachStudent') Route.delete('/:id/detach-student', 'Api/v1/CoursesController.detachStudent')
.middleware('role:admin,teacher') .middleware('role:admin,teacher')
.as('courses.detach-student'); .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'); }).prefix('courses');