mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
+5
-105
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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 };
|
||||
@@ -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>
|
||||
`),
|
||||
}),
|
||||
};
|
||||
@@ -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
@@ -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 },
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user