| 17-eigene-ausschreibungs-quellen-je-nutzer |
02 |
api |
| prisma |
| postgresql |
| nestjs |
| multi-tenancy |
| migration |
| rss |
|
| phase |
provides |
| 17-01 |
Handgeschriebene Migrationstechnik (migrate diff + Handdatei + migrate deploy), tenantId-Denormalisierungsmuster, Nutzerfreigabe fuer beide Datenbank-Umbauten der Phase |
|
|
| TenderRssFeedSource.userId/tenantId (nullable) — plattformweite vs. persoenliche RSS-Feeds (D-02) |
| Handgeschriebene Migration (@@unique([userId,url]) ersetzt url @unique), Bestandszeilen unangetastet |
| TenderRssFeedSourceService: listForUser/createForUser/createPlatform/remove(id,{userId,isAdmin}) |
| GET/POST/DELETE /modules/tender-radar/rss-feeds @UseModule('tender-radar') statt @Roles(ADMIN,SUPER_ADMIN); POST scope:'platform' bleibt admin-only (Inline-Check) |
| RssAdapter taggt Datensaetze aus persoenlichen Feeds mit ownerTenantId (D-06, D-13-Wiederverwendung) |
| seedServiceBundRssFeed() (tenders.seed.ts) — find-then-create statt upsert-on-url, testbar ohne Nest-Bootstrap |
|
|
| tokens |
tasks |
commits |
| 19468 |
3 |
3 |
|
| added |
patterns |
|
|
| Handgeschriebene Prisma-Migration ohne Bestandsdaten-Umzug (nur Spalten+Index-Wechsel) — leerer Besitzer entspricht bereits dem Ist-Zustand, kein Backfill noetig, anders als 17-01 |
| Zusammengesetzte Eindeutigkeitsregel mit einem Feld ohne Pflichtwert: Prismas generierter Compound-Unique-Typ verlangt das Feld trotzdem als Pflicht-String — Zugriff auf die NULL-Seite nur ueber findFirst/deleteMany mit gewoehnlicher Bedingung, nie ueber den Compound-Typ (betraf sowohl die Startbestueckung als auch remove()) |
| Delete-Protection als einzelne bedingte deleteMany-Anweisung (Besitz-Vergleich in der DB-Bedingung, kein TOCTOU-Fenster) statt 'erst lesen, dann loeschen' |
| Auswertender Prisma-Doppelgaenger in Tests (matchesWhere mit OR/AND/Gleichheit) statt eines Doppelgaengers, der Bedingungen ignoriert und Erfolg vortaeuscht |
|
|
| created |
modified |
| apps/api/prisma/migrations/20260812110000_tender_rss_feed_owner/migration.sql |
| apps/api/src/tenders/rss-feed-migration-sql.spec.ts |
|
| apps/api/prisma/schema.prisma |
| apps/api/src/tenders/tender-rss-feed.service.ts |
| apps/api/src/tenders/dto/tender-rss-feed.dto.ts |
| apps/api/src/tenders/tenders.controller.ts |
| apps/api/src/tenders/tenders.module.ts |
| apps/api/src/tenders/tenders.seed.ts |
| apps/api/src/tenders/adapters/rss.adapter.ts |
|
|
| Checkpoint-Freigabe aus 17-01 deckte beide Datenbank-Umbauten der Phase ausdruecklich ab — kein zweiter Halt fuer diese Migration (im Ausfuehrungsauftrag vorgegeben, bestaetigt durch gemessene 1 Bestandszeile lokal, unveraendert plattformweit nach der Migration) |
| [Rule 3] Startbestueckung (tenders.module.ts) von 'upsert auf url' auf 'erst suchen, dann anlegen' umgestellt bereits in Task 1 statt erst in Task 3 — Task 1s eigene <verify> verlangt einen fehlerfreien Type-Check ueber das gesamte Projekt, und der Compile-Fehler (Prismas generierter Compound-Unique-Typ verlangt userId als Pflicht-String) war sofort da, sobald das Schema in Task 1 geaendert war. Gleiche Vorgehensweise wie in 17-01 (dortige Deviation 1/2). |
| seedServiceBundRssFeed() aus TendersModule.onModuleInit() in tenders.seed.ts extrahiert (gleiches Muster wie das bereits vorhandene seedTendersModule) — noetig, damit die in Task 3 verlangte Bestueckungs-Idempotenz-Pruefung eine echte, produktiv laufende Funktion testet statt eine Kopie der Logik |
| DELETE /rss-feeds/:feedId von @Roles(ADMIN,SUPER_ADMIN) auf @UseModule('tender-radar') umgestellt — die Besitzpruefung im Dienst ersetzt die Rollenpruefung vollstaendig (T-17-07) |
| POST /rss-feeds mit scope:'platform' bleibt admin-only, aber als Inline-Pruefung im Controller (role aus demselben Ort wie RolesGuard) statt als Decorator, weil dieselbe Route jetzt auch den persoenlichen Anlegeweg fuer jeden Modulnutzer bedient (T-17-08) |
|
|
| id |
description |
requirement |
verification |
human_judgment |
| D1 |
TenderRssFeedSource.userId/tenantId ergaenzt (Migration ohne Bestandsdaten-Umzug); @@unique([userId,url]) ersetzt url @unique; listForUser/createForUser/createPlatform ersetzen list/create; GET/POST /rss-feeds sind @UseModule-gated, scope:'platform' bleibt admin-only |
SRC-02 |
| kind |
ref |
status |
| unit |
apps/api/src/tenders/tender-rss-feed.service.spec.ts (26 Tests: SSRF/Denylist auf createPlatform, Ownership-Scoping in listForUser, createForUser-Owner-Stempel, gleiche URL fuer zwei Nutzer) |
pass |
|
| kind |
ref |
status |
| unit |
apps/api/src/tenders/tenders.controller.spec.ts (RSS-Feeds-Block: GET/POST-Delegation, isPlatformWide-Mapping, T-17-08 USER/ADMIN-Faelle) |
pass |
|
| kind |
ref |
status |
| integration |
prisma migrate deploy gegen lokale Dev-DB (172.19.0.2) + prisma migrate status + Index-Abfrage (TenderRssFeedSource_userId_url_key vorhanden, TenderRssFeedSource_url_key entfernt) |
pass |
|
|
false |
|
| id |
description |
requirement |
verification |
human_judgment |
| D2 |
Loeschschutz: nur Besitzer oder (Administrator UND plattformweiter Feed) kann loeschen, sonst NotFoundException statt ForbiddenException; Obergrenze 20 persoenliche Feeds je Nutzer; Sperrliste/SSRF-Schutz greift auch auf dem persoenlichen Anlegeweg |
SRC-02 |
| kind |
ref |
status |
| unit |
apps/api/src/tenders/tender-rss-feed.service.spec.ts (remove()-Block mit auswertendem Prisma-Doppelgaenger, 6 Faelle; Obergrenze-Block, 2 Faelle; SSRF-auf-createForUser-Block, 2 Faelle) |
pass |
|
| kind |
ref |
status |
| unit |
apps/api/src/tenders/tenders.controller.spec.ts (DELETE-Faelle mit isAdmin true/false) |
pass |
|
|
false |
|
| id |
description |
requirement |
verification |
human_judgment |
| D3 |
Ausschreibungen aus einem persoenlichen RSS-Feed tragen die ownerTenantId-Herkunftsmarkierung des Feed-Besitzers (D-06); plattformweite Feeds bleiben unmarkiert; der Sammel-Fetch faengt weiterhin einen ausfallenden Feed ab, ohne die uebrigen zu blockieren, auch ueber Besitzer-Grenzen hinweg |
SRC-02 |
| kind |
ref |
status |
| unit |
apps/api/src/tenders/adapters/rss.adapter.spec.ts (D-06-Block: getaggte vs. ungetaggte Datensaetze, zwei Besitzer in einem Durchlauf mit Catch-per-Feed) |
pass |
|
|
false |
|
| id |
description |
requirement |
verification |
human_judgment |
| D4 |
Der seit Phase 14 gesetzte service.bund.de-Feed bleibt nach der Migration unveraendert aktiv und plattformweit (leerer Besitzer); die Startbestueckung ist bei wiederholten Anwendungsstarts folgenlos (kein Duplikat) |
SRC-03 |
| kind |
ref |
status |
| unit |
apps/api/src/tenders/tenders.seed.spec.ts (seedServiceBundRssFeed-Block: Einzelanlage, zweifacher Lauf bleibt bei einer Zeile, vorhandener persoenlicher Feed derselben Adresse bleibt unangetastet) |
pass |
|
| kind |
ref |
status |
| integration |
SELECT auf TenderRssFeedSource nach angewendeter Migration: genau 1 Zeile, service.bund.de, isActive=true, userId/tenantId leer |
pass |
|
|
false |
|
|
58min |
2026-08-12 |
complete |