mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
174 lines
8.3 KiB
JavaScript
174 lines
8.3 KiB
JavaScript
'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);
|
|
}
|
|
};
|