new commits

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-07-03 16:20:58 +08:00
parent 1372f4e975
commit 1d12f04967
93 changed files with 3849 additions and 1063 deletions
+5 -105
View File
@@ -1,119 +1,19 @@
/***********************************************************************************************************************************************************************
* File Name: achievements.data.js
* Type of Program: Data
* Description: Static registry of all available achievements (badges and milestones).
* Add a new achievement here — no other code changes needed.
*
* Available keys:
* Badge:
* early_access — registered before Dec 31, 2026
* premium_first_time — first premium tier purchase
* exclusive_first_time — first exclusive tier purchase
* Milestone:
* first_course_completed — completed first course
* courses_completed_5 — completed 5 courses
* courses_completed_10 — completed 10 courses
* perfect_quiz_score — perfect score on a quiz
* profile_completed — filled out full profile
* first_referral — referred a user
* Description: Static config for the achievement system that isn't part of the
* admin-managed catalog. The achievement catalog itself (keys, type,
* label, description, icon) now lives in the achievement_definitions
* table — see models/users/achievement_definitions.mdl.js and
* controllers/admin/achievements.controller.js for CRUD.
*
* Author: Kenneth Obsequio (@lash0000)
* Date Created: Jun. 19, 2026
***********************************************************************************************************************************************************************/
'use strict';
// ─── Early Access cutoff ──────────────────────────────────────────────────────
const EARLY_ACCESS_CUTOFF = new Date('2026-12-31T23:59:59Z');
// ─── Achievement Registry ─────────────────────────────────────────────────────
// To add a new achievement — add an entry here. No other code changes needed.
//
// trigger categories (for documentation only, not enforced):
// auth — registration / login events
// tier — subscription purchase events
// course — course / lesson / quiz events
// profile — profile completion events
// social — referral / community events
const ACHIEVEMENT_REGISTRY = {
// ── Auth ───────────────────────────────────────────────────────────────────
early_access: {
key: 'early_access',
type: 'badge',
label: 'Early Access',
description: 'Registered during the Philproperties beta period (before Dec 31, 2026).',
trigger: 'auth',
},
// ── Tier ───────────────────────────────────────────────────────────────────
premium_first_time: {
key: 'premium_first_time',
type: 'badge',
label: 'Premium Member',
description: 'Purchased a Premium tier plan for the first time.',
trigger: 'tier',
},
exclusive_first_time: {
key: 'exclusive_first_time',
type: 'badge',
label: 'Exclusive Member',
description: 'Purchased an Exclusive tier plan for the first time.',
trigger: 'tier',
},
// ── Course ─────────────────────────────────────────────────────────────────
first_course_completed: {
key: 'first_course_completed',
type: 'milestone',
label: 'First Course Completed',
description: 'Completed your very first course on Philproperties.',
trigger: 'course',
},
courses_completed_5: {
key: 'courses_completed_5',
type: 'milestone',
label: 'Learning Streak',
description: 'Completed 5 courses.',
trigger: 'course',
},
courses_completed_10: {
key: 'courses_completed_10',
type: 'milestone',
label: 'Knowledge Builder',
description: 'Completed 10 courses.',
trigger: 'course',
},
perfect_quiz_score: {
key: 'perfect_quiz_score',
type: 'milestone',
label: 'Perfect Score',
description: 'Achieved a perfect score on a quiz.',
trigger: 'course',
},
// ── Profile ────────────────────────────────────────────────────────────────
profile_completed: {
key: 'profile_completed',
type: 'milestone',
label: 'Profile Complete',
description: 'Filled out all personal profile information.',
trigger: 'profile',
},
// ── Social ─────────────────────────────────────────────────────────────────
first_referral: {
key: 'first_referral',
type: 'milestone',
label: 'Referral Champion',
description: 'Successfully referred a user to Philproperties.',
trigger: 'social',
},
};
module.exports = {
EARLY_ACCESS_CUTOFF,
ACHIEVEMENT_REGISTRY,
};
+28
View File
@@ -0,0 +1,28 @@
/***********************************************************************************************************************************************************************
* File Name: cronPresets.data.js
* Type of Program: Data
* Description: Friendly schedule presets for admin-configurable notification
* crons. The UI only ever offers these six options — no raw cron
* expressions are accepted from the client, so `updateSetting`
* in notificationSettings.controller.js validates against this map.
*
* Author: Kenneth Obsequio (@lash0000)
* Date Created: Jul. 2, 2026
***********************************************************************************************************************************************************************/
'use strict';
const CRON_PRESETS = {
every_minute: '* * * * *',
every_5_min: '*/5 * * * *',
every_15_min: '*/15 * * * *',
hourly: '0 * * * *',
every_6_hours: '0 */6 * * *',
daily: '0 0 * * *',
};
// Reverse lookup — cron string -> preset key (used to label a job's current schedule)
const CRON_PRESET_BY_EXPRESSION = Object.fromEntries(
Object.entries(CRON_PRESETS).map(([key, expr]) => [expr, key])
);
module.exports = { CRON_PRESETS, CRON_PRESET_BY_EXPRESSION };
-105
View File
@@ -1,105 +0,0 @@
const FONT = 'font-family: Arial, sans-serif; font-size: 14px; color: #000;';
const wrap = (body) => `
<html>
<body style="${FONT} line-height: 1.6;">
${body.trim()}
<br><br>
<p style="margin: 0;">Regards,<br>Philproperties IT Team</p>
<p style="margin: 0; font-size: 12px; color: #555;">This is an automated message from STARR System. Please do not reply.</p>
</body>
</html>`.trim();
export const emailTemplates = {
OTP: ({ otp, expiryMinutes = 10 }) => ({
subject: "Email OTP Verification - STARR System",
html: wrap(`
<p>Dear User,</p>
<p>Please use the One-Time Password (OTP) below to verify your email address. This code is valid for <strong>${expiryMinutes} minutes</strong>.</p>
<p style="font-size: 28px; font-weight: bold; letter-spacing: 6px;">${otp}</p>
<p>For security reasons, please do not share this code with anyone. If you did not request this, please contact the administrator.</p>
`),
}),
WELCOME: ({ name }) => ({
subject: "Welcome to STARR System",
html: wrap(`
<p>Dear ${name},</p>
<p>We are pleased to welcome you to the STARR System. Your account has been successfully created and is now ready for use.</p>
<p>You may now access your dashboard and begin using the available services.</p>
<p>We look forward to supporting you.</p>
`),
}),
PASSWORD_CHANGED: () => ({
subject: "Password Update Confirmation - STARR System",
html: wrap(`
<p>Dear User,</p>
<p>This is to confirm that your account password has been successfully changed.</p>
<p>If you did not perform this action, please reset your password immediately or contact support.</p>
<p>For your security, we recommend using a strong and unique password.</p>
`),
}),
ADDED_TO_GROUP: ({ groupName }) => ({
subject: "Group Assignment Notification - STARR System",
html: wrap(`
<p>Dear User,</p>
<p>You have been assigned to the group <strong>${groupName}</strong> in the STARR System.</p>
<p>This assignment grants you access to shared resources and collaboration tools within the group.</p>
<p>Please log in to your account to view group details.</p>
`),
}),
TASK_ASSIGNED: ({ taskTitle, dueDate }) => ({
subject: "New Task Assignment - STARR System",
html: wrap(`
<p>Dear User,</p>
<p>You have been assigned a new task in the STARR System.</p>
<table style="${FONT}">
<tr><td><strong>Task</strong></td><td>${taskTitle}</td></tr>
<tr><td><strong>Due Date</strong></td><td>${dueDate}</td></tr>
</table>
<p>Kindly ensure completion within the specified timeframe.</p>
`),
}),
BAN_LIFTED: ({ name, email, date }) => ({
subject: "Account Suspension Lifted - STARR System",
html: wrap(`
<p>Dear ${name},</p>
<p>We are writing to inform you that the suspension on your account (<strong>${email}</strong>) has been lifted effective <strong>${date}</strong>.</p>
<p>You may now log in and resume access to all services within the STARR System.</p>
<p>If you have any concerns, please do not hesitate to contact your administrator.</p>
`),
}),
BANNED: ({ name, email, date, reason, ban_type }) => ({
subject: "Account Suspension Notice - STARR System",
html: wrap(`
<p>Dear ${name},</p>
<p>Your account (<strong>${email}</strong>) has been ${ban_type === 'permanent' ? 'permanently' : 'temporarily'} suspended from the STARR System effective <strong>${date}</strong>.</p>
<table style="${FONT}">
<tr><td><strong>Reason</strong></td><td>${reason}</td></tr>
<tr><td><strong>Duration</strong></td><td>${ban_type === 'permanent' ? 'Permanent' : 'Temporary'}</td></tr>
</table>
<p>During this period, access to all system services has been revoked.${ban_type === 'permanent' ? '' : ' This suspension may be lifted upon review by the administrator.'}</p>
<p>If you believe this was made in error, please contact your administrator.</p>
`),
}),
ADD_STAFF: ({ name, email, password, expiryHours = 24 }) => ({
subject: "Your Staff Account Has Been Created - STARR System",
html: wrap(`
<p>Dear ${name},</p>
<p>Your staff account has been successfully created in the STARR System. Below are your login credentials:</p>
<table style="${FONT}">
<tr><td><strong>Email</strong></td><td>${email}</td></tr>
<tr><td><strong>Password</strong></td><td>${password}</td></tr>
</table>
<p>This temporary password is valid for <strong>${expiryHours} hours</strong>. You will be required to change it upon first login.</p>
<p>If you did not request this account or believe this was created in error, please contact your administrator immediately to have it deactivated.</p>
<p>For security, please do not share your credentials with anyone. If you need a new password, contact your administrator.</p>
`),
}),
};
+28
View File
@@ -0,0 +1,28 @@
/***********************************************************************************************************************************************************************
* File Name: email_template_enrichers.data.js
* Type of Program: Data / Registry
* Description: Admin-edited templates are plain HTML — no conditionals or
* expressions allowed. Any type that used to branch on data in
* JS (e.g. BANNED's permanent/temporary wording) gets that branch
* precomputed here into flat placeholder keys BEFORE substitution,
* so the stored HTML only ever needs straight {{key}} swaps.
* Adding a new derived placeholder for a type should only require
* adding/editing one entry here.
* Author: Kenneth Obsequio (@lash0000)
* Date Created: Jul. 3, 2026
***********************************************************************************************************************************************************************/
const ENRICHERS = {
OTP: (data) => ({ expiryMinutes: 10, ...data }),
BANNED: (data) => ({
...data,
duration_word: data.ban_type === 'permanent' ? 'permanently' : 'temporarily',
duration_label: data.ban_type === 'permanent' ? 'Permanent' : 'Temporary',
suspension_note: data.ban_type === 'permanent' ? '' : ' This suspension may be lifted upon review by the administrator.',
}),
};
const enrichEmailData = (type, data = {}) => (ENRICHERS[type] ? ENRICHERS[type](data) : data);
module.exports = { enrichEmailData };
+43 -13
View File
@@ -20,9 +20,10 @@
*
* Current types:
* Admin : task_overdue, user_registration, nogrp_user_registered
* User : user_task_overdue, achievement, course_unlocked,
* User : task_requirements_updated, user_task_overdue, achievement, course_unlocked,
* course_completed, certificate_issued, task_reminder, announcement,
* nogrp_welcome, tier_expired
* Both : broadcast (admin-composed, sent via notification_broadcasts CRUD)
*
* Author: Kenneth Obsequio (@lash0000)
* Date Created: Jun. 19, 2026
@@ -87,6 +88,20 @@ const NOTIFICATION_REGISTRY = {
// ─────────────────────────────────────────────────────────────────────────
// ── 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',
@@ -106,12 +121,12 @@ const NOTIFICATION_REGISTRY = {
type: 'task',
scope: 'user',
trigger: 'cron',
build({ taskName, deadline }) {
build({ taskName, deadline, taskListId = null, groupId = null }) {
return {
type: 'task',
title: 'Task Deadline Approaching',
message: `"${taskName}" is due on ${fmtDate(deadline)}.`,
data: { taskName, deadline },
data: { taskName, deadline, taskListId, groupId },
};
},
},
@@ -136,12 +151,12 @@ const NOTIFICATION_REGISTRY = {
type: 'course',
scope: 'user',
trigger: 'event',
build({ courseTitle }) {
build({ courseTitle, courseUuid = null }) {
return {
type: 'course',
title: 'New Course Available',
message: `"${courseTitle}" has been added to your learning library.`,
data: { courseTitle },
data: { courseTitle, courseUuid },
};
},
},
@@ -150,12 +165,12 @@ const NOTIFICATION_REGISTRY = {
type: 'course',
scope: 'user',
trigger: 'event',
build({ courseTitle }) {
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 },
data: { courseTitle, courseUuid },
};
},
},
@@ -179,7 +194,7 @@ const NOTIFICATION_REGISTRY = {
type: 'announcement',
scope: 'user',
trigger: 'event',
build({ groupName, groupCode, accType }) {
build({ groupName, groupCode, accType, groupId = null }) {
const greeting = accType === 'admin'
? 'Welcome, Administrator!'
: accType === 'staff'
@@ -189,7 +204,7 @@ const NOTIFICATION_REGISTRY = {
type: 'announcement',
title: 'Welcome to Philproperties',
message: groupName ? `${greeting} You have been added to ${groupName}.` : greeting,
data: { groupName, groupCode, accType },
data: { groupName, groupCode, accType, groupId },
};
},
},
@@ -214,12 +229,12 @@ const NOTIFICATION_REGISTRY = {
type: 'assessment',
scope: 'user',
trigger: 'event',
build({ assessmentTitle, courseTitle }) {
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 },
data: { assessmentTitle, courseTitle, courseUuid },
};
},
},
@@ -239,17 +254,32 @@ const NOTIFICATION_REGISTRY = {
},
},
// ── Broadcast (admin-composed, manual) ───────────────────────────────────
broadcast: {
type: 'announcement',
scope: 'both',
trigger: 'manual',
build({ title, message, targetType = null, targetId = null, groupId = null }) {
return {
type: 'announcement',
title,
message,
data: { targetType, targetId, groupId },
};
},
},
// ── Tier ──────────────────────────────────────────────────────────────────
tier_expired: {
type: 'tier_expired',
scope: 'user',
trigger: 'cron',
build({ tier, label }) {
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 },
data: { tier, label, planId },
};
},
},