231 lines
11 KiB
JavaScript
231 lines
11 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.buildScheduledRoutes = buildScheduledRoutes;
|
|
/**
|
|
* Rotas de Agendamentos de Mensagem
|
|
*
|
|
* GET /api/scheduled — Lista agendamentos do tenant
|
|
* POST /api/scheduled — Cria agendamento
|
|
* GET /api/scheduled/:id — Detalhe
|
|
* PUT /api/scheduled/:id — Atualiza
|
|
* DELETE /api/scheduled/:id — Remove
|
|
* POST /api/scheduled/:id/pause — Pausa
|
|
* POST /api/scheduled/:id/resume — Retoma
|
|
* POST /api/scheduled/:id/run-now — Executa imediatamente
|
|
* GET /api/scheduled/:id/logs — Histórico de execuções
|
|
*/
|
|
const express_1 = require("express");
|
|
const zod_1 = require("zod");
|
|
const croner_1 = require("croner");
|
|
const prisma_1 = require("../../infra/database/prisma");
|
|
const recipientSchema = zod_1.z.object({
|
|
jid: zod_1.z.string(),
|
|
name: zod_1.z.string().optional(),
|
|
birthday: zod_1.z.string().optional(),
|
|
anniversary: zod_1.z.string().optional(),
|
|
signupDate: zod_1.z.string().optional(),
|
|
});
|
|
const scheduleSchema = zod_1.z.object({
|
|
instanceId: zod_1.z.string(),
|
|
name: zod_1.z.string().min(1).max(100),
|
|
templateId: zod_1.z.string().optional(),
|
|
payload: zod_1.z.record(zod_1.z.unknown()),
|
|
recipientMode: zod_1.z.enum(['MANUAL', 'ALL_CONTACTS', 'TAG_FILTER']).default('MANUAL'),
|
|
recipients: zod_1.z.array(recipientSchema).default([]),
|
|
scheduleType: zod_1.z.enum(['ONCE', 'RECURRING', 'EVENT']),
|
|
cronExpr: zod_1.z.string().optional(),
|
|
scheduledAt: zod_1.z.string().datetime().optional(),
|
|
eventType: zod_1.z.enum(['BIRTHDAY', 'ANNIVERSARY', 'SIGNUP_DAYS']).optional(),
|
|
timezone: zod_1.z.string().default('America/Sao_Paulo'),
|
|
});
|
|
function getId(req) {
|
|
const id = req.params['id'];
|
|
return Array.isArray(id) ? id[0] : id;
|
|
}
|
|
function calcNextRun(data) {
|
|
if (data.scheduleType === 'ONCE' && data.scheduledAt) {
|
|
return new Date(data.scheduledAt);
|
|
}
|
|
if (data.scheduleType === 'RECURRING' && data.cronExpr) {
|
|
try {
|
|
const job = new croner_1.Cron(data.cronExpr, { paused: true });
|
|
return job.nextRun() ?? null;
|
|
}
|
|
catch {
|
|
return null;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
function buildScheduledRoutes(_scheduler) {
|
|
const router = (0, express_1.Router)();
|
|
// ── LIST ──────────────────────────────────────────────────────────────────
|
|
router.get('/', async (req, res) => {
|
|
const tenantId = req.tenantId;
|
|
const status = Array.isArray(req.query['status']) ? req.query['status'][0] : req.query['status'];
|
|
const type = Array.isArray(req.query['type']) ? req.query['type'][0] : req.query['type'];
|
|
const schedules = await prisma_1.prisma.scheduledMessage.findMany({
|
|
where: {
|
|
tenantId,
|
|
...(status ? { status: status } : {}),
|
|
...(type ? { scheduleType: type } : {}),
|
|
},
|
|
include: {
|
|
template: { select: { id: true, name: true, type: true } },
|
|
_count: { select: { logs: true } },
|
|
},
|
|
orderBy: { createdAt: 'desc' },
|
|
});
|
|
res.json(schedules);
|
|
});
|
|
// ── CREATE ────────────────────────────────────────────────────────────────
|
|
router.post('/', async (req, res) => {
|
|
const tenantId = req.tenantId;
|
|
const parse = scheduleSchema.safeParse(req.body);
|
|
if (!parse.success) {
|
|
res.status(400).json({ error: parse.error.errors });
|
|
return;
|
|
}
|
|
const data = parse.data;
|
|
const nextRunAt = calcNextRun(data);
|
|
const schedule = await prisma_1.prisma.scheduledMessage.create({
|
|
data: {
|
|
tenantId,
|
|
instanceId: data.instanceId,
|
|
name: data.name,
|
|
templateId: data.templateId,
|
|
payload: data.payload,
|
|
recipientMode: data.recipientMode,
|
|
recipients: data.recipients,
|
|
scheduleType: data.scheduleType,
|
|
cronExpr: data.cronExpr,
|
|
scheduledAt: data.scheduledAt ? new Date(data.scheduledAt) : null,
|
|
eventType: data.eventType ?? null,
|
|
timezone: data.timezone,
|
|
nextRunAt,
|
|
},
|
|
});
|
|
res.status(201).json(schedule);
|
|
});
|
|
// ── GET ONE ───────────────────────────────────────────────────────────────
|
|
router.get('/:id', async (req, res) => {
|
|
const tenantId = req.tenantId;
|
|
const sched = await prisma_1.prisma.scheduledMessage.findFirst({
|
|
where: { id: getId(req), tenantId },
|
|
include: {
|
|
template: true,
|
|
logs: { orderBy: { runAt: 'desc' }, take: 5 },
|
|
},
|
|
});
|
|
if (!sched) {
|
|
res.status(404).json({ error: 'Agendamento não encontrado' });
|
|
return;
|
|
}
|
|
res.json(sched);
|
|
});
|
|
// ── UPDATE ────────────────────────────────────────────────────────────────
|
|
router.put('/:id', async (req, res) => {
|
|
const tenantId = req.tenantId;
|
|
const id = getId(req);
|
|
const exists = await prisma_1.prisma.scheduledMessage.findFirst({
|
|
where: { id, tenantId },
|
|
});
|
|
if (!exists) {
|
|
res.status(404).json({ error: 'Agendamento não encontrado' });
|
|
return;
|
|
}
|
|
const parse = scheduleSchema.partial().safeParse(req.body);
|
|
if (!parse.success) {
|
|
res.status(400).json({ error: parse.error.errors });
|
|
return;
|
|
}
|
|
const data = parse.data;
|
|
const nextRunAt = calcNextRun({ ...exists, ...data });
|
|
const { payload, recipients, scheduledAt, eventType, ...rest } = data;
|
|
const updated = await prisma_1.prisma.scheduledMessage.update({
|
|
where: { id },
|
|
data: {
|
|
...rest,
|
|
...(payload !== undefined ? { payload: payload } : {}),
|
|
...(recipients !== undefined ? { recipients: recipients } : {}),
|
|
...(scheduledAt !== undefined ? { scheduledAt: new Date(scheduledAt) } : {}),
|
|
...(eventType !== undefined ? { eventType } : {}),
|
|
status: 'ACTIVE',
|
|
nextRunAt,
|
|
},
|
|
});
|
|
res.json(updated);
|
|
});
|
|
// ── DELETE ────────────────────────────────────────────────────────────────
|
|
router.delete('/:id', async (req, res) => {
|
|
const tenantId = req.tenantId;
|
|
const id = getId(req);
|
|
const exists = await prisma_1.prisma.scheduledMessage.findFirst({
|
|
where: { id, tenantId },
|
|
});
|
|
if (!exists) {
|
|
res.status(404).json({ error: 'Agendamento não encontrado' });
|
|
return;
|
|
}
|
|
await prisma_1.prisma.scheduledMessage.delete({ where: { id } });
|
|
res.status(204).send();
|
|
});
|
|
// ── PAUSE ─────────────────────────────────────────────────────────────────
|
|
router.post('/:id/pause', async (req, res) => {
|
|
const tenantId = req.tenantId;
|
|
const id = getId(req);
|
|
const exists = await prisma_1.prisma.scheduledMessage.findFirst({ where: { id, tenantId } });
|
|
if (!exists) {
|
|
res.status(404).json({ error: 'Agendamento não encontrado' });
|
|
return;
|
|
}
|
|
await prisma_1.prisma.scheduledMessage.update({ where: { id }, data: { status: 'PAUSED' } });
|
|
res.json({ ok: true });
|
|
});
|
|
// ── RESUME ────────────────────────────────────────────────────────────────
|
|
router.post('/:id/resume', async (req, res) => {
|
|
const tenantId = req.tenantId;
|
|
const id = getId(req);
|
|
const exists = await prisma_1.prisma.scheduledMessage.findFirst({ where: { id, tenantId } });
|
|
if (!exists) {
|
|
res.status(404).json({ error: 'Agendamento não encontrado' });
|
|
return;
|
|
}
|
|
await prisma_1.prisma.scheduledMessage.update({ where: { id }, data: { status: 'ACTIVE' } });
|
|
res.json({ ok: true });
|
|
});
|
|
// ── RUN NOW ───────────────────────────────────────────────────────────────
|
|
router.post('/:id/run-now', async (req, res) => {
|
|
const tenantId = req.tenantId;
|
|
const id = getId(req);
|
|
const sched = await prisma_1.prisma.scheduledMessage.findFirst({ where: { id, tenantId } });
|
|
if (!sched) {
|
|
res.status(404).json({ error: 'Agendamento não encontrado' });
|
|
return;
|
|
}
|
|
// Marca nextRunAt no passado — o tick do scheduler pega no próximo minuto
|
|
await prisma_1.prisma.scheduledMessage.update({
|
|
where: { id },
|
|
data: { nextRunAt: new Date(Date.now() - 1000), status: 'ACTIVE' },
|
|
});
|
|
res.json({ ok: true, message: 'Execução agendada para o próximo tick' });
|
|
});
|
|
// ── LOGS ──────────────────────────────────────────────────────────────────
|
|
router.get('/:id/logs', async (req, res) => {
|
|
const tenantId = req.tenantId;
|
|
const id = getId(req);
|
|
const exists = await prisma_1.prisma.scheduledMessage.findFirst({ where: { id, tenantId } });
|
|
if (!exists) {
|
|
res.status(404).json({ error: 'Agendamento não encontrado' });
|
|
return;
|
|
}
|
|
const logs = await prisma_1.prisma.scheduledMessageLog.findMany({
|
|
where: { scheduleId: id },
|
|
orderBy: { runAt: 'desc' },
|
|
take: 50,
|
|
});
|
|
res.json(logs);
|
|
});
|
|
return router;
|
|
}
|