feat(project): implement filter multi status pada get all projects

This commit is contained in:
Muhammad Zhafran Ilham
2026-06-28 01:10:17 +07:00
parent ab6ca1f7bf
commit b82174a1ae
4 changed files with 77 additions and 11 deletions
+25 -2
View File
@@ -88,8 +88,31 @@ const updateProjectModel = joi.object().keys({
}); });
const getAllProjectsModel = joi.object().keys({ const getAllProjectsModel = joi.object().keys({
status: joi.string().valid(...PROJECT_STATUS_VALUES).optional().messages({ status: joi
'string.base': 'Project status must be a string.', .alternatives()
.try(
joi.string(),
joi.array().items(joi.string())
)
.optional()
.custom((value, helpers) => {
const statuses = Array.isArray(value)
? value.flatMap((status) => String(status).split(','))
: String(value).split(',');
const invalidStatuses = statuses
.map((status) => status.trim().toLowerCase())
.filter(Boolean)
.filter((status) => !PROJECT_STATUS_VALUES.includes(status));
if (invalidStatuses.length > 0) {
return helpers.error('any.only');
}
return value;
})
.messages({
'alternatives.types': 'Project status must be a string or an array of strings.',
'any.only': `Project status must be one of: ${PROJECT_STATUS_VALUES.join(', ')}.`, 'any.only': `Project status must be one of: ${PROJECT_STATUS_VALUES.join(', ')}.`,
}), }),
}); });
@@ -58,11 +58,13 @@ class ProjectRepository {
}); });
} }
async findAll({ status, actor }) { async findAll({ statuses, actor }) {
const where = {}; const where = {};
if (status) { if (statuses && statuses.length > 0) {
where.status = status; where.status = {
in: statuses,
};
} }
if (actor?.role === "intern") { if (actor?.role === "intern") {
@@ -109,8 +109,13 @@ class ProjectService {
async getAllProjects(query = {}, actor = {}) { async getAllProjects(query = {}, actor = {}) {
try { try {
const statusFilter = this.parseProjectStatusFilter(query.status);
if (statusFilter.err) {
return statusFilter;
}
const projects = await projectRepository.findAll({ const projects = await projectRepository.findAll({
status: query.status, statuses: statusFilter.data,
actor, actor,
}); });
@@ -293,6 +298,40 @@ class ProjectService {
return new ForbiddenError("Access denied: invalid role"); return new ForbiddenError("Access denied: invalid role");
} }
parseProjectStatusFilter(rawStatus) {
if (!rawStatus) {
return data(null);
}
const allowedStatuses = ["active", "completed", "archived"];
const statuses = Array.isArray(rawStatus)
? rawStatus.flatMap((status) => String(status).split(","))
: String(rawStatus).split(",");
const cleanedStatuses = [
...new Set(
statuses
.map((status) => status.trim().toLowerCase())
.filter(Boolean)
),
];
const invalidStatuses = cleanedStatuses.filter(
(status) => !allowedStatuses.includes(status)
);
if (invalidStatuses.length > 0) {
return error(
new BadRequestError(
`Invalid project status: ${invalidStatuses.join(", ")}`
)
);
}
return data(cleanedStatuses);
}
normalizeEmails(emails = []) { normalizeEmails(emails = []) {
return [ return [
...new Set( ...new Set(
+5 -3
View File
@@ -534,10 +534,10 @@ router.post('/add', verifyJWT, isMentorOrAdmin, projectController.createProject)
* name: status * name: status
* schema: * schema:
* type: string * type: string
* enum: [active, completed, archived] * example: 'active,completed'
* required: false * required: false
* description: Filter projects by status * description: Filter projects by one or more statuses. Use comma-separated values, for example active,completed. Values are **active, completed, archived**
* example: active * example: active,completed
* responses: * responses:
* 200: * 200:
* description: Projects retrieved successfully * description: Projects retrieved successfully
@@ -545,6 +545,8 @@ router.post('/add', verifyJWT, isMentorOrAdmin, projectController.createProject)
* application/json: * application/json:
* schema: * schema:
* $ref: '#/components/schemas/ProjectListResponse' * $ref: '#/components/schemas/ProjectListResponse'
* 400:
* description: Invalid project status
* 401: * 401:
* description: Unauthorized * description: Unauthorized
* 417: * 417: