feat: nambahin API assign member, list interns, update profile, serta list projects/tasks
This commit is contained in:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user