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 }; } }