From ba59e00006efa9d8edaacf3c0a5b2cd649eae155 Mon Sep 17 00:00:00 2001 From: Ahmedscreativeverse Date: Thu, 23 Apr 2026 20:13:53 +0100 Subject: [PATCH] Implement pagination limits and validation tests for DTOs --- README.md | 2 ++ src/common/constants/app.constants.ts | 2 +- src/common/dto/pagination.dto.spec.ts | 41 +++++++++++++++++++++++++++ src/common/dto/pagination.dto.ts | 11 ++++--- src/common/utils/pagination.util.ts | 7 +++-- 5 files changed, 56 insertions(+), 7 deletions(-) create mode 100644 src/common/dto/pagination.dto.spec.ts diff --git a/README.md b/README.md index dba241b4..cfa94f32 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ This is the **NestJS** backend powering TeachLink — offering APIs, authentication, user management, notifications, and knowledge monetization features. +- Pagination is limited to a maximum page size of **100** items per request. + --- ## 🔁 CI / Testing diff --git a/src/common/constants/app.constants.ts b/src/common/constants/app.constants.ts index b1a4858a..07bd4241 100644 --- a/src/common/constants/app.constants.ts +++ b/src/common/constants/app.constants.ts @@ -9,7 +9,7 @@ export const APP_CONSTANTS = { // Pagination defaults DEFAULT_PAGE_SIZE: 10, - MAX_PAGE_SIZE: 100, + MAX_PAGE_SIZE: 100, // Max items allowed per page for paginated API responses // Status values COURSE_STATUS: { diff --git a/src/common/dto/pagination.dto.spec.ts b/src/common/dto/pagination.dto.spec.ts new file mode 100644 index 00000000..596cd092 --- /dev/null +++ b/src/common/dto/pagination.dto.spec.ts @@ -0,0 +1,41 @@ +import { validateSync } from 'class-validator'; +import { PaginationQueryDto, CursorPaginationQueryDto } from './pagination.dto'; +import { APP_CONSTANTS } from '../constants/app.constants'; + +describe('Pagination DTO validation', () => { + const { DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE } = APP_CONSTANTS; + + it('uses the default page size when no limit is provided', () => { + const dto = new PaginationQueryDto(); + const errors = validateSync(dto); + + expect(errors).toHaveLength(0); + expect(dto.limit).toBe(DEFAULT_PAGE_SIZE); + }); + + it('accepts a limit equal to the maximum page size', () => { + const dto = new PaginationQueryDto(); + dto.limit = MAX_PAGE_SIZE; + const errors = validateSync(dto); + + expect(errors).toHaveLength(0); + }); + + it('rejects a limit greater than the maximum page size', () => { + const dto = new PaginationQueryDto(); + dto.limit = MAX_PAGE_SIZE + 1; + const errors = validateSync(dto); + + expect(errors).toHaveLength(1); + expect(errors[0].constraints).toHaveProperty('max'); + }); + + it('validates cursor pagination limit against the same maximum', () => { + const dto = new CursorPaginationQueryDto(); + dto.limit = MAX_PAGE_SIZE + 1; + const errors = validateSync(dto); + + expect(errors).toHaveLength(1); + expect(errors[0].constraints).toHaveProperty('max'); + }); +}); diff --git a/src/common/dto/pagination.dto.ts b/src/common/dto/pagination.dto.ts index 6f2dacca..dec54571 100644 --- a/src/common/dto/pagination.dto.ts +++ b/src/common/dto/pagination.dto.ts @@ -1,5 +1,8 @@ import { IsOptional, IsInt, Min, Max, IsString, IsIn, IsNumber } from 'class-validator'; import { Type } from 'class-transformer'; +import { APP_CONSTANTS } from '../constants/app.constants'; + +const { DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE } = APP_CONSTANTS; export enum SortOrder { ASC = 'ASC', @@ -23,9 +26,9 @@ export class PaginationQueryDto { @Type(() => Number) @IsInt() @Min(1) - @Max(100) + @Max(MAX_PAGE_SIZE, { message: `Page size cannot exceed ${MAX_PAGE_SIZE}` }) @IsNumber() - limit?: number = 10; + limit?: number = DEFAULT_PAGE_SIZE; @IsOptional() @IsString() @@ -49,8 +52,8 @@ export class CursorPaginationQueryDto { @Type(() => Number) @IsInt() @Min(1) - @Max(100) - limit?: number = 10; + @Max(MAX_PAGE_SIZE, { message: `Page size cannot exceed ${MAX_PAGE_SIZE}` }) + limit?: number = DEFAULT_PAGE_SIZE; @IsOptional() @IsString() diff --git a/src/common/utils/pagination.util.ts b/src/common/utils/pagination.util.ts index 0b64a23c..4599c312 100644 --- a/src/common/utils/pagination.util.ts +++ b/src/common/utils/pagination.util.ts @@ -6,6 +6,9 @@ import { CursorPaginationQueryDto, CursorDirection, } from '../dto/pagination.dto'; +import { APP_CONSTANTS } from '../constants/app.constants'; + +const { DEFAULT_PAGE_SIZE } = APP_CONSTANTS; export interface PaginatedResponse { data: T[]; @@ -34,7 +37,7 @@ export async function paginate( options: PaginationQueryDto, ): Promise> { const page = options.page || 1; - const limit = options.limit || 10; + const limit = options.limit || DEFAULT_PAGE_SIZE; const skip = (page - 1) * limit; // Apply sorting @@ -116,7 +119,7 @@ export async function paginateWithCursor>( queryBuilder: SelectQueryBuilder, options: CursorPaginationQueryDto, ): Promise> { - const limit = options.limit || 10; + const limit = options.limit || DEFAULT_PAGE_SIZE; const sortBy = options.sortBy || 'createdAt'; const order = options.order || SortOrder.DESC; const direction = options.direction || CursorDirection.FORWARD;