feat(15-03): ModuleGrantsController und Einbindung in GroupsModule
- GET /module-grants/matrix, GET /module-grants/users/:userId, POST /module-grants, DELETE /module-grants — alle vier rollengeschützt (RolesGuard + Roles ADMIN/SUPER_ADMIN) - matrix vor users/:userId deklariert (Beschattungsfehler-Vermeidung) - GroupsModule bindet ModuleGrantsController/-Service ein; kein Import von ModuleRegistryModule nötig, da der Service nur PrismaService braucht
This commit is contained in:
@@ -1,17 +1,24 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { GroupsController } from './groups.controller';
|
||||
import { GroupsService } from './groups.service';
|
||||
import { ModuleGrantsController } from './module-grants.controller';
|
||||
import { ModuleGrantsService } from './module-grants.service';
|
||||
|
||||
/**
|
||||
* NestJS-Modul für die Gruppenverwaltung (PERM-01, PERM-06).
|
||||
* NestJS-Modul für die Gruppenverwaltung (PERM-01, PERM-06) und die
|
||||
* Schreibseite der Modul-Freigaben (PERM-03, Plan 15-03).
|
||||
*
|
||||
* Exportiert GroupsService, damit UserModule (Plan 15-02 Task 2) darüber
|
||||
* addUserToDefaultGroup aufrufen kann — der einzige Codepfad für die
|
||||
* automatische Standardgruppen-Mitgliedschaft (D-11/D-12).
|
||||
*
|
||||
* ModuleGrantsService injiziert ausschließlich PrismaService, deshalb ist
|
||||
* kein Import von ModuleRegistryModule nötig — anders als
|
||||
* ModuleAccessService (15-01/15-05), das hier nicht verwendet wird.
|
||||
*/
|
||||
@Module({
|
||||
controllers: [GroupsController],
|
||||
providers: [GroupsService],
|
||||
exports: [GroupsService],
|
||||
controllers: [GroupsController, ModuleGrantsController],
|
||||
providers: [GroupsService, ModuleGrantsService],
|
||||
exports: [GroupsService, ModuleGrantsService],
|
||||
})
|
||||
export class GroupsModule {}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
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
|
||||
* Je aktivem Modul: über welche Gruppen der Benutzer erbt, und ob ein
|
||||
* Direkt-Grant besteht (D-16).
|
||||
*/
|
||||
@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 };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user