perform test

test to courses

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-06-24 12:48:51 +08:00
parent 358fecb510
commit 9b8577b79b
27 changed files with 1122 additions and 221 deletions
+109
View File
@@ -0,0 +1,109 @@
/***********************************************************************************************************************************************************************
* File Name : issue_certificates.cron.js
* Type : Cron Job
* Description : Issues certificates for users who passed a course assessment
* 45 minutes ago. Runs every 5 minutes and processes any
* pending_certificates row where issue_at <= NOW() and
* processed_at IS NULL.
*
* For each ready row it:
* 1. Grants the course_completed_<uuid> achievement (the key
* MyCertificates / Profile use to display certificate cards).
* 2. Sends a 'certificate_issued' UserNotification.
* 3. Marks the row processed_at = NOW() so it never fires again.
*
* Safety pattern: processed_at is set only after both step 1 and
* step 2 succeed. If the process restarts mid-run the row will be
* picked up again on the next tick — both DB writes are idempotent.
*
* Schedule : Every hour at minute 5 ("5 * * * *"). Registered by
* cron/client.cron.js.
*
* Author: Kenneth Obsequio (@lash0000)
* Date Created: Jun. 24, 2026
***********************************************************************************************************************************************************************/
'use strict';
const { Op } = require('sequelize');
const PendingCertificate = require('../../models/courses/pending_certificate.mdl');
const mdl_Achievements = require('../../models/users/achievements.mdl');
const UserNotification = require('../../models/notifications/user_notification.mdl');
const { NOTIFICATION_REGISTRY } = require('../../data/notifications.data');
const DELAY_MS = 5 * 60 * 1000; // 5 minutes
async function run() {
// ── 1. Fetch all rows ready to process ────────────────────────────────────
let rows;
try {
rows = await PendingCertificate.findAll({
where: {
issue_at: { [Op.lte]: new Date() },
processed_at: null,
},
raw: true,
});
} catch (err) {
console.error('[CRON][ISSUE CERTS] Failed to query pending_certificates:', err);
return;
}
if (rows.length === 0) return;
console.log(`[CRON][ISSUE CERTS] Processing ${rows.length} pending certificate(s).`);
for (const row of rows) {
const { pending_id, user_id, course_uuid, course_title } = row;
const achKey = `course_completed_${course_uuid}`;
try {
// ── 2. Grant course_completed_<uuid> achievement (idempotent) ──────
const existing = await mdl_Achievements.findOne({ where: { user_id, key: achKey } });
if (!existing) {
await mdl_Achievements.create({
user_id,
type: 'milestone',
key: achKey,
label: 'Certificate of Completion',
description: course_title ?? '',
granted_at: new Date(),
metadata: { courseTitle: course_title, courseUuid: course_uuid },
});
}
// ── 3. Send certificate_issued notification ────────────────────────
await UserNotification.create({
user_id,
...NOTIFICATION_REGISTRY.certificate_issued.build({
courseTitle: course_title ?? '',
courseUuid: course_uuid,
}),
});
// ── 4. Mark row processed ─────────────────────────────────────────
await PendingCertificate.update(
{ processed_at: new Date() },
{ where: { pending_id } }
);
console.log(`[CRON][ISSUE CERTS] Issued certificate for user ${user_id} / course ${course_uuid}.`);
} catch (err) {
// Log and continue — next tick will retry this row
if (err?.parent?.code !== '23505') {
console.error(`[CRON][ISSUE CERTS] Failed for pending_id ${pending_id}:`, err);
} else {
// Unique constraint: achievement already exists — still mark processed
await PendingCertificate.update(
{ processed_at: new Date() },
{ where: { pending_id } }
).catch(() => {});
}
}
}
}
module.exports = {
name: 'issueCertificates',
schedule: '5 * * * *',
run,
};