feat(11-06): TenderSavedSearch model + CRUD service (FILTER-06)
GREEN phase — adds TenderSavedSearch (userId+tenantId scoped, filters
Json, @@unique([userId,name])), the migration (applied to local dev DB),
and TenderSavedSearchService following the FavoritesService/
TenderTriageService pattern: manual where:{userId} scoping (no
forTenant()/RLS), ownership check before update/remove, P2002 unique
conflicts translated to ConflictException.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
-- CreateTable
|
||||
-- FILTER-06 (D-08/D-11) — per-user Suchprofil (benannte Filterkombination).
|
||||
-- Scoping-Muster wie FavoriteLink/TenderTriage (T-08-06, Pitfall 4):
|
||||
-- userId-Scoping im Service, KEIN forTenant()/RLS. tenantId wird
|
||||
-- zusätzlich mitgeführt (spätere Tenant-Isolation), ist aber nicht das
|
||||
-- Scoping-Feld. KEIN Tender-FK (Pitfall 6) — filters speichert nur
|
||||
-- Kriterien, nicht Tender-Ids, daher unkritisch bei der Retention.
|
||||
CREATE TABLE IF NOT EXISTS "TenderSavedSearch" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"tenantId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"filters" JSONB NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "TenderSavedSearch_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
-- Verhindert zwei Profile gleichen Namens pro Nutzer (T-11-14).
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "TenderSavedSearch_userId_name_key" ON "TenderSavedSearch"("userId", "name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX IF NOT EXISTS "TenderSavedSearch_userId_idx" ON "TenderSavedSearch"("userId");
|
||||
@@ -316,6 +316,26 @@ model TenderTriage {
|
||||
@@index([tenderId])
|
||||
}
|
||||
|
||||
// FILTER-06 — per-user Suchprofil (D-08/D-11). Scoping-Muster wie
|
||||
// FavoriteLink/TenderTriage (T-08-06, Pitfall 4): userId-Scoping im
|
||||
// Service, KEIN forTenant()/RLS. tenantId wird zusätzlich mitgeführt
|
||||
// (spätere Tenant-Isolation), ist aber NICHT das Scoping-Feld. KEIN
|
||||
// Tender-FK (Pitfall 6) — ein Profil speichert nur die Filterkriterien
|
||||
// (deckungsgleich mit den URL-searchParams, Plan 11-06), nicht Tender-Ids,
|
||||
// daher unkritisch bei der 90-Tage-Retention.
|
||||
model TenderSavedSearch {
|
||||
id String @id @default(uuid())
|
||||
userId String
|
||||
tenantId String
|
||||
name String
|
||||
filters Json // serialisierte Filterkombination (q, plz, bundesland, region, cpv, deadlineFrom/To, openOnly, valueMin/Max, includeNullValue, sort, favOnly)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([userId, name]) // keine zwei Profile gleichen Namens pro Nutzer
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
// Singleton-per-source admin poll config (INGEST-06 foundation).
|
||||
model TenderSourcePollConfig {
|
||||
id String @id @default(uuid())
|
||||
|
||||
Reference in New Issue
Block a user