feat(project): endpoint untuk complete sebuah project, update project biasa kini tanpa status

This commit is contained in:
Muhammad Zhafran Ilham
2026-07-01 21:16:16 +07:00
parent 78256b68d0
commit d32293b998
5 changed files with 125 additions and 9 deletions
@@ -163,6 +163,29 @@ class ProjectController {
}
}
async completeProject(req, res) {
try {
const { id } = req.params;
const actor = getActor(req);
const result = await projectService.completeProject(id, actor);
if (result.err) {
return response(res, 'fail', result);
}
return response(res, 'success', result, 'Project completed successfully', SUCCESS.OK);
} catch (err) {
return response(
res,
'fail',
error(new InternalServerError(err.message)),
'Unexpected error occurred',
ERROR.INTERNAL_ERROR
);
}
}
async getMyProjects(req, res) {
try {
const actor = getActor(req);
@@ -80,11 +80,6 @@ const updateProjectModel = joi.object().keys({
end_date: joi.date().optional().messages({
'date.base': 'End date must be a valid date.',
}),
status: joi.string().valid(...PROJECT_STATUS_VALUES).optional().messages({
'string.base': 'Project status must be a string.',
'any.only': `Project status must be one of: ${PROJECT_STATUS_VALUES.join(', ')}.`,
}),
});
const getAllProjectsModel = joi.object().keys({
@@ -151,6 +151,18 @@ class ProjectRepository {
});
}
async completeById(id) {
return prisma.project.update({
where: {
id: parseInt(id),
},
data: {
status: "completed",
},
include: this.projectDetailInclude(),
});
}
async checkActiveMember(idProject, idUser) {
return prisma.projectMember.findFirst({
where: {
@@ -259,6 +259,57 @@ class ProjectService {
}
}
async completeProject(id, actor = {}) {
try {
if (!actor?.id) {
return error(new BadRequestError("Admin ID is required"));
}
if (actor.role !== "admin" && actor.role !== "mentor") {
return error(
new ForbiddenError("Access denied: only admin or mentor can complete project"),
);
}
const existingProject = await projectRepository.findByIdOrSlug(id);
if (!existingProject) {
return error(new NotFoundError("Project not found"));
}
const accessError = await this.validateProjectAccess(existingProject.id, actor);
if (accessError) {
return error(accessError);
}
if (existingProject.status === "completed") {
return data(this.mapProjectDetail(existingProject));
}
if (existingProject.status === "archived") {
return error(new ConflictError("Archived project cannot be marked as completed"));
}
if (existingProject.status !== "active") {
return error(new ConflictError("Only active project can be marked as completed"));
}
const completedProject = await projectRepository.completeById(existingProject.id);
await notificationService.createNotificationForProjectInterns(
completedProject.id,
"Proyek Selesai",
`Proyek '${completedProject.project_name}' telah ditandai selesai.`
).catch((err) =>
console.error("Failed to send project completion notifications:", err)
);
return data(this.mapProjectDetail(completedProject));
} catch (err) {
return error(err);
}
}
async validateProjectAccess(idProject, actor = {}) {
if (!actor?.id || !actor?.role) {
return new ForbiddenError(