363 lines
11 KiB
JavaScript
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();
|