educt-server/app/Services/CourseService.ts

599 lines
14 KiB
TypeScript

import { cuid } from '@ioc:Adonis/Core/Helpers';
import { inject, Ioc } from '@adonisjs/core/build/standalone';
import { AuthContract } from '@ioc:Adonis/Addons/Auth';
/**
* Datatypes
*/
import RoleEnum from 'App/Datatypes/Enums/RoleEnum';
import CourseStatusEnum from 'App/Datatypes/Enums/CourseStatusEnum';
import StatusCodeEnum from 'App/Datatypes/Enums/StatusCodeEnum';
import IResponse from 'App/Datatypes/Interfaces/IResponse';
import RoleHelper from 'App/Helpers/RoleHelper';
/**
* Repositories
*/
import CourseRepository from 'App/Repositories/CourseRepository';
import UserRepository from 'App/Repositories/UserRepository';
import CategoryRepository from 'App/Repositories/CategoryRepository';
import ImageRepository from 'App/Repositories/ImageRepository';
/**
* Validators
*/
import CreateCourseValidator from 'App/Validators/Course/CreateCourseValidator';
import UpdateCourseValidator from 'App/Validators/Course/UpdateCourseValidator';
@inject()
export default class CourseService {
private courseRepository: CourseRepository;
private userRepository: UserRepository;
private categoryRepository: CategoryRepository;
private imageRepository: ImageRepository;
constructor(
courseRepository: CourseRepository,
userRepository: UserRepository,
categoryRepository: CategoryRepository,
imageRepository: ImageRepository
) {
this.courseRepository = courseRepository;
this.userRepository = userRepository;
this.categoryRepository = categoryRepository;
this.imageRepository = imageRepository;
}
/**
* Fetch list of courses
*
* @returns Response
*/
public async fetchCourses(): Promise<IResponse> {
const data = await this.courseRepository.getAll();
return {
success: true,
status: StatusCodeEnum.OK,
message: 'Fetched all courses.',
data,
};
}
/**
* Fetch one course by id
*
* @param id Course id
* @returns Response
*/
public async fetchCourse(id: string | number): Promise<IResponse> {
const data = await this.courseRepository.getById(id);
if (!data) {
return {
success: false,
status: StatusCodeEnum.NOT_FOUND,
message: 'Course not found.',
data: {},
error: {
code: 'E_NOT_FOUND',
},
};
}
return {
success: true,
status: StatusCodeEnum.OK,
message: 'Fetched course.',
data,
};
}
/**
* Fetch teacher by course id
*
* @param id Course id
* @returns Response
*/
public async fetchCourseTeacher(id: string | number): Promise<IResponse> {
const data = await this.courseRepository.getTeacher(id);
if (!data) {
return {
success: false,
status: StatusCodeEnum.NOT_FOUND,
message: 'Course not found.',
data: {},
error: {
code: 'E_NOT_FOUND',
},
};
}
return {
success: true,
status: StatusCodeEnum.OK,
message: 'Fetched course teacher.',
data,
};
}
/**
* Fetch category by course id
*
* @param id Course id
* @returns Response
*/
public async fetchCourseCategory(id: string | number): Promise<IResponse> {
const data = await this.courseRepository.getCategory(id);
if (!data) {
return {
success: false,
status: StatusCodeEnum.NOT_FOUND,
message: 'Course not found.',
data: {},
error: {
code: 'E_NOT_FOUND',
},
};
}
return {
success: true,
status: StatusCodeEnum.OK,
message: 'Fetched course category.',
data,
};
}
/**
* Fetch lessons by course id
*
* @param id Course id
* @returns Response
*/
public async fetchCourseLessons(id: string | number): Promise<IResponse> {
const data = await this.courseRepository.getLessons(id);
if (!data) {
return {
success: false,
status: StatusCodeEnum.NOT_FOUND,
message: 'Course not found.',
data: {},
error: {
code: 'E_NOT_FOUND',
},
};
}
return {
success: true,
status: StatusCodeEnum.OK,
message: 'Fetched course lessons.',
data,
};
}
/**
* Fetch students by course id
*
* @param id Course id
* @returns Response
*/
public async fetchCourseStudents(id: string | number): Promise<IResponse> {
const data = await this.courseRepository.getStudents(id);
if (!data) {
return {
success: false,
status: StatusCodeEnum.NOT_FOUND,
message: 'Course not found.',
data: {},
error: {
code: 'E_NOT_FOUND',
},
};
}
return {
success: true,
status: StatusCodeEnum.OK,
message: 'Fetched course students.',
data,
};
}
/**
* Fetch students count by course id
*
* @param id Course id
* @returns Response
*/
public async fetchStudentsCount(id: string | number): Promise<IResponse> {
const count = await this.courseRepository.getStudentsCount(id);
return {
success: true,
status: StatusCodeEnum.OK,
message: 'Fetched students count.',
data: { count },
};
}
/**
* Create course
*
* @param data Data input
* @returns Response
*/
public async createCourse(data: CreateCourseValidator['schema']['props']): Promise<IResponse> {
/**
* Find course teacher
*/
const teacher = await this.userRepository.getById(data.teacher_id);
if (!teacher) {
return {
success: false,
status: StatusCodeEnum.NOT_FOUND,
message: 'Teacher not found.',
data: {},
error: {
code: 'E_NOT_FOUND',
},
};
}
const isTeacher = RoleHelper.userHasRoles(teacher.roles, [RoleEnum.TEACHER]);
if (!isTeacher) {
return {
success: false,
status: StatusCodeEnum.BAD_REQUEST,
message: `User with id "${teacher.id}" not a teacher.`,
data: {},
error: {
code: 'E_BAD_REQUEST',
},
};
}
/**
* Find course category
*/
const category = await this.categoryRepository.getById(data.category_id);
if (!category) {
return {
success: false,
status: StatusCodeEnum.NOT_FOUND,
message: 'Category not found.',
data: {},
error: {
code: 'E_NOT_FOUND',
},
};
}
/* Create course */
const course = await this.courseRepository.create(data);
/* Create image if provided */
if (data.background_image) {
const bgImage = await this.imageRepository.createInCloudinaryCloud(data.background_image, cuid());
await course.related('image').associate(bgImage);
}
return {
success: true,
status: StatusCodeEnum.CREATED,
message: 'Course created.',
data: course,
};
}
/**
* Delete course
*
* @param id Course id
* @returns Response
*/
public async deleteCourse(id: string | number): Promise<IResponse> {
const course = await this.courseRepository.delete(id);
if (!course) {
return {
success: false,
status: StatusCodeEnum.NOT_FOUND,
message: 'Course not found.',
data: {},
error: {
code: 'E_NOT_FOUND',
},
};
}
/**
* Delete background image from course
*/
await this.imageRepository.deleteFormCloudinaryCloud(course.bg_image_id);
return {
success: true,
status: StatusCodeEnum.OK,
message: 'Course deleted.',
data: course,
};
}
/**
* Update course
*
* @param id Course id
* @param data Data to update
* @returns Response
*/
public async updateCourse(id: string | number, data: UpdateCourseValidator['schema']['props']): Promise<IResponse> {
const course = await this.courseRepository.update(id, data);
if (!course) {
return {
success: false,
status: StatusCodeEnum.NOT_FOUND,
message: 'Course not found.',
data: {},
error: {
code: 'E_NOT_FOUND',
},
};
}
return {
success: true,
status: StatusCodeEnum.OK,
message: 'Course updated.',
data: course,
};
}
/**
* Attach student to course
*
* @param id Course id
* @param studentId Student id
* @returns Response
*/
public async attachUserCourse(id: string | number, studentId: string | number): Promise<IResponse> {
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',
},
};
}
const student = await this.userRepository.getById(studentId);
if (!student) {
return {
success: false,
status: StatusCodeEnum.NOT_FOUND,
message: 'Student not found.',
data: {},
error: {
code: 'E_NOT_FOUND',
},
};
}
const isStudent = RoleHelper.userHasRoles(student.roles, [RoleEnum.STUDENT]);
if (!isStudent) {
return {
success: false,
status: StatusCodeEnum.BAD_REQUEST,
message: `User cannot be added to the course without "${RoleEnum.STUDENT}" role.`,
data: {},
error: {
code: 'E_BAD_REQUEST',
},
};
}
try {
await course.related('students').attach([student.id]);
} catch (error) {
if (error.code === '23505') {
return {
success: false,
status: StatusCodeEnum.BAD_REQUEST,
message: 'Student already attached to that course.',
data: {},
error: {
code: 'E_BAD_REQUEST',
},
};
}
throw new Error(error);
}
return {
success: true,
status: StatusCodeEnum.OK,
message: 'Student attached.',
data: {},
};
}
public async detachUserCourse(id: string | number, studentId: string | number): Promise<IResponse> {
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',
},
};
}
const isAttached = course.students.find(student => student.id === studentId);
if (!isAttached) {
return {
success: false,
status: StatusCodeEnum.BAD_REQUEST,
message: 'Student not attached to that course.',
data: {},
error: {
code: 'E_BAD_REQUEST',
},
};
}
const student = await this.userRepository.getById(studentId);
if (!student) {
return {
success: false,
status: StatusCodeEnum.NOT_FOUND,
message: 'Student not found.',
data: {},
error: {
code: 'E_NOT_FOUND',
},
};
}
await course.related('students').detach([student.id]);
return {
success: true,
status: StatusCodeEnum.OK,
message: 'Student detached.',
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: {},
};
}
public async setCourseStatus(id: string | number, status: CourseStatusEnum): Promise<IResponse> {
const data = await this.courseRepository.setStatus(id, status);
if (!data) {
return {
success: false,
status: StatusCodeEnum.BAD_REQUEST,
message: `Cannot set status for course with id "${id}"`,
data: {},
error: {
code: 'E_BAD_REQUEST',
},
};
}
return {
success: true,
status: StatusCodeEnum.OK,
message: 'Status changed.',
data: {},
};
}
public async fetchCourseLikesCount(id: string | number): Promise<IResponse> {
const count = await this.courseRepository.getLikesCount(id);
return {
success: true,
status: StatusCodeEnum.OK,
message: 'Fetched course likes count.',
data: { count },
};
}
}
new Ioc().make(CourseService);