87 lines
3.0 KiB
TypeScript
87 lines
3.0 KiB
TypeScript
import type { Request, Response, NextFunction } from 'express';
|
|
import { z } from 'zod';
|
|
import { prisma } from '../config/database.js';
|
|
import { isPlatformStaff } from '../utils/platform-admin.js';
|
|
import { AppError } from '../middlewares/error.middleware.js';
|
|
import { auditFromReq } from '../utils/audit.js';
|
|
|
|
async function requireStaff(req: Request) {
|
|
if (!req.user?.userId) throw new AppError(401, 'No autenticado');
|
|
const isStaff = await isPlatformStaff(req.user.userId);
|
|
if (!isStaff) throw new AppError(403, 'Acceso restringido a staff de plataforma');
|
|
}
|
|
|
|
const updateSchema = z.object({
|
|
nombre: z.string().min(1).max(200).optional(),
|
|
precio: z.number().nonnegative().optional(),
|
|
active: z.boolean().optional(),
|
|
});
|
|
|
|
/** Lista todo el catálogo de add-ons (incluye inactivos). */
|
|
export async function listCatalogo(req: Request, res: Response, next: NextFunction) {
|
|
try {
|
|
await requireStaff(req);
|
|
const items = await prisma.planAddonCatalogo.findMany({
|
|
orderBy: { codename: 'asc' },
|
|
include: {
|
|
_count: { select: { subscriptionAddons: { where: { status: { in: ['authorized', 'pending'] } } } } },
|
|
},
|
|
});
|
|
return res.json({
|
|
data: items.map(i => ({
|
|
id: i.id,
|
|
codename: i.codename,
|
|
nombre: i.nombre,
|
|
verticalProfile: i.verticalProfile,
|
|
precio: Number(i.precio),
|
|
frecuencia: i.frecuencia,
|
|
active: i.active,
|
|
delta: i.delta,
|
|
createdAt: i.createdAt.toISOString(),
|
|
suscripcionesActivas: i._count.subscriptionAddons,
|
|
})),
|
|
});
|
|
} catch (err) { return next(err); }
|
|
}
|
|
|
|
export async function updateCatalogoItem(req: Request, res: Response, next: NextFunction) {
|
|
try {
|
|
await requireStaff(req);
|
|
const id = String(req.params.id);
|
|
const data = updateSchema.parse(req.body);
|
|
const before = await prisma.planAddonCatalogo.findUnique({ where: { id } });
|
|
if (!before) throw new AppError(404, 'Add-on no encontrado');
|
|
|
|
const updated = await prisma.planAddonCatalogo.update({
|
|
where: { id },
|
|
data: {
|
|
...(data.nombre !== undefined ? { nombre: data.nombre } : {}),
|
|
...(data.precio !== undefined ? { precio: data.precio } : {}),
|
|
...(data.active !== undefined ? { active: data.active } : {}),
|
|
},
|
|
});
|
|
|
|
auditFromReq(req, 'addon.catalogo_updated', {
|
|
entityType: 'PlanAddonCatalogo',
|
|
entityId: id,
|
|
metadata: {
|
|
codename: before.codename,
|
|
before: { nombre: before.nombre, precio: Number(before.precio), active: before.active },
|
|
after: { nombre: updated.nombre, precio: Number(updated.precio), active: updated.active },
|
|
},
|
|
});
|
|
|
|
return res.json({
|
|
id: updated.id,
|
|
codename: updated.codename,
|
|
nombre: updated.nombre,
|
|
precio: Number(updated.precio),
|
|
frecuencia: updated.frecuencia,
|
|
active: updated.active,
|
|
});
|
|
} catch (err: any) {
|
|
if (err instanceof z.ZodError) return next(new AppError(400, err.errors[0].message));
|
|
return next(err);
|
|
}
|
|
}
|