Files
tessera-ctl/.planning/quick/260805-d0r-benutzer-detail-zeigt-gruppenmitgliedsch/260805-d0r-SUMMARY.md
T
schalli c2e9f67a7d
Tessera CI/CD / Lint & Type Check (push) Successful in 50s
Tessera CI/CD / Tests (push) Successful in 52s
Tessera CI/CD / Build & Publish Images (push) Successful in 4m5s
docs(quick-260805-d0r): Gruppenmitgliedschaften im Benutzer-Detail
2026-08-05 09:44:11 +02:00

8.2 KiB

phase, plan, subsystem, tags, status, dependency-graph, tech-stack, key-files, decisions, metrics, actuals
phase plan subsystem tags status dependency-graph tech-stack key-files decisions metrics actuals
quick-260805-d0r 01 admin-users
module-grants
groups
group-membership
i18n-reuse
complete
requires provides affects
GroupMembership (Prisma-Modell, Phase 15-01)
ModuleGrantsService.getUserAccess (Phase 15-07)
GET /module-grants/users/:userId liefert { groups, modules } statt eines Arrays
UserAccessModal-Gruppenmitgliedschafts-Chips aus der tatsaechlichen Mitgliedschaft
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
added patterns
Herkunfts-Badge-Markup 1:1 aus GroupMembersModal.tsx uebernommen statt einer zweiten Variante
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
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.
duration completed
ca. 55 min 2026-08-05
tokens tasks commits
5944 2 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