const path = require('path'); const Certificate = require('../models/certificate'); const Department = require('../models/department'); const Level = require('../models/level'); const writeAuditLog = require('../audit/writeAuditLog'); const AUDIT_ACTIONS = require('../constants/auditAction'); const { generateSignedUrl } = require('../utils/signedUrl'); function normalizePath(filePath) { if (!filePath) return undefined; return path.basename(filePath.replace(/\\/g, '/')); } // GET /admin/certificate exports.index = async (req, res) => { try { const { search, status } = req.query; const filter = {}; if (search) { filter.$or = [ { certification_number: { $regex: search, $options: 'i' } }, { student_name: { $regex: search, $options: 'i' } } ]; } if (status) filter.status = status; const [certificates, departments, levels] = await Promise.all([ Certificate.find(filter).populate('department level').sort({ createdAt: -1 }), Department.find(), Level.find() ]); res.render('admin/certificate/index', { certificates, departments, levels, query: req.query, user: req.session.user, layout: 'layouts/admin', title: 'Certificates' }); } catch (err) { console.error(err); req.flash('error', 'Error loading certificates'); res.redirect('/admin/dashboard'); } }; // GET /admin/certificate/create exports.createForm = async (req, res) => { try { const [departments, levels] = await Promise.all([Department.find(), Level.find()]); res.render('admin/certificate/create', { departments, levels, user: req.session.user, layout: 'layouts/admin', title: 'Create Certificate' }); } catch (err) { req.flash('error', 'Error'); res.redirect('/admin/certificate'); } }; // POST /admin/certificate/create exports.create = async (req, res) => { try { const data = { ...req.body }; const imgPath = req.files?.certificate_image?.[0]?.path; if (imgPath) data.certificate_image = normalizePath(imgPath); const cert = new Certificate(data); await cert.save(); await writeAuditLog({ model: 'Certificate', documentId: cert._id, action: AUDIT_ACTIONS.CREATE_CERTIFICATE, before: null, after: cert.toObject(), req }); req.flash('success', 'Certificate created'); res.redirect('/admin/certificate'); } catch (err) { console.error(err); try { const [departments, levels] = await Promise.all([Department.find(), Level.find()]); res.render('admin/certificate/create', { error: err.message, formData: req.body, departments, levels, user: req.session.user, layout: 'layouts/admin', title: 'Create Certificate' }); } catch { req.flash('error', err.message); res.redirect('/admin/certificate'); } } }; // GET /admin/certificate/:id/edit exports.editForm = async (req, res) => { try { const cert = await Certificate.findById(req.params.id).populate('department level'); if (!cert) { req.flash('error', 'Not found'); return res.redirect('/admin/certificate'); } const [departments, levels] = await Promise.all([Department.find(), Level.find()]); res.render('admin/certificate/edit', { cert, departments, levels, user: req.session.user, layout: 'layouts/admin', title: 'Edit Certificate' }); } catch (err) { req.flash('error', 'Error'); res.redirect('/admin/certificate'); } }; // POST /admin/certificate/:id/edit exports.update = async (req, res) => { try { const cert = await Certificate.findById(req.params.id); if (!cert) { req.flash('error', 'Not found'); return res.redirect('/admin/certificate'); } const before = cert.toObject(); const fields = ['certification_number','student_name','program_name','department','level', 'issued_date','status','passport_number','address']; fields.forEach(f => { if (req.body[f] !== undefined) cert[f] = req.body[f]; }); const imgPath = req.files?.certificate_image?.[0]?.path; if (imgPath) cert.certificate_image = normalizePath(imgPath); await cert.save(); await writeAuditLog({ model: 'Certificate', documentId: cert._id, action: AUDIT_ACTIONS.UPDATE_CERTIFICATE, before, after: cert.toObject(), req }); req.flash('success', 'Certificate updated'); res.redirect('/admin/certificate'); } catch (err) { req.flash('error', err.message); res.redirect('back'); } }; // POST /admin/certificate/:id/delete exports.destroy = async (req, res) => { try { const cert = await Certificate.findById(req.params.id); if (!cert) { req.flash('error', 'Not found'); return res.redirect('/admin/certificate'); } await writeAuditLog({ model: 'Certificate', documentId: cert._id, action: AUDIT_ACTIONS.DELETE_CERTIFICATE, before: cert.toObject(), after: null, req }); await cert.deleteOne(); req.flash('success', 'Certificate deleted'); res.redirect('/admin/certificate'); } catch (err) { req.flash('error', 'Error deleting'); res.redirect('/admin/certificate'); } }; // GET /api/verify-certificate/:cert_id?api_key=xxx exports.apiVerify = async (req, res) => { try { const cert = await Certificate.findOne({ certification_number: { $regex: new RegExp('^' + req.params.cert_id + '$', 'i') } }).populate('department level'); if (!cert) return res.status(404).json({ error: 'Certificate not found' }); if (cert.status === 'revoked') return res.status(404).json({ error: 'Certificate has been revoked' }); const baseUrl = process.env.BACKEND_URL ?? `${req.protocol}://${req.get('host')}`; const buildUrl = (f) => f ? [generateSignedUrl(baseUrl, path.basename(f))] : undefined; const response = { full_name: cert.student_name, certification_title: cert.program_name, certificate_id: cert.certification_number, }; if (cert.passport_number) response.passport_number = cert.passport_number; if (cert.address) response.address = cert.address; const imgs = buildUrl(cert.certificate_image); if (imgs) response.certificate_image = imgs; return res.json(response); } catch (err) { console.error(err); return res.status(500).json({ error: 'Internal server error' }); } };