Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-07-12 12:39:17 +08:00
parent 82ea9c77c4
commit ea3e82e54c
47 changed files with 1301 additions and 481 deletions
@@ -1,65 +0,0 @@
/***********************************************************************************************************************************************************************
* File Name: notification_template_enrichers.data.js
* Type of Program: Data / Registry
* Description: Admin-edited notification templates are plain text — no
* conditionals or expressions allowed. Any type that used to
* branch on data in JS (e.g. task_overdue's singular/plural
* wording) gets that branch precomputed here into flat
* placeholder keys BEFORE substitution, so the stored title/
* message only ever needs straight {{key}} swaps.
* Mirrors data/email_template_enrichers.data.js.
* Author: Kenneth Obsequio (@lash0000)
* Date Created: Jul. 3, 2026
***********************************************************************************************************************************************************************/
const { fmtDate } = require('../utils/datetime.util');
const ENRICHERS = {
task_overdue: (data) => ({
...data,
task_word: Number(data.count) === 1 ? 'task was' : 'tasks were',
}),
user_task_overdue: (data) => ({
...data,
task_label: Number(data.count) === 1 ? '1 task has' : `${data.count} tasks have`,
}),
task_reminder: (data) => ({
...data,
deadline: fmtDate(data.deadline),
}),
task_submission_reviewed: (data) => ({
...data,
statusLabel: data.status === 'approved' ? 'approved' : 'rejected',
reviewNoteSuffix: data.review_note ? ` Note: ${data.review_note}` : '',
}),
welcome: (data) => {
const greeting = data.accType === 'admin'
? 'Welcome, Administrator!'
: data.accType === 'staff'
? 'Welcome to the Philproperties team!'
: 'Welcome to Philproperties!';
return {
...data,
greeting,
group_suffix: data.groupName ? ` You have been added to ${data.groupName}.` : '',
};
},
assessment_updated: (data) => ({
...data,
assessmentTitle: data.assessmentTitle || 'Course Assessment',
courseTitle: data.courseTitle || 'your course',
}),
tier_expired: (data) => ({
...data,
planLabel: data.label ?? data.tier,
}),
};
const enrichNotificationData = (type, data = {}) => (ENRICHERS[type] ? ENRICHERS[type](data) : data);
module.exports = { enrichNotificationData };
+273 -17
View File
@@ -1,37 +1,185 @@
/***********************************************************************************************************************************************************************
* File Name: notifications.data.js
* Type of Program: Data
* Description: Registry of notification types that have no fixed, admin-
* editable wording — content is entirely supplied by the caller
* at trigger time, so there's nothing to template.
* Description: Central registry of all notification types for both admin and
* client (user) notifications.
*
* Every other system-triggered notification type (task overdue,
* welcome, tier expired, etc.) has been moved to the
* notification_templates table — admin-editable, {{placeholder}}-
* based, rendered via services/notificationTemplate.service.js's
* renderNotification()/renderNotificationContent(). See
* controllers/admin/notification_templates.controller.js.
*
* Each entry here describes one notification type:
* Each entry describes one notification type:
* type {string} — stored in the DB 'type' column
* scope {string} — 'admin' | 'user' | 'both'
* trigger {string} — what fires it (event | manual)
* trigger {string} — what fires it (cron | event | manual)
* build {function} — takes a data payload, returns the object
* ready to pass to AdminNotification.create()
* or UserNotification.create() / bulkCreate()
*
* To add a new notification type:
* 1. Add an entry in the relevant section below.
* 2. Call NOTIFICATION_REGISTRY.<key>.build(data) at the trigger
* site (controller, cron, service).
* No other changes needed.
*
* Current types:
* User : achievement, announcement
* Admin : task_overdue, user_registration, nogrp_user_registered
* User : task_requirements_updated, user_task_overdue, task_reminder, achievement,
* course_unlocked, course_completed, certificate_issued, welcome,
* nogrp_welcome, assessment_updated, announcement, tier_expired,
* task_submission_reviewed, task_assigned, task_completed
* Both : broadcast (admin-composed, sent via notification_broadcasts CRUD)
*
* Author: Kenneth Obsequio (@lash0000)
* Date Created: Jun. 19, 2026
* Date Modified: Jul. 3, 2026 — fixed-wording types moved into notification_templates
* Date Modified: Jul. 11, 2026 — reverted from notification_templates (DB-editable) back to hardcoded
***********************************************************************************************************************************************************************/
'use strict';
const { fmtDate } = require('../utils/datetime.util');
const NOTIFICATION_REGISTRY = {
// ─────────────────────────────────────────────────────────────────────────
// ADMIN notifications (scope: 'admin')
// ─────────────────────────────────────────────────────────────────────────
// ── Task ──────────────────────────────────────────────────────────────────
task_overdue: {
type: 'task_overdue',
scope: 'admin',
trigger: 'cron',
build({ count, task_list_ids = [] }) {
return {
type: 'task_overdue',
title: 'Tasks Overdue',
message: `${count} task${count === 1 ? ' was' : 's were'} automatically marked as overdue.`,
data: { count, task_list_ids },
};
},
},
// ── User Registration ─────────────────────────────────────────────────────
user_registration: {
type: 'user_registration',
scope: 'admin',
trigger: 'event',
build({ groupName, groupCode, userEmail }) {
return {
type: 'user_registration',
title: 'New User Registered',
message: `A new user registered in ${groupName}.`,
data: { groupName, groupCode, userEmail },
};
},
},
// ── Unaffiliated User Registration ───────────────────────────────────────
nogrp_user_registered: {
type: 'nogrp_user_registered',
scope: 'admin',
trigger: 'event',
build({ userEmail, regType }) {
return {
type: 'nogrp_user_registered',
title: 'New Unaffiliated User',
message: `A new user (${userEmail}) registered via ${regType} without a group code and was placed in the default group.`,
data: { userEmail, regType },
};
},
},
// ─────────────────────────────────────────────────────────────────────────
// USER notifications (scope: 'user')
// ─────────────────────────────────────────────────────────────────────────
// ── Task ──────────────────────────────────────────────────────────────────
task_requirements_updated: {
type: 'task',
scope: 'user',
trigger: 'event',
build({ taskName, taskListId = null, groupId = null }) {
return {
type: 'task',
title: 'Task Updated',
message: `The requirements for "${taskName}" have been updated by your administrator.`,
data: { taskName, taskListId, groupId },
};
},
},
user_task_overdue: {
type: 'task',
scope: 'user',
trigger: 'cron',
build({ count, task_list_ids = [] }) {
const label = count === 1 ? '1 task has' : `${count} tasks have`;
return {
type: 'task',
title: 'Tasks Overdue',
message: `${label} passed their deadline and been marked as overdue.`,
data: { count, task_list_ids },
};
},
},
task_reminder: {
type: 'task',
scope: 'user',
trigger: 'cron',
build({ taskName, deadline, taskListId = null, groupId = null }) {
return {
type: 'task',
title: 'Task Deadline Approaching',
message: `"${taskName}" is due on ${fmtDate(deadline)}.`,
data: { taskName, deadline, taskListId, groupId },
};
},
},
// ── Task submission review (approve/reject) ─────────────────────────────
task_submission_reviewed: {
type: 'task',
scope: 'user',
trigger: 'event',
build({ taskName, status, review_note = null }) {
const statusLabel = status === 'approved' ? 'approved' : 'rejected';
const reviewNoteSuffix = review_note ? ` Note: ${review_note}` : '';
return {
type: 'task',
title: 'Submission Reviewed',
message: `Your submission for "${taskName}" was ${statusLabel}.${reviewNoteSuffix}`,
data: { taskName, status, review_note },
};
},
},
// ── Task list assignment (group added to a task list) ───────────────────
task_assigned: {
type: 'task',
scope: 'user',
trigger: 'event',
build({ taskListName, taskCount }) {
return {
type: 'task',
title: 'New Task Assigned',
message: `You have been assigned "${taskListName}" — ${taskCount} task(s) to complete.`,
data: { taskListName, taskCount },
};
},
},
// ── Per-task completion (0→1 transition) ─────────────────────────────────
task_completed: {
type: 'task',
scope: 'user',
trigger: 'event',
build({ taskName }) {
return {
type: 'task',
title: 'Task Completed',
message: `You completed "${taskName}".`,
data: { taskName },
};
},
},
// ── Achievement — title/message come from the achievement definition itself ─
achievement: {
type: 'achievement',
@@ -47,7 +195,100 @@ const NOTIFICATION_REGISTRY = {
},
},
// ── Platform — title/body typed fresh by whoever calls this ─────────────────
// ── Course ────────────────────────────────────────────────────────────────
course_unlocked: {
type: 'course',
scope: 'user',
trigger: 'event',
build({ courseTitle, courseUuid = null }) {
return {
type: 'course',
title: 'New Course Available',
message: `"${courseTitle}" has been added to your learning library.`,
data: { courseTitle, courseUuid },
};
},
},
course_completed: {
type: 'course',
scope: 'user',
trigger: 'event',
build({ courseTitle, courseUuid = null }) {
return {
type: 'course',
title: 'Course Completed',
message: `Great job! You've completed "${courseTitle}". Your certificate will be issued within the next hour.`,
data: { courseTitle, courseUuid },
};
},
},
certificate_issued: {
type: 'course',
scope: 'user',
trigger: 'cron',
build({ courseTitle, courseUuid }) {
return {
type: 'course',
title: 'Certificate Issued',
message: `Congratulations! Your certificate for "${courseTitle}" is ready.`,
data: { courseTitle, courseUuid },
};
},
},
// ── Welcome ───────────────────────────────────────────────────────────────
welcome: {
type: 'announcement',
scope: 'user',
trigger: 'event',
build({ groupName, groupCode, accType, groupId = null }) {
const greeting = accType === 'admin'
? 'Welcome, Administrator!'
: accType === 'staff'
? 'Welcome to the Philproperties team!'
: 'Welcome to Philproperties!';
return {
type: 'announcement',
title: 'Welcome to Philproperties',
message: groupName ? `${greeting} You have been added to ${groupName}.` : greeting,
data: { groupName, groupCode, accType, groupId },
};
},
},
// ── No-Group Welcome ──────────────────────────────────────────────────────
nogrp_welcome: {
type: 'announcement',
scope: 'user',
trigger: 'event',
build() {
return {
type: 'announcement',
title: "You're Not in a Group Yet",
message: 'You are currently in the default group. Contact an administrator to be assigned to your team.',
data: { groupCode: 'NOGRP' },
};
},
},
// ── Assessment ────────────────────────────────────────────────────────────
assessment_updated: {
type: 'assessment',
scope: 'user',
trigger: 'event',
build({ assessmentTitle, courseTitle, courseUuid = null }) {
return {
type: 'assessment',
title: 'Assessment Updated',
message: `The administrator has updated the "${assessmentTitle || 'Course Assessment'}" in "${courseTitle || 'your course'}". Your current session is still valid — continue where you left off.`,
data: { assessmentTitle, courseTitle, courseUuid },
};
},
},
// ── Platform ──────────────────────────────────────────────────────────────
announcement: {
type: 'announcement',
scope: 'user',
@@ -68,12 +309,27 @@ const NOTIFICATION_REGISTRY = {
type: 'announcement',
scope: 'both',
trigger: 'manual',
build({ title, message, targetType = null, targetId = null, groupId = null, linkUrl = null }) {
build({ title, message, targetType = null, targetId = null, groupId = null, linkUrl = null, linkLabel = null }) {
return {
type: 'announcement',
title,
message,
data: { targetType, targetId, groupId, linkUrl },
data: { targetType, targetId, groupId, linkUrl, linkLabel },
};
},
},
// ── Tier ──────────────────────────────────────────────────────────────────
tier_expired: {
type: 'tier_expired',
scope: 'user',
trigger: 'cron',
build({ tier, label, planId = null }) {
return {
type: 'tier_expired',
title: 'Subscription Expired',
message: `Your ${label ?? tier} plan has expired. Renew to keep access.`,
data: { tier, label, planId },
};
},
},