feat: add pagination and random sorting to course listing endpoint with configurable page size and Fisher-Yates shuffle algorithm
This commit is contained in:
parent
217c9c29b8
commit
3f93dc8ab5
3 changed files with 57 additions and 6 deletions
|
|
@ -12,12 +12,20 @@ export class CoursesController {
|
||||||
* ดึงรายการคอร์สทั้งหมด (สามารถ filter ด้วย category_id ได้)
|
* ดึงรายการคอร์สทั้งหมด (สามารถ filter ด้วย category_id ได้)
|
||||||
* Get all courses (can filter by category_id)
|
* Get all courses (can filter by category_id)
|
||||||
* @param category_id - รหัสหมวดหมู่ / Category ID (optional)
|
* @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()
|
@Get()
|
||||||
@SuccessResponse('200', 'Courses fetched successfully')
|
@SuccessResponse('200', 'Courses fetched successfully')
|
||||||
@Response('401', 'Invalid or expired token')
|
@Response('401', 'Invalid or expired token')
|
||||||
public async listCourses(@Query() category_id?: number): Promise<listCourseResponse> {
|
public async listCourses(
|
||||||
return await this.coursesService.ListCourses(category_id);
|
@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 });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,15 @@ import { prisma } from '../config/database';
|
||||||
import { Prisma } from '@prisma/client';
|
import { Prisma } from '@prisma/client';
|
||||||
import { config } from '../config';
|
import { config } from '../config';
|
||||||
import { logger } from '../config/logger';
|
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 { UnauthorizedError, ValidationError, ForbiddenError } from '../middleware/errorHandler';
|
||||||
import { getPresignedUrl } from '../config/minio';
|
import { getPresignedUrl } from '../config/minio';
|
||||||
|
|
||||||
export class CoursesService {
|
export class CoursesService {
|
||||||
async ListCourses(category_id?: number): Promise<listCourseResponse> {
|
async ListCourses(input: ListCoursesInput): Promise<listCourseResponse> {
|
||||||
try {
|
try {
|
||||||
|
const { category_id, page = 1, limit = 10, random = false } = input;
|
||||||
|
|
||||||
const where: Prisma.CourseWhereInput = {
|
const where: Prisma.CourseWhereInput = {
|
||||||
status: 'APPROVED',
|
status: 'APPROVED',
|
||||||
};
|
};
|
||||||
|
|
@ -17,7 +19,35 @@ export class CoursesService {
|
||||||
where.category_id = category_id;
|
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
|
// Generate presigned URLs for thumbnails
|
||||||
const coursesWithUrls = await Promise.all(
|
const coursesWithUrls = await Promise.all(
|
||||||
|
|
@ -40,7 +70,10 @@ export class CoursesService {
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
message: 'Courses fetched successfully',
|
message: 'Courses fetched successfully',
|
||||||
total: coursesWithUrls.length,
|
total,
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
totalPages,
|
||||||
data: coursesWithUrls,
|
data: coursesWithUrls,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,20 @@
|
||||||
import { Course } from '@prisma/client';
|
import { Course } from '@prisma/client';
|
||||||
|
|
||||||
|
export interface ListCoursesInput {
|
||||||
|
category_id?: number;
|
||||||
|
page?: number;
|
||||||
|
limit?: number;
|
||||||
|
random?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface listCourseResponse {
|
export interface listCourseResponse {
|
||||||
code: number;
|
code: number;
|
||||||
message: string;
|
message: string;
|
||||||
data: Course[];
|
data: Course[];
|
||||||
total: number;
|
total: number;
|
||||||
|
page: number;
|
||||||
|
limit: number;
|
||||||
|
totalPages: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface getCourseResponse {
|
export interface getCourseResponse {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue