feat(12-04): tender-radar notification-pref service + routes, instantAlert passthrough

- 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>
This commit is contained in:
2026-07-22 09:28:02 +02:00
parent 1a0cd375c2
commit 9e7ba5353d
9 changed files with 343 additions and 1 deletions
+15 -1
View File
@@ -1,4 +1,4 @@
import { IsNotEmpty, IsObject, IsOptional, IsString, MaxLength } from 'class-validator';
import { IsBoolean, IsNotEmpty, IsObject, IsOptional, IsString, MaxLength } from 'class-validator';
/**
* Body DTO for POST /modules/tender-radar/saved-searches (FILTER-06).
@@ -21,6 +21,15 @@ export class CreateSavedSearchDto {
@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;
}
/**
@@ -38,4 +47,9 @@ export class UpdateSavedSearchDto {
@IsOptional()
@IsObject()
filters?: Record<string, unknown>;
/** Sofort-Alert-Toggle (NOTIFY-02, D-04) — see CreateSavedSearchDto. */
@IsOptional()
@IsBoolean()
instantAlert?: boolean;
}