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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { /** * 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 { 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 { 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 { 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 { 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 { 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: {}, }; } public async setCourseStatus(id: string | number, status: CourseStatusEnum): Promise { 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 { const count = await this.courseRepository.getLikesCount(id); return { success: true, status: StatusCodeEnum.OK, message: 'Fetched course likes count.', data: { count }, }; } } new Ioc().make(CourseService);