feat: add dedicated thumbnail upload endpoint for courses with old file cleanup and presigned URL generation.
This commit is contained in:
parent
b0383b78e9
commit
b303c50865
2 changed files with 78 additions and 15 deletions
|
|
@ -111,6 +111,29 @@ export class CoursesInstructorController {
|
|||
return await CoursesInstructorService.createCourse(value, decoded.id, thumbnail);
|
||||
}
|
||||
|
||||
/**
|
||||
* อัปโหลดรูป thumbnail ของคอร์ส
|
||||
* Upload course thumbnail image
|
||||
* @param courseId - รหัสคอร์ส / Course ID
|
||||
* @param file - ไฟล์รูปภาพ / Image file
|
||||
*/
|
||||
@Post('{courseId}/thumbnail')
|
||||
@Security('jwt', ['instructor'])
|
||||
@SuccessResponse('200', 'Thumbnail uploaded successfully')
|
||||
@Response('401', 'Invalid or expired token')
|
||||
@Response('400', 'Validation error')
|
||||
public async uploadThumbnail(
|
||||
@Request() request: any,
|
||||
@Path() courseId: number,
|
||||
@UploadedFile() file: Express.Multer.File
|
||||
): Promise<{ code: number; message: string; data: { course_id: number; thumbnail_url: string } }> {
|
||||
const token = request.headers.authorization?.replace('Bearer ', '');
|
||||
if (!token) throw new ValidationError('No token provided');
|
||||
if (!file.mimetype?.startsWith('image/')) throw new ValidationError('Only image files are allowed');
|
||||
|
||||
return await CoursesInstructorService.uploadThumbnail(token, courseId, file);
|
||||
}
|
||||
|
||||
/**
|
||||
* ลบคอร์ส (เฉพาะผู้สอนหลักเท่านั้น)
|
||||
* Delete a course (only primary instructor can delete)
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ export class CoursesInstructorService {
|
|||
|
||||
static async updateCourse(token: string, courseId: number, courseData: UpdateCourseInput): Promise<createCourseResponse> {
|
||||
try {
|
||||
const courseInstructorId = await this.validateCourseInstructor(token, courseId);
|
||||
await this.validateCourseInstructor(token, courseId);
|
||||
|
||||
const course = await prisma.course.update({
|
||||
where: {
|
||||
|
|
@ -191,23 +191,10 @@ export class CoursesInstructorService {
|
|||
data: courseData
|
||||
});
|
||||
|
||||
// Generate presigned URL for thumbnail
|
||||
let thumbnail_presigned_url: string | null = null;
|
||||
if (course.thumbnail_url) {
|
||||
try {
|
||||
thumbnail_presigned_url = await getPresignedUrl(course.thumbnail_url, 3600);
|
||||
} catch (err) {
|
||||
logger.warn(`Failed to generate presigned URL for thumbnail: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: 'Course updated successfully',
|
||||
data: {
|
||||
...course,
|
||||
thumbnail_url: thumbnail_presigned_url,
|
||||
}
|
||||
data: course
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Failed to update course', { error });
|
||||
|
|
@ -215,6 +202,59 @@ export class CoursesInstructorService {
|
|||
}
|
||||
}
|
||||
|
||||
static async uploadThumbnail(token: string, courseId: number, file: Express.Multer.File): Promise<{ code: number; message: string; data: { course_id: number; thumbnail_url: string } }> {
|
||||
try {
|
||||
await this.validateCourseInstructor(token, courseId);
|
||||
|
||||
// Get current course to check for existing thumbnail
|
||||
const currentCourse = await prisma.course.findUnique({
|
||||
where: { id: courseId }
|
||||
});
|
||||
|
||||
// Delete old thumbnail if exists
|
||||
if (currentCourse?.thumbnail_url) {
|
||||
try {
|
||||
await deleteFile(currentCourse.thumbnail_url);
|
||||
logger.info(`Deleted old thumbnail: ${currentCourse.thumbnail_url}`);
|
||||
} catch (error) {
|
||||
logger.warn(`Failed to delete old thumbnail: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate unique filename
|
||||
const timestamp = Date.now();
|
||||
const uniqueId = Math.random().toString(36).substring(2, 15);
|
||||
const extension = file.originalname.split('.').pop() || 'jpg';
|
||||
const safeFilename = `${timestamp}-${uniqueId}.${extension}`;
|
||||
const filePath = `courses/${courseId}/thumbnail/${safeFilename}`;
|
||||
|
||||
// Upload to MinIO
|
||||
await uploadFile(filePath, file.buffer, file.mimetype || 'image/jpeg');
|
||||
logger.info(`Uploaded thumbnail: ${filePath}`);
|
||||
|
||||
// Update course with new thumbnail path
|
||||
await prisma.course.update({
|
||||
where: { id: courseId },
|
||||
data: { thumbnail_url: filePath }
|
||||
});
|
||||
|
||||
// Generate presigned URL for response
|
||||
const presignedUrl = await getPresignedUrl(filePath, 3600);
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: 'Thumbnail uploaded successfully',
|
||||
data: {
|
||||
course_id: courseId,
|
||||
thumbnail_url: presignedUrl
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Failed to upload thumbnail', { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async deleteCourse(token: string, courseId: number): Promise<createCourseResponse> {
|
||||
try {
|
||||
const courseInstructorId = await this.validateCourseInstructor(token, courseId);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue