feat: nambahin API assign member, list interns, update profile, serta list projects/tasks

This commit is contained in:
Rafi Athallah
2026-06-08 17:28:37 +07:00
parent f0df564499
commit edc287968d
14 changed files with 1295 additions and 36 deletions
@@ -162,6 +162,111 @@ class ProjectController {
);
}
}
async getMyProjects(req, res) {
try {
const actor = getActor(req);
const result = await projectService.getMyProjects(actor);
if (result.err) {
return response(res, 'fail', result);
}
return response(res, 'success', result, 'Intern projects retrieved successfully', SUCCESS.OK);
} catch (err) {
return response(
res,
'fail',
error(new InternalServerError(err.message)),
'Unexpected error occurred',
ERROR.INTERNAL_ERROR
);
}
}
async getMyTasks(req, res) {
try {
const actor = getActor(req);
const result = await projectService.getMyTasks(actor);
if (result.err) {
return response(res, 'fail', result);
}
return response(res, 'success', result, 'Intern tasks retrieved successfully', SUCCESS.OK);
} catch (err) {
return response(
res,
'fail',
error(new InternalServerError(err.message)),
'Unexpected error occurred',
ERROR.INTERNAL_ERROR
);
}
}
async getMentorProjects(req, res) {
try {
const actor = getActor(req);
const result = await projectService.getMentorProjects(actor);
if (result.err) {
return response(res, 'fail', result);
}
return response(res, 'success', result, 'Mentor projects retrieved successfully', SUCCESS.OK);
} catch (err) {
return response(
res,
'fail',
error(new InternalServerError(err.message)),
'Unexpected error occurred',
ERROR.INTERNAL_ERROR
);
}
}
async getInterns(req, res) {
try {
const actor = getActor(req);
const result = await projectService.getInterns(actor);
if (result.err) {
return response(res, 'fail', result);
}
return response(res, 'success', result, 'Interns retrieved successfully', SUCCESS.OK);
} catch (err) {
return response(
res,
'fail',
error(new InternalServerError(err.message)),
'Unexpected error occurred',
ERROR.INTERNAL_ERROR
);
}
}
async assignMember(req, res) {
try {
const actor = getActor(req);
const result = await projectService.assignMember(req.body, actor);
if (result.err) {
return response(res, 'fail', result);
}
return response(res, 'success', result, 'Member assigned successfully', SUCCESS.OK);
} catch (err) {
return response(
res,
'fail',
error(new InternalServerError(err.message)),
'Unexpected error occurred',
ERROR.INTERNAL_ERROR
);
}
}
}
module.exports = new ProjectController();
@@ -203,6 +203,193 @@ class ProjectRepository {
},
};
}
async findProjectsByIntern(internId) {
return prisma.project.findMany({
where: {
members: {
some: {
id_user: parseInt(internId),
status: "active",
},
},
},
orderBy: {
created_at: "desc",
},
include: {
admin: {
select: {
id: true,
nama_depan: true,
nama_belakang: true,
email: true,
profile_picture: true,
professional_bio: true,
},
},
members: {
where: {
status: "active",
},
select: {
id: true,
},
},
tasks: {
select: {
id: true,
},
},
},
});
}
async findTasksByIntern(internId) {
return prisma.task.findMany({
where: {
project: {
members: {
some: {
id_user: parseInt(internId),
status: "active",
},
},
},
},
orderBy: {
deadline_at: "asc",
},
include: {
project: {
select: {
id: true,
project_name: true,
project_icon: true,
},
},
submissions: {
where: {
id_user: parseInt(internId),
},
select: {
id: true,
file_path: true,
url_link: true,
submitted_at: true,
updated_at: true,
},
},
},
});
}
async findProjectsByMentor(adminId) {
return prisma.project.findMany({
where: {
id_admin: parseInt(adminId),
},
orderBy: {
created_at: "desc",
},
include: {
admin: {
select: {
id: true,
nama_depan: true,
nama_belakang: true,
email: true,
profile_picture: true,
professional_bio: true,
},
},
members: {
where: {
status: "active",
},
select: {
id: true,
},
},
tasks: {
select: {
id: true,
},
},
},
});
}
async findActiveInternsWithProjects() {
return prisma.user.findMany({
where: {
role: "intern",
is_active: true,
},
include: {
project_members: {
where: {
status: "active",
},
include: {
project: {
select: {
id: true,
project_name: true,
},
},
},
},
lamaran_magang: {
include: {
lowongan_magang: {
select: {
posisi: true,
},
},
},
},
},
orderBy: {
id: "asc",
},
});
}
async assignMember(id_project, id_user) {
return prisma.projectMember.create({
data: {
id_project: parseInt(id_project),
id_user: parseInt(id_user),
status: "active",
},
});
}
async findUserByIdAndActive(id) {
return prisma.user.findFirst({
where: {
id: parseInt(id),
is_active: true,
},
});
}
async findMembership(idProject, idUser) {
return prisma.projectMember.findFirst({
where: {
id_project: parseInt(idProject),
id_user: parseInt(idUser),
},
});
}
async updateMembershipStatus(id, status) {
return prisma.projectMember.update({
where: { id: parseInt(id) },
data: { status },
});
}
}
module.exports = new ProjectRepository();
@@ -4,6 +4,7 @@ const {
BadRequestError,
NotFoundError,
ForbiddenError,
ConflictError,
} = require("../../../helpers/error");
class ProjectService {
@@ -305,6 +306,209 @@ class ProjectService {
updated_at: member.updated_at,
};
}
async getMyProjects(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 projects"),
);
}
const projects = await projectRepository.findProjectsByIntern(actor.id);
const mappedProjects = projects.map((project) => ({
id: project.id,
project_icon: project.project_icon,
project_name: project.project_name,
description: project.description,
start_date: project.start_date,
end_date: project.end_date,
max_members: project.max_members,
status: project.status,
admin: this.mapAdmin(project.admin),
total_members: project.members?.length || 0,
total_tasks: project.tasks?.length || 0,
created_at: project.created_at,
updated_at: project.updated_at,
}));
return data(mappedProjects);
} catch (err) {
return error(err);
}
}
async getMyTasks(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 tasks"),
);
}
const tasks = await projectRepository.findTasksByIntern(actor.id);
const mappedTasks = tasks.map((task) => {
const submission = task.submissions?.[0] || null;
return {
id: task.id,
id_project: task.id_project,
project_name: task.project?.project_name || null,
project_icon: task.project?.project_icon || null,
title: task.title,
description: task.description,
deadline_at: task.deadline_at,
submission_type: task.submission_type,
created_at: task.created_at,
updated_at: task.updated_at,
submission_status: submission ? "submitted" : "not_submitted",
submission_details: submission ? {
id: submission.id,
file_path: submission.file_path,
url_link: submission.url_link,
submitted_at: submission.submitted_at,
updated_at: submission.updated_at,
} : null,
};
});
return data(mappedTasks);
} catch (err) {
return error(err);
}
}
async getMentorProjects(actor = {}) {
try {
if (!actor?.id) {
return error(new BadRequestError("Admin ID is required"));
}
if (actor.role !== "admin") {
return error(
new ForbiddenError("Access denied: only mentors/admins can view their created projects"),
);
}
const projects = await projectRepository.findProjectsByMentor(actor.id);
const mappedProjects = projects.map((project) => ({
id: project.id,
project_icon: project.project_icon,
project_name: project.project_name,
description: project.description,
start_date: project.start_date,
end_date: project.end_date,
max_members: project.max_members,
status: project.status,
admin: this.mapAdmin(project.admin),
total_members: project.members?.length || 0,
total_tasks: project.tasks?.length || 0,
created_at: project.created_at,
updated_at: project.updated_at,
}));
return data(mappedProjects);
} catch (err) {
return error(err);
}
}
async getInterns(actor = {}) {
try {
if (!actor?.id) {
return error(new BadRequestError("Admin ID is required"));
}
if (actor.role !== "admin") {
return error(
new ForbiddenError("Access denied: only admin/mentor can access interns list"),
);
}
const interns = await projectRepository.findActiveInternsWithProjects();
const mappedInterns = interns.map((intern) => {
const activeMembership = intern.project_members?.[0] || null;
const projectName = activeMembership?.project?.project_name || "Unassigned";
const role = intern.lamaran_magang?.lowongan_magang?.posisi || "-";
return {
id: intern.id,
name: intern.full_name,
email: intern.email,
projectName: projectName,
role: role,
isAssignedByMentor: projectName !== "Unassigned",
avatar: null
};
});
return data(mappedInterns);
} catch (err) {
return error(err);
}
}
async assignMember(payload, actor = {}) {
try {
if (!actor?.id) {
return error(new BadRequestError("Admin ID is required"));
}
if (actor.role !== "admin") {
return error(
new ForbiddenError("Access denied: only mentors/admins can assign members"),
);
}
const { id_project, id_user } = payload;
if (!id_project || !id_user) {
return error(new BadRequestError("Project ID and User ID are required"));
}
// Check if project exists
const project = await projectRepository.findById(id_project);
if (!project) {
return error(new NotFoundError("Project not found"));
}
// Check if user exists and is active
const user = await projectRepository.findUserByIdAndActive(id_user);
if (!user) {
return error(new NotFoundError("User not found or inactive"));
}
// Check if already a member of this project
const existingMembership = await projectRepository.findMembership(id_project, id_user);
let membership;
if (existingMembership) {
if (existingMembership.status === 'active') {
return error(new ConflictError("User is already an active member of this project"));
}
// Reactivate membership
membership = await projectRepository.updateMembershipStatus(existingMembership.id, 'active');
} else {
// Create new membership
membership = await projectRepository.assignMember(id_project, id_user);
}
return data(membership);
} catch (err) {
return error(err);
}
}
}
module.exports = new ProjectService();