149 lines
6.5 KiB
TypeScript
149 lines
6.5 KiB
TypeScript
/**
|
|
* Endpoints de administración de configuración.
|
|
*
|
|
* Protegidos con requireAdmin (admin o manager) excepto /acciones que
|
|
* cualquiera autenticado puede consultar. Las operaciones sensibles —crear
|
|
* usuarios y editar permisos de roles— usan requireAdminEstricto (solo admin):
|
|
* el rol manager no las puede hacer.
|
|
*/
|
|
import type { NextFunction, Request, Response } from 'express'
|
|
import { z } from 'zod'
|
|
import * as configService from '../services/config.service.js'
|
|
import { esRolValido } from '../config/roles.js'
|
|
import { esAdmin, esAdminOManager } from '../utils/roles.js'
|
|
import { AppError, BadRequestError } from '../utils/errors.js'
|
|
|
|
// ── Middleware admin ────────────────────────────────────────────────────
|
|
|
|
export function requireAdmin(req: Request, _res: Response, next: NextFunction): void {
|
|
if (!esAdminOManager(req.usuarioToken?.rol)) {
|
|
throw new AppError(403, 'Se requiere rol admin o manager')
|
|
}
|
|
next()
|
|
}
|
|
|
|
/** Solo admin: crear usuarios y editar permisos de roles (manager NO) */
|
|
export function requireAdminEstricto(req: Request, _res: Response, next: NextFunction): void {
|
|
if (!esAdmin(req.usuarioToken?.rol)) {
|
|
throw new AppError(403, 'Solo un administrador puede hacer esta operación')
|
|
}
|
|
next()
|
|
}
|
|
|
|
// ── GET /api/config/sistema ────────────────────────────────────────────
|
|
|
|
export async function obtenerConfigsEndpoint(_req: Request, res: Response): Promise<void> {
|
|
const configs = await configService.obtenerConfigs()
|
|
res.json(configs)
|
|
}
|
|
|
|
// ── PATCH /api/config/sistema ──────────────────────────────────────────
|
|
|
|
const actualizarConfigsSchema = z.record(z.string(), z.string())
|
|
|
|
export async function actualizarConfigsEndpoint(req: Request, res: Response): Promise<void> {
|
|
const parcial = actualizarConfigsSchema.parse(req.body)
|
|
if (Object.keys(parcial).length === 0) throw new BadRequestError('Body vacío')
|
|
await configService.actualizarConfigs(parcial)
|
|
res.status(204).end()
|
|
}
|
|
|
|
// ── GET /api/config/roles ──────────────────────────────────────────────
|
|
|
|
export async function obtenerRolesConfigEndpoint(_req: Request, res: Response): Promise<void> {
|
|
const roles = await configService.obtenerRolesConfig()
|
|
// Parsear JSON para que el frontend reciba objetos
|
|
res.json(
|
|
roles.map((r) => ({
|
|
rol: r.rol,
|
|
modulos: JSON.parse(r.modulos) as string[],
|
|
acciones: JSON.parse(r.acciones) as Record<string, boolean>,
|
|
updatedAt: r.updatedAt,
|
|
})),
|
|
)
|
|
}
|
|
|
|
// ── PATCH /api/config/roles/:rol ───────────────────────────────────────
|
|
|
|
const actualizarRolSchema = z.object({
|
|
modulos: z.array(z.string()),
|
|
acciones: z.record(z.string(), z.boolean()),
|
|
})
|
|
|
|
export async function actualizarRolConfigEndpoint(req: Request, res: Response): Promise<void> {
|
|
const rol = req.params.rol as string
|
|
if (!rol) throw new BadRequestError('Falta el parámetro :rol')
|
|
const body = actualizarRolSchema.parse(req.body)
|
|
await configService.actualizarRolConfig(rol, body)
|
|
res.status(204).end()
|
|
}
|
|
|
|
// ── GET /api/config/usuarios ───────────────────────────────────────────
|
|
|
|
export async function obtenerUsuariosEndpoint(_req: Request, res: Response): Promise<void> {
|
|
const usuarios = await configService.obtenerUsuarios()
|
|
res.json(usuarios)
|
|
}
|
|
|
|
// ── PATCH /api/config/usuarios/:id ─────────────────────────────────────
|
|
|
|
const actualizarUsuarioSchema = z.object({
|
|
rol: z.string().optional(),
|
|
asignacion: z.string().nullable().optional(),
|
|
activo: z.boolean().optional(),
|
|
})
|
|
|
|
export async function actualizarUsuarioEndpoint(req: Request, res: Response): Promise<void> {
|
|
const id = req.params.id as string
|
|
if (!id) throw new BadRequestError('Falta el parámetro :id')
|
|
const parsed = actualizarUsuarioSchema.parse(req.body)
|
|
// El rol es un permiso: solo el administrador puede cambiarlo (manager no)
|
|
if (parsed.rol !== undefined && !esAdmin(req.usuarioToken?.rol)) {
|
|
throw new AppError(403, 'Solo un administrador puede cambiar el rol de un usuario')
|
|
}
|
|
// Filtra undefined para enviar solo campos con valor
|
|
const body: { rol?: string; asignacion?: string | null; activo?: boolean } = {}
|
|
if (parsed.rol !== undefined) body.rol = parsed.rol
|
|
if (parsed.asignacion !== undefined) body.asignacion = parsed.asignacion
|
|
if (parsed.activo !== undefined) body.activo = parsed.activo
|
|
if (Object.keys(body).length === 0) throw new BadRequestError('Body vacío')
|
|
await configService.actualizarUsuario(id, body)
|
|
res.status(204).end()
|
|
}
|
|
|
|
// ── POST /api/config/usuarios — dar de alta un usuario (solo admin) ────
|
|
|
|
const crearUsuarioSchema = z.object({
|
|
nombre: z.string().trim().min(3, 'Nombre demasiado corto').max(80),
|
|
rol: z.string().min(1),
|
|
asignacion: z.string().nullable().optional(),
|
|
password: z.string().min(8, 'La contraseña necesita al menos 8 caracteres').max(72),
|
|
})
|
|
|
|
export async function crearUsuarioEndpoint(req: Request, res: Response): Promise<void> {
|
|
const body = crearUsuarioSchema.parse(req.body)
|
|
if (!esRolValido(body.rol)) throw new BadRequestError(`Rol inválido: ${body.rol}`)
|
|
const usuario = await configService.crearUsuario({
|
|
nombre: body.nombre,
|
|
rol: body.rol,
|
|
asignacion: body.asignacion ?? null,
|
|
password: body.password,
|
|
})
|
|
res.status(201).json(usuario)
|
|
}
|
|
|
|
// ── POST /api/config/reset-roles ───────────────────────────────────────
|
|
|
|
export async function resetRolesEndpoint(_req: Request, res: Response): Promise<void> {
|
|
await configService.resetRoles()
|
|
res.status(204).end()
|
|
}
|
|
|
|
// ── GET /api/config/acciones ───────────────────────────────────────────
|
|
|
|
export async function obtenerAccionesEndpoint(req: Request, res: Response): Promise<void> {
|
|
if (!req.usuarioToken) throw new BadRequestError('Falta el token verificado')
|
|
const acciones = await configService.obtenerAcciones(req.usuarioToken.rol)
|
|
res.json(acciones)
|
|
}
|