mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
114 lines
5.5 KiB
JavaScript
114 lines
5.5 KiB
JavaScript
/***********************************************************************************************************************************************************************
|
|
* File Name: certificate.controller.js (client)
|
|
* Type of Program: Controller
|
|
* Description: Issues a PDF certificate for a completed course.
|
|
* A certificate is available only when the user has passed the course assessment.
|
|
* Certificate records are persisted (findOrCreate) so the same cert_no/ref_no is
|
|
* returned on every subsequent download.
|
|
*
|
|
* Author: Kenneth Obsequio (@lash0000)
|
|
* Date Created: Jun. 18, 2026
|
|
***********************************************************************************************************************************************************************/
|
|
'use strict';
|
|
|
|
const R = require('../../utils/response.util');
|
|
const mdl_Users = require('../../models/users/users.mdl');
|
|
const { generateCertificate } = require('../../services/certificate.service');
|
|
const { ensureCertificateRecord, formatInstructors } = require('../../services/certificate-record.service');
|
|
const { fmtDate } = require('../../utils/datetime.util');
|
|
|
|
const {
|
|
Course,
|
|
CourseAssessment,
|
|
CourseInstructor,
|
|
} = require('../../models/courses/courses.associations');
|
|
|
|
const notDeleted = { deletedAt: null };
|
|
|
|
// ─── GET /api/client/certificates/:courseUuid ──────────────────────────────────
|
|
|
|
exports.getCertificate = async (req, res) => {
|
|
try {
|
|
const { courseUuid } = req.params;
|
|
const user_id = req.user.user_id;
|
|
|
|
// ── 1. Resolve course ──────────────────────────────────────────────────────
|
|
const course = await Course.findOne({
|
|
where: { uuid: courseUuid, ...notDeleted },
|
|
attributes: ['course_id', 'title', 'course_code', 'duration_seconds'],
|
|
include: [
|
|
{
|
|
model: CourseAssessment,
|
|
as: 'assessment',
|
|
attributes: ['assessment_id'],
|
|
required: false,
|
|
},
|
|
{
|
|
model: CourseInstructor,
|
|
as: 'instructors',
|
|
attributes: ['display_name', 'order_index'],
|
|
required: false,
|
|
order: [['order_index', 'ASC']],
|
|
},
|
|
],
|
|
});
|
|
|
|
if (!course) return R.error(res, 'Course not found.', 404);
|
|
|
|
if (!course.assessment) {
|
|
return R.error(res, 'This course does not have an assessment — no certificate available.', 404);
|
|
}
|
|
|
|
// ── 2. Get user's name ──────────────────────────────────────────────────────
|
|
const user = await mdl_Users.findByPk(user_id, { attributes: ['personal_info'] });
|
|
const fullName = user?.personal_info?.name?.full_name?.trim() || 'Participant';
|
|
|
|
// ── 3. Resolve or create the certificate record ─────────────────────────────
|
|
// Shared with the hourly issuance cron (cron/jobs/issue_certificates.cron.js)
|
|
// so both write through the same cert_no/ref_no sequence.
|
|
const cert = await ensureCertificateRecord({ userId: user_id, courseId: course.course_id });
|
|
if (!cert) {
|
|
return R.error(res, 'Certificate not available — course assessment not passed yet.', 403);
|
|
}
|
|
|
|
// Always use live instructors from course_instructors table for the PDF,
|
|
// in case they changed since the certificate row was created.
|
|
const liveInstructors = formatInstructors(course.instructors ?? []);
|
|
|
|
// ── 4. Format issued date as MM/DD/YY HH:MM AM/PM ───────────────────────────
|
|
const issuedDate = new Date(cert.issued_at);
|
|
const dateStr = fmtDate(issuedDate);
|
|
|
|
// ── 5. Generate PDF ────────────────────────────────────────────────────────
|
|
const pdf = await generateCertificate({
|
|
name: fullName,
|
|
course: course.title,
|
|
date: dateStr,
|
|
cert_no: cert.cert_no,
|
|
ref_no: cert.ref_no,
|
|
instructors: liveInstructors,
|
|
length: cert.length_str ?? '',
|
|
});
|
|
|
|
// ── 6. Stream response ─────────────────────────────────────────────────────
|
|
const nameParts = fullName.trim().split(/\s+/);
|
|
const lastName = nameParts.length > 1 ? nameParts[nameParts.length - 1] : nameParts[0];
|
|
const firstName = nameParts.length > 1 ? nameParts.slice(0, -1).join(' ') : '';
|
|
const safeTitle = course.title.replace(/[/\\:*?"<>|]/g, '').trim();
|
|
const filename = `${lastName},${firstName}_${safeTitle}_${cert.cert_no}.pdf`;
|
|
const asciiName = filename.replace(/[^\x20-\x7E]/g, '_').replace(/"/g, '_');
|
|
const encodedName = encodeURIComponent(filename);
|
|
|
|
res.set({
|
|
'Content-Type': 'application/pdf',
|
|
'Content-Disposition': `attachment; filename="${asciiName}"; filename*=UTF-8''${encodedName}`,
|
|
'Content-Length': pdf.length,
|
|
});
|
|
|
|
return res.send(pdf);
|
|
} catch (err) {
|
|
console.error('[CLIENT][CERTIFICATE]', err);
|
|
return R.error(res, 'Could not generate certificate.', 500);
|
|
}
|
|
};
|