elearning/Backend/src/services/courses.service.ts
JakkrapartXD c5aa195b13
All checks were successful
Build and Deploy Backend / Build Backend Docker Image (push) Successful in 24s
Build and Deploy Backend / Deploy E-learning Backend to Dev Server (push) Successful in 3s
Build and Deploy Backend / Notify Deployment Status (push) Successful in 1s
feat: implement course cloning functionality including chapters, lessons, quizzes, and attachments for instructors.
2026-02-13 17:41:01 +07:00

164 lines
No EOL
5.8 KiB
TypeScript

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';
import { auditService } from './audit.service';
import { AuditAction } from '@prisma/client';
export class CoursesService {
async ListCourses(input: ListCoursesInput): Promise<listCourseResponse> {
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 });
await auditService.logSync({
userId: 0,
action: AuditAction.ERROR,
entityType: 'Course',
entityId: 0,
metadata: {
operation: 'list_courses',
error: error instanceof Error ? error.message : String(error)
}
});
throw error;
}
}
async GetCourseById(id: number): Promise<getCourseResponse> {
try {
const course = await prisma.course.findFirst({
where: {
id,
status: 'APPROVED' // Only show approved courses to students
},
include: {
chapters: {
select: {
id: true,
title: true,
lessons: {
select: {
id: true,
title: true,
}
}
}
}
}
});
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 });
await auditService.logSync({
userId: 0,
action: AuditAction.ERROR,
entityType: 'Course',
entityId: id,
metadata: {
operation: 'get_course',
error: error instanceof Error ? error.message : String(error)
}
});
throw error;
}
}
}