import { prisma } from '../config/database'; import { Prisma } from '@prisma/client'; import { config } from '../config'; import { logger } from '../config/logger'; import { listCourseResponse, getCourseResponse, ListCoursesInput } from '../types/courses.types'; import { UnauthorizedError, ValidationError, ForbiddenError } from '../middleware/errorHandler'; import { getPresignedUrl } from '../config/minio'; export class CoursesService { async ListCourses(input: ListCoursesInput): Promise { try { const { category_id, is_recommended, page = 1, limit = 10, random = false } = input; const where: Prisma.CourseWhereInput = { status: 'APPROVED', }; if (category_id) { where.category_id = category_id; } if (is_recommended !== undefined) { where.is_recommended = is_recommended; } // Get total count for pagination const total = await prisma.course.count({ where }); const totalPages = Math.ceil(total / limit); let courses; if (random) { // Random mode: ดึงทั้งหมดแล้วสุ่ม const allCourses = await prisma.course.findMany({ where }); // Fisher-Yates shuffle for (let i = allCourses.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [allCourses[i], allCourses[j]] = [allCourses[j], allCourses[i]]; } // Apply pagination after shuffle const skip = (page - 1) * limit; courses = allCourses.slice(skip, skip + limit); } else { // Normal mode: pagination with skip/take const skip = (page - 1) * limit; courses = await prisma.course.findMany({ where, skip, take: limit, orderBy: { created_at: 'desc' } }); } // 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, page, limit, totalPages, data: coursesWithUrls, }; } catch (error) { logger.error('Failed to fetch courses', { error }); throw error; } } async GetCourseById(id: number): Promise { try { const course = await prisma.course.findFirst({ where: { id, status: 'APPROVED' // Only show approved courses to students } }); if (!course) { return { code: 200, message: 'no 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, thumbnail_url: thumbnail_presigned_url, }, }; } catch (error) { logger.error('Failed to fetch course', { error }); throw error; } } }