2026-01-21 16:52:54 +07:00
|
|
|
import { Body, Delete, Get, Path, Post, Put, Request, Response, Route, Security, SuccessResponse, Tags } from 'tsoa';
|
|
|
|
|
import { ValidationError } from '../middleware/errorHandler';
|
|
|
|
|
import { ChaptersLessonService } from '../services/ChaptersLesson.service';
|
|
|
|
|
import {
|
|
|
|
|
CreateChapterResponse,
|
|
|
|
|
UpdateChapterResponse,
|
|
|
|
|
DeleteChapterResponse,
|
|
|
|
|
ReorderChapterResponse,
|
|
|
|
|
GetLessonResponse,
|
|
|
|
|
CreateLessonResponse,
|
|
|
|
|
UpdateLessonResponse,
|
|
|
|
|
DeleteLessonResponse,
|
|
|
|
|
ReorderLessonsResponse,
|
|
|
|
|
AddQuestionResponse,
|
|
|
|
|
UpdateQuestionResponse,
|
|
|
|
|
DeleteQuestionResponse,
|
|
|
|
|
CreateChapterBody,
|
|
|
|
|
UpdateChapterBody,
|
|
|
|
|
ReorderChapterBody,
|
|
|
|
|
CreateLessonBody,
|
|
|
|
|
UpdateLessonBody,
|
|
|
|
|
ReorderLessonsBody,
|
|
|
|
|
AddQuestionBody,
|
|
|
|
|
UpdateQuestionBody,
|
|
|
|
|
} from '../types/ChaptersLesson.typs';
|
|
|
|
|
|
|
|
|
|
const chaptersLessonService = new ChaptersLessonService();
|
|
|
|
|
|
|
|
|
|
@Route('api/instructors/courses/{courseId}')
|
|
|
|
|
@Tags('ChaptersLessons - Instructor')
|
|
|
|
|
export class ChaptersLessonInstructorController {
|
|
|
|
|
|
|
|
|
|
// ============================================
|
|
|
|
|
// Chapter Endpoints
|
2026-01-23 11:29:46 +07:00
|
|
|
// Note: Use CoursesInstructorController.getMyCourse to get chapters with full details
|
2026-01-21 16:52:54 +07:00
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* สร้าง chapter ใหม่
|
|
|
|
|
* Create a new chapter
|
|
|
|
|
*/
|
|
|
|
|
@Post('chapters')
|
|
|
|
|
@Security('jwt', ['instructor'])
|
|
|
|
|
@SuccessResponse('201', 'Chapter created successfully')
|
|
|
|
|
@Response('401', 'Unauthorized')
|
|
|
|
|
@Response('403', 'Forbidden')
|
|
|
|
|
public async createChapter(
|
|
|
|
|
@Request() request: any,
|
|
|
|
|
@Path() courseId: number,
|
|
|
|
|
@Body() body: CreateChapterBody
|
|
|
|
|
): Promise<CreateChapterResponse> {
|
|
|
|
|
const token = request.headers.authorization?.replace('Bearer ', '');
|
|
|
|
|
if (!token) throw new ValidationError('No token provided');
|
|
|
|
|
return await chaptersLessonService.createChapter({
|
|
|
|
|
token,
|
|
|
|
|
course_id: courseId,
|
|
|
|
|
title: body.title,
|
|
|
|
|
description: body.description,
|
|
|
|
|
sort_order: body.sort_order,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* อัปเดต chapter
|
|
|
|
|
* Update a chapter
|
|
|
|
|
*/
|
|
|
|
|
@Put('chapters/{chapterId}')
|
|
|
|
|
@Security('jwt', ['instructor'])
|
|
|
|
|
@SuccessResponse('200', 'Chapter updated successfully')
|
|
|
|
|
@Response('401', 'Unauthorized')
|
|
|
|
|
@Response('403', 'Forbidden')
|
|
|
|
|
@Response('404', 'Chapter not found')
|
|
|
|
|
public async updateChapter(
|
|
|
|
|
@Request() request: any,
|
|
|
|
|
@Path() courseId: number,
|
|
|
|
|
@Path() chapterId: number,
|
|
|
|
|
@Body() body: UpdateChapterBody
|
|
|
|
|
): Promise<UpdateChapterResponse> {
|
|
|
|
|
const token = request.headers.authorization?.replace('Bearer ', '');
|
|
|
|
|
if (!token) throw new ValidationError('No token provided');
|
|
|
|
|
return await chaptersLessonService.updateChapter({
|
|
|
|
|
token,
|
|
|
|
|
course_id: courseId,
|
|
|
|
|
chapter_id: chapterId,
|
|
|
|
|
...body,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* ลบ chapter
|
|
|
|
|
* Delete a chapter
|
|
|
|
|
*/
|
|
|
|
|
@Delete('chapters/{chapterId}')
|
|
|
|
|
@Security('jwt', ['instructor'])
|
|
|
|
|
@SuccessResponse('200', 'Chapter deleted successfully')
|
|
|
|
|
@Response('401', 'Unauthorized')
|
|
|
|
|
@Response('403', 'Forbidden')
|
|
|
|
|
@Response('404', 'Chapter not found')
|
|
|
|
|
public async deleteChapter(
|
|
|
|
|
@Request() request: any,
|
|
|
|
|
@Path() courseId: number,
|
|
|
|
|
@Path() chapterId: number
|
|
|
|
|
): Promise<DeleteChapterResponse> {
|
|
|
|
|
const token = request.headers.authorization?.replace('Bearer ', '');
|
|
|
|
|
if (!token) throw new ValidationError('No token provided');
|
|
|
|
|
return await chaptersLessonService.deleteChapter({ token, course_id: courseId, chapter_id: chapterId });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* เรียงลำดับ chapter ใหม่
|
|
|
|
|
* Reorder chapter
|
|
|
|
|
*/
|
|
|
|
|
@Put('chapters/{chapterId}/reorder')
|
|
|
|
|
@Security('jwt', ['instructor'])
|
|
|
|
|
@SuccessResponse('200', 'Chapter reordered successfully')
|
|
|
|
|
public async reorderChapter(
|
|
|
|
|
@Request() request: any,
|
|
|
|
|
@Path() courseId: number,
|
|
|
|
|
@Path() chapterId: number,
|
|
|
|
|
@Body() body: ReorderChapterBody
|
|
|
|
|
): Promise<ReorderChapterResponse> {
|
|
|
|
|
const token = request.headers.authorization?.replace('Bearer ', '');
|
|
|
|
|
if (!token) throw new ValidationError('No token provided');
|
|
|
|
|
return await chaptersLessonService.reorderChapter({
|
|
|
|
|
token,
|
|
|
|
|
course_id: courseId,
|
|
|
|
|
chapter_id: chapterId,
|
|
|
|
|
sort_order: body.sort_order,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================
|
|
|
|
|
// Lesson Endpoints
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* ดึงข้อมูล lesson พร้อม attachments และ quiz
|
|
|
|
|
* Get lesson with attachments and quiz
|
|
|
|
|
*/
|
|
|
|
|
@Get('chapters/{chapterId}/lessons/{lessonId}')
|
|
|
|
|
@Security('jwt', ['instructor'])
|
|
|
|
|
@SuccessResponse('200', 'Lesson retrieved successfully')
|
|
|
|
|
public async getLesson(
|
|
|
|
|
@Request() request: any,
|
|
|
|
|
@Path() courseId: number,
|
|
|
|
|
@Path() chapterId: number,
|
|
|
|
|
@Path() lessonId: number
|
|
|
|
|
): Promise<GetLessonResponse> {
|
|
|
|
|
const token = request.headers.authorization?.replace('Bearer ', '');
|
|
|
|
|
if (!token) throw new ValidationError('No token provided');
|
|
|
|
|
return await chaptersLessonService.getLesson({ token, course_id: courseId, chapter_id: chapterId, lesson_id: lessonId });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* สร้าง lesson ใหม่ (สำหรับ QUIZ จะสร้าง quiz shell อัตโนมัติ)
|
|
|
|
|
* Create a new lesson (for QUIZ type, quiz shell is created automatically)
|
|
|
|
|
*/
|
|
|
|
|
@Post('chapters/{chapterId}/lessons')
|
|
|
|
|
@Security('jwt', ['instructor'])
|
|
|
|
|
@SuccessResponse('201', 'Lesson created successfully')
|
|
|
|
|
public async createLesson(
|
|
|
|
|
@Request() request: any,
|
|
|
|
|
@Path() courseId: number,
|
|
|
|
|
@Path() chapterId: number,
|
|
|
|
|
@Body() body: CreateLessonBody
|
|
|
|
|
): Promise<CreateLessonResponse> {
|
|
|
|
|
const token = request.headers.authorization?.replace('Bearer ', '');
|
|
|
|
|
if (!token) throw new ValidationError('No token provided');
|
|
|
|
|
return await chaptersLessonService.createLesson({
|
|
|
|
|
token,
|
|
|
|
|
course_id: courseId,
|
|
|
|
|
chapter_id: chapterId,
|
|
|
|
|
title: body.title,
|
|
|
|
|
content: body.content,
|
|
|
|
|
type: body.type,
|
|
|
|
|
sort_order: body.sort_order,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* อัปเดต lesson
|
|
|
|
|
* Update a lesson
|
|
|
|
|
*/
|
|
|
|
|
@Put('chapters/{chapterId}/lessons/{lessonId}')
|
|
|
|
|
@Security('jwt', ['instructor'])
|
|
|
|
|
@SuccessResponse('200', 'Lesson updated successfully')
|
|
|
|
|
public async updateLesson(
|
|
|
|
|
@Request() request: any,
|
|
|
|
|
@Path() courseId: number,
|
|
|
|
|
@Path() chapterId: number,
|
|
|
|
|
@Path() lessonId: number,
|
|
|
|
|
@Body() body: UpdateLessonBody
|
|
|
|
|
): Promise<UpdateLessonResponse> {
|
|
|
|
|
const token = request.headers.authorization?.replace('Bearer ', '');
|
|
|
|
|
if (!token) throw new ValidationError('No token provided');
|
|
|
|
|
return await chaptersLessonService.updateLesson({
|
|
|
|
|
token,
|
|
|
|
|
course_id: courseId,
|
|
|
|
|
chapter_id: chapterId,
|
|
|
|
|
lesson_id: lessonId,
|
|
|
|
|
data: body,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================
|
|
|
|
|
// Video Upload Endpoints (multipart/form-data)
|
|
|
|
|
// Note: These endpoints should be registered with express router
|
|
|
|
|
// using multer middleware for actual file upload handling.
|
|
|
|
|
// See: src/routes/lessons.routes.ts (to be created)
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* ลบ lesson
|
|
|
|
|
* Delete a lesson (cannot delete if published)
|
|
|
|
|
*/
|
|
|
|
|
@Delete('chapters/{chapterId}/lessons/{lessonId}')
|
|
|
|
|
@Security('jwt', ['instructor'])
|
|
|
|
|
@SuccessResponse('200', 'Lesson deleted successfully')
|
|
|
|
|
public async deleteLesson(
|
|
|
|
|
@Request() request: any,
|
|
|
|
|
@Path() courseId: number,
|
|
|
|
|
@Path() chapterId: number,
|
|
|
|
|
@Path() lessonId: number
|
|
|
|
|
): Promise<DeleteLessonResponse> {
|
|
|
|
|
const token = request.headers.authorization?.replace('Bearer ', '');
|
|
|
|
|
if (!token) throw new ValidationError('No token provided');
|
|
|
|
|
return await chaptersLessonService.deleteLesson({ token, course_id: courseId, chapter_id: chapterId, lesson_id: lessonId });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* เรียงลำดับ lessons ใน chapter
|
|
|
|
|
* Reorder lessons within a chapter
|
|
|
|
|
*/
|
2026-01-23 13:33:47 +07:00
|
|
|
@Put('chapters/{chapterId}/reorder-lessons')
|
2026-01-21 16:52:54 +07:00
|
|
|
@Security('jwt', ['instructor'])
|
|
|
|
|
@SuccessResponse('200', 'Lessons reordered successfully')
|
|
|
|
|
public async reorderLessons(
|
|
|
|
|
@Request() request: any,
|
|
|
|
|
@Path() courseId: number,
|
|
|
|
|
@Path() chapterId: number,
|
|
|
|
|
@Body() body: ReorderLessonsBody
|
|
|
|
|
): Promise<ReorderLessonsResponse> {
|
|
|
|
|
const token = request.headers.authorization?.replace('Bearer ', '');
|
|
|
|
|
if (!token) throw new ValidationError('No token provided');
|
|
|
|
|
return await chaptersLessonService.reorderLessons({
|
|
|
|
|
token,
|
|
|
|
|
course_id: courseId,
|
|
|
|
|
chapter_id: chapterId,
|
2026-01-22 15:56:56 +07:00
|
|
|
lesson_id: body.lesson_id,
|
|
|
|
|
sort_order: body.sort_order,
|
2026-01-21 16:52:54 +07:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================
|
|
|
|
|
// Quiz Question Endpoints
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* เพิ่มคำถามให้ quiz lesson
|
|
|
|
|
* Add a question to a quiz lesson
|
|
|
|
|
*/
|
|
|
|
|
@Post('chapters/{chapterId}/lessons/{lessonId}/questions')
|
|
|
|
|
@Security('jwt', ['instructor'])
|
|
|
|
|
@SuccessResponse('201', 'Question added successfully')
|
|
|
|
|
public async addQuestion(
|
|
|
|
|
@Request() request: any,
|
|
|
|
|
@Path() courseId: number,
|
|
|
|
|
@Path() chapterId: number,
|
|
|
|
|
@Path() lessonId: number,
|
|
|
|
|
@Body() body: AddQuestionBody
|
|
|
|
|
): Promise<AddQuestionResponse> {
|
|
|
|
|
const token = request.headers.authorization?.replace('Bearer ', '');
|
|
|
|
|
if (!token) throw new ValidationError('No token provided');
|
|
|
|
|
return await chaptersLessonService.addQuestion({
|
|
|
|
|
token,
|
|
|
|
|
course_id: courseId,
|
|
|
|
|
lesson_id: lessonId,
|
|
|
|
|
...body,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* อัปเดตคำถาม
|
|
|
|
|
* Update a question
|
|
|
|
|
*/
|
|
|
|
|
@Put('chapters/{chapterId}/lessons/{lessonId}/questions/{questionId}')
|
|
|
|
|
@Security('jwt', ['instructor'])
|
|
|
|
|
@SuccessResponse('200', 'Question updated successfully')
|
|
|
|
|
public async updateQuestion(
|
|
|
|
|
@Request() request: any,
|
|
|
|
|
@Path() courseId: number,
|
|
|
|
|
@Path() chapterId: number,
|
|
|
|
|
@Path() lessonId: number,
|
|
|
|
|
@Path() questionId: number,
|
|
|
|
|
@Body() body: UpdateQuestionBody
|
|
|
|
|
): Promise<UpdateQuestionResponse> {
|
|
|
|
|
const token = request.headers.authorization?.replace('Bearer ', '');
|
|
|
|
|
if (!token) throw new ValidationError('No token provided');
|
|
|
|
|
return await chaptersLessonService.updateQuestion({
|
|
|
|
|
token,
|
|
|
|
|
course_id: courseId,
|
|
|
|
|
lesson_id: lessonId,
|
|
|
|
|
question_id: questionId,
|
|
|
|
|
...body,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* ลบคำถาม
|
|
|
|
|
* Delete a question
|
|
|
|
|
*/
|
|
|
|
|
@Delete('chapters/{chapterId}/lessons/{lessonId}/questions/{questionId}')
|
|
|
|
|
@Security('jwt', ['instructor'])
|
|
|
|
|
@SuccessResponse('200', 'Question deleted successfully')
|
|
|
|
|
public async deleteQuestion(
|
|
|
|
|
@Request() request: any,
|
|
|
|
|
@Path() courseId: number,
|
|
|
|
|
@Path() chapterId: number,
|
|
|
|
|
@Path() lessonId: number,
|
|
|
|
|
@Path() questionId: number
|
|
|
|
|
): Promise<DeleteQuestionResponse> {
|
|
|
|
|
const token = request.headers.authorization?.replace('Bearer ', '');
|
|
|
|
|
if (!token) throw new ValidationError('No token provided');
|
|
|
|
|
return await chaptersLessonService.deleteQuestion({
|
|
|
|
|
token,
|
|
|
|
|
course_id: courseId,
|
|
|
|
|
lesson_id: lessonId,
|
|
|
|
|
question_id: questionId,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|