const { listJobs, getJob, updateJob, deleteJob } = require('../utils/scheduledEmails'); // Normalize job for client UI function toClient(job) { const kind = job.broadcast ? 'broadcast' : (job.eventId ? 'attendees' : 'unknown'); const subject = job?.payload?.subject || ''; const html = job?.payload?.html || ''; const text = job?.payload?.text || ''; return { id: job.id, kind, eventId: job.eventId || null, broadcast: !!job.broadcast, channel: job.channel || 'email', recipient: job.recipientSummary || null, scheduledAt: job.scheduledAt, createdAt: job.createdAt, status: job.status, attempts: job.attempts || 0, sentAt: job.sentAt || null, lastError: job.lastError || null, subject, hasHtml: !!html, hasText: !!text, }; } // GET /api/scheduled-emails?channel=email|whatsapp // Sent jobs are purged from storage 24h after sending, so nothing older than that is ever returned here const listScheduledEmails = async (req, res) => { try { const { channel } = req.query || {}; const raw = listJobs(channel ? { channel: String(channel) } : {}); const filtered = raw // Provide most-relevant first: queued -> sending -> error -> recent sent .sort((a, b) => { const order = { queued: 0, sending: 1, error: 2, sent: 3 }; const oa = order[a.status] ?? 99; const ob = order[b.status] ?? 99; if (oa !== ob) return oa - ob; // Then by scheduledAt asc return new Date(a.scheduledAt).getTime() - new Date(b.scheduledAt).getTime(); }) .map(toClient); return res.json({ jobs: filtered }); } catch (e) { return res.status(400).json({ message: e?.message || 'Failed to list scheduled emails' }); } }; // PATCH /api/scheduled-emails/:id // Allows editing scheduledAt, subject, html/text on queued jobs only const updateScheduledEmail = async (req, res) => { try { const { id } = req.params; const job = getJob(id); if (!job) return res.status(404).json({ message: 'Job not found' }); if (job.status !== 'queued') return res.status(400).json({ message: 'Only queued jobs can be edited' }); const { scheduledAt, subject, html, text } = req.body || {}; const patch = {}; if (scheduledAt) { const when = new Date(scheduledAt); if (isNaN(when.getTime())) return res.status(400).json({ message: 'scheduledAt must be a valid ISO date-time' }); patch.scheduledAt = when.toISOString(); } if (subject != null || html != null || text != null) { const payload = { ...(job.payload || {}) }; if (subject != null) payload.subject = subject; if (html != null || text != null) { // If html provided explicitly, set html; if text provided, set text if (html != null) payload.html = html; if (text != null) payload.text = text; } patch.payload = payload; } const updated = updateJob(id, patch); return res.json({ message: 'Updated', job: toClient(updated) }); } catch (e) { return res.status(400).json({ message: e?.message || 'Failed to update job' }); } }; // DELETE /api/scheduled-emails/:id // Only queued jobs can be removed const deleteScheduledEmail = async (req, res) => { try { const { id } = req.params; const job = getJob(id); if (!job) return res.status(404).json({ message: 'Job not found' }); if (job.status !== 'queued') return res.status(400).json({ message: 'Only queued jobs can be deleted' }); const ok = deleteJob(id); if (!ok) return res.status(404).json({ message: 'Job not found' }); return res.json({ message: 'Deleted' }); } catch (e) { return res.status(400).json({ message: e?.message || 'Failed to delete job' }); } }; module.exports = { listScheduledEmails, updateScheduledEmail, deleteScheduledEmail };