// /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 };