feat: add presigned URL generation for course thumbnails across all course services.
This commit is contained in:
parent
b28dd410e2
commit
10821d093c
3 changed files with 131 additions and 27 deletions
|
|
@ -4,7 +4,7 @@ import { config } from '../config';
|
|||
import { logger } from '../config/logger';
|
||||
import { UnauthorizedError, ValidationError, ForbiddenError, NotFoundError } from '../middleware/errorHandler';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { uploadFile, deleteFile } from '../config/minio';
|
||||
import { uploadFile, deleteFile, getPresignedUrl } from '../config/minio';
|
||||
import {
|
||||
CreateCourseInput,
|
||||
UpdateCourseInput,
|
||||
|
|
@ -94,7 +94,22 @@ export class CoursesInstructorService {
|
|||
}
|
||||
});
|
||||
|
||||
const courses = courseInstructors.map(ci => ci.course);
|
||||
const courses = await Promise.all(
|
||||
courseInstructors.map(async (ci) => {
|
||||
let thumbnail_presigned_url: string | null = null;
|
||||
if (ci.course.thumbnail_url) {
|
||||
try {
|
||||
thumbnail_presigned_url = await getPresignedUrl(ci.course.thumbnail_url, 3600);
|
||||
} catch (err) {
|
||||
logger.warn(`Failed to generate presigned URL for thumbnail: ${err}`);
|
||||
}
|
||||
}
|
||||
return {
|
||||
...ci.course,
|
||||
thumbnail_url: thumbnail_presigned_url,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
|
|
@ -141,10 +156,23 @@ export class CoursesInstructorService {
|
|||
throw new ForbiddenError('You are not an instructor of this course');
|
||||
}
|
||||
|
||||
// Generate presigned URL for thumbnail
|
||||
let thumbnail_presigned_url: string | null = null;
|
||||
if (courseInstructor.course.thumbnail_url) {
|
||||
try {
|
||||
thumbnail_presigned_url = await getPresignedUrl(courseInstructor.course.thumbnail_url, 3600);
|
||||
} catch (err) {
|
||||
logger.warn(`Failed to generate presigned URL for thumbnail: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: 'Course retrieved successfully',
|
||||
data: courseInstructor.course
|
||||
data: {
|
||||
...courseInstructor.course,
|
||||
thumbnail_url: thumbnail_presigned_url,
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Failed to retrieve course', { error });
|
||||
|
|
@ -162,10 +190,24 @@ 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
|
||||
data: {
|
||||
...course,
|
||||
thumbnail_url: thumbnail_presigned_url,
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Failed to update course', { error });
|
||||
|
|
|
|||
|
|
@ -109,23 +109,35 @@ export class CoursesStudentService {
|
|||
},
|
||||
});
|
||||
|
||||
const data = enrollments.map(enrollment => ({
|
||||
id: enrollment.id,
|
||||
course_id: enrollment.course_id,
|
||||
course: {
|
||||
id: enrollment.course.id,
|
||||
title: enrollment.course.title as { th: string; en: string },
|
||||
slug: enrollment.course.slug,
|
||||
thumbnail_url: enrollment.course.thumbnail_url,
|
||||
description: enrollment.course.description as { th: string; en: string },
|
||||
},
|
||||
status: enrollment.status,
|
||||
progress_percentage: enrollment.progress_percentage,
|
||||
enrolled_at: enrollment.enrolled_at,
|
||||
started_at: enrollment.started_at,
|
||||
completed_at: enrollment.completed_at,
|
||||
last_accessed_at: enrollment.last_accessed_at,
|
||||
}));
|
||||
const data = await Promise.all(
|
||||
enrollments.map(async (enrollment) => {
|
||||
let thumbnail_presigned_url: string | null = null;
|
||||
if (enrollment.course.thumbnail_url) {
|
||||
try {
|
||||
thumbnail_presigned_url = await getPresignedUrl(enrollment.course.thumbnail_url, 3600);
|
||||
} catch (err) {
|
||||
logger.warn(`Failed to generate presigned URL for thumbnail: ${err}`);
|
||||
}
|
||||
}
|
||||
return {
|
||||
id: enrollment.id,
|
||||
course_id: enrollment.course_id,
|
||||
course: {
|
||||
id: enrollment.course.id,
|
||||
title: enrollment.course.title as { th: string; en: string },
|
||||
slug: enrollment.course.slug,
|
||||
thumbnail_url: thumbnail_presigned_url,
|
||||
description: enrollment.course.description as { th: string; en: string },
|
||||
},
|
||||
status: enrollment.status,
|
||||
progress_percentage: enrollment.progress_percentage,
|
||||
enrolled_at: enrollment.enrolled_at,
|
||||
started_at: enrollment.started_at,
|
||||
completed_at: enrollment.completed_at,
|
||||
last_accessed_at: enrollment.last_accessed_at,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
|
|
@ -256,6 +268,16 @@ export class CoursesStudentService {
|
|||
const total_lessons = lessonIds.length;
|
||||
const completed_lessons = lessonProgress.filter(p => p.is_completed).length;
|
||||
|
||||
// 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 learning retrieved successfully',
|
||||
|
|
@ -265,7 +287,7 @@ export class CoursesStudentService {
|
|||
title: course.title as { th: string; en: string },
|
||||
slug: course.slug,
|
||||
description: course.description as { th: string; en: string },
|
||||
thumbnail_url: course.thumbnail_url,
|
||||
thumbnail_url: thumbnail_presigned_url,
|
||||
have_certificate: course.have_certificate,
|
||||
},
|
||||
enrollment: {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { config } from '../config';
|
|||
import { logger } from '../config/logger';
|
||||
import { listCourseResponse, getCourseResponse } from '../types/courses.types';
|
||||
import { UnauthorizedError, ValidationError, ForbiddenError } from '../middleware/errorHandler';
|
||||
import { getPresignedUrl } from '../config/minio';
|
||||
|
||||
export class CoursesService {
|
||||
async ListCourses(category_id?: number): Promise<listCourseResponse> {
|
||||
|
|
@ -17,12 +18,30 @@ export class CoursesService {
|
|||
}
|
||||
|
||||
const courses = await prisma.course.findMany({ where });
|
||||
|
||||
// Generate presigned URLs for thumbnails
|
||||
const coursesWithUrls = await Promise.all(
|
||||
courses.map(async (course) => {
|
||||
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 {
|
||||
...course,
|
||||
thumbnail_url: thumbnail_presigned_url,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: 'Courses fetched successfully',
|
||||
total: courses.length,
|
||||
data: courses,
|
||||
|
||||
total: coursesWithUrls.length,
|
||||
data: coursesWithUrls,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Failed to fetch courses', { error });
|
||||
|
|
@ -38,11 +57,32 @@ export class CoursesService {
|
|||
status: 'APPROVED' // Only show approved courses to students
|
||||
}
|
||||
});
|
||||
|
||||
if (!course) {
|
||||
return {
|
||||
code: 200,
|
||||
message: 'Course fetched successfully',
|
||||
data: null,
|
||||
};
|
||||
}
|
||||
|
||||
// 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 fetched successfully',
|
||||
data: course,
|
||||
|
||||
data: {
|
||||
...course,
|
||||
thumbnail_url: thumbnail_presigned_url,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Failed to fetch course', { error });
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue