feat: CRUD untuk feature project intern

This commit is contained in:
Muhammad Zhafran Ilham
2026-06-08 05:44:53 +07:00
parent 9bdb781f34
commit f0df564499
12 changed files with 1156 additions and 78 deletions
+4
View File
@@ -63,6 +63,10 @@ const swaggerOptions = {
{
name: "Feedback",
description: "Internship feedback and testimonial management endpoints"
},
{
name: "Project",
description: "Internify LMS project management endpoints"
}
],
components: {
+83 -78
View File
@@ -1,78 +1,83 @@
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const path = require('path');
const adminRoutes = require('./routes/admin.routes');
const authRoutes = require('./routes/auth.routes');
const lowonganMagangRoutes = require('./routes/lowonganMagang.routes');
const lamaranMagangRoutes = require('./routes/lamaranMagang.routes');
const mahasiswaRoutes = require('./routes/mahasiswa.routes');
const partnershipRoutes = require('./routes/partnership.routes');
const hasilResearchRoutes = require('./routes/hasilResearch.routes');
const faqRoutes = require('./routes/faq.routes');
const feedbackRoutes = require('./routes/feedback.routes');
const batchRoutes = require('./routes/batch.routes');
const setupSwaggerDocs = require('./docs/swagger');
const PORT = process.env.PORT;
const app = express();
const corsOptions = {
origin: true,
methods: ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE", "OPTIONS"],
credentials: true,
allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With", "Accept"],
exposedHeaders: ["Content-Range", "X-Content-Range"],
optionsSuccessStatus: 200,
};
const uploadDir = process.env.NODE_ENV === 'production'
? '/tmp/uploads'
: path.join(__dirname, 'uploads');
app.options('*', cors(corsOptions));
app.use(express.json());
app.use(cors(corsOptions));
app.use("/uploads", express.static(uploadDir), (req, res, next) => {
if (!res.headersSent) {
res.status(404).json({
status: false,
message: `File not found: ${req.path}`,
code: 404
});
}
});
app.use("/admin-api", adminRoutes);
app.use("/auth-api", authRoutes);
app.use("/lowongan-magang-api", lowonganMagangRoutes);
app.use("/lamaran-magang-api", lamaranMagangRoutes);
app.use("/mahasiswa-api", mahasiswaRoutes);
app.use("/partnership-api", partnershipRoutes);
app.use("/hasil-research-api", hasilResearchRoutes);
app.use("/faq-api", faqRoutes);
app.use("/feedback-api", feedbackRoutes);
app.use("/batch-api", batchRoutes);
setupSwaggerDocs(app);
app.use((req, res) => {
res.status(404).json({
status: false,
message: `Route not found: ${req.method} ${req.path}`,
code: 404
});
});
if (process.env.NODE_ENV !== 'production') {
app.listen(PORT, () => {
console.log(`listening at http://localhost:${PORT}`);
});
}
module.exports = app;
/*
NOTE : saran saya untuk penamaan variabel untuk setiap response dibuat konsisten menjadi inggris, tolong komunikasi aja ke fe 😁
*/
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const path = require('path');
const adminRoutes = require('./routes/admin.routes');
const authRoutes = require('./routes/auth.routes');
const lowonganMagangRoutes = require('./routes/lowonganMagang.routes');
const lamaranMagangRoutes = require('./routes/lamaranMagang.routes');
const mahasiswaRoutes = require('./routes/mahasiswa.routes');
const partnershipRoutes = require('./routes/partnership.routes');
const hasilResearchRoutes = require('./routes/hasilResearch.routes');
const faqRoutes = require('./routes/faq.routes');
const feedbackRoutes = require('./routes/feedback.routes');
const batchRoutes = require('./routes/batch.routes');
const projectRoutes = require('./routes/project.routes');
const setupSwaggerDocs = require('./docs/swagger');
const PORT = process.env.PORT;
const app = express();
const corsOptions = {
origin: true,
methods: ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE", "OPTIONS"],
credentials: true,
allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With", "Accept"],
exposedHeaders: ["Content-Range", "X-Content-Range"],
optionsSuccessStatus: 200,
};
const uploadDir = process.env.NODE_ENV === 'production'
? '/tmp/uploads'
: path.join(__dirname, 'uploads');
app.options('*', cors(corsOptions));
app.use(express.json());
app.use(cors(corsOptions));
app.use("/uploads", express.static(uploadDir), (req, res, next) => {
if (!res.headersSent) {
res.status(404).json({
status: false,
message: `File not found: ${req.path}`,
code: 404
});
}
});
app.use("/admin-api", adminRoutes);
app.use("/auth-api", authRoutes);
app.use("/lowongan-magang-api", lowonganMagangRoutes);
app.use("/lamaran-magang-api", lamaranMagangRoutes);
app.use("/mahasiswa-api", mahasiswaRoutes);
app.use("/partnership-api", partnershipRoutes);
app.use("/hasil-research-api", hasilResearchRoutes);
app.use("/faq-api", faqRoutes);
app.use("/feedback-api", feedbackRoutes);
app.use("/batch-api", batchRoutes);
app.use("/project-api", projectRoutes);
// app.use("/project-member-api", projectMemberRoutes);
// app.use("/task-api", taskRoutes);
setupSwaggerDocs(app);
app.use((req, res) => {
res.status(404).json({
status: false,
message: `Route not found: ${req.method} ${req.path}`,
code: 404
});
});
if (process.env.NODE_ENV !== 'production') {
app.listen(PORT, () => {
console.log(`listening at http://localhost:${PORT}`);
});
}
module.exports = app;
/*
NOTE : saran saya untuk penamaan variabel untuk setiap response dibuat konsisten menjadi inggris, tolong komunikasi aja ke fe 😁
*/
@@ -0,0 +1,167 @@
const projectService = require('../services/project.service');
const { response, error } = require('../../../helpers/utils/wrapper');
const { SUCCESS, ERROR } = require('../../../helpers/http-status/status_code');
const { InternalServerError } = require('../../../helpers/error');
const { isValidPayload } = require('../../../helpers/utils/validator');
const {
createProjectModel,
updateProjectModel,
getAllProjectsModel,
} = require('../models/project.model');
const getActor = (req) => ({
id: req.id,
role: req.role,
email: req.email,
});
class ProjectController {
async createProject(req, res) {
try {
const validatePayload = isValidPayload(req.body, createProjectModel);
if (validatePayload.err) {
return response(
res,
'fail',
{ err: validatePayload.err, data: null },
'Invalid project data',
ERROR.EXPECTATION_FAILED
);
}
const actor = getActor(req);
const result = await projectService.createProject(validatePayload.data, actor);
if (result.err) {
return response(res, 'fail', result);
}
return response(res, 'success', result, 'Project created successfully', SUCCESS.CREATED);
} catch (err) {
return response(
res,
'fail',
error(new InternalServerError(err.message)),
'Unexpected error occurred',
ERROR.INTERNAL_ERROR
);
}
}
async getAllProjects(req, res) {
try {
const validatePayload = isValidPayload(req.query, getAllProjectsModel);
if (validatePayload.err) {
return response(
res,
'fail',
{ err: validatePayload.err, data: null },
'Invalid query data',
ERROR.EXPECTATION_FAILED
);
}
const actor = getActor(req);
const result = await projectService.getAllProjects(validatePayload.data, actor);
if (result.err) {
return response(res, 'fail', result);
}
return response(res, 'success', result, 'Projects retrieved successfully', SUCCESS.OK);
} catch (err) {
return response(
res,
'fail',
error(new InternalServerError(err.message)),
'Unexpected error occurred',
ERROR.INTERNAL_ERROR
);
}
}
async getProjectById(req, res) {
try {
const { id } = req.params;
const actor = getActor(req);
const result = await projectService.getProjectById(id, actor);
if (result.err) {
return response(res, 'fail', result);
}
return response(res, 'success', result, 'Project detail retrieved successfully', SUCCESS.OK);
} catch (err) {
return response(
res,
'fail',
error(new InternalServerError(err.message)),
'Unexpected error occurred',
ERROR.INTERNAL_ERROR
);
}
}
async updateProject(req, res) {
try {
const { id } = req.params;
const validatePayload = isValidPayload(req.body, updateProjectModel);
if (validatePayload.err) {
return response(
res,
'fail',
{ err: validatePayload.err, data: null },
'Invalid project data',
ERROR.EXPECTATION_FAILED
);
}
const actor = getActor(req);
const result = await projectService.updateProject(id, validatePayload.data, actor);
if (result.err) {
return response(res, 'fail', result);
}
return response(res, 'success', result, 'Project updated successfully', SUCCESS.OK);
} catch (err) {
return response(
res,
'fail',
error(new InternalServerError(err.message)),
'Unexpected error occurred',
ERROR.INTERNAL_ERROR
);
}
}
async archiveProject(req, res) {
try {
const { id } = req.params;
const actor = getActor(req);
const result = await projectService.archiveProject(id, actor);
if (result.err) {
return response(res, 'fail', result);
}
return response(res, 'success', result, 'Project archived successfully', SUCCESS.OK);
} catch (err) {
return response(
res,
'fail',
error(new InternalServerError(err.message)),
'Unexpected error occurred',
ERROR.INTERNAL_ERROR
);
}
}
}
module.exports = new ProjectController();
+9
View File
@@ -0,0 +1,9 @@
const projectController = require("./controllers/project.controller");
const projectRepository = require("./repositories/project.repository");
const projectService = require("./services/project.service");
module.exports = {
projectController,
projectService,
projectRepository
}
+101
View File
@@ -0,0 +1,101 @@
const joi = require('joi');
const PROJECT_ICON_VALUES = [
'code',
'chart',
'cloud',
'mobile',
'gear',
'users',
'clipboard',
'speedometer',
'lightbulb',
'shield',
];
const PROJECT_STATUS_VALUES = [
'active',
'completed',
'archived',
];
const createProjectModel = joi.object().keys({
project_icon: joi.string().valid(...PROJECT_ICON_VALUES).required().messages({
'string.base': 'Project icon must be a string.',
'string.empty': 'Project icon cannot be empty.',
'any.only': `Project icon must be one of: ${PROJECT_ICON_VALUES.join(', ')}.`,
'any.required': 'Project icon is required.',
}),
project_name: joi.string().max(255).required().messages({
'string.base': 'Project name must be a string.',
'string.empty': 'Project name cannot be empty.',
'string.max': 'Project name must not exceed {#limit} characters.',
'any.required': 'Project name is required.',
}),
description: joi.string().optional().allow('', null).messages({
'string.base': 'Project description must be a string.',
}),
start_date: joi.date().required().messages({
'date.base': 'Start date must be a valid date.',
'any.required': 'Start date is required.',
}),
end_date: joi.date().min(joi.ref('start_date')).required().messages({
'date.base': 'End date must be a valid date.',
'date.min': 'End date must be greater than or equal to start date.',
'any.required': 'End date is required.',
}),
member_emails: joi.array().items(
joi.string().email({ tlds: { allow: false } }).messages({
'string.email': 'Member email format is invalid.',
})
).optional().default([]).messages({
'array.base': 'Member emails must be an array.',
}),
});
const updateProjectModel = joi.object().keys({
project_icon: joi.string().valid(...PROJECT_ICON_VALUES).optional().messages({
'string.base': 'Project icon must be a string.',
'any.only': `Project icon must be one of: ${PROJECT_ICON_VALUES.join(', ')}.`,
}),
project_name: joi.string().max(255).optional().messages({
'string.base': 'Project name must be a string.',
'string.max': 'Project name must not exceed {#limit} characters.',
}),
description: joi.string().optional().allow('', null).messages({
'string.base': 'Project description must be a string.',
}),
start_date: joi.date().optional().messages({
'date.base': 'Start date must be a valid date.',
}),
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({
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(', ')}.`,
}),
});
module.exports = {
createProjectModel,
updateProjectModel,
getAllProjectsModel,
};
@@ -0,0 +1,208 @@
const prisma = require("../../../helpers/db/db_connection");
class ProjectRepository {
async findAdminById(id) {
return prisma.admin.findUnique({
where: {
id: parseInt(id),
},
});
}
async findUsersByEmails(emails) {
return prisma.user.findMany({
where: {
email: {
in: emails,
},
is_active: true,
},
include: {
lamaran_magang: {
include: {
lowongan_magang: {
select: {
posisi: true,
kelompok_peminatan: true,
},
},
},
},
},
});
}
async createWithMembers(projectData, userIds = []) {
return prisma.$transaction(async (tx) => {
const project = await tx.project.create({
data: projectData,
});
if (userIds.length > 0) {
await tx.projectMember.createMany({
data: userIds.map((id_user) => ({
id_project: project.id,
id_user,
status: "active",
})),
skipDuplicates: true,
});
}
return tx.project.findUnique({
where: {
id: project.id,
},
include: this.projectDetailInclude(),
});
});
}
async findAll({ status, actor }) {
const where = {};
if (status) {
where.status = status;
}
if (actor?.role === "intern") {
where.members = {
some: {
id_user: parseInt(actor.id),
status: "active",
},
};
}
return prisma.project.findMany({
where,
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 findById(id) {
return prisma.project.findUnique({
where: {
id: parseInt(id),
},
include: this.projectDetailInclude(),
});
}
async updateById(id, projectData) {
return prisma.project.update({
where: {
id: parseInt(id),
},
data: projectData,
include: this.projectDetailInclude(),
});
}
async archiveById(id) {
return prisma.project.update({
where: {
id: parseInt(id),
},
data: {
status: "archived",
},
include: this.projectDetailInclude(),
});
}
async checkActiveMember(idProject, idUser) {
return prisma.projectMember.findFirst({
where: {
id_project: parseInt(idProject),
id_user: parseInt(idUser),
status: "active",
},
});
}
projectDetailInclude() {
return {
admin: {
select: {
id: true,
nama_depan: true,
nama_belakang: true,
email: true,
profile_picture: true,
professional_bio: true,
},
},
members: {
where: {
status: "active",
},
include: {
user: {
select: {
id: true,
full_name: true,
email: true,
professional_bio: true,
is_active: true,
lamaran_magang: {
select: {
id: true,
lowongan_magang: {
select: {
posisi: true,
kelompok_peminatan: true,
},
},
},
},
},
},
},
},
tasks: {
select: {
id: true,
title: true,
description: true,
deadline_at: true,
submission_type: true,
created_at: true,
updated_at: true,
},
orderBy: {
deadline_at: "asc",
},
},
};
}
}
module.exports = new ProjectRepository();
@@ -0,0 +1,310 @@
const projectRepository = require("../repositories/project.repository");
const { data, error } = require("../../../helpers/utils/wrapper");
const {
BadRequestError,
NotFoundError,
ForbiddenError,
} = require("../../../helpers/error");
class ProjectService {
async createProject(projectPayload, 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 can create project"),
);
}
const { member_emails, ...projectData } = projectPayload;
const admin = await projectRepository.findAdminById(actor.id);
if (!admin) {
return error(new NotFoundError("Admin not found"));
}
const normalizedEmails = this.normalizeEmails(member_emails || []);
if (normalizedEmails.length > 8) {
return error(
new BadRequestError(
"Project member limit exceeded. Maximum member is 8",
),
);
}
const users =
normalizedEmails.length > 0
? await projectRepository.findUsersByEmails(normalizedEmails)
: [];
if (normalizedEmails.length > 0) {
const foundEmails = users.map((user) => user.email.toLowerCase());
const missingEmails = normalizedEmails.filter(
(email) => !foundEmails.includes(email),
);
if (missingEmails.length > 0) {
return error(
new NotFoundError(
`User with these emails not found: ${missingEmails.join(", ")}`,
),
);
}
}
const newProjectData = {
id_admin: parseInt(actor.id),
project_icon: projectData.project_icon,
project_name: projectData.project_name,
description: projectData.description || "",
start_date: projectData.start_date,
end_date: projectData.end_date,
max_members: 8,
status: "active",
};
const userIds = users.map((user) => user.id);
const project = await projectRepository.createWithMembers(
newProjectData,
userIds,
);
return data(this.mapProjectDetail(project));
} catch (err) {
return error(err);
}
}
async getAllProjects(query = {}, actor = {}) {
try {
const projects = await projectRepository.findAll({
status: query.status,
actor,
});
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 getProjectById(id, actor = {}) {
try {
const project = await projectRepository.findById(id);
if (!project) {
return error(new NotFoundError("Project not found"));
}
const accessError = await this.validateProjectAccess(project.id, actor);
if (accessError) {
return error(accessError);
}
return data(this.mapProjectDetail(project));
} catch (err) {
return error(err);
}
}
async updateProject(id, updatePayload, 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 can update project"),
);
}
const existingProject = await projectRepository.findById(id);
if (!existingProject) {
return error(new NotFoundError("Project not found"));
}
if (Object.keys(updatePayload).length === 0) {
return error(
new BadRequestError(
"At least one field is required to update project",
),
);
}
const updateData = { ...updatePayload };
if (updateData.start_date || updateData.end_date) {
const startDate = updateData.start_date || existingProject.start_date;
const endDate = updateData.end_date || existingProject.end_date;
if (new Date(endDate) < new Date(startDate)) {
return error(
new BadRequestError(
"End date must be greater than or equal to start date",
),
);
}
}
if (updateData.description === null) {
updateData.description = "";
}
const updatedProject = await projectRepository.updateById(id, updateData);
return data(this.mapProjectDetail(updatedProject));
} catch (err) {
return error(err);
}
}
async archiveProject(id, 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 can archive project"),
);
}
const existingProject = await projectRepository.findById(id);
if (!existingProject) {
return error(new NotFoundError("Project not found"));
}
if (existingProject.status === "archived") {
return data(this.mapProjectDetail(existingProject));
}
const archivedProject = await projectRepository.archiveById(id);
return data(this.mapProjectDetail(archivedProject));
} catch (err) {
return error(err);
}
}
async validateProjectAccess(idProject, actor = {}) {
if (!actor?.id || !actor?.role) {
return new ForbiddenError(
"Access denied: authentication data is missing",
);
}
if (actor.role === "admin") {
return null;
}
if (actor.role === "intern") {
const membership = await projectRepository.checkActiveMember(
idProject,
actor.id,
);
if (!membership) {
return new ForbiddenError(
"Access denied: you are not a member of this project",
);
}
return null;
}
return new ForbiddenError("Access denied: invalid role");
}
normalizeEmails(emails = []) {
return [
...new Set(
emails.filter(Boolean).map((email) => email.trim().toLowerCase()),
),
];
}
mapProjectDetail(project) {
if (!project) return null;
return {
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),
members:
project.members?.map((member) => this.mapProjectMember(member)) || [],
tasks: project.tasks || [],
total_members: project.members?.length || 0,
total_tasks: project.tasks?.length || 0,
created_at: project.created_at,
updated_at: project.updated_at,
};
}
mapAdmin(admin) {
if (!admin) return null;
return {
id: admin.id,
full_name: `${admin.nama_depan}${admin.nama_belakang ? ` ${admin.nama_belakang}` : ""}`,
email: admin.email,
profile_picture: admin.profile_picture || null,
professional_bio: admin.professional_bio || null,
};
}
mapProjectMember(member) {
if (!member) return null;
const user = member.user;
const lowongan = user?.lamaran_magang?.lowongan_magang;
return {
id: member.id,
id_user: member.id_user,
status: member.status,
user: {
id: user.id,
full_name: user.full_name,
email: user.email,
professional_bio: user.professional_bio || null,
position: lowongan?.posisi || null,
kelompok_peminatan: lowongan?.kelompok_peminatan || null,
},
created_at: member.created_at,
updated_at: member.updated_at,
};
}
}
module.exports = new ProjectService();
View File
View File
+274
View File
@@ -0,0 +1,274 @@
const express = require('express');
const { projectController } = require('../modules/project');
const { verifyJWT } = require('../middleware/verifyJWT');
const isAdmin = require('../middleware/isAdmin');
const router = express.Router();
/**
* @swagger
* components:
* schemas:
* Project:
* type: object
* properties:
* id:
* type: integer
* example: 1
* project_icon:
* type: string
* enum: [code, chart, cloud, mobile, gear, users, clipboard, speedometer, lightbulb, shield]
* example: code
* project_name:
* type: string
* example: Internify LMS
* description:
* type: string
* example: Project for developing Internify LMS features
* start_date:
* type: string
* format: date
* example: 2026-06-10
* end_date:
* type: string
* format: date
* example: 2026-08-10
* max_members:
* type: integer
* example: 8
* status:
* type: string
* enum: [active, completed, archived]
* example: active
* total_members:
* type: integer
* example: 5
* total_tasks:
* type: integer
* example: 3
* ProjectCreateRequest:
* type: object
* required:
* - project_icon
* - project_name
* - start_date
* - end_date
* properties:
* project_icon:
* type: string
* enum: [code, chart, cloud, mobile, gear, users, clipboard, speedometer, lightbulb, shield]
* example: code
* project_name:
* type: string
* example: Internify LMS
* description:
* type: string
* example: Project for developing Internify LMS features
* start_date:
* type: string
* format: date
* example: 2026-06-10
* end_date:
* type: string
* format: date
* example: 2026-08-10
* member_emails:
* type: array
* items:
* type: string
* format: email
* example: ["intern1@example.com", "intern2@example.com"]
* ProjectUpdateRequest:
* type: object
* properties:
* project_icon:
* type: string
* enum: [code, chart, cloud, mobile, gear, users, clipboard, speedometer, lightbulb, shield]
* example: cloud
* project_name:
* type: string
* example: Internify LMS Updated
* description:
* type: string
* example: Updated project description
* start_date:
* type: string
* format: date
* example: 2026-06-15
* end_date:
* type: string
* format: date
* example: 2026-08-20
* status:
* type: string
* enum: [active, completed, archived]
* example: completed
*/
/**
* @swagger
* /project-api/add:
* post:
* summary: Create a new project
* description: Create a new Internify LMS project. Admin can optionally invite interns by email.
* tags: [Project]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/ProjectCreateRequest'
* responses:
* 201:
* description: Project created successfully
* 400:
* description: Bad request
* 401:
* description: Unauthorized
* 403:
* description: Forbidden
* 404:
* description: Admin or invited user not found
* 417:
* description: Validation error
* 500:
* description: Internal server error
*/
router.post('/add', verifyJWT, isAdmin, projectController.createProject);
/**
* @swagger
* /project-api/get:
* get:
* summary: Get all projects
* description: Admin can see all projects. Intern can only see projects where they are active members.
* tags: [Project]
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: status
* schema:
* type: string
* enum: [active, completed, archived]
* required: false
* description: Filter projects by status
* example: active
* responses:
* 200:
* description: Projects retrieved successfully
* 401:
* description: Unauthorized
* 417:
* description: Validation error
* 500:
* description: Internal server error
*/
router.get('/get', verifyJWT, projectController.getAllProjects);
/**
* @swagger
* /project-api/get/{id}:
* get:
* summary: Get project detail
* description: Admin can access any project. Intern can only access project where they are active members.
* tags: [Project]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: Project ID
* example: 1
* responses:
* 200:
* description: Project detail retrieved successfully
* 401:
* description: Unauthorized
* 403:
* description: Forbidden
* 404:
* description: Project not found
* 500:
* description: Internal server error
*/
router.get('/get/:id', verifyJWT, projectController.getProjectById);
/**
* @swagger
* /project-api/update/{id}:
* patch:
* summary: Update project
* description: Update project information. Only admin can access this endpoint.
* tags: [Project]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: Project ID
* example: 1
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/ProjectUpdateRequest'
* responses:
* 200:
* description: Project updated successfully
* 400:
* description: Bad request
* 401:
* description: Unauthorized
* 403:
* description: Forbidden
* 404:
* description: Project not found
* 417:
* description: Validation error
* 500:
* description: Internal server error
*/
router.patch('/update/:id', verifyJWT, isAdmin, projectController.updateProject);
/**
* @swagger
* /project-api/delete/{id}:
* delete:
* summary: Archive project
* description: Archive project by changing its status to archived. This endpoint does not hard delete the project.
* tags: [Project]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: Project ID
* example: 1
* responses:
* 200:
* description: Project archived successfully
* 401:
* description: Unauthorized
* 403:
* description: Forbidden
* 404:
* description: Project not found
* 500:
* description: Internal server error
*/
router.delete('/delete/:id', verifyJWT, isAdmin, projectController.archiveProject);
module.exports = router;
View File
View File