import { prisma } from '../config/database'; import { Prisma } from '@prisma/client'; import { config } from '../config'; import { logger } from '../config/logger'; import { UnauthorizedError, ValidationError, ForbiddenError } from '../middleware/errorHandler'; import jwt from 'jsonwebtoken'; import { CreateCourseInput, UpdateCourseInput, createCourseResponse, GetMyCourseResponse, ListMyCourseResponse, addinstructorCourse, addinstructorCourseResponse, removeinstructorCourse, removeinstructorCourseResponse, setprimaryCourseInstructor, setprimaryCourseInstructorResponse, submitCourseResponse, listinstructorCourseResponse, sendCourseForReview, getmyCourse, listinstructorCourse, } from "../types/CoursesInstructor.types"; export class CoursesInstructorService { static async createCourse(courseData: CreateCourseInput, userId: number): Promise { try { // Use transaction to create course and instructor together const result = await prisma.$transaction(async (tx) => { // Create the course const courseCreated = await tx.course.create({ data: { category_id: courseData.category_id, title: courseData.title, slug: courseData.slug, description: courseData.description, thumbnail_url: courseData.thumbnail_url, price: courseData.price || 0, is_free: courseData.is_free ?? false, have_certificate: courseData.have_certificate ?? false, created_by: userId, status: 'DRAFT' } }); // Add creator as primary instructor await tx.courseInstructor.create({ data: { course_id: courseCreated.id, user_id: userId, is_primary: true, } }); return courseCreated; }); return { code: 201, message: 'Course created successfully', data: result }; } catch (error) { logger.error('Failed to create course', { error }); throw error; } } static async listMyCourses(token: string): Promise { try { const decoded = jwt.verify(token, config.jwt.secret) as { id: number; type: string }; const courseInstructors = await prisma.courseInstructor.findMany({ where: { user_id: decoded.id }, include: { course: true } }); const courses = courseInstructors.map(ci => ci.course); return { code: 200, message: 'Courses retrieved successfully', data: courses, total: courses.length }; } catch (error) { logger.error('Failed to retrieve courses', { error }); throw error; } } static async getmyCourse(getmyCourse: getmyCourse): Promise { try { const decoded = jwt.verify(getmyCourse.token, config.jwt.secret) as { id: number; type: string }; // Check if user is instructor of this course const courseInstructor = await prisma.courseInstructor.findFirst({ where: { user_id: decoded.id, course_id: getmyCourse.course_id }, include: { course: { include: { chapters: { include: { lessons: { include: { attachments: true, progress: true, quiz: true } } } } } } } }); if (!courseInstructor) { throw new ForbiddenError('You are not an instructor of this course'); } return { code: 200, message: 'Course retrieved successfully', data: courseInstructor.course }; } catch (error) { logger.error('Failed to retrieve course', { error }); throw error; } } static async updateCourse(token: string, courseId: number, courseData: UpdateCourseInput): Promise { try { const courseInstructorId = await this.validateCourseInstructor(token, courseId); const course = await prisma.course.update({ where: { id: courseInstructorId.user_id }, data: courseData }); return { code: 200, message: 'Course updated successfully', data: course }; } catch (error) { logger.error('Failed to update course', { error }); throw error; } } static async deleteCourse(token: string, courseId: number): Promise { try { const courseInstructorId = await this.validateCourseInstructor(token, courseId); if (!courseInstructorId.is_primary) { throw new ForbiddenError('You have no permission to delete this course'); } const course = await prisma.course.delete({ where: { id: courseInstructorId.user_id } }); return { code: 200, message: 'Course deleted successfully', data: course }; } catch (error) { logger.error('Failed to delete course', { error }); throw error; } } static async sendCourseForReview(sendCourseForReview: sendCourseForReview): Promise { try { const decoded = jwt.verify(sendCourseForReview.token, config.jwt.secret) as { id: number; type: string }; await prisma.courseApproval.create({ data: { course_id: sendCourseForReview.course_id, submitted_by: decoded.id, } }); return { code: 200, message: 'Course sent for review successfully', }; } catch (error) { logger.error('Failed to send course for review', { error }); throw error; } } static async addInstructorToCourse(addinstructorCourse: addinstructorCourse): Promise { try { const decoded = jwt.verify(addinstructorCourse.token, config.jwt.secret) as { id: number; type: string }; await prisma.courseInstructor.create({ data: { course_id: addinstructorCourse.course_id, user_id: decoded.id, } }); return { code: 200, message: 'Instructor added to course successfully', }; } catch (error) { logger.error('Failed to add instructor to course', { error }); throw error; } } static async removeInstructorFromCourse(removeinstructorCourse: removeinstructorCourse): Promise { try { const decoded = jwt.verify(removeinstructorCourse.token, config.jwt.secret) as { id: number; type: string }; await prisma.courseInstructor.delete({ where: { course_id_user_id: { course_id: removeinstructorCourse.course_id, user_id: removeinstructorCourse.user_id, }, } }); return { code: 200, message: 'Instructor removed from course successfully', }; } catch (error) { logger.error('Failed to remove instructor from course', { error }); throw error; } } static async listInstructorsOfCourse(listinstructorCourse: listinstructorCourse): Promise { try { const decoded = jwt.verify(listinstructorCourse.token, config.jwt.secret) as { id: number; type: string }; const courseInstructors = await prisma.courseInstructor.findMany({ where: { course_id: listinstructorCourse.course_id, }, include: { user: true, } }); return { code: 200, message: 'Instructors retrieved successfully', data: courseInstructors, }; } catch (error) { logger.error('Failed to retrieve instructors of course', { error }); throw error; } } static async setPrimaryInstructor(setprimaryCourseInstructor: setprimaryCourseInstructor): Promise { try { const decoded = jwt.verify(setprimaryCourseInstructor.token, config.jwt.secret) as { id: number; type: string }; await prisma.courseInstructor.update({ where: { course_id_user_id: { course_id: setprimaryCourseInstructor.course_id, user_id: setprimaryCourseInstructor.user_id, }, }, data: { is_primary: true, } }); return { code: 200, message: 'Primary instructor set successfully', }; } catch (error) { logger.error('Failed to set primary instructor', { error }); throw error; } } private static async validateCourseInstructor(token: string, courseId: number): Promise<{ user_id: number; is_primary: boolean }> { const decoded = jwt.verify(token, config.jwt.secret) as { id: number; type: string }; const courseInstructor = await prisma.courseInstructor.findFirst({ where: { user_id: decoded.id, course_id: courseId } }); if (!courseInstructor) { throw new ForbiddenError('You are not an instructor of this course'); } else return { user_id: courseInstructor.user_id, is_primary: courseInstructor.is_primary }; } }