feat(260805-d0r): getUserAccess returns groups from GroupMembership (D-16)
- New groupMembership.findMany query, tenant-scoped via group.tenantId
(GroupMembership has no own tenantId column)
- Response shape changes from an array to { groups, modules }; modules
entries stay field-identical to before
- Group without any module grant now stays visible, closing the
reproduced defect
This commit is contained in:
@@ -52,8 +52,11 @@ export class ModuleGrantsController {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /module-grants/users/:userId
|
* GET /module-grants/users/:userId
|
||||||
* Je aktivem Modul: über welche Gruppen der Benutzer erbt, und ob ein
|
* Liefert { groups, modules } (D-16). `groups` sind die tatsächlichen
|
||||||
* Direkt-Grant besteht (D-16).
|
* Gruppenmitgliedschaften des Benutzers (aus GroupMembership, unabhängig
|
||||||
|
* von Modul-Freigaben). `modules` ist je aktivem Modul, über welche
|
||||||
|
* Gruppen der Benutzer das Modul erbt, und ob zusätzlich ein
|
||||||
|
* Direkt-Grant besteht.
|
||||||
*/
|
*/
|
||||||
@Get('users/:userId')
|
@Get('users/:userId')
|
||||||
@UseGuards(RolesGuard)
|
@UseGuards(RolesGuard)
|
||||||
|
|||||||
@@ -203,15 +203,20 @@ export class ModuleGrantsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Datenlieferung für das Benutzer-Detail (D-16): je aktivem Modul die
|
* Datenlieferung für das Benutzer-Detail (D-16): zwei unabhängige
|
||||||
* Namen der Gruppen, über die der Benutzer das Modul erbt, und ein
|
* Antworten in einem Objekt.
|
||||||
* Kennzeichen für einen bestehenden Direkt-Grant. Ohne diese Anzeige ist
|
*
|
||||||
* im Benutzer-Detail nicht erkennbar, warum jemand Zugriff hat.
|
* `groups` stammt aus GroupMembership und ist bewusst unabhängig von
|
||||||
|
* ModuleGrant — eine Mitgliedschaft ohne jede Modul-Freigabe bleibt
|
||||||
|
* dadurch sichtbar. `modules` beantwortet je aktivem Modul die andere
|
||||||
|
* Frage (welche Gruppe gewährt dieses Modul, und besteht zusätzlich ein
|
||||||
|
* Direkt-Grant) und behält dafür je Eintrag exakt die Form
|
||||||
|
* { module, viaGroups, direct }.
|
||||||
*/
|
*/
|
||||||
async getUserAccess(tenantId: string, userId: string) {
|
async getUserAccess(tenantId: string, userId: string) {
|
||||||
await this.assertTargetBelongsToTenant(tenantId, undefined, userId);
|
await this.assertTargetBelongsToTenant(tenantId, undefined, userId);
|
||||||
|
|
||||||
const [activations, groupGrants, directGrants] = await Promise.all([
|
const [activations, groupGrants, directGrants, memberships] = await Promise.all([
|
||||||
this.prisma.tenantModuleActivation.findMany({
|
this.prisma.tenantModuleActivation.findMany({
|
||||||
where: { tenantId, isActive: true },
|
where: { tenantId, isActive: true },
|
||||||
include: { module: true },
|
include: { module: true },
|
||||||
@@ -224,6 +229,16 @@ export class ModuleGrantsService {
|
|||||||
where: { tenantId, userId },
|
where: { tenantId, userId },
|
||||||
select: { moduleId: true },
|
select: { moduleId: true },
|
||||||
}),
|
}),
|
||||||
|
// Kein forTenant hier — dieselbe Begründung wie bei den drei
|
||||||
|
// Abfragen oben: die Datenbankrolle umgeht RLS ohnehin (siehe
|
||||||
|
// Migration 20260804130918_groups_rls_policies), der `where`-Filter
|
||||||
|
// ist wie im Rest dieser Methode und in GroupsService der primäre
|
||||||
|
// Schutz. GroupMembership trägt keine eigene tenantId-Spalte, daher
|
||||||
|
// läuft der Mandantenfilter über die Relation `group: { tenantId }`.
|
||||||
|
this.prisma.groupMembership.findMany({
|
||||||
|
where: { userId, group: { tenantId } },
|
||||||
|
include: { group: { select: { id: true, name: true } } },
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const directModuleIds = new Set(directGrants.map((g: any) => g.moduleId as string));
|
const directModuleIds = new Set(directGrants.map((g: any) => g.moduleId as string));
|
||||||
@@ -242,10 +257,22 @@ export class ModuleGrantsService {
|
|||||||
a.category.localeCompare(b.category) || a.name.localeCompare(b.name),
|
a.category.localeCompare(b.category) || a.name.localeCompare(b.name),
|
||||||
);
|
);
|
||||||
|
|
||||||
return modules.map((module: any) => ({
|
const groups = (memberships as any[])
|
||||||
|
.filter((m) => m.group)
|
||||||
|
.map((m) => ({
|
||||||
|
id: m.group.id as string,
|
||||||
|
name: m.group.name as string,
|
||||||
|
source: m.source as string,
|
||||||
|
}))
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
|
||||||
|
return {
|
||||||
|
groups,
|
||||||
|
modules: modules.map((module: any) => ({
|
||||||
module,
|
module,
|
||||||
viaGroups: groupNamesByModule.get(module.id) ?? [],
|
viaGroups: groupNamesByModule.get(module.id) ?? [],
|
||||||
direct: directModuleIds.has(module.id),
|
direct: directModuleIds.has(module.id),
|
||||||
}));
|
})),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user