feat: better error messages
This commit is contained in:
@@ -38,8 +38,8 @@ const onSubmit = async (event: FormSubmitEvent<Schema>) => {
|
|||||||
toast.add({ title: "Link created", color: "success" });
|
toast.add({ title: "Link created", color: "success" });
|
||||||
}
|
}
|
||||||
emit("close", true);
|
emit("close", true);
|
||||||
} catch {
|
} catch (error) {
|
||||||
toast.add({ title: "Something went wrong", color: "error" });
|
toast.add({ title: getApiError(error), color: "error" });
|
||||||
} finally {
|
} finally {
|
||||||
submitting.value = false;
|
submitting.value = false;
|
||||||
}
|
}
|
||||||
|
|||||||
22
app/utils/api.ts
Normal file
22
app/utils/api.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const apiErrorSchema = z.object({
|
||||||
|
data: z.object({
|
||||||
|
statusCode: z.number(),
|
||||||
|
message: z.string(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getApiError = (
|
||||||
|
error: unknown,
|
||||||
|
defaultMessage: string = "Something went wrong",
|
||||||
|
): string => {
|
||||||
|
const parsedError = apiErrorSchema.safeParse(error);
|
||||||
|
if (parsedError.success) {
|
||||||
|
if (parsedError.data.data.statusCode === 500) {
|
||||||
|
return `ERR-500: Internal server error`;
|
||||||
|
}
|
||||||
|
return `ERR-${parsedError.data.data.statusCode}: ${parsedError.data.data.message}`;
|
||||||
|
}
|
||||||
|
return defaultMessage;
|
||||||
|
};
|
||||||
@@ -17,6 +17,11 @@ export default defineEventHandler(async (event) => {
|
|||||||
const params = await getValidatedRouterParams(event, paramsSchema.parse);
|
const params = await getValidatedRouterParams(event, paramsSchema.parse);
|
||||||
const body = await readValidatedBody(event, bodySchema.parse);
|
const body = await readValidatedBody(event, bodySchema.parse);
|
||||||
|
|
||||||
|
const conflicts = await findConflictingFields(body, params.id);
|
||||||
|
if (conflicts.length > 0) {
|
||||||
|
throw createError({ statusCode: 409, message: `A link with this ${joinFields(conflicts)} already exists` });
|
||||||
|
}
|
||||||
|
|
||||||
const [link] = await db
|
const [link] = await db
|
||||||
.update(tables.links)
|
.update(tables.links)
|
||||||
.set(body)
|
.set(body)
|
||||||
|
|||||||
@@ -11,6 +11,12 @@ const bodySchema = z.object({
|
|||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const body = await readValidatedBody(event, bodySchema.parse);
|
const body = await readValidatedBody(event, bodySchema.parse);
|
||||||
|
|
||||||
|
const conflicts = await findConflictingFields(body);
|
||||||
|
if (conflicts.length > 0) {
|
||||||
|
throw createError({ statusCode: 409, message: `A link with this ${joinFields(conflicts)} already exists` });
|
||||||
|
}
|
||||||
|
|
||||||
const [link] = await db.insert(tables.links).values(body).returning();
|
const [link] = await db.insert(tables.links).values(body).returning();
|
||||||
return link;
|
return link;
|
||||||
});
|
});
|
||||||
|
|||||||
34
server/utils/links.ts
Normal file
34
server/utils/links.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { db } from "#server/db";
|
||||||
|
import * as tables from "#server/db/schema";
|
||||||
|
import { and, eq, ne } from "drizzle-orm";
|
||||||
|
|
||||||
|
const UNIQUE_FIELDS = ["name", "path", "url"] as const;
|
||||||
|
type UniqueField = (typeof UNIQUE_FIELDS)[number];
|
||||||
|
|
||||||
|
export const joinFields = (fields: string[]): string => {
|
||||||
|
if (fields.length <= 1) return fields.join("");
|
||||||
|
return `${fields.slice(0, -1).join(", ")} and ${fields.at(-1)}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const findConflictingFields = async (
|
||||||
|
values: Partial<Record<UniqueField, string>>,
|
||||||
|
excludeId?: number,
|
||||||
|
): Promise<UniqueField[]> => {
|
||||||
|
const conflicts: UniqueField[] = [];
|
||||||
|
|
||||||
|
for (const field of UNIQUE_FIELDS) {
|
||||||
|
const value = values[field];
|
||||||
|
if (!value) continue;
|
||||||
|
|
||||||
|
const conflict = await db.query.links.findFirst({
|
||||||
|
where: and(
|
||||||
|
eq(tables.links[field], value),
|
||||||
|
excludeId !== undefined ? ne(tables.links.id, excludeId) : undefined,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (conflict) conflicts.push(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
return conflicts;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user