feat(03-01): add ModuleRegistry NestJS module with CRUD and activation endpoints
- ModuleRegistryService with findAll, findBySlug, findActiveForTenant, activate/deactivate, seedModule - ModuleRegistryController with GET /modules, GET /modules/active, POST activate/deactivate - ModuleGuard + @UseModule() decorator for tenant-scoped module access control - ActivateModuleDto with UUID validation - Registered ModuleRegistryModule in AppModule imports
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
import {
|
||||
Controller,
|
||||
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 { ModuleRegistryService } from './module-registry.service';
|
||||
|
||||
/**
|
||||
* REST controller for the module registry.
|
||||
*
|
||||
* - GET /modules — list all registered modules (any authenticated user)
|
||||
* - GET /modules/active — list active modules for current tenant
|
||||
* - 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,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 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 that are active for the requesting tenant.
|
||||
*/
|
||||
@Get('active')
|
||||
async findActive(@Req() req: Request) {
|
||||
const tenantId = (req as any).tenantId;
|
||||
if (!tenantId) {
|
||||
throw new ForbiddenException('No tenant context');
|
||||
}
|
||||
return this.moduleRegistryService.findActiveForTenant(tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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: Request,
|
||||
) {
|
||||
const tenantId = (req as any).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: Request,
|
||||
) {
|
||||
const tenantId = (req as any).tenantId;
|
||||
if (!tenantId) {
|
||||
throw new ForbiddenException('No tenant context');
|
||||
}
|
||||
return this.moduleRegistryService.deactivateForTenant(tenantId, moduleId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user