import { ApiPropertyOptional } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { IsBooleanString, IsIn, IsISO8601, IsOptional, IsString, IsUUID, MaxLength } from 'class-validator'; import { PaginationQueryDto } from '../../../common/dto/pagination-query.dto'; const AUDITED_METHODS = ['POST', 'PUT', 'PATCH', 'DELETE'] as const; /** * Filters for the audit log read endpoint. * * Extends the shared pagination DTO so page/pageSize behave (and are capped) * exactly as they do on every other list endpoint. */ export class AuditLogQueryDto extends PaginationQueryDto { @ApiPropertyOptional({ description: 'Entity type, e.g. "Contract", "Booking", "Locomotive".', example: 'Contract', }) @IsOptional() @IsString() @MaxLength(50) type?: string; @ApiPropertyOptional({ description: 'IAM id of the acting backoffice user.' }) @IsOptional() @IsUUID() userId?: string; @ApiPropertyOptional({ enum: AUDITED_METHODS }) @IsOptional() @Transform(({ value }) => String(value).toUpperCase()) @IsIn([...AUDITED_METHODS]) method?: string; @ApiPropertyOptional({ description: 'Id of the affected record.' }) @IsOptional() @IsString() @MaxLength(64) resourceId?: string; @ApiPropertyOptional({ description: 'Human identifier of the affected record — booking reference, schedule number, train number. Case-insensitive prefix match.', example: 'S-2026-00045', }) @IsOptional() @IsString() @MaxLength(64) reference?: string; @ApiPropertyOptional({ description: 'Staff name, case-insensitive substring match.', example: 'Mulu', }) @IsOptional() @IsString() @MaxLength(150) userName?: string; @ApiPropertyOptional({ description: 'Action title, case-insensitive substring match.', example: 'Cancel booking', }) @IsOptional() @IsString() @MaxLength(255) title?: string; @ApiPropertyOptional({ description: 'Free-text search across reference, resource id, staff name and action title.', example: 'B-2026-00120', }) @IsOptional() @IsString() @MaxLength(100) q?: string; @ApiPropertyOptional({ description: 'Filter by outcome: true = succeeded, false = failed.', }) @IsOptional() @IsBooleanString() isSuccess?: string; @ApiPropertyOptional({ description: 'Inclusive start of the range (ISO 8601).', example: '2026-01-01T00:00:00.000Z', }) @IsOptional() @IsISO8601() from?: string; @ApiPropertyOptional({ description: 'Inclusive end of the range (ISO 8601).', example: '2026-01-31T23:59:59.999Z', }) @IsOptional() @IsISO8601() to?: string; }