2026-02-03 17:23:35 +07:00
|
|
|
import { Body, Delete, Path, Post, Put, Request, Response, Route, Security, SuccessResponse, Tags, UploadedFile } from 'tsoa';
|
2026-01-20 16:51:42 +07:00
|
|
|
import { ValidationError } from '../middleware/errorHandler';
|
2026-01-21 16:52:38 +07:00
|
|
|
import { ChaptersLessonService } from '../services/ChaptersLesson.service';
|
2026-01-26 17:23:26 +07:00
|
|
|
import {
|
|
|
|
|
UploadedFileInfo,
|
|
|
|
|
CreateLessonResponse,
|
|
|
|
|
UpdateLessonResponse,
|
|
|
|
|
VideoOperationResponse,
|
|
|
|
|
AttachmentOperationResponse,
|
2026-02-03 17:23:35 +07:00
|
|
|
DeleteAttachmentResponse,
|
|
|
|
|
YouTubeVideoResponse,
|
|
|
|
|
SetYouTubeVideoBody,
|
2026-01-26 17:23:26 +07:00
|
|
|
} from '../types/ChaptersLesson.typs';
|
2026-02-18 15:59:40 +07:00
|
|
|
import { SetYouTubeVideoValidator } from '../validators/Lessons.validator';
|
2026-01-20 16:51:42 +07:00
|
|
|
|
|
|
|
|
const chaptersLessonService = new ChaptersLessonService();
|
|
|
|
|
|
2026-01-21 16:52:38 +07:00
|
|
|
@Route('api/instructors/courses/{courseId}/chapters/{chapterId}/lessons')
|
|
|
|
|
@Tags('Lessons - File Upload')
|
2026-01-20 16:51:42 +07:00
|
|
|
export class LessonsController {
|
2026-01-21 16:52:38 +07:00
|
|
|
|
|
|
|
|
/**
|
2026-01-26 17:23:26 +07:00
|
|
|
* อัพโหลดวิดีโอใหม่ให้บทเรียน (ครั้งแรก)
|
|
|
|
|
* Upload video to lesson (first time)
|
2026-01-21 16:52:38 +07:00
|
|
|
*
|
|
|
|
|
* @param courseId Course ID
|
2026-01-26 17:23:26 +07:00
|
|
|
* @param chapterId Chapter ID
|
2026-01-21 16:52:38 +07:00
|
|
|
* @param lessonId Lesson ID
|
|
|
|
|
* @param video ไฟล์วิดีโอ (required)
|
|
|
|
|
*/
|
|
|
|
|
@Post('{lessonId}/video')
|
|
|
|
|
@Security('jwt', ['instructor'])
|
2026-01-26 17:23:26 +07:00
|
|
|
@SuccessResponse('200', 'Video uploaded successfully')
|
|
|
|
|
@Response('400', 'Validation error - Video already exists')
|
2026-01-21 16:52:38 +07:00
|
|
|
@Response('401', 'Unauthorized')
|
|
|
|
|
@Response('403', 'Forbidden')
|
|
|
|
|
@Response('404', 'Lesson not found')
|
2026-01-26 17:23:26 +07:00
|
|
|
public async uploadVideo(
|
2026-01-21 16:52:38 +07:00
|
|
|
@Request() request: any,
|
|
|
|
|
@Path() courseId: number,
|
|
|
|
|
@Path() chapterId: number,
|
|
|
|
|
@Path() lessonId: number,
|
2026-01-26 17:23:26 +07:00
|
|
|
@UploadedFile() video: Express.Multer.File
|
|
|
|
|
): Promise<VideoOperationResponse> {
|
2026-01-21 16:52:38 +07:00
|
|
|
|
|
|
|
|
if (!video) {
|
|
|
|
|
throw new ValidationError('Video file is required');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const videoInfo: UploadedFileInfo = {
|
|
|
|
|
originalname: video.originalname,
|
|
|
|
|
mimetype: video.mimetype,
|
|
|
|
|
size: video.size,
|
|
|
|
|
buffer: video.buffer,
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-26 17:23:26 +07:00
|
|
|
return await chaptersLessonService.uploadVideo({
|
2026-03-04 17:19:58 +07:00
|
|
|
userId: request.user.id,
|
2026-01-21 16:52:38 +07:00
|
|
|
course_id: courseId,
|
|
|
|
|
lesson_id: lessonId,
|
|
|
|
|
video: videoInfo,
|
|
|
|
|
});
|
2026-01-20 16:51:42 +07:00
|
|
|
}
|
|
|
|
|
|
2026-01-21 16:52:38 +07:00
|
|
|
/**
|
2026-01-26 17:23:26 +07:00
|
|
|
* อัพเดต (เปลี่ยน) วิดีโอของบทเรียน
|
|
|
|
|
* Update (replace) video in lesson
|
2026-01-21 16:52:38 +07:00
|
|
|
*
|
|
|
|
|
* @param courseId Course ID
|
|
|
|
|
* @param chapterId Chapter ID
|
|
|
|
|
* @param lessonId Lesson ID
|
2026-01-26 17:23:26 +07:00
|
|
|
* @param video ไฟล์วิดีโอใหม่ (required)
|
2026-01-21 16:52:38 +07:00
|
|
|
*/
|
|
|
|
|
@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')
|
2026-01-26 17:23:26 +07:00
|
|
|
public async updateVideo(
|
2026-01-21 16:52:38 +07:00
|
|
|
@Request() request: any,
|
|
|
|
|
@Path() courseId: number,
|
|
|
|
|
@Path() chapterId: number,
|
|
|
|
|
@Path() lessonId: number,
|
2026-01-26 17:23:26 +07:00
|
|
|
@UploadedFile() video: Express.Multer.File
|
|
|
|
|
): Promise<VideoOperationResponse> {
|
2026-01-21 16:52:38 +07:00
|
|
|
|
2026-01-26 17:23:26 +07:00
|
|
|
if (!video) {
|
|
|
|
|
throw new ValidationError('Video file is required');
|
2026-01-21 16:52:38 +07:00
|
|
|
}
|
|
|
|
|
|
2026-01-26 17:23:26 +07:00
|
|
|
const videoInfo: UploadedFileInfo = {
|
|
|
|
|
originalname: video.originalname,
|
|
|
|
|
mimetype: video.mimetype,
|
|
|
|
|
size: video.size,
|
|
|
|
|
buffer: video.buffer,
|
|
|
|
|
};
|
2026-01-20 16:51:42 +07:00
|
|
|
|
2026-01-26 17:23:26 +07:00
|
|
|
return await chaptersLessonService.updateVideo({
|
2026-03-04 17:19:58 +07:00
|
|
|
userId: request.user.id,
|
2026-01-21 16:52:38 +07:00
|
|
|
course_id: courseId,
|
|
|
|
|
lesson_id: lessonId,
|
|
|
|
|
video: videoInfo,
|
2026-01-26 17:23:26 +07:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* อัพโหลดไฟล์แนบทีละไฟล์
|
|
|
|
|
* 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> {
|
|
|
|
|
|
|
|
|
|
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({
|
2026-03-04 17:19:58 +07:00
|
|
|
userId: request.user.id,
|
2026-01-26 17:23:26 +07:00
|
|
|
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> {
|
|
|
|
|
|
|
|
|
|
return await chaptersLessonService.deleteAttachment({
|
2026-03-04 17:19:58 +07:00
|
|
|
userId: request.user.id,
|
2026-01-26 17:23:26 +07:00
|
|
|
course_id: courseId,
|
|
|
|
|
lesson_id: lessonId,
|
|
|
|
|
attachment_id: attachmentId,
|
2026-01-21 16:52:38 +07:00
|
|
|
});
|
|
|
|
|
}
|
2026-02-03 17:23:35 +07:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* ตั้งค่าวิดีโอ 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> {
|
|
|
|
|
|
2026-02-18 15:59:40 +07:00
|
|
|
const { error } = SetYouTubeVideoValidator.validate(body);
|
|
|
|
|
if (error) throw new ValidationError(error.details[0].message);
|
2026-02-03 17:23:35 +07:00
|
|
|
|
|
|
|
|
return await chaptersLessonService.setYouTubeVideo({
|
2026-03-04 17:19:58 +07:00
|
|
|
userId: request.user.id,
|
2026-02-03 17:23:35 +07:00
|
|
|
course_id: courseId,
|
|
|
|
|
lesson_id: lessonId,
|
|
|
|
|
youtube_video_id: body.youtube_video_id,
|
|
|
|
|
video_title: body.video_title,
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-01-21 16:52:38 +07:00
|
|
|
}
|