Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/common/constants/app.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
41 changes: 41 additions & 0 deletions src/common/dto/pagination.dto.spec.ts
Original file line number Diff line number Diff line change
@@ -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');
});
});
11 changes: 7 additions & 4 deletions src/common/dto/pagination.dto.ts
Original file line number Diff line number Diff line change
@@ -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',
Expand All @@ -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()
Expand All @@ -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()
Expand Down
7 changes: 5 additions & 2 deletions src/common/utils/pagination.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> {
data: T[];
Expand Down Expand Up @@ -34,7 +37,7 @@ export async function paginate<T>(
options: PaginationQueryDto,
): Promise<PaginatedResponse<T>> {
const page = options.page || 1;
const limit = options.limit || 10;
const limit = options.limit || DEFAULT_PAGE_SIZE;
const skip = (page - 1) * limit;

// Apply sorting
Expand Down Expand Up @@ -116,7 +119,7 @@ export async function paginateWithCursor<T extends Record<string, any>>(
queryBuilder: SelectQueryBuilder<T>,
options: CursorPaginationQueryDto,
): Promise<CursorPaginatedResponse<T>> {
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;
Expand Down
Loading