toClient() never returned the message body (html/text for email, message for WhatsApp), so "Manage scheduled" showed nothing to view or edit even though the content existed in storage. Editing a scheduled WhatsApp message also saved to the wrong payload field (text instead of message), silently discarding the edit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
106 lines
3.8 KiB
JavaScript
106 lines
3.8 KiB
JavaScript
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 || '';
|
|
const message = job?.payload?.message || '';
|
|
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,
|
|
html,
|
|
text,
|
|
message,
|
|
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 and message content (subject/html/text for email jobs, message for WhatsApp jobs) 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, message } = 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 || message != null) {
|
|
const payload = { ...(job.payload || {}) };
|
|
if (subject != null) payload.subject = subject;
|
|
if (html != null) payload.html = html;
|
|
if (text != null) payload.text = text;
|
|
if (message != null) payload.message = message;
|
|
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 };
|