feat: Implement granular API for video upload/update and attachment management with dedicated types and endpoints.

This commit is contained in:
JakkrapartXD 2026-01-26 17:23:26 +07:00
parent e082c77946
commit be7348c74d
4 changed files with 580 additions and 215 deletions

View file

@ -24,6 +24,8 @@ import {
UpdateQuestionBody,
ReorderQuestionResponse,
ReorderQuestionBody,
UpdateQuizResponse,
UpdateQuizBody,
} from '../types/ChaptersLesson.typs';
const chaptersLessonService = new ChaptersLessonService();
@ -352,4 +354,28 @@ export class ChaptersLessonInstructorController {
question_id: questionId,
});
}
/**
* Quiz
* Update quiz settings
*/
@Put('chapters/{chapterId}/lessons/{lessonId}/quiz')
@Security('jwt', ['instructor'])
@SuccessResponse('200', 'Quiz updated successfully')
public async updateQuiz(
@Request() request: any,
@Path() courseId: number,
@Path() chapterId: number,
@Path() lessonId: number,
@Body() body: UpdateQuizBody
): Promise<UpdateQuizResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) throw new ValidationError('No token provided');
return await chaptersLessonService.updateQuiz({
token,
course_id: courseId,
lesson_id: lessonId,
...body,
});
}
}

View file

@ -1,7 +1,14 @@
import { Path, Post, Put, Request, Response, Route, Security, SuccessResponse, Tags, UploadedFile, UploadedFiles } from 'tsoa';
import { 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 } from '../types/ChaptersLesson.typs';
import {
UploadedFileInfo,
CreateLessonResponse,
UpdateLessonResponse,
VideoOperationResponse,
AttachmentOperationResponse,
DeleteAttachmentResponse
} from '../types/ChaptersLesson.typs';
const chaptersLessonService = new ChaptersLessonService();
@ -10,30 +17,28 @@ const chaptersLessonService = new ChaptersLessonService();
export class LessonsController {
/**
* VIDEO
* Add video and attachments to an existing VIDEO type lesson
* ()
* Upload video to lesson (first time)
*
* @param courseId Course ID
* @param chapterId Chapter 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')
@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 addVideoToLesson(
public async uploadVideo(
@Request() request: any,
@Path() courseId: number,
@Path() chapterId: number,
@Path() lessonId: number,
@UploadedFile() video: Express.Multer.File,
@UploadedFiles() attachments?: Express.Multer.File[]
): Promise<CreateLessonResponse> {
@UploadedFile() video: Express.Multer.File
): Promise<VideoOperationResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) throw new ValidationError('No token provided');
@ -41,7 +46,6 @@ export class LessonsController {
throw new ValidationError('Video file is required');
}
// Transform files to UploadedFileInfo
const videoInfo: UploadedFileInfo = {
originalname: video.originalname,
mimetype: video.mimetype,
@ -49,34 +53,22 @@ export class LessonsController {
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,
}));
}
return await chaptersLessonService.addVideoLesson({
return await chaptersLessonService.uploadVideo({
token,
course_id: courseId,
lesson_id: lessonId,
video: videoInfo,
attachments: attachmentsInfo,
});
}
/**
* VIDEO
* Update video and attachments of an existing VIDEO type lesson
* ()
* Update (replace) video in lesson
*
* @param courseId Course ID
* @param chapterId Chapter ID
* @param lessonId Lesson ID
* @param video
* @param attachments
* @param video (required)
*/
@Put('{lessonId}/video')
@Security('jwt', ['instructor'])
@ -85,45 +77,111 @@ export class LessonsController {
@Response('401', 'Unauthorized')
@Response('403', 'Forbidden')
@Response('404', 'Lesson not found')
public async updateVideoLesson(
public async updateVideo(
@Request() request: any,
@Path() courseId: number,
@Path() chapterId: number,
@Path() lessonId: number,
@UploadedFile() video?: Express.Multer.File,
@UploadedFiles() attachments?: Express.Multer.File[]
): Promise<UpdateLessonResponse> {
@UploadedFile() video: Express.Multer.File
): Promise<VideoOperationResponse> {
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 (!video) {
throw new ValidationError('Video file is required');
}
if (attachments && attachments.length > 0) {
attachmentsInfo = attachments.map(file => ({
originalname: file.originalname,
mimetype: file.mimetype,
size: file.size,
buffer: file.buffer,
}));
}
const videoInfo: UploadedFileInfo = {
originalname: video.originalname,
mimetype: video.mimetype,
size: video.size,
buffer: video.buffer,
};
return await chaptersLessonService.updateVideoLesson({
return await chaptersLessonService.updateVideo({
token,
course_id: courseId,
lesson_id: lessonId,
video: videoInfo,
attachments: attachmentsInfo,
});
}
/**
*
* 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,
});
}
}