146 lines
9.5 KiB
Markdown
146 lines
9.5 KiB
Markdown
---
|
|
phase: quick-260805-fok
|
|
plan: 01
|
|
subsystem: api
|
|
tags: [nestjs, prisma, groups, multi-tenancy, onapplicationbootstrap]
|
|
|
|
requires:
|
|
- phase: 15-modul-berechtigungen-gruppen-user-grants
|
|
provides: "Group/GroupMembership/ModuleGrant-Schema, partieller Unique-Index Group_one_default_per_tenant, GroupsService.addUserToDefaultGroup"
|
|
provides:
|
|
- "GroupsService.ensureDefaultGroup(tenantId) — race-sichere, idempotente Standardgruppen-Anlage mit Null-Gruppen-Wächter (D-13)"
|
|
- "TenantService.create verdrahtet mit ensureDefaultGroup"
|
|
- "AdminSeedService.ensureDefaultGroupsForAllTenants() — Startup-Reparatur über alle Mandanten"
|
|
affects: [groups, tenant, user, admin-seed]
|
|
|
|
tech-stack:
|
|
added: []
|
|
patterns:
|
|
- "Sequenzielle await-Kette in onApplicationBootstrap statt Verlass auf Nest-Hook-Reihenfolge zwischen Modulen (Muster aus tender-scheduler.service.ts übernommen)"
|
|
- "Interaktive Prisma-Transaktion ($transaction mit async-Callback) statt Array-Form, wenn spätere Schritte die id eines vorherigen Schritts brauchen"
|
|
|
|
key-files:
|
|
created:
|
|
- apps/api/src/tenant/tenant.service.spec.ts
|
|
- apps/api/src/user/admin-seed.service.spec.ts
|
|
modified:
|
|
- apps/api/src/groups/groups.service.ts
|
|
- apps/api/src/groups/groups.service.spec.ts
|
|
- apps/api/src/tenant/tenant.service.ts
|
|
- apps/api/src/tenant/tenant.module.ts
|
|
- apps/api/src/user/admin-seed.service.ts
|
|
|
|
key-decisions:
|
|
- "Wächter prüft ausschließlich group.count === 0, nie das Fehlen der isDefault-Markierung — ein Mandant mit Gruppen ohne Markierung ist eine bewusste Admin-Entscheidung (D-13) und wird nie überschrieben"
|
|
- "Reparatur läuft als zweiter sequenzieller await-Schritt in AdminSeedService.onApplicationBootstrap, NICHT als eigener Hook in GroupsModule — GroupsModule ist Dependency von UserModule, seine Hooks liefen sonst vor der Tenant-Anlage"
|
|
- "P2002 aus dem partiellen Index Group_one_default_per_tenant wird in ensureDefaultGroup abgefangen und liefert null, propagiert nicht — der Index bleibt der eigentliche Durchsetzungspunkt"
|
|
|
|
requirements-completed: [260805-fok]
|
|
|
|
coverage:
|
|
- id: D1
|
|
description: "GroupsService.ensureDefaultGroup(tenantId) legt für einen Mandanten ohne jede Gruppe die Standardgruppe 'Alle Benutzer' an, mit Mitgliedern und Modul-Grants, race-sicher und idempotent"
|
|
requirement: "260805-fok"
|
|
verification:
|
|
- kind: unit
|
|
ref: "apps/api/src/groups/groups.service.spec.ts#ensureDefaultGroup()"
|
|
status: pass
|
|
human_judgment: false
|
|
- id: D2
|
|
description: "TenantService.create ruft ensureDefaultGroup auf; Fehler werden protokolliert, nicht propagiert"
|
|
requirement: "260805-fok"
|
|
verification:
|
|
- kind: unit
|
|
ref: "apps/api/src/tenant/tenant.service.spec.ts"
|
|
status: pass
|
|
human_judgment: false
|
|
- id: D3
|
|
description: "AdminSeedService führt die Standardgruppen-Anlage vor dem Super-Admin-Anlegen aus und repariert am Ende von onApplicationBootstrap alle Mandanten ohne Gruppe, unabhängig von den frühen Rückkehrpfaden"
|
|
requirement: "260805-fok"
|
|
verification:
|
|
- kind: unit
|
|
ref: "apps/api/src/user/admin-seed.service.spec.ts"
|
|
status: pass
|
|
human_judgment: false
|
|
- id: D4
|
|
description: "Kein Schemawechsel, keine neue Migration — 24 Migrationsverzeichnisse unverändert, schema.prisma unverändert"
|
|
verification:
|
|
- kind: other
|
|
ref: "test \"$(ls -1d apps/api/prisma/migrations/*/ | wc -l)\" -eq 24 && git diff --quiet -- apps/api/prisma/schema.prisma"
|
|
status: pass
|
|
human_judgment: false
|
|
|
|
duration: 6min
|
|
completed: 2026-08-05
|
|
status: complete
|
|
---
|
|
|
|
# Quick Task 260805-fok: Standardgruppe bei Mandanten-Anlage und Startup-Reparatur Summary
|
|
|
|
**GroupsService.ensureDefaultGroup(tenantId) verlegt die "jeder Mandant hat eine Standardgruppe"-Invariante von der einmaligen Migration in beide Mandanten-Entstehungspfade (TenantService.create, AdminSeedService) plus eine Startup-Reparatur über alle Bestandsmandanten.**
|
|
|
|
## Performance
|
|
|
|
- **Duration:** 6 min
|
|
- **Started:** 2026-08-05T11:25:00+02:00 (circa)
|
|
- **Completed:** 2026-08-05T11:30:49+02:00
|
|
- **Tasks:** 2
|
|
- **Files modified:** 7 (2 neu, 5 geändert)
|
|
|
|
## Accomplishments
|
|
- `GroupsService.ensureDefaultGroup(tenantId)`: legt für einen Mandanten ohne jede Gruppe "Alle Benutzer" (isDefault:true) an, nimmt alle Bestandsbenutzer als MANUAL-Mitglieder auf und erzeugt Grants für alle aktiven Module — derselbe Endzustand wie die drei Backfill-INSERTs der Migration `20260804130130_add_groups_and_module_grants`. Wächter prüft ausschließlich `group.count === 0` (D-13), fängt P2002 aus dem partiellen Index `Group_one_default_per_tenant` ab und liefert `null` statt zu werfen.
|
|
- `TenantService.create` ruft `ensureDefaultGroup` nach der Mandanten-Anlage auf; ein Fehler wird protokolliert, die Mandanten-Anlage schlägt trotzdem nicht fehl.
|
|
- `AdminSeedService.onApplicationBootstrap` läuft jetzt als zwei sequenzielle await-Schritte: `seedAdmin()` (bisheriger Rumpf, jetzt mit `ensureDefaultGroup` nach dem Tenant-Upsert und VOR `user.create`) und danach `ensureDefaultGroupsForAllTenants()` — die einmalige Reparatur über jeden Mandanten aus `prisma.tenant.findMany`, unabhängig von `seedAdmin()`s beiden frühen Rückkehrpfaden (fehlende ENV-Variablen, Admin existiert bereits — genau der Fall auf dem Testserver).
|
|
|
|
## Task Commits
|
|
|
|
Each task was committed atomically:
|
|
|
|
1. **Task 1: GroupsService.ensureDefaultGroup(tenantId) + Spec** - `9d1254c` (feat)
|
|
2. **Task 2: Beide Mandanten-Entstehungspfade verdrahten + Startup-Reparatur + Specs** - `0d7d8a5` (feat)
|
|
|
|
**Plan metadata:** wird vom Orchestrator committet (SUMMARY.md, STATE.md)
|
|
|
|
## Files Created/Modified
|
|
- `apps/api/src/groups/groups.service.ts` - neue Methode `ensureDefaultGroup(tenantId)` vor `addUserToDefaultGroup`
|
|
- `apps/api/src/groups/groups.service.spec.ts` - Fake erweitert (`group.count`, `tenantModuleActivation`, `moduleGrant.findMany/createMany`, `$transaction`-Callback-Form) plus voller `ensureDefaultGroup`-Testblock
|
|
- `apps/api/src/tenant/tenant.service.ts` - `GroupsService` injiziert, `create()` ruft `ensureDefaultGroup` in try/catch mit Logger auf
|
|
- `apps/api/src/tenant/tenant.module.ts` - `GroupsModule` importiert
|
|
- `apps/api/src/tenant/tenant.service.spec.ts` (neu) - beweist Aufruf und nicht-fatalen Fehlerpfad
|
|
- `apps/api/src/user/admin-seed.service.ts` - `seedAdmin()` extrahiert, `ensureDefaultGroup` nach dem Tenant-Upsert eingefügt, `ensureDefaultGroupsForAllTenants()` als abschließende Reparatur
|
|
- `apps/api/src/user/admin-seed.service.spec.ts` (neu) - Reihenfolge, Reparatur über alle Mandanten, Idempotenz über zwei Bootstrap-Läufe, Fehlerisolation je Mandant
|
|
|
|
## Decisions Made
|
|
- Wächter prüft ausschließlich `group.count === 0`, nie das Fehlen der `isDefault`-Markierung — D-13 erlaubt dem Admin ausdrücklich, die Markierung abzuhängen oder umzuhängen; die Reparatur darf diese Entscheidung nie überschreiben.
|
|
- Die Reparatur läuft als zweiter sequenzieller `await`-Schritt in `AdminSeedService.onApplicationBootstrap`, nicht als eigener Hook in `GroupsModule`: `GroupsModule` ist Dependency von `UserModule`, seine `onApplicationBootstrap`-Hooks liefen deshalb vor dem Admin-Seed und würden auf einer frischen Installation den noch nicht existierenden Default-Mandanten übergehen — dieselbe Falle, die `tender-scheduler.service.ts` für den DÖE-Poll-Config-Seed dokumentiert.
|
|
- `ensureDefaultGroup` nutzt eine interaktive Transaktion (`$transaction` mit async-Callback) statt der Array-Form, weil Schritt 2 (Mitgliedschaften) und Schritt 3 (Grants) die `id` der in Schritt 1 erzeugten Gruppe brauchen.
|
|
- P2002 aus `group.create` wird ausschließlich anhand `err.code === 'P2002'` abgefangen und in `null` übersetzt; jeder andere Fehler wird weitergeworfen. Der partielle Unique-Index `Group_one_default_per_tenant` (15-01) bleibt der eigentliche Durchsetzungspunkt.
|
|
|
|
## Deviations from Plan
|
|
|
|
None — Plan exakt wie geschrieben umgesetzt.
|
|
|
|
## Issues Encountered
|
|
|
|
Keine funktionalen Probleme. Beim ersten Testlauf für `admin-seed.service.spec.ts` waren zwei Testerwartungen selbst fehlerhaft formuliert (Reihenfolge-Filter schloss `user.findUnique` nicht aus; Aufrufzähler für den Neustart-Test berücksichtigte nicht, dass `seedAdmin()` beim ersten Lauf zusätzlich zur Reparatur einen eigenen `ensureDefaultGroup`-Aufruf auslöst) — beide Assertions korrigiert, die Implementierung war in beiden Fällen bereits korrekt.
|
|
|
|
## User Setup Required
|
|
|
|
None - keine externe Konfiguration nötig.
|
|
|
|
**Kein Deploy auf den Testserver aus diesem Plan heraus** — das übernimmt der Benutzer laut Vorgabe. Erwarteter Effekt dort nach dem nächsten Container-Start (unbeaufsichtigter `prisma migrate deploy` + API-Start): der Mandant „Default" bekommt die Gruppe „Alle Benutzer" mit `isDefault:true`, die vier vorhandenen Benutzer werden Mitglieder, und für jedes aktive Modul entsteht ein Gruppen-Grant.
|
|
|
|
## Next Phase Readiness
|
|
|
|
- Lokaler Stack geprüft: der einzige lokale Mandant hat bereits genau eine Gruppe (`docker exec tessera-ctl-db-1 psql` bestätigt `groups=1` für den Tenant `default`) — die Startup-Reparatur lässt ihn beim nächsten API-Neustart unangetastet, genau wie im behavior-Block gefordert.
|
|
- Live-Testserver (192.168.13.12, `tenants=1 users=4 groups=0`) profitiert vom nächsten Container-Neustart automatisch — kein manueller Eingriff nötig, keine neue Migration.
|
|
- Keine offenen Punkte.
|
|
|
|
---
|
|
*Phase: quick-260805-fok*
|
|
*Completed: 2026-08-05*
|
|
|
|
## Self-Check: PASSED
|
|
|
|
Alle 7 in `files_modified` gelisteten Dateien vorhanden, beide Task-Commits (`9d1254c`, `0d7d8a5`) im Git-Log gefunden.
|