Files
tessera-ctl/apps/api/src/groups/module-grants.controller.ts
T
schalli ecadf69e14 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
2026-08-05 09:33:51 +02:00

92 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
Body,
Controller,
Delete,
ForbiddenException,
Get,
Param,
Post,
Req,
UseGuards,
} from '@nestjs/common';
import { Role } from '@prisma/client';
import { Request } from 'express';
import { Roles } from '../auth/decorators/roles.decorator';
import { RolesGuard } from '../auth/guards/roles.guard';
import { CreateModuleGrantDto } from './dto/create-module-grant.dto';
import { ModuleGrantsService } from './module-grants.service';
/**
* REST-Controller für die Schreibseite der Modul-Freigaben (PERM-03).
*
* tenantId kommt ausschließlich aus dem JWT (req.tenantId ?? req.user?.tenantId),
* niemals aus Body/Params (T-03-04) — identisch zum Muster in
* GroupsController/ModuleRegistryController. Jede Route ist rollengeschützt.
*
* Statische Segmente stehen vor Parameter-Routen: `matrix` ist vor
* `users/:userId` deklariert. Dieses Projekt hat den Beschattungsfehler
* schon einmal gehabt und Unit-Tests fangen ihn nicht.
*/
@Controller('module-grants')
export class ModuleGrantsController {
constructor(private readonly moduleGrantsService: ModuleGrantsService) {}
private getTenantId(req: Request): string {
const tenantId = (req as any).tenantId ?? (req as any).user?.tenantId;
if (!tenantId) {
throw new ForbiddenException('No tenant context');
}
return tenantId;
}
/**
* GET /module-grants/matrix
* Module × Gruppen mit den bestehenden Gruppen-Grants (D-15).
*/
@Get('matrix')
@UseGuards(RolesGuard)
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
async matrix(@Req() req: Request) {
return this.moduleGrantsService.getMatrix(this.getTenantId(req));
}
/**
* GET /module-grants/users/:userId
* Liefert { groups, modules } (D-16). `groups` sind die tatsächlichen
* 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')
@UseGuards(RolesGuard)
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
async userAccess(@Param('userId') userId: string, @Req() req: Request) {
return this.moduleGrantsService.getUserAccess(this.getTenantId(req), userId);
}
/**
* POST /module-grants
* Legt einen Grant für eine Gruppe oder einen Benutzer an.
*/
@Post()
@UseGuards(RolesGuard)
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
async create(@Body() dto: CreateModuleGrantDto, @Req() req: Request) {
return this.moduleGrantsService.grant(this.getTenantId(req), dto);
}
/**
* DELETE /module-grants
* Entzieht einen Grant. Ziel im Body, weil die Kombination aus drei
* Feldern besteht und nicht sinnvoll in einen Pfadparameter passt.
*/
@Delete()
@UseGuards(RolesGuard)
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
async remove(@Body() dto: CreateModuleGrantDto, @Req() req: Request) {
await this.moduleGrantsService.revoke(this.getTenantId(req), dto);
return { success: true };
}
}