2026-01-21 16:52:38 +07:00
|
|
|
import { Body, FormField, Path, Post, Put, Request, Response, Route, Security, SuccessResponse, Tags, UploadedFile, UploadedFiles } 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';
|
|
|
|
|
import { MultiLanguageText } from '../types/index';
|
|
|
|
|
import { UploadedFileInfo, CreateLessonInput, CreateLessonResponse, UpdateLessonResponse } from '../types/ChaptersLesson.typs';
|
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-20 16:51:42 +07:00
|
|
|
/**
|
|
|
|
|
* สร้างบทเรียนใหม่พร้อมไฟล์แนบ
|
|
|
|
|
* Create a new lesson with optional video and attachments
|
|
|
|
|
*
|
2026-01-21 16:52:38 +07:00
|
|
|
* @param courseId Course ID
|
|
|
|
|
* @param chapterId Chapter ID
|
|
|
|
|
* @param title ชื่อบทเรียน (JSON string: { th: "", en: "" })
|
|
|
|
|
* @param type ประเภทบทเรียน (VIDEO | QUIZ)
|
|
|
|
|
* @param content เนื้อหาบทเรียน (JSON string: { th: "", en: "" })
|
|
|
|
|
* @param sort_order ลำดับการแสดงผล
|
|
|
|
|
* @param video ไฟล์วิดีโอ (สำหรับ type=VIDEO เท่านั้น)
|
|
|
|
|
* @param attachments ไฟล์แนบ (PDFs, เอกสาร, รูปภาพ)
|
2026-01-20 16:51:42 +07:00
|
|
|
*/
|
2026-01-21 16:52:54 +07:00
|
|
|
@Post('upload')
|
|
|
|
|
@Security('jwt', ['instructor'])
|
|
|
|
|
@SuccessResponse('201', 'Lesson created successfully')
|
|
|
|
|
@Response('400', 'Validation error')
|
|
|
|
|
@Response('401', 'Unauthorized')
|
|
|
|
|
@Response('403', 'Forbidden')
|
|
|
|
|
public async createLessonWithFiles(
|
|
|
|
|
@Request() request: any,
|
|
|
|
|
@Path() courseId: number,
|
|
|
|
|
@Path() chapterId: number,
|
|
|
|
|
@FormField() title: string,
|
|
|
|
|
@FormField() type: 'VIDEO' | 'QUIZ',
|
|
|
|
|
@FormField() content?: string,
|
|
|
|
|
@FormField() sort_order?: string,
|
|
|
|
|
@UploadedFile() video?: Express.Multer.File,
|
|
|
|
|
@UploadedFiles() attachments?: Express.Multer.File[]
|
|
|
|
|
): Promise<CreateLessonResponse> {
|
|
|
|
|
const token = request.headers.authorization?.replace('Bearer ', '');
|
|
|
|
|
if (!token) throw new ValidationError('No token provided');
|
|
|
|
|
|
|
|
|
|
// Parse JSON fields
|
|
|
|
|
const parsedTitle: MultiLanguageText = JSON.parse(title);
|
|
|
|
|
const parsedContent = content ? JSON.parse(content) : undefined;
|
|
|
|
|
const sortOrder = sort_order ? parseInt(sort_order, 10) : undefined;
|
2026-01-20 16:51:42 +07:00
|
|
|
|
2026-01-21 16:52:38 +07:00
|
|
|
if (!parsedTitle.th || !parsedTitle.en) {
|
|
|
|
|
throw new ValidationError('Title must have both Thai (th) and English (en) values');
|
|
|
|
|
}
|
2026-01-20 16:51:42 +07:00
|
|
|
|
2026-01-21 16:52:38 +07:00
|
|
|
// Transform files to UploadedFileInfo
|
|
|
|
|
let videoInfo: UploadedFileInfo | undefined;
|
|
|
|
|
let attachmentsInfo: UploadedFileInfo[] | undefined;
|
2026-01-20 16:51:42 +07:00
|
|
|
|
2026-01-21 16:52:38 +07:00
|
|
|
if (video) {
|
|
|
|
|
videoInfo = {
|
|
|
|
|
originalname: video.originalname,
|
|
|
|
|
mimetype: video.mimetype,
|
|
|
|
|
size: video.size,
|
|
|
|
|
buffer: video.buffer,
|
|
|
|
|
};
|
|
|
|
|
}
|
2026-01-20 16:51:42 +07:00
|
|
|
|
2026-01-21 16:52:38 +07:00
|
|
|
if (attachments && attachments.length > 0) {
|
|
|
|
|
attachmentsInfo = attachments.map(file => ({
|
|
|
|
|
originalname: file.originalname,
|
|
|
|
|
mimetype: file.mimetype,
|
|
|
|
|
size: file.size,
|
|
|
|
|
buffer: file.buffer,
|
|
|
|
|
}));
|
|
|
|
|
}
|
2026-01-20 16:51:42 +07:00
|
|
|
|
2026-01-21 16:52:38 +07:00
|
|
|
const input: CreateLessonInput = {
|
|
|
|
|
token,
|
|
|
|
|
course_id: courseId,
|
|
|
|
|
chapter_id: chapterId,
|
|
|
|
|
title: parsedTitle,
|
|
|
|
|
content: parsedContent,
|
|
|
|
|
type,
|
|
|
|
|
sort_order: sortOrder,
|
|
|
|
|
video: videoInfo,
|
|
|
|
|
attachments: attachmentsInfo,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return await chaptersLessonService.createLesson(input);
|
|
|
|
|
}
|
2026-01-20 16:51:42 +07:00
|
|
|
|
2026-01-21 16:52:38 +07:00
|
|
|
/**
|
|
|
|
|
* เพิ่มวิดีโอและไฟล์แนบให้บทเรียน VIDEO ที่มีอยู่แล้ว
|
|
|
|
|
* Add video and attachments to an existing VIDEO type lesson
|
|
|
|
|
*
|
|
|
|
|
* @param courseId Course ID
|
|
|
|
|
* @param chapterId Chapter ID
|
|
|
|
|
* @param lessonId Lesson ID
|
|
|
|
|
* @param video ไฟล์วิดีโอ (required)
|
|
|
|
|
* @param attachments ไฟล์แนบ
|
|
|
|
|
*/
|
|
|
|
|
@Post('{lessonId}/video')
|
|
|
|
|
@Security('jwt', ['instructor'])
|
|
|
|
|
@SuccessResponse('200', 'Video added successfully')
|
|
|
|
|
@Response('400', 'Validation error')
|
|
|
|
|
@Response('401', 'Unauthorized')
|
|
|
|
|
@Response('403', 'Forbidden')
|
|
|
|
|
@Response('404', 'Lesson not found')
|
|
|
|
|
public async addVideoToLesson(
|
|
|
|
|
@Request() request: any,
|
|
|
|
|
@Path() courseId: number,
|
|
|
|
|
@Path() chapterId: number,
|
|
|
|
|
@Path() lessonId: number,
|
|
|
|
|
@UploadedFile() video: Express.Multer.File,
|
|
|
|
|
@UploadedFiles() attachments?: Express.Multer.File[]
|
|
|
|
|
): Promise<CreateLessonResponse> {
|
|
|
|
|
const token = request.headers.authorization?.replace('Bearer ', '');
|
|
|
|
|
if (!token) throw new ValidationError('No token provided');
|
|
|
|
|
|
|
|
|
|
if (!video) {
|
|
|
|
|
throw new ValidationError('Video file is required');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Transform files to UploadedFileInfo
|
|
|
|
|
const videoInfo: UploadedFileInfo = {
|
|
|
|
|
originalname: video.originalname,
|
|
|
|
|
mimetype: video.mimetype,
|
|
|
|
|
size: video.size,
|
|
|
|
|
buffer: video.buffer,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let attachmentsInfo: UploadedFileInfo[] | undefined;
|
|
|
|
|
if (attachments && attachments.length > 0) {
|
|
|
|
|
attachmentsInfo = attachments.map(file => ({
|
|
|
|
|
originalname: file.originalname,
|
|
|
|
|
mimetype: file.mimetype,
|
|
|
|
|
size: file.size,
|
|
|
|
|
buffer: file.buffer,
|
|
|
|
|
}));
|
2026-01-20 16:51:42 +07:00
|
|
|
}
|
2026-01-21 16:52:38 +07:00
|
|
|
|
|
|
|
|
return await chaptersLessonService.addVideoLesson({
|
|
|
|
|
token,
|
|
|
|
|
course_id: courseId,
|
|
|
|
|
lesson_id: lessonId,
|
|
|
|
|
video: videoInfo,
|
|
|
|
|
attachments: attachmentsInfo,
|
|
|
|
|
});
|
2026-01-20 16:51:42 +07:00
|
|
|
}
|
|
|
|
|
|
2026-01-21 16:52:38 +07:00
|
|
|
/**
|
|
|
|
|
* อัปเดตวิดีโอและไฟล์แนบของบทเรียน VIDEO
|
|
|
|
|
* Update video and attachments of an existing VIDEO type lesson
|
|
|
|
|
*
|
|
|
|
|
* @param courseId Course ID
|
|
|
|
|
* @param chapterId Chapter ID
|
|
|
|
|
* @param lessonId Lesson ID
|
|
|
|
|
* @param video ไฟล์วิดีโอใหม่
|
|
|
|
|
* @param attachments ไฟล์แนบใหม่
|
|
|
|
|
*/
|
|
|
|
|
@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 updateVideoLesson(
|
|
|
|
|
@Request() request: any,
|
|
|
|
|
@Path() courseId: number,
|
|
|
|
|
@Path() chapterId: number,
|
|
|
|
|
@Path() lessonId: number,
|
|
|
|
|
@UploadedFile() video?: Express.Multer.File,
|
|
|
|
|
@UploadedFiles() attachments?: Express.Multer.File[]
|
|
|
|
|
): Promise<UpdateLessonResponse> {
|
|
|
|
|
const token = request.headers.authorization?.replace('Bearer ', '');
|
|
|
|
|
if (!token) throw new ValidationError('No token provided');
|
|
|
|
|
|
|
|
|
|
// Transform files to UploadedFileInfo
|
|
|
|
|
let videoInfo: UploadedFileInfo | undefined;
|
|
|
|
|
let attachmentsInfo: UploadedFileInfo[] | undefined;
|
|
|
|
|
|
|
|
|
|
if (video) {
|
|
|
|
|
videoInfo = {
|
|
|
|
|
originalname: video.originalname,
|
|
|
|
|
mimetype: video.mimetype,
|
|
|
|
|
size: video.size,
|
|
|
|
|
buffer: video.buffer,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (attachments && attachments.length > 0) {
|
|
|
|
|
attachmentsInfo = attachments.map(file => ({
|
|
|
|
|
originalname: file.originalname,
|
|
|
|
|
mimetype: file.mimetype,
|
|
|
|
|
size: file.size,
|
|
|
|
|
buffer: file.buffer,
|
|
|
|
|
}));
|
2026-01-20 16:51:42 +07:00
|
|
|
}
|
|
|
|
|
|
2026-01-21 16:52:38 +07:00
|
|
|
return await chaptersLessonService.updateVideoLesson({
|
|
|
|
|
token,
|
|
|
|
|
course_id: courseId,
|
|
|
|
|
lesson_id: lessonId,
|
|
|
|
|
video: videoInfo,
|
|
|
|
|
attachments: attachmentsInfo,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|