elearning/Backend/src/services/ChaptersLesson.service.ts

297 lines
13 KiB
TypeScript
Raw Normal View History

import { prisma } from '../config/database';
import { Prisma } from '@prisma/client';
import { config } from '../config';
import { logger } from '../config/logger';
import { generateFilePath, uploadFile } from '../config/minio';
import { UnauthorizedError, ValidationError, ForbiddenError, NotFoundError } from '../middleware/errorHandler';
import jwt from 'jsonwebtoken';
import {
LessonAttachmentData,
LessonData,
ChapterData,
CreateLessonInput,
UpdateLessonInput,
CreateChapterInput,
UpdateChapterInput,
ChaptersRequest,
DeleteChapterRequest,
ReorderChapterRequest,
ListChaptersResponse,
GetChapterResponse,
CreateChapterResponse,
UpdateChapterResponse,
DeleteChapterResponse,
ReorderChapterResponse,
ChapterWithLessonsResponse,
ListLessonsRequest,
GetLessonRequest,
CreateLessonRequest,
UpdateLessonRequest,
DeleteLessonRequest,
ReorderLessonsRequest,
ListLessonsResponse,
GetLessonResponse,
CreateLessonResponse,
UpdateLessonResponse,
DeleteLessonResponse,
ReorderLessonsResponse,
} from "../types/ChaptersLesson.typs";
import { CoursesInstructorService } from './CoursesInstructor.service';
export class ChaptersLessonService {
async listChapters(request: ChaptersRequest): Promise<ListChaptersResponse> {
try {
const { token, course_id } = request;
const decodedToken = jwt.verify(token, config.jwt.secret) as { id: number };
const user = await prisma.user.findUnique({ where: { id: decodedToken.id } });
if (!user) {
throw new UnauthorizedError('Invalid token');
}
const chapters = await prisma.chapter.findMany({
where: { course_id }, orderBy: { sort_order: 'asc' },
include: { lessons: { orderBy: { sort_order: 'asc' } } }
});
return { code: 200, message: 'Chapters fetched successfully', data: chapters as ChapterData[], total: chapters.length };
} catch (error) {
logger.error(`Error fetching chapters: ${error}`);
throw error;
}
}
async createChapter(request: CreateChapterInput): Promise<CreateChapterResponse> {
try {
const { token, course_id, title, description, sort_order } = request;
const decodedToken = jwt.verify(token, config.jwt.secret) as { id: number };
await CoursesInstructorService.validateCourseStatus(course_id);
const user = await prisma.user.findUnique({ where: { id: decodedToken.id } });
if (!user) {
throw new UnauthorizedError('Invalid token');
}
const courseInstructor = await CoursesInstructorService.validateCourseInstructor(token, course_id);
if (!courseInstructor) {
throw new ForbiddenError('You are not permitted to create chapter');
}
const chapter = await prisma.chapter.create({ data: { course_id, title, description, sort_order } });
return { code: 200, message: 'Chapter created successfully', data: chapter as ChapterData };
} catch (error) {
logger.error(`Error creating chapter: ${error}`);
throw error;
}
}
async updateChapter(request: UpdateChapterInput): Promise<UpdateChapterResponse> {
try {
const { token, course_id, chapter_id, title, description, sort_order } = request;
const decodedToken = jwt.verify(token, config.jwt.secret) as { id: number };
await CoursesInstructorService.validateCourseStatus(course_id);
const user = await prisma.user.findUnique({ where: { id: decodedToken.id } });
if (!user) {
throw new UnauthorizedError('Invalid token');
}
const courseInstructor = await CoursesInstructorService.validateCourseInstructor(token, course_id);
if (!courseInstructor) {
throw new ForbiddenError('You are not permitted to update chapter');
}
const chapter = await prisma.chapter.update({ where: { id: chapter_id }, data: { title, description, sort_order } });
return { code: 200, message: 'Chapter updated successfully', data: chapter as ChapterData };
} catch (error) {
logger.error(`Error updating chapter: ${error}`);
throw error;
}
}
async deleteChapter(request: DeleteChapterRequest): Promise<DeleteChapterResponse> {
try {
const { token, course_id, chapter_id } = request;
const decodedToken = jwt.verify(token, config.jwt.secret) as { id: number };
await CoursesInstructorService.validateCourseStatus(course_id);
const user = await prisma.user.findUnique({ where: { id: decodedToken.id } });
if (!user) {
throw new UnauthorizedError('Invalid token');
}
const courseInstructor = await CoursesInstructorService.validateCourseInstructor(token, course_id);
if (!courseInstructor) {
throw new ForbiddenError('You are not permitted to delete chapter');
}
await prisma.chapter.delete({ where: { id: chapter_id } });
return { code: 200, message: 'Chapter deleted successfully' };
} catch (error) {
logger.error(`Error deleting chapter: ${error}`);
throw error;
}
}
async reorderChapter(request: ReorderChapterRequest): Promise<ReorderChapterResponse> {
try {
const { token, course_id, chapter_id, sort_order } = request;
const decodedToken = jwt.verify(token, config.jwt.secret) as { id: number };
await CoursesInstructorService.validateCourseStatus(course_id);
const user = await prisma.user.findUnique({ where: { id: decodedToken.id } });
if (!user) {
throw new UnauthorizedError('Invalid token');
}
const courseInstructor = await CoursesInstructorService.validateCourseInstructor(token, course_id);
if (!courseInstructor) {
throw new ForbiddenError('You are not permitted to reorder chapter');
}
const chapter = await prisma.chapter.update({ where: { id: chapter_id }, data: { sort_order } });
return { code: 200, message: 'Chapter reordered successfully', data: [chapter as ChapterData] };
} catch (error) {
logger.error(`Error reordering chapter: ${error}`);
throw error;
}
}
async createLesson(request: CreateLessonInput): Promise<CreateLessonResponse> {
try {
const { token, course_id, chapter_id, title, content, type, sort_order, video, attachments } = request;
const decodedToken = jwt.verify(token, config.jwt.secret) as { id: number };
await CoursesInstructorService.validateCourseStatus(course_id);
const user = await prisma.user.findUnique({ where: { id: decodedToken.id } });
if (!user) {
throw new UnauthorizedError('Invalid token');
}
const courseInstructor = await CoursesInstructorService.validateCourseInstructor(token, course_id);
if (!courseInstructor) {
throw new ForbiddenError('You are not permitted to create lesson');
}
// Create the lesson first
const lesson = await prisma.lesson.create({
data: { chapter_id, title, content, type, sort_order }
});
const uploadedAttachments: { file_name: string; file_path: string; file_size: number; mime_type: string }[] = [];
// Handle video upload for VIDEO type lessons
if (type === 'VIDEO' && video) {
const videoPath = generateFilePath(course_id, lesson.id, 'video', video.originalname);
await uploadFile(videoPath, video.buffer, video.mimetype);
// Save video as attachment
await prisma.lessonAttachment.create({
data: {
lesson_id: lesson.id,
file_name: video.originalname,
file_size: video.size,
mime_type: video.mimetype,
file_path: videoPath,
sort_order: 0,
}
});
uploadedAttachments.push({
file_name: video.originalname,
file_path: videoPath,
file_size: video.size,
mime_type: video.mimetype,
});
// Handle additional attachments (PDFs, documents, etc.)
if (attachments && attachments.length > 0) {
for (let i = 0; i < attachments.length; i++) {
const attachment = attachments[i];
const attachmentPath = generateFilePath(course_id, lesson.id, 'attachment', attachment.originalname);
await uploadFile(attachmentPath, attachment.buffer, attachment.mimetype);
await prisma.lessonAttachment.create({
data: {
lesson_id: lesson.id,
file_name: attachment.originalname,
file_size: attachment.size,
mime_type: attachment.mimetype,
file_path: attachmentPath,
sort_order: i + 1, // Start from 1 since video is 0
}
});
uploadedAttachments.push({
file_name: attachment.originalname,
file_path: attachmentPath,
file_size: attachment.size,
mime_type: attachment.mimetype,
});
}
}
} else if (type === 'QUIZ' && request.quiz_data) {
// Create Quiz with Questions and Choices
const { quiz_data } = request;
const userId = decodedToken.id;
// Create the quiz
const quiz = await prisma.quiz.create({
data: {
lesson_id: lesson.id,
title: quiz_data.title,
description: quiz_data.description,
passing_score: quiz_data.passing_score ?? 60,
time_limit: quiz_data.time_limit,
shuffle_questions: quiz_data.shuffle_questions ?? false,
shuffle_choices: quiz_data.shuffle_choices ?? false,
show_answers_after_completion: quiz_data.show_answers_after_completion ?? true,
created_by: userId,
}
});
// Create questions with choices
if (quiz_data.questions && quiz_data.questions.length > 0) {
for (let i = 0; i < quiz_data.questions.length; i++) {
const questionInput = quiz_data.questions[i];
// Create the question
const question = await prisma.question.create({
data: {
quiz_id: quiz.id,
question: questionInput.question,
explanation: questionInput.explanation,
question_type: questionInput.question_type,
score: questionInput.score ?? 1,
sort_order: questionInput.sort_order ?? i,
}
});
// Create choices for this question
if (questionInput.choices && questionInput.choices.length > 0) {
for (let j = 0; j < questionInput.choices.length; j++) {
const choiceInput = questionInput.choices[j];
await prisma.choice.create({
data: {
question_id: question.id,
text: choiceInput.text,
is_correct: choiceInput.is_correct,
sort_order: choiceInput.sort_order ?? j,
}
});
}
}
}
}
}
// Fetch the complete lesson with attachments and quiz
const completeLesson = await prisma.lesson.findUnique({
where: { id: lesson.id },
include: {
attachments: { orderBy: { sort_order: 'asc' } },
quiz: {
include: {
questions: {
orderBy: { sort_order: 'asc' },
include: {
choices: { orderBy: { sort_order: 'asc' } }
}
}
}
}
}
});
return { code: 200, message: 'Lesson created successfully', data: completeLesson as LessonData };
} catch (error) {
logger.error(`Error creating lesson: ${error}`);
throw error;
}
}
}