feat: Nambahin template certificate endpoint
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -2,6 +2,9 @@ const express = require('express');
|
||||
const { certificateController } = require('../modules/certificate');
|
||||
const { verifyJWT } = require('../middleware/verifyJWT');
|
||||
const isAdmin = require('../middleware/isAdmin');
|
||||
const isMentorOrAdmin = require('../middleware/isMentorOrAdmin');
|
||||
const multer = require('../middleware/multer');
|
||||
const { multerErrorHandler, checkFileSizes } = require('../middleware/multer');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -366,4 +369,83 @@ router.get('/verify', certificateController.verifyCertificate);
|
||||
*/
|
||||
router.get('/verify-uuid/:uuid', certificateController.verifyCertificateByUuid);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /certificate-api/projects/{id_project}/template:
|
||||
* post:
|
||||
* summary: Upload certificate template for a project
|
||||
* description: Admin or Mentor can upload a certificate template file (image/pdf) for a specific project. Mentors can only upload to projects they own.
|
||||
* tags: [Certificate]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id_project
|
||||
* schema:
|
||||
* type: integer
|
||||
* required: true
|
||||
* description: The ID of the project to associate the template with
|
||||
* example: 1
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* multipart/form-data:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - template
|
||||
* properties:
|
||||
* template:
|
||||
* type: string
|
||||
* format: binary
|
||||
* description: The certificate template file (JPEG, JPG, PNG, or PDF). Max size 5MB.
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Certificate template uploaded successfully
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* status:
|
||||
* type: boolean
|
||||
* example: true
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* id_project:
|
||||
* type: integer
|
||||
* example: 1
|
||||
* certificate_template:
|
||||
* type: string
|
||||
* example: "/uploads/1718000000000-987654321.png"
|
||||
* message:
|
||||
* type: string
|
||||
* example: "Certificate template uploaded successfully"
|
||||
* code:
|
||||
* type: integer
|
||||
* example: 200
|
||||
* 400:
|
||||
* description: Bad request (missing file or invalid input)
|
||||
* 401:
|
||||
* description: Unauthorized
|
||||
* 403:
|
||||
* description: Forbidden (Mentor does not own the project, or unauthorized role)
|
||||
* 404:
|
||||
* description: Project not found
|
||||
* 413:
|
||||
* description: File size too large (max 5MB)
|
||||
* 500:
|
||||
* description: Internal server error
|
||||
*/
|
||||
router.post(
|
||||
'/projects/:id_project/template',
|
||||
verifyJWT,
|
||||
isMentorOrAdmin,
|
||||
multer.single('template'),
|
||||
checkFileSizes,
|
||||
multerErrorHandler,
|
||||
certificateController.uploadCertificateTemplate
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user