fixed project slug uniqueness issue
This commit is contained in:
parent
91c24165bf
commit
a9a2866b23
@ -34,6 +34,7 @@ const errors = {
|
|||||||
revokingOwnPermission: `You can't revoke your own owner permission`,
|
revokingOwnPermission: `You can't revoke your own owner permission`,
|
||||||
deletingHimself: `You can't delete yourself`,
|
deletingHimself: `You can't delete yourself`,
|
||||||
emailRequired: 'Email is required',
|
emailRequired: 'Email is required',
|
||||||
|
slugAlreadyExists: 'This slug is already in use by another project',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -43,9 +43,47 @@ module.exports = class ProjectsService {
|
|||||||
return uniqueSlug;
|
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) {
|
static async create(data, currentUser) {
|
||||||
const transaction = await db.sequelize.transaction();
|
const transaction = await db.sequelize.transaction();
|
||||||
try {
|
try {
|
||||||
|
// Validate slug uniqueness if provided
|
||||||
|
if (data.slug) {
|
||||||
|
data.slug = await ProjectsService.validateSlugUniqueness(
|
||||||
|
data.slug,
|
||||||
|
null,
|
||||||
|
transaction,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const createdProject = await ProjectsDBApi.create(data, {
|
const createdProject = await ProjectsDBApi.create(data, {
|
||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
@ -196,6 +234,15 @@ module.exports = class ProjectsService {
|
|||||||
throw new ValidationError('projectsNotFound');
|
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, {
|
const updatedProjects = await ProjectsDBApi.update(id, data, {
|
||||||
currentUser,
|
currentUser,
|
||||||
transaction,
|
transaction,
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import BaseButton from '../../components/BaseButton';
|
|||||||
import { deleteItem, update, fetch } from '../../stores/projects/projectsSlice';
|
import { deleteItem, update, fetch } from '../../stores/projects/projectsSlice';
|
||||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
|
import { toast, ToastContainer } from 'react-toastify';
|
||||||
import type { Project } from '../../types/entities';
|
import type { Project } from '../../types/entities';
|
||||||
import { logger } from '../../lib/logger';
|
import { logger } from '../../lib/logger';
|
||||||
|
|
||||||
@ -143,8 +144,22 @@ const EditProjectsPage = () => {
|
|||||||
og_image_url: data.og_image_url,
|
og_image_url: data.og_image_url,
|
||||||
};
|
};
|
||||||
|
|
||||||
await dispatch(update({ id: id as string, data: apiData }));
|
try {
|
||||||
await router.push('/projects/projects-list');
|
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 () => {
|
const handleDelete = async () => {
|
||||||
@ -322,6 +337,7 @@ const EditProjectsPage = () => {
|
|||||||
)}
|
)}
|
||||||
</Formik>
|
</Formik>
|
||||||
</CardBox>
|
</CardBox>
|
||||||
|
<ToastContainer />
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user