feat(10-05): add SourceConfigDto and TenderQueryDto

- SourceConfigDto: pollIntervalMin (@Min(5) @Max(1440)), isActive
- TenderQueryDto: page/limit pagination (copied from DkvHistoryQueryDto) + status filter
This commit is contained in:
2026-07-21 11:15:36 +02:00
parent 9227cc9e78
commit f6e636c37d
2 changed files with 79 additions and 0 deletions
@@ -0,0 +1,35 @@
import { IsBoolean, IsInt, IsOptional, Max, Min } from 'class-validator';
/**
* DTO for reading/updating the singleton `doe-opendata` poll config
* (`TenderSourcePollConfig`, `sourceType: 'doe-opendata'`).
*
* Security:
* - T-10-15: pollIntervalMin bounded 5-1440 — same DoS-mitigation floor as
* DkvConfigDto.pollIntervalMin (no sub-5-minute polling of the platform-wide
* scheduler).
*
* Note: `pollIntervalMin` is the cron-tick frequency (D-04 default 60) —
* NOT the DÖE fetch frequency. The actual upstream HTTP call is gated
* separately by the day-cursor check inside TenderIngestionService
* (Pitfall A: cron tick frequency != actual-fetch frequency for this
* day-granular source).
*/
export class SourceConfigDto {
/**
* Cron-tick interval in minutes. Minimum 5 minutes (DoS mitigation, T-10-15).
* Maximum 1440 (24h).
*/
@IsOptional()
@IsInt()
@Min(5)
@Max(1440)
pollIntervalMin?: number;
/**
* Whether the platform-wide DÖE poll cron job is active.
*/
@IsOptional()
@IsBoolean()
isActive?: boolean;
}
@@ -0,0 +1,44 @@
import { Type } from 'class-transformer';
import { IsIn, IsInt, IsOptional, Max, Min } from 'class-validator';
/**
* Query DTO for paginating + filtering the global tender catalog.
*
* Security:
* - T-10-15: pagination bounds (limit @Max(100)) mitigate oversized
* result-set DoS — same convention as DkvHistoryQueryDto.
*
* Used for: GET /modules/tender-radar?page=1&limit=20&status=active
*
* No region/CPV filters here — rich filtering is Phase 11 (FILTER-*).
*/
export class TenderQueryDto {
/**
* Page number (1-based). Defaults to 1 when omitted.
* @Type(() => Number) coerces the query-string value to a number before
* validation — required because HTTP query params always arrive as strings.
*/
@IsOptional()
@IsInt()
@Min(1)
@Type(() => Number)
page?: number;
/**
* Number of records per page. Defaults to 20 when omitted.
* T-10-15: bounded integer mitigates oversized result-set DoS.
*/
@IsOptional()
@IsInt()
@Min(1)
@Max(100)
@Type(() => Number)
limit?: number;
/**
* Status filter. Defaults to active-only at the query layer when omitted.
*/
@IsOptional()
@IsIn(['active', 'expired'])
status?: string;
}