erp-construccion-backend-v2/src/modules/auth/controllers/permission.controller.ts
Adrian Flores Cortes ebc526acb2 [REMEDIATION] feat: Backend remediation - auth controllers, construction entities, storage services
Add 5 auth controllers (device, MFA, permission, role, session), 18 construction entities,
5 storage services, 2 document services. Enhance auth middleware, fix budget/construction
controllers. Addresses gaps from TASK-2026-02-05 analysis.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-05 23:18:17 -06:00

331 lines
9.9 KiB
TypeScript

/**
* 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<void> => {
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<void> => {
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<void> => {
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<void> => {
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<void> => {
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<void> => {
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<void> => {
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<void> => {
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;