feat: add thumbnail upload support to course creation endpoint with multipart form data handling.

This commit is contained in:
JakkrapartXD 2026-01-28 14:38:11 +07:00
parent 19844f343b
commit cf12ef965e
3 changed files with 54 additions and 44 deletions

View file

@ -1,4 +1,4 @@
import { Get, Body, Post, Route, Tags, SuccessResponse, Response, Security, Put, Path, Delete, Request, Example } from 'tsoa';
import { Get, Body, Post, Route, Tags, SuccessResponse, Response, Security, Put, Path, Delete, Request, Example, FormField, UploadedFile } from 'tsoa';
import { ValidationError } from '../middleware/errorHandler';
import { CoursesInstructorService } from '../services/CoursesInstructor.service';
import {
@ -82,21 +82,33 @@ export class CoursesInstructorController {
}
/**
*
* Create a new course (status will be DRAFT by default)
* ( thumbnail)
* Create a new course with optional thumbnail upload (status will be DRAFT by default)
* @param data - JSON string containing course data
* @param thumbnail - Optional thumbnail image file
*/
@Post('')
@Security('jwt', ['instructor'])
@SuccessResponse('201', 'Course created successfully')
@Response('400', 'Validation error')
@Response('500', 'Internal server error')
public async createCourse(@Body() body: createCourses, @Request() request: any): Promise<createCourseResponse> {
public async createCourse(
@Request() request: any,
@FormField() data: string,
@UploadedFile() thumbnail?: Express.Multer.File
): Promise<createCourseResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) throw new ValidationError('No token provided');
const decoded = jwt.verify(token, config.jwt.secret) as { id: number };
const { error, value } = CreateCourseValidator.validate(body.data);
const parsed = JSON.parse(data);
const { error, value } = CreateCourseValidator.validate(parsed);
if (error) throw new ValidationError(error.details[0].message);
const course = await CoursesInstructorService.createCourse(value, decoded.id);
return course;
// Validate thumbnail file type if provided
if (thumbnail && !thumbnail.mimetype?.startsWith('image/')) throw new ValidationError('Only image files are allowed for thumbnail');
return await CoursesInstructorService.createCourse(value, decoded.id, thumbnail);
}
/**
@ -111,9 +123,7 @@ export class CoursesInstructorController {
@Response('404', 'Course not found')
public async deleteCourse(@Request() request: any, @Path() courseId: number): Promise<DeleteMyCourseResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) {
throw new ValidationError('No token provided');
}
if (!token) throw new ValidationError('No token provided')
return await CoursesInstructorService.deleteCourse(token, courseId);
}
@ -129,9 +139,7 @@ export class CoursesInstructorController {
@Response('404', 'Course not found')
public async submitCourse(@Request() request: any, @Path() courseId: number): Promise<submitCourseResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) {
throw new ValidationError('No token provided');
}
if (!token) throw new ValidationError('No token provided')
return await CoursesInstructorService.sendCourseForReview({ token, course_id: courseId });
}
@ -148,9 +156,7 @@ export class CoursesInstructorController {
@Response('404', 'Course not found')
public async getCourseApprovals(@Request() request: any, @Path() courseId: number): Promise<GetCourseApprovalsResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) {
throw new ValidationError('No token provided');
}
if (!token) throw new ValidationError('No token provided')
return await CoursesInstructorService.getCourseApprovals(token, courseId);
}
@ -166,9 +172,7 @@ export class CoursesInstructorController {
@Response('404', 'Instructors not found')
public async listInstructorCourses(@Request() request: any, @Path() courseId: number): Promise<listinstructorCourseResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) {
throw new ValidationError('No token provided');
}
if (!token) throw new ValidationError('No token provided')
return await CoursesInstructorService.listInstructorsOfCourse({ token, course_id: courseId });
}
@ -185,9 +189,7 @@ export class CoursesInstructorController {
@Response('404', 'Instructor not found')
public async addInstructor(@Request() request: any, @Path() courseId: number, @Path() userId: number): Promise<addinstructorCourseResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) {
throw new ValidationError('No token provided');
}
if (!token) throw new ValidationError('No token provided')
return await CoursesInstructorService.addInstructorToCourse({ token, course_id: courseId, user_id: userId });
}
@ -204,9 +206,7 @@ export class CoursesInstructorController {
@Response('404', 'Instructor not found')
public async removeInstructor(@Request() request: any, @Path() courseId: number, @Path() userId: number): Promise<removeinstructorCourseResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) {
throw new ValidationError('No token provided');
}
if (!token) throw new ValidationError('No token provided')
return await CoursesInstructorService.removeInstructorFromCourse({ token, course_id: courseId, user_id: userId });
}
@ -223,9 +223,7 @@ export class CoursesInstructorController {
@Response('404', 'Primary instructor not found')
public async setPrimaryInstructor(@Request() request: any, @Path() courseId: number, @Path() userId: number): Promise<setprimaryCourseInstructorResponse> {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) {
throw new ValidationError('No token provided');
}
if (!token) throw new ValidationError('No token provided')
return await CoursesInstructorService.setPrimaryInstructor({ token, course_id: courseId, user_id: userId });
}
}