12 KiB
phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, coverage, duration, completed, status
| phase | plan | subsystem | tags | requires | provides | affects | tech-stack | key-files | key-decisions | patterns-established | requirements-completed | coverage | duration | completed | status | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 12-tender-notifications | 04 | api+ui |
|
|
|
|
|
|
|
|
|
|
12min | 2026-07-22 | complete |
Phase 12 Plan 04: Digest-Intervall + Sofort-Alert Bedienoberfläche Summary
Per-user digest-interval CRUD (GET/PUT notification-pref, default 'daily') plus instantAlert passthrough on saved-search create/update — both wired into a settings-page selector and a per-profile SavedSearchBar toggle, closing the loop from 12-01/02/03's pipeline to a user-controllable UI.
Performance
- Duration: ~12 min
- Completed: 2026-07-22
- Tasks: 3/3 completed
- Files modified: 10 (3 created, 7 modified)
Accomplishments
TenderNotificationPrefService(per-user digestInterval CRUD, default'daily'when no row exists, upsert on@@unique(userId)) +UpdateNotificationPrefDto(@IsIn(['daily','weekly','off']))GET/PUT /modules/tender-radar/notification-pref, declared before@Get(':id')(route-order pitfall avoided, regression test added)instantAlertnow flows throughCreate/UpdateSavedSearchDtoandTenderSavedSearchService.create/update(optional,@IsBoolean, falls back to the Prisma column defaultfalsewhen omitted)tender-radar-api.ts:fetchNotificationPref/saveNotificationPref, andSavedSearch/CreateSavedSearchPayload/UpdateSavedSearchPayloadextended withinstantAlert- Settings page: "Benachrichtigungen" section with a Täglich/Wöchentlich/Aus select, loads via
fetchNotificationPrefon mount, saves viasaveNotificationPrefon change SavedSearchBar: per-profile Sofort-Alert checkbox (aria-label="Sofort-Alert für {name}"), reflectsprofile.instantAlert, callsupdateSavedSearch(id, { instantAlert })+ reloads on toggle
Task Commits
- Task 1: Backend — Pref-Service + Routen + instantAlert-Durchreichung -
9e7ba53(feat, TDD RED→GREEN) - Task 2: Frontend API-Client — Pref-Funktionen + instantAlert in SavedSearch -
73a7e49(feat) - Task 3: Frontend UI — Digest-Intervall-Auswahl + Sofort-Alert-Toggle -
d60080e(feat)
Note: no separate metadata commit yet — this SUMMARY/STATE/ROADMAP commit follows.
Files Created/Modified
apps/api/src/tenders/dto/notification-pref.dto.ts-UpdateNotificationPrefDto(@IsIn)apps/api/src/tenders/tender-notification-pref.service.ts- per-user digestInterval CRUD, default'daily', upsert onuserIdapps/api/src/tenders/tender-notification-pref.service.spec.ts- RED-first TDD spec (4 tests)apps/api/src/tenders/dto/saved-search.dto.ts-instantAlert?: booleanon both Create/Update DTOsapps/api/src/tenders/tender-saved-search.service.ts- passesinstantAlertthrough create/update when suppliedapps/api/src/tenders/tender-saved-search.service.spec.ts- 3 new instantAlert passthrough testsapps/api/src/tenders/tenders.controller.ts-getNotificationPref/setNotificationPrefroutes + constructor DIapps/api/src/tenders/tenders.controller.spec.ts- fake pref service added to all 20 controller instantiations + new route-order/IDOR testsapps/api/src/tenders/tenders.module.ts- registersTenderNotificationPrefService, doc comment updatedapps/web/src/lib/tender-radar-api.ts-NotificationPreftype +fetchNotificationPref/saveNotificationPref;instantAlertadded toSavedSearch/payloadsapps/web/src/app/(portal)/modules/tender-radar/settings/page.tsx- Benachrichtigungen section (digest-interval select)apps/web/src/app/(portal)/modules/tender-radar/components/SavedSearchBar.tsx- per-profile Sofort-Alert checkboxapps/web/src/app/(portal)/modules/tender-radar/components/SavedSearchBar.test.tsx- checkbox-state + toggle-calls-updateSavedSearch tests
Decisions Made
- Kept the digest-interval selector inline in
settings/page.tsxrather than extracting a separateNotificationPrefForm.tsxcomponent — the page is already a'use client'shell (unlikeSourceConfigForm's admin-only split), and this plan's file list/test scope didn't call for standalone unit coverage of that piece. - Used a plain checkbox (not a switch) for the Sofort-Alert toggle to match the existing minimal chip-based
SavedSearchBarstyling.
Deviations from Plan
Auto-fixed Issues
1. [Rule 3 - Blocking] Updated tenders.controller.spec.ts fakes for the new constructor parameter
- Found during: Task 1 (adding
TenderNotificationPrefServicetoTendersController's constructor) - Issue: Adding a 5th required constructor parameter broke all 20 existing
new TendersController(...)call sites in the spec file (missing-argument TS errors) - Fix: Added a
makeFakeNotificationPrefService()helper and appended it as the 5th argument to every existing constructor call; also added two new tests (route-declaration-order guard + GET/PUT IDOR-scoping proof) for the new routes - Files modified:
apps/api/src/tenders/tenders.controller.spec.ts - Verification:
npx tsc --noEmitclean; full API test suite (19 files / 209 tests) green - Committed in:
9e7ba53(Task 1 commit)
Total deviations: 1 auto-fixed (blocking — test compilation) Impact on plan: Necessary to keep the existing test suite compiling and passing after the planned constructor change. No scope creep — this is the direct, unavoidable consequence of the plan's own "Injiziere TenderNotificationPrefService in den Controller-Konstruktor" instruction.
Issues Encountered
None.
IDOR Mitigation (T-12-14/15/16)
UpdateNotificationPrefDtoand both saved-search DTOs carry nouserId/tenantIdfield — both routes derive them exclusively fromextractTriageContext(req)(cookie-backed auth context), never from body/query.TenderNotificationPrefServicescopes every read/write byuserIdvia the@@unique(userId)constraint — a foreignuserIdcan never read or overwrite another user's pref (proven intender-notification-pref.service.spec.ts: "getForUser() is scoped strictly by userId").digestIntervalis validated server-side to exactly'daily'|'weekly'|'off'via@IsIn(T-12-15) before it reaches the service/scheduler due-check logic.instantAlertvalidated via@IsBoolean; ownership of the saved-search profile is still enforced inTenderSavedSearchService.update()(pre-existing NotFound-collapse pattern, T-11-14) — unchanged by this plan.- No new package installs; no new trust boundary beyond the two already-modeled routes (matches
12-04-PLAN.md's threat register — no## Threat Flagsneeded).
Known Stubs
None.
User Setup Required
None - no external service configuration required. Both TenderNotificationPref.digestInterval and TenderSavedSearch.instantAlert columns already existed in the local dev DB (seeded in Plan 12-01) — no migration needed for this plan.
Next Phase Readiness
- NOTIFY-01/02 are now fully wired end-to-end: user sets digest interval / Sofort-Alert in the web UI → the 12-01/02/03 matching/digest/instant pipeline reads and honors those settings.
- Remaining end-of-phase UAT (per
12-04-PLAN.md<verification>): manually confirm in the running app that (a) setting the interval to "Wöchentlich" and reloading the settings page keeps the value, and (b) toggling a profile's Sofort-Alert and reloading keeps it on. Requires a docker rebuild/restart of the web+api containers to pick up these changes (per project convention, the user performs the rebuild, not this agent). - Phase 12 is otherwise code-complete pending that UAT + docker rebuild (consistent with the phase-level pause note in
eb10bd3).
Phase: 12-tender-notifications Completed: 2026-07-22
Self-Check: PASSED
All 12 listed files/paths verified present on disk; all 3 task commit hashes (9e7ba53, 73a7e49, d60080e) verified present in git log.