Initial commit: teaching institution management API.
Express/MongoDB backend with JWT auth, RBAC, S3 uploads, notifications, and scheduled jobs.
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
// /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 surname 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 surname 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
|
||||
};
|
||||
Reference in New Issue
Block a user