elearning/Backend/src/controllers/LessonsController.ts
JakkrapartXD ff841c7638 feat: add search and filter capabilities to student and quiz endpoints, implement YouTube video support for lessons
Add search and status filter parameters to getEnrolledStudents endpoint to filter students by name/email/username and enrollment status. Add search and isPassed filter parameters to getQuizScores endpoint to filter quiz results by student details and pass status. Remove separate searchStudents endpoint as its functionality is now integrated into getEnrolledStudents. Add setYouTubeVideo endpoint to
2026-02-03 17:23:47 +07:00

231 lines
7.9 KiB
TypeScript

import { Body, Delete, Path, Post, Put, Request, Response, Route, Security, SuccessResponse, Tags, UploadedFile } from 'tsoa';
import { ValidationError } from '../middleware/errorHandler';
import { ChaptersLessonService } from '../services/ChaptersLesson.service';
import {
UploadedFileInfo,
CreateLessonResponse,
UpdateLessonResponse,
VideoOperationResponse,
AttachmentOperationResponse,
DeleteAttachmentResponse,
YouTubeVideoResponse,
SetYouTubeVideoBody,
} from '../types/ChaptersLesson.typs';
const chaptersLessonService = new ChaptersLessonService();
@Route('api/instructors/courses/{courseId}/chapters/{chapterId}/lessons')
@Tags('Lessons - File Upload')
export class LessonsController {
/**
* อัพโหลดวิดีโอใหม่ให้บทเรียน (ครั้งแรก)
* Upload video to lesson (first time)
*
* @param courseId Course ID
* @param chapterId Chapter ID
* @param lessonId Lesson ID
* @param video ไฟล์วิดีโอ (required)
*/
@Post('{lessonId}/video')
@Security('jwt', ['instructor'])
@SuccessResponse('200', 'Video uploaded successfully')
@Response('400', 'Validation error - Video already exists')
@Response('401', 'Unauthorized')
@Response('403', 'Forbidden')
@Response('404', 'Lesson not found')
public async uploadVideo(
@Request() request: any,
@Path() courseId: number,
@Path() chapterId: number,
@Path() lessonId: number,
@UploadedFile() video: Express.Multer.File
): Promise<VideoOperationResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) throw new ValidationError('No token provided');
if (!video) {
throw new ValidationError('Video file is required');
}
const videoInfo: UploadedFileInfo = {
originalname: video.originalname,
mimetype: video.mimetype,
size: video.size,
buffer: video.buffer,
};
return await chaptersLessonService.uploadVideo({
token,
course_id: courseId,
lesson_id: lessonId,
video: videoInfo,
});
}
/**
* อัพเดต (เปลี่ยน) วิดีโอของบทเรียน
* Update (replace) video in lesson
*
* @param courseId Course ID
* @param chapterId Chapter ID
* @param lessonId Lesson ID
* @param video ไฟล์วิดีโอใหม่ (required)
*/
@Put('{lessonId}/video')
@Security('jwt', ['instructor'])
@SuccessResponse('200', 'Video updated successfully')
@Response('400', 'Validation error')
@Response('401', 'Unauthorized')
@Response('403', 'Forbidden')
@Response('404', 'Lesson not found')
public async updateVideo(
@Request() request: any,
@Path() courseId: number,
@Path() chapterId: number,
@Path() lessonId: number,
@UploadedFile() video: Express.Multer.File
): Promise<VideoOperationResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) throw new ValidationError('No token provided');
if (!video) {
throw new ValidationError('Video file is required');
}
const videoInfo: UploadedFileInfo = {
originalname: video.originalname,
mimetype: video.mimetype,
size: video.size,
buffer: video.buffer,
};
return await chaptersLessonService.updateVideo({
token,
course_id: courseId,
lesson_id: lessonId,
video: videoInfo,
});
}
/**
* อัพโหลดไฟล์แนบทีละไฟล์
* Upload a single attachment to lesson
*
* @param courseId Course ID
* @param chapterId Chapter ID
* @param lessonId Lesson ID
* @param attachment ไฟล์แนบ (required)
*/
@Post('{lessonId}/attachments')
@Security('jwt', ['instructor'])
@SuccessResponse('200', 'Attachment uploaded successfully')
@Response('400', 'Validation error')
@Response('401', 'Unauthorized')
@Response('403', 'Forbidden')
@Response('404', 'Lesson not found')
public async uploadAttachment(
@Request() request: any,
@Path() courseId: number,
@Path() chapterId: number,
@Path() lessonId: number,
@UploadedFile() attachment: Express.Multer.File
): Promise<AttachmentOperationResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) throw new ValidationError('No token provided');
if (!attachment) {
throw new ValidationError('Attachment file is required');
}
const attachmentInfo: UploadedFileInfo = {
originalname: attachment.originalname,
mimetype: attachment.mimetype,
size: attachment.size,
buffer: attachment.buffer,
};
return await chaptersLessonService.uploadAttachment({
token,
course_id: courseId,
lesson_id: lessonId,
attachment: attachmentInfo,
});
}
/**
* ลบไฟล์แนบทีละไฟล์
* Delete a single attachment from lesson
*
* @param courseId Course ID
* @param chapterId Chapter ID
* @param lessonId Lesson ID
* @param attachmentId Attachment ID
*/
@Delete('{lessonId}/attachments/{attachmentId}')
@Security('jwt', ['instructor'])
@SuccessResponse('200', 'Attachment deleted successfully')
@Response('400', 'Validation error')
@Response('401', 'Unauthorized')
@Response('403', 'Forbidden')
@Response('404', 'Attachment not found')
public async deleteAttachment(
@Request() request: any,
@Path() courseId: number,
@Path() chapterId: number,
@Path() lessonId: number,
@Path() attachmentId: number
): Promise<DeleteAttachmentResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) throw new ValidationError('No token provided');
return await chaptersLessonService.deleteAttachment({
token,
course_id: courseId,
lesson_id: lessonId,
attachment_id: attachmentId,
});
}
/**
* ตั้งค่าวิดีโอ YouTube ให้บทเรียน (แทนที่วิดีโอเดิมถ้ามี)
* Set YouTube video for a lesson (replaces existing video if any)
*
* @param courseId Course ID
* @param chapterId Chapter ID
* @param lessonId Lesson ID
* @param body YouTube video info
*/
@Post('{lessonId}/youtube-video')
@Security('jwt', ['instructor'])
@SuccessResponse('200', 'YouTube video set successfully')
@Response('400', 'Validation error')
@Response('401', 'Unauthorized')
@Response('403', 'Forbidden')
@Response('404', 'Lesson not found')
public async setYouTubeVideo(
@Request() request: any,
@Path() courseId: number,
@Path() chapterId: number,
@Path() lessonId: number,
@Body() body: SetYouTubeVideoBody
): Promise<YouTubeVideoResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) throw new ValidationError('No token provided');
if (!body.youtube_video_id) {
throw new ValidationError('YouTube video ID is required');
}
if (!body.video_title) {
throw new ValidationError('Video title is required');
}
return await chaptersLessonService.setYouTubeVideo({
token,
course_id: courseId,
lesson_id: lessonId,
youtube_video_id: body.youtube_video_id,
video_title: body.video_title,
});
}
}