const certificateRepository = require('../repositories/certificate.repository'); const { data, error } = require('../../../helpers/utils/wrapper'); const { BadRequestError, NotFoundError, ForbiddenError, ConflictError, } = require('../../../helpers/error'); class CertificateService { async claimCertificate(payload, actor) { try { if (!actor?.id) { return error(new BadRequestError('User ID is required')); } if (actor.role !== 'intern') { return error( new ForbiddenError('Access denied: only interns can claim certificates'), ); } const { id_project } = payload; const existingCert = await certificateRepository.findByUserAndProject(actor.id, id_project); if (existingCert) { return error(new ConflictError('Certificate already claimed for this project')); } const projectInfo = await certificateRepository.getProjectTasksAndSubmissions(id_project, actor.id); if (!projectInfo) { return error(new NotFoundError('Project not found')); } if (!projectInfo.members || projectInfo.members.length === 0) { return error( new ForbiddenError('Access denied: you are not an active member of this project'), ); } if (!projectInfo.tasks || projectInfo.tasks.length === 0) { return error( new BadRequestError('Cannot claim certificate: this project does not have any tasks assigned yet'), ); } const completedTasks = projectInfo.tasks.filter( (task) => task.submissions && task.submissions.length > 0 ); if (completedTasks.length < projectInfo.tasks.length) { return error( new BadRequestError( `Cannot claim certificate: you have completed ${completedTasks.length} out of ${projectInfo.tasks.length} tasks` ), ); } // Generate certificate number berdsarkan format ini: CERT/YYYYMMDD/{count} [Rafi 12/07/2026 14:32] const now = new Date(); const year = String(now.getFullYear()).slice(-2); const day = String(now.getDate()).padStart(2, '0'); const month = String(now.getMonth() + 1).padStart(2, '0'); const dateString = `${year}${day}${month}`; const count = await certificateRepository.countByProject(id_project); const certificateNo = `CERT/${dateString}/${count + 1}`; const certificateData = { id_project: parseInt(id_project), id_user: parseInt(actor.id), certificate_no: certificateNo, issued_at: now, }; const certificate = await certificateRepository.create(certificateData); return data(this.mapCertificate(certificate)); } catch (err) { return error(err); } } async generateCertificates(payload, actor) { try { if (!actor?.id) { return error(new BadRequestError('User ID is required')); } if (actor.role !== 'admin' && actor.role !== 'mentor') { return error( new ForbiddenError('Access denied: only admins and mentors can generate certificates'), ); } const { id_project, id_users } = payload; const project = await certificateRepository.findProjectById(id_project); if (!project) { return error(new NotFoundError('Project not found')); } // Authorization check: if (actor.role === 'mentor' && project.id_admin !== parseInt(actor.id)) { return error( new ForbiddenError('Access denied: you are not the mentor of this project'), ); } const validatedUsers = []; for (const userId of id_users) { const existingCert = await certificateRepository.findByUserAndProject(userId, id_project); if (existingCert) { return error( new ConflictError(`Certificate already generated/claimed for user ID ${userId}`), ); } const projectInfo = await certificateRepository.getProjectTasksAndSubmissions(id_project, userId); if (!projectInfo) { return error(new NotFoundError('Project not found')); } if (!projectInfo.members || projectInfo.members.length === 0) { return error( new ForbiddenError(`User ID ${userId} is not an active member of this project`), ); } if (!projectInfo.tasks || projectInfo.tasks.length === 0) { return error( new BadRequestError(`Project does not have any tasks assigned yet`), ); } const completedTasks = projectInfo.tasks.filter( (task) => task.submissions && task.submissions.length > 0 ); if (completedTasks.length < projectInfo.tasks.length) { return error( new BadRequestError( `User ID ${userId} is not eligible: completed ${completedTasks.length} out of ${projectInfo.tasks.length} tasks`, ), ); } validatedUsers.push(userId); } const generatedCertificates = []; const now = new Date(); const year = String(now.getFullYear()).slice(-2); const day = String(now.getDate()).padStart(2, '0'); const month = String(now.getMonth() + 1).padStart(2, '0'); const dateString = `${year}${day}${month}`; let currentCount = await certificateRepository.countByProject(id_project); for (const userId of validatedUsers) { currentCount++; const certificateNo = `CERT/${dateString}/${currentCount}`; const certificateData = { id_project: parseInt(id_project), id_user: parseInt(userId), certificate_no: certificateNo, issued_at: now, }; const certificate = await certificateRepository.create(certificateData); generatedCertificates.push(this.mapCertificate(certificate)); } return data({ message: `${generatedCertificates.length} certificates generated successfully.`, certificates: generatedCertificates, }); } catch (err) { return error(err); } } async getMyCertificates(actor) { try { if (!actor?.id) { return error(new BadRequestError('User ID is required')); } if (actor.role !== 'intern') { return error( new ForbiddenError('Access denied: only interns can view their certificates'), ); } const certificates = await certificateRepository.findUserCertificates(actor.id); return data(certificates.map((cert) => this.mapCertificate(cert))); } catch (err) { return error(err); } } async getCertificateDetail(id, actor) { try { if (!actor?.id) { return error(new BadRequestError('User ID is required')); } const certificate = await certificateRepository.findById(id); if (!certificate) { return error(new NotFoundError('Certificate not found')); } if (actor.role === 'intern' && certificate.id_user !== actor.id) { return error( new ForbiddenError("Access denied: you cannot view other intern's certificates"), ); } return data(this.mapCertificate(certificate)); } catch (err) { return error(err); } } async getProjectCertificates(id_project, actor) { try { if (!actor?.id) { return error(new BadRequestError('User ID is required')); } const project = await certificateRepository.findProjectById(id_project); if (!project) { return error(new NotFoundError('Project not found')); } if (actor.role === 'mentor') { if (project.id_admin !== parseInt(actor.id)) { return error( new ForbiddenError('Access denied: you are not the mentor of this project'), ); } } else if (actor.role !== 'admin') { return error( new ForbiddenError('Access denied: only admins/mentors can view project certificates'), ); } const certificates = await certificateRepository.findProjectCertificates(id_project); return data(certificates.map((cert) => this.mapCertificate(cert))); } catch (err) { return error(err); } } async getAllCertificates(actor) { try { if (!actor?.id) { return error(new BadRequestError('Admin ID is required')); } if (actor.role !== 'admin') { return error( new ForbiddenError('Access denied: only admins/mentors can view all certificates'), ); } const certificates = await certificateRepository.findAll(); return data(certificates.map((cert) => this.mapCertificate(cert))); } catch (err) { return error(err); } } async verifyCertificateByUuid(uuid) { try { if (!uuid) { return error(new BadRequestError('Certificate UUID is required')); } const certificate = await certificateRepository.findByUuid(uuid); if (!certificate) { return error(new NotFoundError('Certificate not found or invalid')); } return data({ uuid: certificate.uuid, intern_name: certificate.user ? certificate.user.full_name : null, project_name: certificate.project ? certificate.project.project_name : null, created_at: certificate.issued_at, }); } catch (err) { return error(err); } } async uploadCertificateTemplate(id_project, file, actor) { try { if (!id_project) { return error(new BadRequestError('Project ID is required')); } if (!file) { return error(new BadRequestError('No file uploaded')); } if (!actor?.id || !actor?.role) { return error(new ForbiddenError('Access denied: authentication data is missing')); } const project = await certificateRepository.findProjectById(id_project); if (!project) { return error(new NotFoundError('Project not found')); } // Authorization checks: if (actor.role === 'mentor') { if (project.id_admin !== parseInt(actor.id)) { return error(new ForbiddenError('Access denied: you are not the mentor of this project')); } } else if (actor.role !== 'admin') { return error(new ForbiddenError('Access denied: only admins and mentors can upload certificate templates')); } const templatePath = `/uploads/${file.filename}`; await certificateRepository.updateProjectTemplate(id_project, templatePath); return data({ id_project: parseInt(id_project), certificate_template: templatePath, }); } catch (err) { return error(err); } } mapCertificate(cert) { if (!cert) return null; return { id: cert.id, uuid: cert.uuid, id_project: cert.id_project, id_user: cert.id_user, certificate_no: cert.certificate_no, issued_at: cert.issued_at, user: cert.user ? { id: cert.user.id, full_name: cert.user.full_name, email: cert.user.email, } : null, project: cert.project ? { id: cert.project.id, project_name: cert.project.project_name, description: cert.project.description, certificate_template: cert.project.certificate_template, } : null, }; } } module.exports = new CertificateService();