Files
internify-be/src/modules/certificate/services/certificate.service.js
T
2026-07-12 15:02:11 +07:00

363 lines
11 KiB
JavaScript

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();