import { Controller, ForbiddenException, Get, Param, Post, Req, UseGuards, } from '@nestjs/common'; import { Role } from '@prisma/client'; import type { AuthenticatedRequest } from '../auth/types/auth-user'; import { Roles } from '../auth/decorators/roles.decorator'; import { RolesGuard } from '../auth/guards/roles.guard'; import { ModuleAccessService } from './module-access.service'; import { ModuleRegistryService } from './module-registry.service'; /** * REST controller for the module registry. * * - GET /modules — list all registered modules (any authenticated user) * - GET /modules/active — list modules accessible to the requesting user * (Aktivierung UND Grant/Rolle, via ModuleAccessService — D-01: dieselbe * Auflösung wie ModuleGuard) * - POST /modules/:moduleId/activate — activate a module (ADMIN/SUPER_ADMIN) * - POST /modules/:moduleId/deactivate — deactivate a module (ADMIN/SUPER_ADMIN) * * All routes require JWT auth (global JwtAuthGuard). * Activation/deactivation require ADMIN or SUPER_ADMIN role (T-03-01). */ @Controller('modules') export class ModuleRegistryController { constructor( private readonly moduleRegistryService: ModuleRegistryService, private readonly moduleAccessService: ModuleAccessService, ) {} /** * GET /modules * Returns all registered modules in the system. * Available to any authenticated user (T-03-03: module catalog is non-sensitive). */ @Get() async findAll() { return this.moduleRegistryService.findAll(); } /** * GET /modules/active * Returns modules accessible to the requesting user (D-01): the same * ModuleAccessService.getAccessibleModuleIds resolution ModuleGuard * uses, not just tenant-wide activation. findActiveForTenant on * ModuleRegistryService stays unchanged for Plan 15-03's marketplace * catalog (mandantenweite Sicht, kein Benutzerfilter). */ @Get('active') async findActive(@Req() req: AuthenticatedRequest) { const tenantId = req.tenantId ?? req.user?.tenantId; const userId = req.user?.id; const role = req.user?.role; if (!tenantId || !userId || !role) { throw new ForbiddenException('No user context'); } return this.moduleAccessService.findAccessibleModules(tenantId, userId, role); } /** * GET /modules/catalog * Liefert je registriertem Modul den vollständigen Datensatz plus zwei * Flags: isActiveForTenant (TenantModuleActivation) und hasAccess * (ModuleAccessService.getAccessibleModuleIds) — beide in einer Antwort * (D-08), damit der Marketplace für einen USER zwischen "nicht * aktiviert" und "aktiviert, aber nicht freigegeben" unterscheiden kann, * ohne dass eine Karte kurzzeitig ohne Sperrhinweis klickbar erscheint. * Erreichbar für jeden authentifizierten Benutzer, wie GET /modules. */ @Get('catalog') async findCatalog(@Req() req: AuthenticatedRequest) { const tenantId = req.tenantId ?? req.user?.tenantId; const userId = req.user?.id; const role = req.user?.role; if (!tenantId || !userId || !role) { throw new ForbiddenException('No user context'); } const [modules, flags] = await Promise.all([ this.moduleRegistryService.findAll(), this.moduleAccessService.getCatalogFlags(tenantId, userId, role), ]); return modules.map((module) => ({ ...module, isActiveForTenant: flags.get(module.id)?.isActiveForTenant ?? false, hasAccess: flags.get(module.id)?.hasAccess ?? false, })); } /** * POST /modules/:moduleId/activate * Activates a module for the current tenant. * T-03-01: Only ADMIN or SUPER_ADMIN can activate modules. * T-03-02: moduleId validated as UUID by Prisma parameterized queries. */ @Post(':moduleId/activate') @UseGuards(RolesGuard) @Roles(Role.ADMIN, Role.SUPER_ADMIN) async activate( @Param('moduleId') moduleId: string, @Req() req: AuthenticatedRequest, ) { const tenantId = req.tenantId ?? req.user?.tenantId; if (!tenantId) { throw new ForbiddenException('No tenant context'); } return this.moduleRegistryService.activateForTenant(tenantId, moduleId); } /** * POST /modules/:moduleId/deactivate * Deactivates a module for the current tenant (soft-delete). * T-03-01: Only ADMIN or SUPER_ADMIN can deactivate modules. */ @Post(':moduleId/deactivate') @UseGuards(RolesGuard) @Roles(Role.ADMIN, Role.SUPER_ADMIN) async deactivate( @Param('moduleId') moduleId: string, @Req() req: AuthenticatedRequest, ) { const tenantId = req.tenantId ?? req.user?.tenantId; if (!tenantId) { throw new ForbiddenException('No tenant context'); } return this.moduleRegistryService.deactivateForTenant(tenantId, moduleId); } }