import { ApiPropertyOptional } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { IsIn, IsInt, IsOptional, Max, Min } from 'class-validator'; /** * Base query DTO for every paginated list endpoint. Extend it and add the * module's own filter fields; sort-field whitelists stay in the subclass * because the allowed columns differ per resource. * * All list endpoints built on this return the shared `PaginatedResponse` * envelope from `@edr/types` (`items` + `meta`), produced by * `common/utils/pagination.util.ts`. */ export class PaginationQueryDto { @ApiPropertyOptional({ default: 1, minimum: 1 }) @IsOptional() @Transform(({ value }) => parseInt(String(value), 10) || 1) @IsInt() @Min(1) page?: number; @ApiPropertyOptional({ default: 20, minimum: 1, maximum: 100 }) @IsOptional() @Transform(({ value }) => parseInt(String(value), 10) || 20) @IsInt() @Min(1) @Max(100) pageSize?: number; @ApiPropertyOptional({ description: 'Free-text search, applied server-side (resource-specific columns).', }) @IsOptional() @Transform(({ value }) => typeof value === 'string' && value.trim() ? value.trim() : undefined, ) search?: string; @ApiPropertyOptional({ enum: ['ASC', 'DESC'], default: 'DESC' }) @IsOptional() @Transform(({ value }) => String(value).toUpperCase()) @IsIn(['ASC', 'DESC']) sortOrder?: 'ASC' | 'DESC'; /** * Column to sort by, as a public field name (not a raw SQL column). The * actual whitelist lives in `applySort`'s `sortable` map at each call site, * not here — a per-DTO `@IsIn` is opt-in and has been forgotten before. * An unrecognized value falls back silently rather than 400ing, so a stale * bookmark or shared link never breaks. */ @ApiPropertyOptional({ description: 'Public field name; unknown values fall back to the endpoint default.' }) @IsOptional() @Transform(({ value }) => (typeof value === 'string' && value.trim() ? value.trim() : undefined)) sortBy?: string; }