diff --git a/src/client/routes/admin/items/$itemId.tsx b/src/client/routes/admin/items/$itemId.tsx
index b42d586..078b260 100644
--- a/src/client/routes/admin/items/$itemId.tsx
+++ b/src/client/routes/admin/items/$itemId.tsx
@@ -161,13 +161,17 @@ function AdminItemEditPage() {
setFetchingImage(true);
setFetchError(null);
try {
- const result = await apiPost<{ filename: string; sourceUrl: string }>(
- "/api/images/from-url",
- { url: fetchUrl.trim() },
- );
+ const result = await apiPost<{
+ filename: string;
+ sourceUrl: string;
+ presignedUrl: string;
+ dominantColor: string | null;
+ }>("/api/images/from-url", { url: fetchUrl.trim() });
setForm((prev) => ({
...prev,
imageFilename: result.filename,
+ imageUrl: result.presignedUrl,
+ dominantColor: result.dominantColor ?? "",
imageSourceUrl: fetchUrl.trim(),
}));
setFetchUrl("");
diff --git a/src/client/routes/admin/tags/index.tsx b/src/client/routes/admin/tags/index.tsx
index e70ee1a..ffd3d08 100644
--- a/src/client/routes/admin/tags/index.tsx
+++ b/src/client/routes/admin/tags/index.tsx
@@ -124,8 +124,10 @@ function AdminTagsPage() {
});
setNewName("");
setNewParentId(null);
- } catch {
- setCreateError("Failed to create tag. Please try again.");
+ } catch (err: unknown) {
+ const message =
+ err instanceof Error ? err.message : "Failed to create tag";
+ setCreateError(message);
}
}
@@ -156,39 +158,50 @@ function AdminTagsPage() {
{/* Quick-add form */}
-
- {createError && (
- {createError}
- )}
+
+
+ Add Tag
+
+
+ {createError && (
+
{createError}
+ )}
+
{/* Error state */}
{isError && (
diff --git a/src/server/routes/admin-tags.ts b/src/server/routes/admin-tags.ts
index 4683df4..8c466b3 100644
--- a/src/server/routes/admin-tags.ts
+++ b/src/server/routes/admin-tags.ts
@@ -45,8 +45,22 @@ app.get("/:id", async (c) => {
app.post("/", zValidator("json", createTagSchema), async (c) => {
const db = c.get("db");
const data = c.req.valid("json");
- const tag = await createTag(db, data);
- return c.json(tag, 201);
+ try {
+ const tag = await createTag(db, data);
+ return c.json(tag, 201);
+ } catch (err) {
+ if (
+ err instanceof Error &&
+ (err.message.includes("UNIQUE constraint failed") ||
+ err.message.includes("unique constraint"))
+ ) {
+ return c.json(
+ { error: `A tag named "${data.name}" already exists` },
+ 409,
+ );
+ }
+ throw err;
+ }
});
// PUT /api/admin/tags/:id — rename and/or reparent a tag
diff --git a/src/server/routes/images.ts b/src/server/routes/images.ts
index 7644ac2..bb50bb0 100644
--- a/src/server/routes/images.ts
+++ b/src/server/routes/images.ts
@@ -6,7 +6,7 @@ import {
extractDominantColor,
fetchImageFromUrl,
} from "../services/image.service";
-import { uploadImage } from "../services/storage.service";
+import { getImageUrl, uploadImage } from "../services/storage.service";
const ALLOWED_TYPES = ["image/jpeg", "image/png", "image/webp"];
const MAX_SIZE = 5 * 1024 * 1024; // 5MB
@@ -19,7 +19,8 @@ app.post("/from-url", zValidator("json", fromUrlSchema), async (c) => {
const { url } = c.req.valid("json");
try {
const result = await fetchImageFromUrl(url);
- return c.json(result, 201);
+ const presignedUrl = await getImageUrl(result.filename);
+ return c.json({ ...result, presignedUrl }, 201);
} catch (err) {
const message = (err as Error).message;
// Known validation errors from the service