prevent duplicate label names

This commit is contained in:
miloschwartz
2026-06-08 11:59:15 -07:00
parent fae258b145
commit 8fe45ba78c
6 changed files with 89 additions and 20 deletions

View File

@@ -292,6 +292,8 @@
"labelDelete": "Delete Label",
"labelAdd": "Add Label",
"labelCreateSuccessMessage": "Label Created Successfully",
"labelDuplicateError": "Duplicate Label",
"labelDuplicateErrorDescription": "A label with this name already exists.",
"labelEditSuccessMessage": "Label Modified Successfully",
"labelNameField": "Label Name",
"labelColorField": "Label Color",

View File

@@ -22,7 +22,7 @@ import response from "@server/lib/response";
import logger from "@server/logger";
import type { CreateOrEditLabelResponse } from "@server/routers/labels/types";
import HttpCode from "@server/types/HttpCode";
import { and, eq } from "drizzle-orm";
import { and, eq, sql } from "drizzle-orm";
import { NextFunction, Request, Response } from "express";
import createHttpError from "http-errors";
import { z } from "zod";
@@ -107,6 +107,26 @@ export async function createOrgLabel(
}
}
const [existingLabel] = await db
.select({ labelId: labels.labelId })
.from(labels)
.where(
and(
eq(labels.orgId, orgId),
sql`LOWER(${labels.name}) = ${name.toLowerCase()}`
)
)
.limit(1);
if (existingLabel) {
return next(
createHttpError(
HttpCode.CONFLICT,
"A label with this name already exists"
)
);
}
const label = await db.transaction(async (tx) => {
const [label] = await tx
.insert(labels)

View File

@@ -16,7 +16,7 @@ import response from "@server/lib/response";
import logger from "@server/logger";
import type { CreateOrEditLabelResponse } from "@server/routers/labels/types";
import HttpCode from "@server/types/HttpCode";
import { and, eq } from "drizzle-orm";
import { and, eq, ne, sql } from "drizzle-orm";
import { NextFunction, Request, Response } from "express";
import createHttpError from "http-errors";
import { z } from "zod";
@@ -74,6 +74,29 @@ export async function updateOrgLabel(
const { name, color } = parsedBody.data;
if (name && name.toLowerCase() !== existing.name.toLowerCase()) {
const [duplicateLabel] = await db
.select({ labelId: labels.labelId })
.from(labels)
.where(
and(
eq(labels.orgId, orgId),
ne(labels.labelId, labelId),
sql`LOWER(${labels.name}) = ${name.toLowerCase()}`
)
)
.limit(1);
if (duplicateLabel) {
return next(
createHttpError(
HttpCode.CONFLICT,
"A label with this name already exists"
)
);
}
}
const [label] = await db
.update(labels)
.set({

View File

@@ -52,12 +52,20 @@ export function CreateOrgLabelDialog({
description: t("labelCreateSuccessMessage")
});
}
} catch (e) {
toast({
title: t("error"),
description: formatAxiosError(e, t("errorOccurred")),
variant: "destructive"
});
} catch (e: any) {
if (e.response?.status === 409) {
toast({
title: t("labelDuplicateError"),
description: t("labelDuplicateErrorDescription"),
variant: "destructive"
});
} else {
toast({
title: t("error"),
description: formatAxiosError(e, t("errorOccurred")),
variant: "destructive"
});
}
}
}

View File

@@ -58,12 +58,20 @@ export function EditOrgLabelDialog({
description: t("labelEditSuccessMessage")
});
}
} catch (e) {
toast({
title: t("error"),
description: formatAxiosError(e, t("errorOccurred")),
variant: "destructive"
});
} catch (e: any) {
if (e.response?.status === 409) {
toast({
title: t("labelDuplicateError"),
description: t("labelDuplicateErrorDescription"),
variant: "destructive"
});
} else {
toast({
title: t("error"),
description: formatAxiosError(e, t("errorOccurred")),
variant: "destructive"
});
}
}
}

View File

@@ -112,12 +112,20 @@ export function LabelsSelector({
},
"attach"
);
} catch (e) {
toast({
title: t("error"),
description: formatAxiosError(e, t("errorOccurred")),
variant: "destructive"
});
} catch (e: any) {
if (e.response?.status === 409) {
toast({
title: t("labelDuplicateError"),
description: t("labelDuplicateErrorDescription"),
variant: "destructive"
});
} else {
toast({
title: t("error"),
description: formatAxiosError(e, t("errorOccurred")),
variant: "destructive"
});
}
}
setlabelsSearchQuery("");
}