fixed project slug uniqueness issue

This commit is contained in:
Dmitri 2026-03-31 13:03:50 +04:00
parent 91c24165bf
commit a9a2866b23
3 changed files with 66 additions and 2 deletions

View File

@ -34,6 +34,7 @@ const errors = {
revokingOwnPermission: `You can't revoke your own owner permission`,
deletingHimself: `You can't delete yourself`,
emailRequired: 'Email is required',
slugAlreadyExists: 'This slug is already in use by another project',
},
},

View File

@ -43,9 +43,47 @@ module.exports = class ProjectsService {
return uniqueSlug;
}
/**
* Validate slug uniqueness before create/update
* @param {string} slug - Slug to validate
* @param {string|null} excludeId - Project ID to exclude (for updates)
* @param {Transaction} transaction - DB transaction
* @throws {ValidationError} if slug already exists
* @returns {string} Normalized slug
*/
static async validateSlugUniqueness(slug, excludeId, transaction) {
const normalizedSlug = ProjectsService.normalizeSlug(slug);
const whereClause = { slug: normalizedSlug };
if (excludeId) {
whereClause.id = { [db.Sequelize.Op.ne]: excludeId };
}
const existing = await db.projects.findOne({
where: whereClause,
paranoid: false,
transaction,
});
if (existing) {
throw new ValidationError('iam.errors.slugAlreadyExists');
}
return normalizedSlug;
}
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
// Validate slug uniqueness if provided
if (data.slug) {
data.slug = await ProjectsService.validateSlugUniqueness(
data.slug,
null,
transaction,
);
}
const createdProject = await ProjectsDBApi.create(data, {
currentUser,
transaction,
@ -196,6 +234,15 @@ module.exports = class ProjectsService {
throw new ValidationError('projectsNotFound');
}
// Validate slug uniqueness if slug is being changed
if (data.slug && data.slug !== projects.slug) {
data.slug = await ProjectsService.validateSlugUniqueness(
data.slug,
id,
transaction,
);
}
const updatedProjects = await ProjectsDBApi.update(id, data, {
currentUser,
transaction,

View File

@ -24,6 +24,7 @@ import BaseButton from '../../components/BaseButton';
import { deleteItem, update, fetch } from '../../stores/projects/projectsSlice';
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
import { useRouter } from 'next/router';
import { toast, ToastContainer } from 'react-toastify';
import type { Project } from '../../types/entities';
import { logger } from '../../lib/logger';
@ -143,8 +144,22 @@ const EditProjectsPage = () => {
og_image_url: data.og_image_url,
};
await dispatch(update({ id: id as string, data: apiData }));
await router.push('/projects/projects-list');
try {
await dispatch(update({ id: id as string, data: apiData })).unwrap();
toast('Project settings saved', {
type: 'success',
position: 'bottom-center',
});
} catch (error: unknown) {
const errorMessage =
error && typeof error === 'object' && 'message' in error
? String((error as { message: string }).message)
: 'Failed to save project settings';
toast(errorMessage, {
type: 'error',
position: 'bottom-center',
});
}
};
const handleDelete = async () => {
@ -322,6 +337,7 @@ const EditProjectsPage = () => {
)}
</Formik>
</CardBox>
<ToastContainer />
</SectionMain>
</>
);