import { Body, Controller, Delete, ForbiddenException, Get, Param, Patch, 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 { AddGroupMembersDto } from './dto/add-group-members.dto'; import { CreateGroupDto } from './dto/create-group.dto'; import { UpdateGroupDto } from './dto/update-group.dto'; import { GroupsService } from './groups.service'; /** * REST-Controller für die Gruppenverwaltung (PERM-01, D-14). * * tenantId kommt ausschließlich aus dem JWT (req.tenantId ?? req.user?.tenantId), * niemals aus Body/Params (T-03-04). Jede Route ist rollengeschützt — * identisch zu den Modul-Aktivierungsrouten in ModuleRegistryController. */ @Controller('groups') export class GroupsController { constructor(private readonly groupsService: GroupsService) {} 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 /groups * Liste aller Gruppen des Mandanten, alphabetisch, mit Mitgliederzahl. */ @Get() @UseGuards(RolesGuard) @Roles(Role.ADMIN, Role.SUPER_ADMIN) async list(@Req() req: Request) { return this.groupsService.listForTenant(this.getTenantId(req)); } /** * POST /groups * Legt eine neue Gruppe im Mandanten an. */ @Post() @UseGuards(RolesGuard) @Roles(Role.ADMIN, Role.SUPER_ADMIN) async create(@Body() dto: CreateGroupDto, @Req() req: Request) { return this.groupsService.create(this.getTenantId(req), dto); } /** * PATCH /groups/:id * Umbenennen, Standardmarkierung setzen/entfernen, AD-Bindung setzen/lösen. */ @Patch(':id') @UseGuards(RolesGuard) @Roles(Role.ADMIN, Role.SUPER_ADMIN) async update( @Param('id') id: string, @Body() dto: UpdateGroupDto, @Req() req: Request, ) { return this.groupsService.update(this.getTenantId(req), id, dto); } /** * DELETE /groups/:id * Löscht die Gruppe; Mitgliedschaften und Grants folgen per Cascade (D-17). */ @Delete(':id') @UseGuards(RolesGuard) @Roles(Role.ADMIN, Role.SUPER_ADMIN) async remove(@Param('id') id: string, @Req() req: Request) { return this.groupsService.remove(this.getTenantId(req), id); } /** * GET /groups/:id/impact * Zahlenmaterial für den Löschdialog (D-17): { memberCount, grantCount }. */ @Get(':id/impact') @UseGuards(RolesGuard) @Roles(Role.ADMIN, Role.SUPER_ADMIN) async impact(@Param('id') id: string, @Req() req: Request) { return this.groupsService.getImpact(this.getTenantId(req), id); } /** * GET /groups/:id/members * Mitgliederliste inkl. Kern-Benutzerdaten. */ @Get(':id/members') @UseGuards(RolesGuard) @Roles(Role.ADMIN, Role.SUPER_ADMIN) async members(@Param('id') id: string, @Req() req: Request) { return this.groupsService.listMembers(this.getTenantId(req), id); } /** * POST /groups/:id/members * Fügt Mitglieder manuell hinzu (source: MANUAL). */ @Post(':id/members') @UseGuards(RolesGuard) @Roles(Role.ADMIN, Role.SUPER_ADMIN) async addMembers( @Param('id') id: string, @Body() dto: AddGroupMembersDto, @Req() req: Request, ) { return this.groupsService.addMembers(this.getTenantId(req), id, dto.userIds); } /** * DELETE /groups/:id/members/:userId * Entfernt ein manuell hinzugefügtes Mitglied (LDAP-Mitgliedschaften * bleiben unberührt, D-19). */ @Delete(':id/members/:userId') @UseGuards(RolesGuard) @Roles(Role.ADMIN, Role.SUPER_ADMIN) async removeMember( @Param('id') id: string, @Param('userId') userId: string, @Req() req: Request, ) { await this.groupsService.removeMember(this.getTenantId(req), id, userId); return { success: true }; } }