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:
@@ -203,15 +203,20 @@ export class ModuleGrantsService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Datenlieferung für das Benutzer-Detail (D-16): je aktivem Modul die
|
||||
* Namen der Gruppen, über die der Benutzer das Modul erbt, und ein
|
||||
* Kennzeichen für einen bestehenden Direkt-Grant. Ohne diese Anzeige ist
|
||||
* im Benutzer-Detail nicht erkennbar, warum jemand Zugriff hat.
|
||||
* Datenlieferung für das Benutzer-Detail (D-16): zwei unabhängige
|
||||
* Antworten in einem Objekt.
|
||||
*
|
||||
* `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) {
|
||||
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({
|
||||
where: { tenantId, isActive: true },
|
||||
include: { module: true },
|
||||
@@ -224,6 +229,16 @@ export class ModuleGrantsService {
|
||||
where: { tenantId, userId },
|
||||
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));
|
||||
@@ -242,10 +257,22 @@ export class ModuleGrantsService {
|
||||
a.category.localeCompare(b.category) || a.name.localeCompare(b.name),
|
||||
);
|
||||
|
||||
return modules.map((module: any) => ({
|
||||
module,
|
||||
viaGroups: groupNamesByModule.get(module.id) ?? [],
|
||||
direct: directModuleIds.has(module.id),
|
||||
}));
|
||||
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,
|
||||
viaGroups: groupNamesByModule.get(module.id) ?? [],
|
||||
direct: directModuleIds.has(module.id),
|
||||
})),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user