forked from UKSOURCE/cms.hailearning.edu.vn
first commit
This commit is contained in:
261
controllers/degreeController.js
Normal file
261
controllers/degreeController.js
Normal file
@@ -0,0 +1,261 @@
|
||||
const path = require('path');
|
||||
const Degree = require('../models/degree');
|
||||
const Department = require('../models/department');
|
||||
const Level = require('../models/level');
|
||||
const writeAuditLog = require('../audit/writeAuditLog');
|
||||
const AUDIT_ACTIONS = require('../constants/auditAction');
|
||||
|
||||
// Helper: store only filename, served via /secure-files/ route
|
||||
function normalizePath(filePath) {
|
||||
if (!filePath) return undefined;
|
||||
return path.basename(filePath.replace(/\\/g, '/'));
|
||||
}
|
||||
|
||||
// GET /admin/degree
|
||||
exports.index = async (req, res) => {
|
||||
try {
|
||||
const { search, type, department, level, status } = req.query;
|
||||
const filter = {};
|
||||
if (search) {
|
||||
filter.$or = [
|
||||
{ qualification_number: { $regex: search, $options: 'i' } },
|
||||
{ certification_number: { $regex: search, $options: 'i' } },
|
||||
{ student_name: { $regex: search, $options: 'i' } }
|
||||
];
|
||||
}
|
||||
if (type) filter.type = type;
|
||||
if (department) filter.department = department;
|
||||
if (level) filter.level = level;
|
||||
if (status) filter.status = status;
|
||||
|
||||
const [degrees, departments, levels] = await Promise.all([
|
||||
Degree.find(filter).populate('department level').sort({ createdAt: -1 }),
|
||||
Department.find(),
|
||||
Level.find()
|
||||
]);
|
||||
|
||||
res.render('admin/degree/index', {
|
||||
degrees, departments, levels,
|
||||
query: req.query,
|
||||
user: req.session.user,
|
||||
layout: 'layouts/admin',
|
||||
title: 'Quản lý Văn bằng'
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('degreeController.index error:', err);
|
||||
req.flash('error', 'Đã xảy ra lỗi khi tải danh sách văn bằng');
|
||||
res.redirect('/admin/dashboard');
|
||||
}
|
||||
};
|
||||
|
||||
// GET /admin/degree/create
|
||||
exports.createForm = async (req, res) => {
|
||||
try {
|
||||
const [departments, levels] = await Promise.all([Department.find(), Level.find()]);
|
||||
res.render('admin/degree/create', {
|
||||
departments, levels,
|
||||
user: req.session.user,
|
||||
layout: 'layouts/admin',
|
||||
title: 'Tạo Văn bằng mới'
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('degreeController.createForm error:', err);
|
||||
req.flash('error', 'Đã xảy ra lỗi');
|
||||
res.redirect('/admin/degree');
|
||||
}
|
||||
};
|
||||
|
||||
// POST /admin/degree/create
|
||||
exports.create = async (req, res) => {
|
||||
try {
|
||||
const degreeData = { ...req.body };
|
||||
const degreeImagePath = req.files?.degree_image?.[0]?.path;
|
||||
const certificateImagePath = req.files?.certificate_image?.[0]?.path;
|
||||
if (degreeImagePath) degreeData.degree_image = normalizePath(degreeImagePath);
|
||||
if (certificateImagePath) degreeData.certificate_image = normalizePath(certificateImagePath);
|
||||
|
||||
const degree = new Degree(degreeData);
|
||||
await degree.save();
|
||||
|
||||
await writeAuditLog({
|
||||
model: 'Degree', documentId: degree._id,
|
||||
action: AUDIT_ACTIONS.CREATE_DEGREE,
|
||||
before: null, after: degree.toObject(), req
|
||||
});
|
||||
|
||||
req.flash('success', 'Degree created');
|
||||
res.redirect('/admin/degree');
|
||||
} catch (err) {
|
||||
console.error('degreeController.create error:', err);
|
||||
try {
|
||||
const [departments, levels] = await Promise.all([Department.find(), Level.find()]);
|
||||
res.render('admin/degree/create', {
|
||||
error: err.message, formData: req.body,
|
||||
departments, levels,
|
||||
user: req.session.user,
|
||||
layout: 'layouts/admin',
|
||||
title: 'Tạo Văn bằng mới'
|
||||
});
|
||||
} catch (renderErr) {
|
||||
req.flash('error', err.message);
|
||||
res.redirect('/admin/degree');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// GET /admin/degree/:id/edit
|
||||
exports.editForm = async (req, res) => {
|
||||
try {
|
||||
const degree = await Degree.findById(req.params.id).populate('department level');
|
||||
if (!degree) {
|
||||
req.flash('error', 'Degree not found');
|
||||
return res.redirect('/admin/degree');
|
||||
}
|
||||
const [departments, levels] = await Promise.all([Department.find(), Level.find()]);
|
||||
res.render('admin/degree/edit', {
|
||||
degree, departments, levels,
|
||||
user: req.session.user,
|
||||
layout: 'layouts/admin',
|
||||
title: 'Chỉnh sửa Văn bằng'
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('degreeController.editForm error:', err);
|
||||
req.flash('error', 'Đã xảy ra lỗi');
|
||||
res.redirect('/admin/degree');
|
||||
}
|
||||
};
|
||||
|
||||
// POST /admin/degree/:id/edit
|
||||
exports.update = async (req, res) => {
|
||||
try {
|
||||
const degree = await Degree.findById(req.params.id);
|
||||
if (!degree) {
|
||||
req.flash('error', 'Degree not found');
|
||||
return res.redirect('/admin/degree');
|
||||
}
|
||||
const beforeData = degree.toObject();
|
||||
const fields = [
|
||||
'qualification_number', 'certification_number', 'student_name', 'program_name',
|
||||
'type', 'department', 'level', 'issued_date', 'status',
|
||||
'passport_number', 'address', 'topic_name', 'topic_short_desc'
|
||||
];
|
||||
fields.forEach(field => { if (req.body[field] !== undefined) degree[field] = req.body[field]; });
|
||||
|
||||
const degreeImagePath = req.files?.degree_image?.[0]?.path;
|
||||
const certificateImagePath = req.files?.certificate_image?.[0]?.path;
|
||||
if (degreeImagePath) degree.degree_image = normalizePath(degreeImagePath);
|
||||
if (certificateImagePath) degree.certificate_image = normalizePath(certificateImagePath);
|
||||
|
||||
await degree.save();
|
||||
await writeAuditLog({
|
||||
model: 'Degree', documentId: degree._id,
|
||||
action: AUDIT_ACTIONS.UPDATE_DEGREE,
|
||||
before: beforeData, after: degree.toObject(), req
|
||||
});
|
||||
|
||||
req.flash('success', 'Degree updated');
|
||||
res.redirect('/admin/degree');
|
||||
} catch (err) {
|
||||
console.error('degreeController.update error:', err);
|
||||
req.flash('error', err.message);
|
||||
res.redirect('back');
|
||||
}
|
||||
};
|
||||
|
||||
// POST /admin/degree/:id/delete
|
||||
exports.destroy = async (req, res) => {
|
||||
try {
|
||||
const degree = await Degree.findById(req.params.id);
|
||||
if (!degree) {
|
||||
req.flash('error', 'Degree not found');
|
||||
return res.redirect('/admin/degree');
|
||||
}
|
||||
await writeAuditLog({
|
||||
model: 'Degree', documentId: degree._id,
|
||||
action: AUDIT_ACTIONS.DELETE_DEGREE,
|
||||
before: degree.toObject(), after: null, req
|
||||
});
|
||||
await degree.deleteOne();
|
||||
req.flash('success', 'Degree deleted');
|
||||
res.redirect('/admin/degree');
|
||||
} catch (err) {
|
||||
console.error('degreeController.destroy error:', err);
|
||||
req.flash('error', 'Đã xảy ra lỗi khi xóa văn bằng');
|
||||
res.redirect('/admin/degree');
|
||||
}
|
||||
};
|
||||
|
||||
// ─── Public API ───────────────────────────────────────────────────────────────
|
||||
|
||||
function buildSecureUrl(req, filename) {
|
||||
if (!filename) return undefined;
|
||||
const baseUrl = `${req.protocol}://${req.get('host')}`;
|
||||
const name = path.basename(filename);
|
||||
return `${baseUrl}/secure-files/${name}?api_key=${req.query.api_key}`;
|
||||
}
|
||||
|
||||
// GET /api/verify-degree/:degree_id?api_key=xxx
|
||||
// Lookup by qualification_number — returns degree fields + topic_name if PhD
|
||||
exports.apiGetByQualification = async (req, res) => {
|
||||
try {
|
||||
const degree = await Degree.findOne({
|
||||
qualification_number: { $regex: new RegExp('^' + req.params.degree_id + '$', 'i') }
|
||||
}).populate('department level');
|
||||
|
||||
if (!degree) return res.status(404).json({ error: 'Degree not found' });
|
||||
if (degree.status === 'revoked') return res.status(404).json({ error: 'Degree has been revoked' });
|
||||
|
||||
const imageUrl = buildSecureUrl(req, degree.degree_image);
|
||||
|
||||
const response = {
|
||||
full_name: degree.student_name,
|
||||
program_name: degree.program_name,
|
||||
degree_id: degree.qualification_number,
|
||||
};
|
||||
|
||||
if (degree.passport_number) response.passport_number = degree.passport_number;
|
||||
if (degree.address) response.address = degree.address;
|
||||
if (imageUrl) response.degree_image = [imageUrl];
|
||||
|
||||
// topic_name present → PhD view; absent → MBA/Master view
|
||||
if (degree.topic_name) {
|
||||
response.topic_name = degree.topic_name;
|
||||
if (degree.topic_short_desc) response.topic_short_desc = degree.topic_short_desc;
|
||||
}
|
||||
|
||||
return res.json(response);
|
||||
} catch (err) {
|
||||
console.error('apiGetByQualification error:', err);
|
||||
return res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
};
|
||||
|
||||
// GET /api/verify-certificate/:cert_id?api_key=xxx
|
||||
// Lookup by certification_number — returns certificate fields (no topic_name)
|
||||
exports.apiGetByCertification = async (req, res) => {
|
||||
try {
|
||||
const degree = await Degree.findOne({
|
||||
certification_number: { $regex: new RegExp('^' + req.params.cert_id + '$', 'i') }
|
||||
}).populate('department level');
|
||||
|
||||
if (!degree) return res.status(404).json({ error: 'Certificate not found' });
|
||||
if (degree.status === 'revoked') return res.status(404).json({ error: 'Certificate has been revoked' });
|
||||
|
||||
const imageUrl = buildSecureUrl(req, degree.certificate_image);
|
||||
|
||||
const response = {
|
||||
full_name: degree.student_name,
|
||||
certification_title: degree.program_name,
|
||||
certificate_id: degree.certification_number,
|
||||
};
|
||||
|
||||
if (degree.passport_number) response.passport_number = degree.passport_number;
|
||||
if (degree.address) response.address = degree.address;
|
||||
if (imageUrl) response.certificate_image = [imageUrl];
|
||||
|
||||
return res.json(response);
|
||||
} catch (err) {
|
||||
console.error('apiGetByCertification error:', err);
|
||||
return res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user