feat: menambahkan kolom slug dan implement slug di module project

This commit is contained in:
Muhammad Zhafran Ilham
2026-06-23 22:07:21 +07:00
parent 08f7ca8319
commit b630ebbe3c
6 changed files with 138 additions and 11 deletions
+30
View File
@@ -0,0 +1,30 @@
function slugify(value, fallback = 'item') {
if (!value || typeof value !== 'string') {
return fallback;
}
let slug = value
.toString()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase()
.trim()
.replace(/[^a-z0-9\s-]/g, '')
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '');
if (!slug) {
slug = fallback;
}
if (/^\d+$/.test(slug)) {
slug = `${fallback}-${slug}`;
}
return slug;
}
module.exports = {
slugify,
};
@@ -420,6 +420,44 @@ class ProjectRepository {
},
});
}
async findBySlug(slug) {
return prisma.project.findUnique({
where: {
slug,
},
include: this.projectDetailInclude(),
});
}
async findByIdOrSlug(identifier) {
if (/^\d+$/.test(String(identifier))) {
return this.findById(identifier);
}
return this.findBySlug(identifier);
}
async findSlugExists(slug, excludeProjectId = null) {
const where = {
slug,
};
if (excludeProjectId) {
where.id = {
not: parseInt(excludeProjectId),
};
}
const project = await prisma.project.findFirst({
where,
select: {
id: true,
},
});
return Boolean(project);
}
}
module.exports = new ProjectRepository();
@@ -7,6 +7,7 @@ const {
ForbiddenError,
ConflictError,
} = require("../../../helpers/error");
const { slugify } = require("../../../helpers/utils/slug");
class ProjectService {
async createProject(projectPayload, actor) {
@@ -72,8 +73,11 @@ class ProjectService {
}
}
const projectSlug = await this.generateUniqueProjectSlug(projectData.project_name);
const newProjectData = {
id_admin: parseInt(actor.id),
slug: projectSlug,
project_icon: projectData.project_icon,
project_name: projectData.project_name,
description: projectData.description || "",
@@ -110,6 +114,7 @@ class ProjectService {
const mappedProjects = projects.map((project) => ({
id: project.id,
slug: project.slug,
project_icon: project.project_icon,
project_name: project.project_name,
description: project.description,
@@ -131,7 +136,7 @@ class ProjectService {
async getProjectById(id, actor = {}) {
try {
const project = await projectRepository.findById(id);
const project = await projectRepository.findByIdOrSlug(id);
if (!project) {
return error(new NotFoundError("Project not found"));
@@ -160,7 +165,7 @@ class ProjectService {
);
}
const existingProject = await projectRepository.findById(id);
const existingProject = await projectRepository.findByIdOrSlug(id);
if (!existingProject) {
return error(new NotFoundError("Project not found"));
@@ -193,7 +198,17 @@ class ProjectService {
updateData.description = "";
}
const updatedProject = await projectRepository.updateById(id, updateData);
if (
updateData.project_name !== undefined &&
updateData.project_name !== existingProject.project_name
) {
updateData.slug = await this.generateUniqueProjectSlug(
updateData.project_name,
existingProject.id
);
}
const updatedProject = await projectRepository.updateById(existingProject.id, updateData);
return data(this.mapProjectDetail(updatedProject));
} catch (err) {
@@ -213,7 +228,7 @@ class ProjectService {
);
}
const existingProject = await projectRepository.findById(id);
const existingProject = await projectRepository.findByIdOrSlug(id);
if (!existingProject) {
return error(new NotFoundError("Project not found"));
@@ -223,7 +238,7 @@ class ProjectService {
return data(this.mapProjectDetail(existingProject));
}
const archivedProject = await projectRepository.archiveById(id);
const archivedProject = await projectRepository.archiveById(existingProject.id);
return data(this.mapProjectDetail(archivedProject));
} catch (err) {
@@ -283,6 +298,7 @@ class ProjectService {
return {
id: project.id,
slug: project.slug,
project_icon: project.project_icon,
project_name: project.project_name,
description: project.description,
@@ -351,6 +367,7 @@ class ProjectService {
const mappedProjects = projects.map((project) => ({
id: project.id,
slug: project.slug,
project_icon: project.project_icon,
project_name: project.project_name,
description: project.description,
@@ -430,6 +447,7 @@ class ProjectService {
const mappedProjects = projects.map((project) => ({
id: project.id,
slug: project.slug,
project_icon: project.project_icon,
project_name: project.project_name,
description: project.description,
@@ -536,6 +554,20 @@ class ProjectService {
return error(err);
}
}
async generateUniqueProjectSlug(projectName, excludeProjectId = null) {
const baseSlug = slugify(projectName, 'project');
let slug = baseSlug;
let counter = 2;
while (await projectRepository.findSlugExists(slug, excludeProjectId)) {
slug = `${baseSlug}-${counter}`;
counter += 1;
}
return slug;
}
}
module.exports = new ProjectService();