| 15-modul-berechtigungen-gruppen-user-grants |
02 |
auth |
| nestjs |
| prisma |
| groups |
| rbac |
| vitest |
|
| phase |
plan |
provides |
| 15-modul-berechtigungen-gruppen-user-grants |
01 |
Group/GroupMembership/ModuleGrant-Schema mit DB-erzwungener Entweder-oder- und Ein-Default-Invariante, RLS auf allen drei Tabellen |
|
|
| GroupsService/GroupsController — vollständiges CRUD für Gruppen und Mitgliedschaften eines Mandanten (PERM-01) |
| GroupsService.getImpact — Zahlenmaterial ({ memberCount, grantCount }) für den Löschdialog (D-17), konsumiert von Plan 15-06 |
| GroupsService.addUserToDefaultGroup — einziger Codepfad für die automatische Standardgruppen-Mitgliedschaft, exportiert für UserModule |
| UserService.create erweitert um die Standardgruppen-Zuordnung (D-11/D-12) — erbt an LdapService.upsertMappedUser/importUsersByDn ohne eigene Kopie der Regel |
|
|
| tokens |
tasks |
commits |
| 10100 |
2 |
2 |
|
| added |
patterns |
|
|
| Ownership-Check-Muster aus DashboardService.removeWidget auf Gruppen-IDs übertragen: private findOwned(tenantId, id) vor jeder Mutation/jedem Lookup, NotFoundException statt eines Fremdmandanten-Treffers |
| P2002/P2025-Fehlercode-Übersetzung im Service (ConflictException/NotFoundException) statt unbehandelter Prisma-Fehler bis zum Controller durchzureichen |
| Hand-rolled In-Memory-Prisma-Fake im Stil von tender-saved-search.service.spec.ts statt eines Mocking-Frameworks — auch für $transaction (Promise.all über bereits erstellte Promises) |
|
|
| created |
modified |
| apps/api/src/groups/groups.module.ts |
| apps/api/src/groups/groups.service.ts |
| apps/api/src/groups/groups.controller.ts |
| apps/api/src/groups/groups.service.spec.ts |
| apps/api/src/groups/dto/create-group.dto.ts |
| apps/api/src/groups/dto/update-group.dto.ts |
| apps/api/src/groups/dto/add-group-members.dto.ts |
| apps/api/src/user/user.service.spec.ts |
|
| apps/api/src/app.module.ts |
| apps/api/src/user/user.service.ts |
| apps/api/src/user/user.module.ts |
|
|
| isDefault:true läuft in this.prisma.$transaction([updateMany, update]) statt zwei sequenziellen Calls — der partielle Unique-Index aus 15-01 bleibt das Sicherheitsnetz, die Transaktion der normale Pfad (D-13, wie im Plan vorgegeben) |
| remove() fängt zusätzlich P2025 (Record-not-found) ab und übersetzt es in NotFoundException — nicht explizit im Plan-Action-Text benannt, aber notwendig für die im must_haves-Block geforderte Concurrency-Eigenschaft (zwei DELETE auf dieselbe ID erzeugen kein HTTP 500) |
| GroupsService.listMembers als zusätzliche Methode ergänzt (nicht im <behavior>-Block des Plans einzeln aufgeführt, aber in den Artefakten/Routen gefordert: GET /groups/:id/members) — Rule 2, notwendig für die vollständige CRUD-Oberfläche |
|
|
| id |
description |
requirement |
verification |
human_judgment |
| D1 |
GroupsService/GroupsController: vollständiges CRUD für Gruppen (create/update/remove/list), Mitgliederverwaltung (addMembers/removeMember/listMembers) und Löschauswirkung (getImpact), jede Lookup-Query tenantId-gescoped |
PERM-01 |
| kind |
ref |
status |
| unit |
apps/api/src/groups/groups.service.spec.ts (19 Tests, deckt jeden <behavior>-Fall inkl. adjacency/empty/encoding/idempotency) |
pass |
|
| kind |
ref |
status |
| e2e |
curl gegen laufende lokale API: POST /groups mit vergebenem Namen -> 409; GET /groups/:id/impact -> {memberCount,grantCount}; DELETE /groups/:id einer echten Fremdmandanten-Gruppe -> 404, Gruppe unangetastet in der DB |
pass |
|
|
false |
|
| id |
description |
requirement |
verification |
human_judgment |
| D2 |
addUserToDefaultGroup + UserService.create-Erweiterung: jeder neu angelegte Benutzer wird automatisch Mitglied der markierten Standardgruppe seines Mandanten, an genau einer Codestelle |
PERM-06 |
| kind |
ref |
status |
| unit |
apps/api/src/groups/groups.service.spec.ts (addUserToDefaultGroup-Fälle) + apps/api/src/user/user.service.spec.ts (4 Tests) |
pass |
|
| kind |
ref |
status |
| e2e |
curl gegen laufende lokale API: Gruppe als Standard markiert, POST /users angelegt, GET /groups/:defaultGroupId/members zeigt den neuen Benutzer direkt danach |
pass |
|
|
false |
|
| id |
description |
requirement |
verification |
human_judgment |
| D3 |
Cascade-Löschung: DELETE /groups/:id entfernt GroupMembership-Zeilen ohne anwendungsseitiges Aufräumen (onDelete:Cascade aus 15-01) |
PERM-01 |
| kind |
ref |
status |
| e2e |
curl + psql gegen laufende lokale API: Gruppe mit 2 Mitgliedern gelöscht, anschließend SELECT count(*) FROM GroupMembership WHERE groupId=... liefert 0 |
pass |
|
|
false |
|
|
11min |
2026-08-04 |
complete |