/** * PermissionController - Controlador de Permisos * * Endpoints REST para gestión de permisos del sistema. * Implementa CRUD completo con filtros y búsqueda. * * @module Auth */ import { Router, Request, Response, NextFunction } from 'express'; import { DataSource } from 'typeorm'; import { PermissionService, CreatePermissionDto, UpdatePermissionDto, PermissionFilters } from '../services/permission.service'; import { AuthMiddleware } from '../middleware/auth.middleware'; import { AuthService } from '../services/auth.service'; import { Permission, Role, UserRole } from '../entities'; import { User } from '../../core/entities/user.entity'; import { Tenant } from '../../core/entities/tenant.entity'; import { RefreshToken } from '../entities/refresh-token.entity'; /** * Crear router de permisos * @param dataSource - DataSource de TypeORM * @returns Router de Express configurado */ export function createPermissionController(dataSource: DataSource): Router { const router = Router(); // Inicializar repositorios const permissionRepository = dataSource.getRepository(Permission); const userRepository = dataSource.getRepository(User); const tenantRepository = dataSource.getRepository(Tenant); const refreshTokenRepository = dataSource.getRepository(RefreshToken); // Inicializar servicios const permissionService = new PermissionService(permissionRepository); const authService = new AuthService( userRepository, tenantRepository, refreshTokenRepository as any ); // Inicializar middleware const authMiddleware = new AuthMiddleware(authService, dataSource); /** * GET /permissions * Listar permisos con filtros y paginación */ router.get('/', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise => { try { const tenantId = req.tenantId; const userId = req.user?.sub; if (!tenantId) { res.status(400).json({ error: 'Bad Request', message: 'Tenant ID is required' }); return; } const filters: PermissionFilters = { code: req.query.code as string | undefined, module: req.query.module as string | undefined, action: req.query.action as string | undefined, isActive: req.query.isActive !== undefined ? req.query.isActive === 'true' : undefined, search: req.query.search as string | undefined, }; const page = parseInt(req.query.page as string) || 1; const limit = Math.min(parseInt(req.query.limit as string) || 50, 100); const result = await permissionService.findWithFilters( { tenantId, userId }, filters, page, limit ); res.status(200).json({ success: true, data: result }); } catch (error) { next(error); } }); /** * GET /permissions/modules * Listar módulos disponibles */ router.get('/modules', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise => { try { const tenantId = req.tenantId; const userId = req.user?.sub; if (!tenantId) { res.status(400).json({ error: 'Bad Request', message: 'Tenant ID is required' }); return; } const modules = await permissionService.getModules({ tenantId, userId }); res.status(200).json({ success: true, data: modules }); } catch (error) { next(error); } }); /** * GET /permissions/by-role/:roleId * Obtener permisos asignados a un rol */ router.get('/by-role/:roleId', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise => { try { const tenantId = req.tenantId; const userId = req.user?.sub; const { roleId } = req.params; if (!tenantId) { res.status(400).json({ error: 'Bad Request', message: 'Tenant ID is required' }); return; } if (!roleId) { res.status(400).json({ error: 'Bad Request', message: 'Role ID is required' }); return; } // Obtener rol con sus permisos const roleRepository = dataSource.getRepository(Role); const role = await roleRepository.findOne({ where: { id: roleId, tenantId }, relations: ['permissions'], }); if (!role) { res.status(404).json({ error: 'Not Found', message: 'Role not found' }); return; } res.status(200).json({ success: true, data: { roleId: role.id, roleName: role.name, permissions: role.permissions || [], }, }); } catch (error) { next(error); } }); /** * GET /permissions/:id * Obtener detalle de un permiso */ router.get('/:id', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise => { try { const tenantId = req.tenantId; const userId = req.user?.sub; const { id } = req.params; if (!tenantId) { res.status(400).json({ error: 'Bad Request', message: 'Tenant ID is required' }); return; } const permission = await permissionService.findById({ tenantId, userId }, id); if (!permission) { res.status(404).json({ error: 'Not Found', message: 'Permission not found' }); return; } res.status(200).json({ success: true, data: permission }); } catch (error) { next(error); } }); /** * POST /permissions * Crear nuevo permiso */ router.post('/', authMiddleware.authenticate, authMiddleware.requireAdmin, async (req: Request, res: Response, next: NextFunction): Promise => { try { const tenantId = req.tenantId; const userId = req.user?.sub; const dto: CreatePermissionDto = req.body; if (!tenantId) { res.status(400).json({ error: 'Bad Request', message: 'Tenant ID is required' }); return; } if (!dto.code || !dto.name) { res.status(400).json({ error: 'Bad Request', message: 'Code and name are required' }); return; } // Validar formato de código (snake_case) const codeRegex = /^[a-z][a-z0-9_]*$/; if (!codeRegex.test(dto.code)) { res.status(400).json({ error: 'Bad Request', message: 'Code must be in snake_case format (lowercase letters, numbers, and underscores)', }); return; } const permission = await permissionService.create({ tenantId, userId }, dto); res.status(201).json({ success: true, data: permission }); } catch (error) { if (error instanceof Error && error.message.includes('already exists')) { res.status(409).json({ error: 'Conflict', message: error.message }); return; } next(error); } }); /** * PUT /permissions/:id * Actualizar permiso */ router.put('/:id', authMiddleware.authenticate, authMiddleware.requireAdmin, async (req: Request, res: Response, next: NextFunction): Promise => { try { const tenantId = req.tenantId; const userId = req.user?.sub; const { id } = req.params; const dto: UpdatePermissionDto = req.body; if (!tenantId) { res.status(400).json({ error: 'Bad Request', message: 'Tenant ID is required' }); return; } // Validar formato de código si se está actualizando if (dto.code) { const codeRegex = /^[a-z][a-z0-9_]*$/; if (!codeRegex.test(dto.code)) { res.status(400).json({ error: 'Bad Request', message: 'Code must be in snake_case format', }); return; } } const permission = await permissionService.update({ tenantId, userId }, id, dto); if (!permission) { res.status(404).json({ error: 'Not Found', message: 'Permission not found' }); return; } res.status(200).json({ success: true, data: permission }); } catch (error) { if (error instanceof Error && error.message.includes('already exists')) { res.status(409).json({ error: 'Conflict', message: error.message }); return; } next(error); } }); /** * DELETE /permissions/:id * Eliminar permiso (soft delete) */ router.delete('/:id', authMiddleware.authenticate, authMiddleware.requireAdmin, async (req: Request, res: Response, next: NextFunction): Promise => { try { const tenantId = req.tenantId; const userId = req.user?.sub; const { id } = req.params; if (!tenantId) { res.status(400).json({ error: 'Bad Request', message: 'Tenant ID is required' }); return; } const deleted = await permissionService.delete({ tenantId, userId }, id); if (!deleted) { res.status(404).json({ error: 'Not Found', message: 'Permission not found' }); return; } res.status(200).json({ success: true, message: 'Permission deleted successfully' }); } catch (error) { next(error); } }); /** * POST /permissions/bulk * Crear múltiples permisos */ router.post('/bulk', authMiddleware.authenticate, authMiddleware.requireAdmin, async (req: Request, res: Response, next: NextFunction): Promise => { try { const tenantId = req.tenantId; const userId = req.user?.sub; const { permissions } = req.body; if (!tenantId) { res.status(400).json({ error: 'Bad Request', message: 'Tenant ID is required' }); return; } if (!Array.isArray(permissions) || permissions.length === 0) { res.status(400).json({ error: 'Bad Request', message: 'Permissions array is required' }); return; } const created = await permissionService.bulkCreate({ tenantId, userId }, permissions); res.status(201).json({ success: true, data: { created: created.length, permissions: created, }, }); } catch (error) { next(error); } }); return router; } export default createPermissionController;