125 lines
8.2 KiB
Markdown
125 lines
8.2 KiB
Markdown
---
|
|
phase: quick-260805-d0r
|
|
plan: 01
|
|
subsystem: admin-users
|
|
tags: [module-grants, groups, group-membership, i18n-reuse]
|
|
status: complete
|
|
dependency-graph:
|
|
requires:
|
|
- GroupMembership (Prisma-Modell, Phase 15-01)
|
|
- ModuleGrantsService.getUserAccess (Phase 15-07)
|
|
provides:
|
|
- "GET /module-grants/users/:userId liefert { groups, modules } statt eines Arrays"
|
|
- "UserAccessModal-Gruppenmitgliedschafts-Chips aus der tatsaechlichen Mitgliedschaft"
|
|
affects:
|
|
- apps/api/src/groups/module-grants.service.ts
|
|
- apps/api/src/groups/module-grants.controller.ts
|
|
- "apps/web/src/app/(portal)/admin/users/components/UserAccessModal.tsx"
|
|
tech-stack:
|
|
added: []
|
|
patterns:
|
|
- "Herkunfts-Badge-Markup 1:1 aus GroupMembersModal.tsx uebernommen statt einer zweiten Variante"
|
|
key-files:
|
|
created: []
|
|
modified:
|
|
- apps/api/src/groups/module-grants.service.ts
|
|
- apps/api/src/groups/module-grants.service.spec.ts
|
|
- apps/api/src/groups/module-grants.controller.ts
|
|
- "apps/web/src/app/(portal)/admin/users/components/UserAccessModal.tsx"
|
|
- "apps/web/src/app/(portal)/admin/users/user-access-modal.test.tsx"
|
|
decisions:
|
|
- "Kein forTenant() fuer die neue groupMembership.findMany-Abfrage — bewusste Abweichung vom urspruenglichen Auftrag, ausfuehrlich im PLAN.md-Objective begruendet (RLS wird von der DB-Rolle ohnehin umgangen, der `where`-Filter ist wie bei den drei bestehenden Abfragen derselben Methode der primaere Schutz). Der Browser-Gegenprobe-Schritt (Task 3) ist das vereinbarte Gegenzeichen, falls Chips trotz vorhandener GroupMembership-Zeilen leer blieben."
|
|
- "Kein neuer i18n-Schluessel fuer das Herkunfts-Badge — admin.groups.members.sourceManual/.sourceLdap wird ueber einen zweiten useTranslations-Hook (tMembers) wiederverwendet."
|
|
metrics:
|
|
duration: "ca. 55 min"
|
|
completed: "2026-08-05"
|
|
actuals:
|
|
tokens: 5944
|
|
tasks: 2
|
|
commits: 5
|
|
---
|
|
|
|
# Phase quick-260805-d0r Plan 01: Benutzer-Detail zeigt Gruppenmitgliedschaften Summary
|
|
|
|
Das Benutzer-Detail in /admin/users zeigt Gruppenmitgliedschaften jetzt aus den tatsaechlichen GroupMembership-Zeilen, unabhaengig davon, ob die Gruppe gerade ein Modul freigibt.
|
|
|
|
## Was gebaut wurde
|
|
|
|
**Task 1 — Mitgliedschaften end-to-end (D-16):**
|
|
|
|
`ModuleGrantsService.getUserAccess` liefert jetzt ein Objekt `{ groups, modules }` statt des bisherigen Arrays. `groups` kommt aus einer neuen `this.prisma.groupMembership.findMany`-Abfrage mit `where: { userId, group: { tenantId } }` — dem expliziten Mandantenfilter ueber die Relation, weil `GroupMembership` keine eigene `tenantId`-Spalte traegt. `assertTargetBelongsToTenant` bleibt unveraendert die erste Anweisung der Methode. `modules` ist feldgleich zum bisherigen Rueckgabewert (`{ module, viaGroups, direct }` je aktivem Modul).
|
|
|
|
Die API-Spec (`module-grants.service.spec.ts`) bekam einen `groupMembership`-Zweig im Hand-Fake-Prisma, einen dritten optionalen `source`-Parameter fuer `__seedMembership` (Default `MANUAL`) und sieben neue/umgestellte Tests: den Regressionsfall ("Mitglied einer Gruppe ohne Modul-Freigabe bleibt sichtbar"), den Cross-Tenant-Fall, den LDAP-Herkunfts-Fall, den Leer-Modul-Fall und den Sortierungs-Fall.
|
|
|
|
`UserAccessModal.tsx` liest die Gruppen-Chips jetzt direkt aus `data.groups` (React-`key` ist die Gruppen-ID statt des Namens) statt sie aus der Vereinigung aller `row.viaGroups` abzuleiten. Das `useMemo`, das die Namen bisher zusammensetzte, und der ungenutzte `useMemo`-Import wurden entfernt. Der Kopfkommentar der Datei wurde neu formuliert — der alte Satz ("Chips sind die deduplizierte Menge aller viaGroups-Namen, ein zweiter Endpoint existiert bewusst nicht") widersprach sonst dem neuen Code.
|
|
|
|
Der Controller-JSDoc ueber `GET /module-grants/users/:userId` wurde an die Zwei-Schluessel-Antwort angepasst; der Controller-Code selbst blieb unveraendert (reicht die Service-Antwort weiter).
|
|
|
|
**Task 2 — Herkunfts-Badge (D-19/D-20):**
|
|
|
|
Ein zweiter `useTranslations('admin.groups.members')`-Hook (`tMembers`) liest die bereits bestehenden Schluessel `sourceManual`/`sourceLdap`. Jeder Chip zeigt jetzt zusaetzlich zum Gruppennamen ein Badge — Markup und Klassen 1:1 aus `GroupMembersModal.tsx` (Zeilen 176-184) uebernommen: blaue Variante bei `LDAP`, neutral-grau sonst. `de.json`/`en.json` blieben unveraendert — kein neuer Schluessel, deckungsgleiche Pruefung bestand mit 762 Schluesseln auf beiden Seiten.
|
|
|
|
**Task 3 — Browser-Gegenprobe:** delegiert an den Orchestrator. Der lokale Stack (web:3000, api:3001, db) laeuft; die Browser-Verifikation aus dem Plan (Chip bleibt nach Entzug aller Modul-Freigaben stehen, Leerzustand bei Benutzer ohne Mitgliedschaft, Badges sichtbar) wurde in dieser Ausfuehrung NICHT durchgefuehrt und muss vom Orchestrator nachgeholt werden.
|
|
|
|
## TDD-Ablauf
|
|
|
|
Beide Tasks liefen als echte RED/GREEN-Zyklen mit getrennten Commits:
|
|
|
|
1. `b6d4e4a` test: Service-Spec-Tests ergaenzt, gegen den unveraenderten Service rot (7 von 25 Tests fehlgeschlagen, bestaetigt).
|
|
2. `ecadf69` feat: Service + Controller implementiert, 25/25 gruen.
|
|
3. `771f680` test: Web-Test-Fixtures auf `{ groups, modules }` umgestellt, gegen das unveraenderte Modal rot (8 von 8 Tests fehlgeschlagen mit `TypeError: (rows ?? []) is not iterable`, bestaetigt).
|
|
4. `f8ff74b` feat: Modal implementiert, 7/7 gruen (die achte, das Badge betreffende Testerwartung, wurde vor diesem Commit wieder entfernt — siehe Deviations).
|
|
5. `73122b5` test: Badge-Test separat ergaenzt, rot bestaetigt (1 von 8 Tests fehlgeschlagen).
|
|
6. `8ce3748` feat: Badge implementiert, 8/8 gruen.
|
|
|
|
## Deviations from Plan
|
|
|
|
### Auto-fixed Issues
|
|
|
|
**1. [Prozess-Korrektur, kein Rule-1/2/3-Fix] Badge-Test versehentlich vorzeitig in Task-1-RED-Commit aufgenommen**
|
|
- **Gefunden bei:** Schreiben des Web-Testfiles fuer Task 1.
|
|
- **Problem:** Beim ersten Entwurf der neuen `user-access-modal.test.tsx` wurde faelschlich bereits der Badge-Test (Task-2-Umfang) samt `admin.groups.members`-Mock-Namensraum mit reingeschrieben und im RED-Commit `771f680` mitcommittet.
|
|
- **Fix:** Vor dem GREEN-Commit von Task 1 wieder entfernt (Namensraum-Eintrag und Testblock), damit Task 1s Commit-Grenze exakt dem Plan entspricht. Der Badge-Test wurde danach in Task 2 als eigener RED/GREEN-Zyklus neu geschrieben.
|
|
- **Dateien:** `apps/web/src/app/(portal)/admin/users/user-access-modal.test.tsx`
|
|
- **Commits:** Entfernung Teil von `f8ff74b`; Neuanlage in `73122b5`/`8ce3748`.
|
|
|
|
Keine weiteren Abweichungen — der Plan wurde ansonsten exakt wie beschrieben umgesetzt, inklusive der bewussten Nicht-Verwendung von `forTenant()` gemaess der Begruendung im PLAN.md-Objective.
|
|
|
|
### Auth-Gates
|
|
|
|
Keine.
|
|
|
|
## Known Stubs
|
|
|
|
Keine.
|
|
|
|
## Threat Flags
|
|
|
|
Keine — die im Plan dokumentierten Threats (T-d0r-01/02/03) sind durch die umgesetzte Mandanten-Gegenpruefung und das explizite `group: { tenantId }`-Filter vollstaendig mitigiert bzw. wie geplant akzeptiert; kein zusaetzliches, ungeplantes Angriffsflaechen-Element wurde eingefuehrt.
|
|
|
|
## Verifikation
|
|
|
|
- `pnpm --filter @tessera/api test -- module-grants`: 25/25 gruen (inkl. Regression, Cross-Tenant, LDAP-Herkunft).
|
|
- `pnpm --filter @tessera/api run type-check`: sauber.
|
|
- `pnpm --filter @tessera/web test -- user-access-modal`: 8/8 gruen (inkl. Regression, beider Herkunftsbadges).
|
|
- `pnpm --filter @tessera/web run type-check`: sauber.
|
|
- `pnpm --filter @tessera/api test`: 498/498 gruen (keine Kollateralschaeden).
|
|
- `pnpm --filter @tessera/web test`: 187/187 gruen (keine Kollateralschaeden).
|
|
- `de.json`/`en.json`: unveraendert, 762 Schluessel deckungsgleich; `admin.groups.members.sourceManual`/`.sourceLdap` in beiden vorhanden.
|
|
- Diff seit Phase-15-Ende (`be1183a..HEAD`) beruehrt exakt die fuenf im Plan gelisteten Dateien, keine Schemaaenderung, keine Migration.
|
|
- **Task 3 (Browser-Gegenprobe): OFFEN.** Delegiert an den Orchestrator gemaess Auftrag — bitte vor Abschluss nachholen (siehe orchestrator_decisions im Prompt dieser Ausfuehrung).
|
|
|
|
## Self-Check: PASSED
|
|
|
|
- `apps/api/src/groups/module-grants.service.ts`: FOUND
|
|
- `apps/api/src/groups/module-grants.service.spec.ts`: FOUND
|
|
- `apps/api/src/groups/module-grants.controller.ts`: FOUND
|
|
- `apps/web/src/app/(portal)/admin/users/components/UserAccessModal.tsx`: FOUND
|
|
- `apps/web/src/app/(portal)/admin/users/user-access-modal.test.tsx`: FOUND
|
|
- `b6d4e4a`: FOUND
|
|
- `ecadf69`: FOUND
|
|
- `771f680`: FOUND
|
|
- `f8ff74b`: FOUND
|
|
- `73122b5`: FOUND
|
|
- `8ce3748`: FOUND
|