Files

9.4 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
10-ausschreibungs-radar-foundation-d-e-ingestion 06 execute 6
10-02
10-05
apps/web/src/lib/tender-radar-api.ts
apps/web/src/app/(portal)/modules/tender-radar/settings/page.tsx
apps/web/src/app/(portal)/modules/tender-radar/settings/components/SourceConfigForm.tsx
apps/web/src/app/(portal)/modules/tender-radar/settings/components/SourceConfigForm.test.tsx
true
INGEST-06
truths artifacts key_links
Admin can read and change the shared DÖE poll interval / active state from a web form in the module settings, not only via the raw API (INGEST-06: 'Intervall pro Quelle im Admin-Bereich konfigurierbar')
Saving the form calls PUT /modules/tender-radar/source-config and reflects the persisted value on reload
apps/web/src/app/(portal)/modules/tender-radar/settings/page.tsx
apps/web/src/app/(portal)/modules/tender-radar/settings/components/SourceConfigForm.tsx
apps/web/src/lib/tender-radar-api.ts
SourceConfigForm ↔ tender-radar-api.ts (fetchSourceConfig/saveSourceConfig) ↔ GET/PUT /modules/tender-radar/source-config (Plan 05 controller)
Admin poll-interval config UI — the Next.js frontend tier assigned in 10-RESEARCH.md Architectural Responsibility Map, mirroring DKV InboxConfigForm
Deliver the admin-facing configuration UI for the shared DÖE poll — the frontend half of INGEST-06 that REQUIREMENTS.md ("Intervall pro Quelle im Admin-Bereich konfigurierbar") and the RESEARCH Architectural Responsibility Map ("Admin poll-interval config UI" → Next.js frontend, mirroring DKV `InboxConfigForm`) both require. Plan 05 built the backend endpoint; this plan gives an admin a real form to drive it.

Phase Goal (user story)

As a Tessera-Administrator, I want to das DÖE-Poll-Intervall und den Aktiv-Status ueber ein Formular in den Modul-Einstellungen aendern, so that ich den gemeinsamen Zeitplan ohne API-Aufrufe steuern kann (INGEST-06).

Purpose: Closes the INGEST-06 gap flagged by the plan checker — the requirement is admin-configurable "im Admin-Bereich", i.e. a UI, not only a REST surface. Thin slice: API client + settings form → the existing Plan 05 endpoint. Output: tender-radar-api.ts client, settings/page.tsx, SourceConfigForm + its test.

<execution_context> @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/10-ausschreibungs-radar-foundation-d-e-ingestion/10-PATTERNS.md @apps/web/src/lib/dkv-api.ts @apps/web/src/app/(portal)/modules/dkv-fleet/settings/page.tsx @apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/InboxConfigForm.tsx @apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/VehicleTable.test.tsx Task 1: tender-radar-api client + admin source-config settings form - apps/web/src/lib/dkv-api.ts (client conventions: API_URL, credentials:'include', typed config interface, fetchConfig/saveConfig) - apps/web/src/app/(portal)/modules/dkv-fleet/settings/page.tsx (settings route shell pattern) - apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/InboxConfigForm.tsx (load-on-mount + form-state + save flow to mirror; the tender form is much simpler — only interval + active) apps/web/src/lib/tender-radar-api.ts, apps/web/src/app/(portal)/modules/tender-radar/settings/page.tsx, apps/web/src/app/(portal)/modules/tender-radar/settings/components/SourceConfigForm.tsx Create `tender-radar-api.ts` mirroring `dkv-api.ts` conventions: `API_URL` from `NEXT_PUBLIC_API_URL`, all calls `credentials: 'include'`. Export a `SourceConfig` interface (`pollIntervalMin: number`, `isActive: boolean`, and read-only display fields `sourceType: string`, `lastIngestedDay?: string | null`) plus `fetchSourceConfig()` (GET `/modules/tender-radar/source-config`) and `saveSourceConfig(payload)` (PUT same path with `{ pollIntervalMin, isActive }`).
Create `SourceConfigForm.tsx` (`'use client'`), a much simpler analog of `InboxConfigForm` — no credentials/password. On mount, `fetchSourceConfig()` to populate form state. Fields: a numeric `pollIntervalMin` input (min 5, max 1440 — mirror the backend `SourceConfigDto` bounds so client and server agree) and an `isActive` toggle. A "Speichern" button calls `saveSourceConfig()` and shows a success/error state. Optionally display `sourceType` (doe-opendata) and `lastIngestedDay` read-only so the admin sees the day-cursor state. Note in a comment that `pollIntervalMin` is the cron-tick frequency (D-04 default 60), decoupled from the day-granularity DÖE fetch.

Create `settings/page.tsx` (`'use client'`, default export) rendering a title + `<SourceConfigForm />`. Use hardcoded German strings for now (full i18n is CONFIG-03, Phase 14) and note the intentional MVP stub in the summary. No module-loader whitelist change is needed — `settings/page.tsx` is a standard Next App Router route.
cd apps/web && test -f "src/app/(portal)/modules/tender-radar/settings/page.tsx" && grep -c "saveSourceConfig" src/lib/tender-radar-api.ts && pnpm exec tsc --noEmit 2>&1 | tail -5 - `tender-radar-api.ts` exports `fetchSourceConfig` and `saveSourceConfig` hitting `/modules/tender-radar/source-config`. - `SourceConfigForm` loads config on mount and has a numeric interval input (min 5 / max 1440) + isActive toggle + save action. - `settings/page.tsx` exists and renders the form; web `tsc --noEmit` passes. Admin can view and change the shared poll interval/active state from the module settings UI. Task 2: SourceConfigForm component test - apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/VehicleTable.test.tsx (existing web Vitest + Testing Library component-test style) - apps/web/src/app/(portal)/modules/tender-radar/settings/components/SourceConfigForm.tsx (component under test) - Test: on mount the form fetches config and renders the returned pollIntervalMin value in the interval input. - Test: editing the interval and clicking Speichern calls saveSourceConfig with the new pollIntervalMin and the isActive value. - Test: an interval below 5 or above 1440 is rejected client-side (no save call fired). apps/web/src/app/(portal)/modules/tender-radar/settings/components/SourceConfigForm.test.tsx Write a Vitest + Testing Library test mirroring `VehicleTable.test.tsx`. Mock `tender-radar-api` (`fetchSourceConfig` resolving a known config, `saveSourceConfig` spied). Assert the load-on-mount renders the interval, that Speichern calls `saveSourceConfig` with the edited payload, and that out-of-bounds intervals do not trigger a save. This is the automated proof for the INGEST-06 admin-UI slice. cd apps/web && pnpm test -- SourceConfigForm 2>&1 | tail -15 - Test asserts fetch-on-mount populates the interval input. - Test asserts Speichern calls `saveSourceConfig` with the edited interval + isActive. - Out-of-bounds interval does not call `saveSourceConfig`. - `pnpm --filter @tessera/web test -- SourceConfigForm` green. Admin config form behaviour has automated coverage; green.

<threat_model>

Trust Boundaries

Boundary Description
admin browser → PUT /source-config Privileged mutation of the platform-wide poll schedule via the settings form

STRIDE Threat Register

Threat ID Category Component Severity Disposition Mitigation Plan
T-10-16 Elevation of Privilege SourceConfigForm save path high mitigate The form calls the Plan 05 PUT /source-config route, which is @Roles(Role.ADMIN, Role.SUPER_ADMIN)-guarded server-side — a non-admin cannot mutate the schedule even if the form is reachable. Client bounds (min 5 / max 1440) mirror the server DTO; the server remains the authority
T-10-17 Input Validation pollIntervalMin input medium mitigate Client-side min/max clamp to 5..1440 matches SourceConfigDto server bounds (DoS floor on poll frequency); server re-validates regardless of client
T-10-18 Information Disclosure config fetch response low accept The DÖE source config holds no secrets (auth-free API, no credentials) — unlike DKV there is no password field to protect in this form
</threat_model>
- `pnpm --filter @tessera/web test -- SourceConfigForm` green. - Settings route renders the form; save round-trips to the Plan 05 endpoint. - Web `tsc --noEmit` clean.

<success_criteria>

  • INGEST-06 (admin UI): the shared DÖE poll interval is configurable from a form in the module's admin settings, satisfying "Intervall pro Quelle im Admin-Bereich konfigurierbar". </success_criteria>
Create `.planning/phases/10-ausschreibungs-radar-foundation-d-e-ingestion/10-06-SUMMARY.md` when done.