9e7ba5353d
- TenderNotificationPrefService: per-user digestInterval CRUD (default
'daily', upsert on @@unique userId, D-01/D-03)
- UpdateNotificationPrefDto: @IsIn(['daily','weekly','off']) validation (V5)
- GET/PUT /modules/tender-radar/notification-pref, declared before
@Get(':id') (route-order pitfall)
- instantAlert passthrough in Create/UpdateSavedSearchDto and
TenderSavedSearchService.create/update (NOTIFY-02, D-04)
- All pref/profile routes scoped strictly via extractTriageContext(req),
never from body/query (T-12-14, IDOR)
- Updated tenders.controller.spec.ts fakes for the new constructor param
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
56 lines
1.6 KiB
TypeScript
56 lines
1.6 KiB
TypeScript
import { IsBoolean, IsNotEmpty, IsObject, IsOptional, IsString, MaxLength } from 'class-validator';
|
|
|
|
/**
|
|
* Body DTO for POST /modules/tender-radar/saved-searches (FILTER-06).
|
|
*
|
|
* Security (T-11-14 / V4 — IDOR): deliberately has NO userId or tenantId
|
|
* field — both are always derived server-side from the auth context
|
|
* (FavoritesController.extractContext pattern) in TendersController, never
|
|
* trusted from the request body.
|
|
*
|
|
* `filters` is validated only as a plain object (T-11-17 / V5) — its shape
|
|
* mirrors the FilterPanel's URL-searchParams contract on the frontend, but
|
|
* the backend does not re-validate individual filter keys here; Prisma
|
|
* stores it as parametrized JSONB (no string interpolation, V5).
|
|
*/
|
|
export class CreateSavedSearchDto {
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
@MaxLength(100)
|
|
name!: string;
|
|
|
|
@IsObject()
|
|
filters!: Record<string, unknown>;
|
|
|
|
/**
|
|
* Sofort-Alert-Toggle (NOTIFY-02, D-04) — default false when omitted
|
|
* (Prisma column default). Optional so existing callers that don't know
|
|
* about this field keep working unchanged.
|
|
*/
|
|
@IsOptional()
|
|
@IsBoolean()
|
|
instantAlert?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Body DTO for PATCH /modules/tender-radar/saved-searches/:searchId.
|
|
* Both fields optional — only provided fields are updated (FavoritesService
|
|
* UpdateFavoriteDto pattern).
|
|
*/
|
|
export class UpdateSavedSearchDto {
|
|
@IsOptional()
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
@MaxLength(100)
|
|
name?: string;
|
|
|
|
@IsOptional()
|
|
@IsObject()
|
|
filters?: Record<string, unknown>;
|
|
|
|
/** Sofort-Alert-Toggle (NOTIFY-02, D-04) — see CreateSavedSearchDto. */
|
|
@IsOptional()
|
|
@IsBoolean()
|
|
instantAlert?: boolean;
|
|
}
|