Support uploading structured JSON to create courses, classes, and students without wiping existing data, and merge user name fields while adding gender and registration details from source sheets.
182 lines
5.8 KiB
JavaScript
182 lines
5.8 KiB
JavaScript
// /components/certificates/certificateService.js
|
|
|
|
const path = require('path');
|
|
const Certificate = require('./certificateModel');
|
|
const User = require('../users/userModel');
|
|
const AppError = require('../../utils/AppError');
|
|
const { commitTempFile, generatePresignedUrl, deleteFromBucket } = require('../../utils/s3Client');
|
|
const eventEmitter = require('../../events/eventEmitter');
|
|
const EVENT_NAMES = require('../../constants/eventNames');
|
|
const { parsePaginationAndSort, buildFilterQuery, calculateMeta } = require('../../utils/pagination');
|
|
|
|
const createCertificate = async (data) => {
|
|
const user = await User.findById(data.user);
|
|
if (!user) throw new AppError('USER_NOT_FOUND');
|
|
|
|
// Move file from Temp bucket to Permanent Storage bucket
|
|
const targetKey = `certificates/cert-${Date.now()}-${path.basename(data.tempFileName)}`;
|
|
const { fileKey } = await commitTempFile(data.tempFileName, targetKey);
|
|
|
|
const certificate = await Certificate.create({
|
|
user: data.user,
|
|
course: data.course || null,
|
|
title: data.title,
|
|
issuer: data.issuer || '',
|
|
fileKey,
|
|
originalName: data.originalName || path.basename(data.tempFileName),
|
|
isOfficial: data.isOfficial || false
|
|
});
|
|
|
|
user.certificates.push(certificate._id);
|
|
await user.save();
|
|
|
|
eventEmitter.emit(EVENT_NAMES.CERTIFICATE_ISSUED, { certificateId: certificate._id, userId: user._id });
|
|
|
|
const resultObj = certificate.toObject();
|
|
resultObj.presignedUrl = await generatePresignedUrl(certificate.fileKey);
|
|
return resultObj;
|
|
};
|
|
|
|
const getCertificateById = async (id) => {
|
|
const certificate = await Certificate.findById(id)
|
|
.populate('user', 'name username nationalIdCode')
|
|
.populate('course', 'title type');
|
|
if (!certificate) {
|
|
throw new AppError('CERTIFICATE_NOT_FOUND');
|
|
}
|
|
|
|
const resultObj = certificate.toObject();
|
|
resultObj.presignedUrl = await generatePresignedUrl(certificate.fileKey);
|
|
return resultObj;
|
|
};
|
|
|
|
const getAllCertificates = async (queryParams) => {
|
|
const { page, limit, skip, sort } = parsePaginationAndSort(queryParams);
|
|
const filter = buildFilterQuery(queryParams, ['title', 'issuer']);
|
|
|
|
const [certificates, totalCount] = await Promise.all([
|
|
Certificate.find(filter)
|
|
.populate('user', 'name username')
|
|
.populate('course', 'title')
|
|
.sort(sort)
|
|
.skip(skip)
|
|
.limit(limit),
|
|
Certificate.countDocuments(filter)
|
|
]);
|
|
|
|
const listWithUrls = await Promise.all(
|
|
certificates.map(async (cert) => {
|
|
const item = cert.toObject();
|
|
item.presignedUrl = await generatePresignedUrl(cert.fileKey);
|
|
return item;
|
|
})
|
|
);
|
|
|
|
const meta = calculateMeta(totalCount, page, limit);
|
|
return { data: listWithUrls, meta };
|
|
};
|
|
|
|
const updateCertificate = async (id, updateData) => {
|
|
const certificate = await Certificate.findById(id);
|
|
if (!certificate) {
|
|
throw new AppError('CERTIFICATE_NOT_FOUND');
|
|
}
|
|
|
|
if (updateData.tempFileName) {
|
|
const targetKey = `certificates/cert-${Date.now()}-${path.basename(updateData.tempFileName)}`;
|
|
const { fileKey } = await commitTempFile(updateData.tempFileName, targetKey);
|
|
// Delete old file from storage bucket
|
|
await deleteFromBucket(certificate.fileKey);
|
|
certificate.fileKey = fileKey;
|
|
}
|
|
|
|
if (updateData.title) certificate.title = updateData.title;
|
|
if (updateData.issuer !== undefined) certificate.issuer = updateData.issuer;
|
|
if (updateData.isOfficial !== undefined) certificate.isOfficial = updateData.isOfficial;
|
|
|
|
await certificate.save();
|
|
|
|
const resultObj = certificate.toObject();
|
|
resultObj.presignedUrl = await generatePresignedUrl(certificate.fileKey);
|
|
return resultObj;
|
|
};
|
|
|
|
const deleteCertificate = async (id) => {
|
|
const certificate = await Certificate.findById(id);
|
|
if (!certificate) {
|
|
throw new AppError('CERTIFICATE_NOT_FOUND');
|
|
}
|
|
|
|
await deleteFromBucket(certificate.fileKey);
|
|
await User.findByIdAndUpdate(certificate.user, { $pull: { certificates: certificate._id } });
|
|
await Certificate.findByIdAndDelete(id);
|
|
return null;
|
|
};
|
|
|
|
const searchCertificates = async (queryParams) => {
|
|
return getAllCertificates(queryParams);
|
|
};
|
|
|
|
const uploadUserCertificate = async (userId, data) => {
|
|
const user = await User.findById(userId);
|
|
if (!user) throw new AppError('USER_NOT_FOUND');
|
|
|
|
const targetKey = `certificates/cert-${Date.now()}-${path.basename(data.tempFileName)}`;
|
|
const { fileKey } = await commitTempFile(data.tempFileName, targetKey);
|
|
|
|
const certificate = await Certificate.create({
|
|
user: userId,
|
|
course: data.course || null,
|
|
title: data.title,
|
|
issuer: data.issuer || 'Self-Uploaded',
|
|
fileKey,
|
|
originalName: data.originalName || path.basename(data.tempFileName),
|
|
isOfficial: false
|
|
});
|
|
|
|
user.certificates.push(certificate._id);
|
|
await user.save();
|
|
|
|
eventEmitter.emit(EVENT_NAMES.CERTIFICATE_UPLOADED, { certificateId: certificate._id, userId });
|
|
|
|
const resultObj = certificate.toObject();
|
|
resultObj.presignedUrl = await generatePresignedUrl(certificate.fileKey);
|
|
return resultObj;
|
|
};
|
|
|
|
const getMyCertificates = async (userId, queryParams) => {
|
|
const filter = { user: userId, ...buildFilterQuery(queryParams) };
|
|
const { page, limit, skip, sort } = parsePaginationAndSort(queryParams);
|
|
|
|
const [certificates, totalCount] = await Promise.all([
|
|
Certificate.find(filter)
|
|
.populate('course', 'title type')
|
|
.sort(sort)
|
|
.skip(skip)
|
|
.limit(limit),
|
|
Certificate.countDocuments(filter)
|
|
]);
|
|
|
|
const listWithUrls = await Promise.all(
|
|
certificates.map(async (cert) => {
|
|
const item = cert.toObject();
|
|
item.presignedUrl = await generatePresignedUrl(cert.fileKey);
|
|
return item;
|
|
})
|
|
);
|
|
|
|
const meta = calculateMeta(totalCount, page, limit);
|
|
return { data: listWithUrls, meta };
|
|
};
|
|
|
|
module.exports = {
|
|
createCertificate,
|
|
getCertificateById,
|
|
getAllCertificates,
|
|
updateCertificate,
|
|
deleteCertificate,
|
|
searchCertificates,
|
|
uploadUserCertificate,
|
|
getMyCertificates
|
|
};
|