feat: Nambahin template certificate endpoint

This commit is contained in:
Rafi Athallah
2026-06-25 18:47:40 +07:00
parent 955545876f
commit d86164f8df
8 changed files with 474 additions and 1 deletions
@@ -1,7 +1,7 @@
const certificateService = require('../services/certificate.service');
const { response, error } = require('../../../helpers/utils/wrapper');
const { SUCCESS, ERROR } = require('../../../helpers/http-status/status_code');
const { InternalServerError } = require('../../../helpers/error');
const { InternalServerError, BadRequestError } = require('../../../helpers/error');
const { isValidPayload } = require('../../../helpers/utils/validator');
const { claimCertificateModel } = require('../models/certificate.model');
@@ -172,6 +172,40 @@ class CertificateController {
);
}
}
async uploadCertificateTemplate(req, res) {
try {
const { id_project } = req.params;
const file = req.file;
if (!file) {
return response(
res,
'fail',
error(new BadRequestError('Certificate template file is required')),
'Certificate template file is required',
ERROR.BAD_REQUEST
);
}
const actor = getActor(req);
const result = await certificateService.uploadCertificateTemplate(id_project, file, actor);
if (result.err) {
return response(res, 'fail', result);
}
return response(res, 'success', result, 'Certificate template uploaded successfully', SUCCESS.OK);
} catch (err) {
return response(
res,
'fail',
error(new InternalServerError(err.message)),
'Unexpected error occurred',
ERROR.INTERNAL_ERROR
);
}
}
}
module.exports = new CertificateController();
@@ -95,6 +95,19 @@ class CertificateRepository {
});
}
async findProjectById(id_project) {
return prisma.project.findUnique({
where: { id: parseInt(id_project) },
});
}
async updateProjectTemplate(id_project, templatePath) {
return prisma.project.update({
where: { id: parseInt(id_project) },
data: { certificate_template: templatePath },
});
}
certificateDetailInclude() {
return {
user: {
@@ -109,6 +122,7 @@ class CertificateRepository {
id: true,
project_name: true,
description: true,
certificate_template: true,
},
},
};
@@ -199,6 +199,47 @@ class CertificateService {
}
}
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 {
@@ -217,6 +258,7 @@ class CertificateService {
id: cert.project.id,
project_name: cert.project.project_name,
description: cert.project.description,
certificate_template: cert.project.certificate_template,
} : null,
};
}
@@ -302,6 +302,7 @@ class ProjectService {
project_icon: project.project_icon,
project_name: project.project_name,
description: project.description,
certificate_template: project.certificate_template,
start_date: project.start_date,
end_date: project.end_date,
status: project.status,