Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-07-11 12:12:29 +08:00
parent e1ffdab190
commit 82ea9c77c4
19 changed files with 530 additions and 122 deletions
@@ -4,6 +4,9 @@ const mdl_NotificationTemplate = require('../../models/notifications/notificatio
const R = require('../../utils/response.util');
const logActivity = require('../../utils/logActivity.util');
const slugify = (str) =>
str.toLowerCase().trim().replace(/[^a-z0-9]+/g, '_').replace(/(^_|_$)/g, '');
// ─── GET /admin/notification-templates ─────────────────────────────────────────
exports.getNotificationTemplates = async (req, res) => {
@@ -31,10 +34,71 @@ exports.getNotificationTemplate = async (req, res) => {
}
};
// ─── POST /admin/notification-templates ────────────────────────────────────────
// Only creates custom (is_system: false) rows. System types still can't be
// added here — they need a code call site (services/notificationTemplate
// .service.js's renderNotification()) before a type means anything. Custom
// rows have no call site at all: they're reusable title/message presets an
// admin can load into the Announcements composer (see AddNotificationBroadcast
// .jsx), so `type` only exists to satisfy the unique key — nothing looks it up.
exports.createNotificationTemplate = async (req, res) => {
try {
const { label, title, message } = req.body;
if (!label?.trim()) return R.error(res, 'label is required.', 400);
if (!title?.trim()) return R.error(res, 'title is required.', 400);
if (!message?.trim()) return R.error(res, 'message cannot be empty.', 400);
const base = slugify(label) || 'template';
let type = `custom_${base}`;
let suffix = 1;
while (await mdl_NotificationTemplate.findOne({ where: { type } })) {
suffix += 1;
type = `custom_${base}_${suffix}`;
}
const template = await mdl_NotificationTemplate.create({
type,
notify_type: 'announcement',
scope: 'both',
label: label.trim(),
status: 'sent',
title: title.trim(),
message: message.trim(),
is_system: false,
});
logActivity(req.user?.user_id, 'create_notification_template', { entityType: 'notification_template', entityId: template.notification_template_id, details: { label: template.label } });
return R.success(res, 'Announcement template created.', template, 201);
} catch (err) {
console.error('[ADMIN][CREATE NOTIFICATION TEMPLATE]', err);
return R.error(res, 'Could not create announcement template.', 500);
}
};
// ─── DELETE /admin/notification-templates/:id ──────────────────────────────────
// System templates stay protected — deleting one would break the code call
// site that references its type.
exports.deleteNotificationTemplate = async (req, res) => {
try {
const template = await mdl_NotificationTemplate.findByPk(req.params.id);
if (!template) return R.error(res, 'Announcement template not found.', 404);
if (template.is_system) return R.error(res, 'System templates cannot be deleted.', 400);
await template.destroy();
logActivity(req.user?.user_id, 'delete_notification_template', { entityType: 'notification_template', entityId: template.notification_template_id, details: { label: template.label } });
return R.success(res, 'Announcement template deleted.');
} catch (err) {
console.error('[ADMIN][DELETE NOTIFICATION TEMPLATE]', err);
return R.error(res, 'Could not delete announcement template.', 500);
}
};
// ─── PUT /admin/notification-templates/:id ─────────────────────────────────────
// No create/delete endpoints — every row is is_system by definition (a new
// type needs a code call site before it means anything), so there is nothing
// valid to create or delete through this UI.
exports.updateNotificationTemplate = async (req, res) => {
try {
@@ -46,6 +110,21 @@ exports.updateNotificationTemplate = async (req, res) => {
if (title !== undefined && !title.trim()) return R.error(res, 'title cannot be empty.', 400);
if (message !== undefined && !message.trim()) return R.error(res, 'message cannot be empty.', 400);
// Custom templates are just reusable presets — nothing reads them at a
// fixed publish time, so there's no draft/publish workflow: title/message
// save straight to the live columns.
if (!template.is_system) {
await template.update({
label: label ?? template.label,
title: title ?? template.title,
message: message ?? template.message,
});
logActivity(req.user?.user_id, 'update_notification_template', { entityType: 'notification_template', entityId: template.notification_template_id, details: { custom: true } });
return R.success(res, 'Announcement template updated.', template);
}
// "Publish" writes title/message straight to the live columns
// renderNotification() reads and clears any pending draft. A plain save
// (no publish flag) writes into draft_title/draft_message instead, so