feat: add pagination and random sorting to course listing endpoint with configurable page size and Fisher-Yates shuffle algorithm

This commit is contained in:
JakkrapartXD 2026-02-06 17:41:03 +07:00
parent 217c9c29b8
commit 3f93dc8ab5
3 changed files with 57 additions and 6 deletions

View file

@ -12,12 +12,20 @@ export class CoursesController {
* ( filter category_id )
* Get all courses (can filter by category_id)
* @param category_id - / Category ID (optional)
* @param page - / Page number (default: 1)
* @param limit - / Items per page (default: 10)
* @param random - / Randomize courses order (default: false)
*/
@Get()
@SuccessResponse('200', 'Courses fetched successfully')
@Response('401', 'Invalid or expired token')
public async listCourses(@Query() category_id?: number): Promise<listCourseResponse> {
return await this.coursesService.ListCourses(category_id);
public async listCourses(
@Query() category_id?: number,
@Query() page?: number,
@Query() limit?: number,
@Query() random?: boolean
): Promise<listCourseResponse> {
return await this.coursesService.ListCourses({ category_id, page, limit, random });
}
/**

View file

@ -2,13 +2,15 @@ import { prisma } from '../config/database';
import { Prisma } from '@prisma/client';
import { config } from '../config';
import { logger } from '../config/logger';
import { listCourseResponse, getCourseResponse } from '../types/courses.types';
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(category_id?: number): Promise<listCourseResponse> {
async ListCourses(input: ListCoursesInput): Promise<listCourseResponse> {
try {
const { category_id, page = 1, limit = 10, random = false } = input;
const where: Prisma.CourseWhereInput = {
status: 'APPROVED',
};
@ -17,7 +19,35 @@ export class CoursesService {
where.category_id = category_id;
}
const courses = await prisma.course.findMany({ where });
// 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(
@ -40,7 +70,10 @@ export class CoursesService {
return {
code: 200,
message: 'Courses fetched successfully',
total: coursesWithUrls.length,
total,
page,
limit,
totalPages,
data: coursesWithUrls,
};
} catch (error) {

View file

@ -1,10 +1,20 @@
import { Course } from '@prisma/client';
export interface ListCoursesInput {
category_id?: number;
page?: number;
limit?: number;
random?: boolean;
}
export interface listCourseResponse {
code: number;
message: string;
data: Course[];
total: number;
page: number;
limit: number;
totalPages: number;
}
export interface getCourseResponse {