fix(SEC-API-21): Add DTO validation for semantic/hybrid search body
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

Replace inline type annotations with proper class-validator DTOs for the
semantic and hybrid search endpoints. Adds SemanticSearchBodyDto,
HybridSearchBodyDto (query: @IsString @MaxLength(500), status:
@IsOptional @IsEnum(EntryStatus)), and SemanticSearchQueryDto (page/limit
with @IsInt @Min/@Max validation). Includes 22 new tests covering DTO
validation edge cases and controller integration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jason Woltje
2026-02-06 13:35:06 -06:00
parent 17cfeb974b
commit bb6e08208c
4 changed files with 338 additions and 14 deletions

View File

@@ -4,7 +4,14 @@ export { EntryQueryDto } from "./entry-query.dto";
export { CreateTagDto } from "./create-tag.dto";
export { UpdateTagDto } from "./update-tag.dto";
export { RestoreVersionDto } from "./restore-version.dto";
export { SearchQueryDto, TagSearchDto, RecentEntriesDto } from "./search-query.dto";
export {
SearchQueryDto,
TagSearchDto,
RecentEntriesDto,
SemanticSearchBodyDto,
SemanticSearchQueryDto,
HybridSearchBodyDto,
} from "./search-query.dto";
export { GraphQueryDto, GraphFilterDto } from "./graph-query.dto";
export { ExportQueryDto, ExportFormat } from "./import-export.dto";
export type { ImportResult, ImportResponseDto } from "./import-export.dto";

View File

@@ -1,4 +1,4 @@
import { IsOptional, IsString, IsInt, Min, Max, IsArray, IsEnum } from "class-validator";
import { IsOptional, IsString, IsInt, Min, Max, IsArray, IsEnum, MaxLength } from "class-validator";
import { Type, Transform } from "class-transformer";
import { EntryStatus } from "@prisma/client";
@@ -75,3 +75,49 @@ export class RecentEntriesDto {
@IsEnum(EntryStatus, { message: "status must be a valid EntryStatus" })
status?: EntryStatus;
}
/**
* DTO for semantic search request body
* Validates the query string and optional status filter
*/
export class SemanticSearchBodyDto {
@IsString({ message: "query must be a string" })
@MaxLength(500, { message: "query must not exceed 500 characters" })
query!: string;
@IsOptional()
@IsEnum(EntryStatus, { message: "status must be a valid EntryStatus" })
status?: EntryStatus;
}
/**
* DTO for semantic/hybrid search query parameters (pagination)
*/
export class SemanticSearchQueryDto {
@IsOptional()
@Type(() => Number)
@IsInt({ message: "page must be an integer" })
@Min(1, { message: "page must be at least 1" })
page?: number;
@IsOptional()
@Type(() => Number)
@IsInt({ message: "limit must be an integer" })
@Min(1, { message: "limit must be at least 1" })
@Max(100, { message: "limit must not exceed 100" })
limit?: number;
}
/**
* DTO for hybrid search request body
* Validates the query string and optional status filter
*/
export class HybridSearchBodyDto {
@IsString({ message: "query must be a string" })
@MaxLength(500, { message: "query must not exceed 500 characters" })
query!: string;
@IsOptional()
@IsEnum(EntryStatus, { message: "status must be a valid EntryStatus" })
status?: EntryStatus;
}