'use strict'; const mdl_EmailTemplate = require('../../models/email_templates/email_templates.mdl'); const mdl_EmailBroadcast = require('../../models/email_templates/email_broadcast.mdl'); const R = require('../../utils/response.util'); const logActivity = require('../../utils/logActivity.util'); const TYPE_PATTERN = /^[A-Z][A-Z0-9_]*$/; const VALID_CATEGORIES = ['announcement', 'advertisement', 'system', 'other']; // ─── GET /admin/email-templates ─────────────────────────────────────────────── exports.getEmailTemplates = async (req, res) => { try { const templates = await mdl_EmailTemplate.findAll({ order: [['category', 'ASC'], ['is_system', 'DESC'], ['type', 'ASC']], }); return R.success(res, 'Email templates retrieved.', templates); } catch (err) { console.error('[ADMIN][GET EMAIL TEMPLATES]', err); return R.error(res, 'Could not retrieve email templates.', 500); } }; // ─── GET /admin/email-templates/:id ─────────────────────────────────────────── exports.getEmailTemplate = async (req, res) => { try { const template = await mdl_EmailTemplate.findByPk(req.params.id); if (!template) return R.error(res, 'Email template not found.', 404); return R.success(res, 'Email template retrieved.', template); } catch (err) { console.error('[ADMIN][GET EMAIL TEMPLATE]', err); return R.error(res, 'Could not retrieve email template.', 500); } }; // ─── POST /admin/email-templates ────────────────────────────────────────────── exports.createEmailTemplate = async (req, res) => { try { const { type, label, category, subject, html_body, body_markdown, publish } = req.body; if (!type || !label || !subject || !html_body) { return R.error(res, 'type, label, subject and html_body are required.', 400); } if (!TYPE_PATTERN.test(type)) { return R.error(res, 'type must be uppercase letters, numbers or underscores, starting with a letter (e.g. INVOICE_RECEIPT).', 400); } if (category !== undefined && !VALID_CATEGORIES.includes(category)) { return R.error(res, `category must be one of: ${VALID_CATEGORIES.join(', ')}.`, 400); } const exists = await mdl_EmailTemplate.findOne({ where: { type } }); if (exists) return R.error(res, `An email template with type "${type}" already exists.`, 409); // "Send Now" writes straight to the live columns sendEmail() reads. // "Save as Draft" keeps the content out of the live columns entirely, so // there's nothing for sendEmail() to pick up until it's published. const isPublishing = publish === true || publish === 'true'; const template = await mdl_EmailTemplate.create({ type, label, category: category || 'other', status: isPublishing ? 'sent' : 'draft', subject: isPublishing ? subject : null, html_body: isPublishing ? html_body : null, body_markdown: isPublishing ? (body_markdown ?? null) : null, draft_subject: isPublishing ? null : subject, draft_html_body: isPublishing ? null : html_body, draft_body_markdown: isPublishing ? null : (body_markdown ?? null), last_sent_at: isPublishing ? new Date() : null, is_system: false, // only seed data may be system-protected }); logActivity(req.user?.user_id, 'create_email_template', { entityType: 'email_template', details: { type, label, category: template.category, status: template.status } }); return R.success(res, 'Email template created.', template, 201); } catch (err) { console.error('[ADMIN][CREATE EMAIL TEMPLATE]', err); return R.error(res, 'Could not create email template.', 500); } }; // ─── PUT /admin/email-templates/:id ─────────────────────────────────────────── exports.updateEmailTemplate = async (req, res) => { try { const template = await mdl_EmailTemplate.findByPk(req.params.id); if (!template) return R.error(res, 'Email template not found.', 404); const { type, label, category, subject, html_body, body_markdown, publish } = req.body; if (template.is_system && type !== undefined && type !== template.type) { return R.error(res, 'The type of a system email template cannot be changed.', 400); } if (type !== undefined && !TYPE_PATTERN.test(type)) { return R.error(res, 'type must be uppercase letters, numbers or underscores, starting with a letter (e.g. INVOICE_RECEIPT).', 400); } if (category !== undefined && !VALID_CATEGORIES.includes(category)) { return R.error(res, `category must be one of: ${VALID_CATEGORIES.join(', ')}.`, 400); } if (!template.is_system && type !== undefined && type !== template.type) { const exists = await mdl_EmailTemplate.findOne({ where: { type } }); if (exists) return R.error(res, `An email template with type "${type}" already exists.`, 409); } if (subject !== undefined && !subject.trim()) return R.error(res, 'subject cannot be empty.', 400); if (html_body !== undefined && !html_body.trim()) return R.error(res, 'html_body cannot be empty.', 400); // "Send" publishes subject/html_body straight to the live columns that // sendEmail() reads and clears any pending draft. A plain save (no // publish flag) writes into draft_subject/draft_html_body instead, so // real outgoing mail keeps using the last-published content until an // admin comes back and explicitly sends again. const isPublishing = publish === true || publish === 'true'; const nextSubject = subject ?? template.draft_subject ?? template.subject; const nextHtmlBody = html_body ?? template.draft_html_body ?? template.html_body; const nextMarkdown = body_markdown ?? template.draft_body_markdown ?? template.body_markdown; await template.update({ type: (!template.is_system && type !== undefined) ? type : template.type, label: label ?? template.label, category: category ?? template.category, ...(isPublishing ? { status: 'sent', subject: nextSubject, html_body: nextHtmlBody, body_markdown: nextMarkdown, draft_subject: null, draft_html_body: null, draft_body_markdown: null, last_sent_at: new Date(), } : { draft_subject: nextSubject, draft_html_body: nextHtmlBody, draft_body_markdown: nextMarkdown, }), }); logActivity(req.user?.user_id, 'update_email_template', { entityType: 'email_template', entityId: template.email_template_id, details: { type: template.type, published: isPublishing } }); return R.success(res, 'Email template updated.', template); } catch (err) { console.error('[ADMIN][UPDATE EMAIL TEMPLATE]', err); return R.error(res, 'Could not update email template.', 500); } }; // ─── DELETE /admin/email-templates/:id ──────────────────────────────────────── exports.deleteEmailTemplate = async (req, res) => { try { const template = await mdl_EmailTemplate.findByPk(req.params.id); if (!template) return R.error(res, 'Email template not found.', 404); if (template.is_system) return R.error(res, 'Built-in system email templates cannot be deleted.', 400); const broadcastCount = await mdl_EmailBroadcast.count({ where: { email_template_id: template.email_template_id } }); if (broadcastCount > 0) { return R.error(res, `Cannot delete — ${broadcastCount} broadcast(s) reference this template. Its send history would be lost.`, 409); } await template.destroy(); logActivity(req.user?.user_id, 'delete_email_template', { entityType: 'email_template', details: { type: template.type } }); return R.success(res, 'Email template deleted.'); } catch (err) { console.error('[ADMIN][DELETE EMAIL TEMPLATE]', err); return R.error(res, 'Could not delete email template.', 500); } };