refactor(admin): validate JSON feed update via @hono/zod-validator

Moves validation of POST /api/feeds/:feedId/update from inline
schema.parse() to zValidator middleware. The route now receives
typed validated data via c.req.valid("json"), and returns a
structured {success: false, error: ZodIssue[]} on invalid input.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Julien Herr
2026-05-21 23:46:29 +02:00
parent 57e0cc5413
commit 5723fd36f9
2 changed files with 70 additions and 49 deletions
+17
View File
@@ -187,6 +187,23 @@ describe("Admin Routes", () => {
}); });
}); });
describe("API Feed Update", () => {
it("returns 400 with structured validation error for empty title", async () => {
const authCookie = await loginAndGetCookie();
const res = await request("/admin/api/feeds/test-feed/update", {
method: "POST",
headers: {
Cookie: authCookie,
"Content-Type": "application/json",
},
body: JSON.stringify({ title: "", description: "desc" }),
});
expect(res.status).toBe(400);
const body = await res.json<{ success: boolean }>();
expect(body.success).toBe(false);
});
});
describe("Feed Management", () => { describe("Feed Management", () => {
it("should prevent feed deletion without authentication", async () => { it("should prevent feed deletion without authentication", async () => {
const res = await request("/admin/feeds/test-feed/delete", { const res = await request("/admin/feeds/test-feed/delete", {
+53 -49
View File
@@ -1,6 +1,7 @@
import { Context, Hono } from "hono"; import { Context, Hono } from "hono";
import { deleteCookie, getSignedCookie, setSignedCookie } from "hono/cookie"; import { deleteCookie, getSignedCookie, setSignedCookie } from "hono/cookie";
import { html, raw } from "hono/html"; import { html, raw } from "hono/html";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod"; import { z } from "zod";
import { import {
Env, Env,
@@ -3756,60 +3757,63 @@ async function removeFeedFromList(
} }
// Update feed via API (for in-place editing) // Update feed via API (for in-place editing)
app.post("/api/feeds/:feedId/update", async (c) => { app.post(
// Type assertion for environment variables "/api/feeds/:feedId/update",
const env = c.env as unknown as Env; zValidator(
const emailStorage = env.EMAIL_STORAGE; "json",
const feedId = c.req.param("feedId"); updateFeedSchema.pick({ title: true, description: true }),
(result, c) => {
if (!result.success)
return c.json({ success: false, error: result.error.issues }, 400);
},
),
async (c) => {
// Type assertion for environment variables
const env = c.env as unknown as Env;
const emailStorage = env.EMAIL_STORAGE;
const feedId = c.req.param("feedId");
try { try {
// Parse JSON data from request const { title, description } = c.req.valid("json");
const data = await c.req.json(); const parsedData = { title, description, language: "en" as const };
const { title, description } = data;
// Validate inputs // Get existing feed config
const parsedData = updateFeedSchema.parse({ const feedConfigKey = `feed:${feedId}:config`;
title, const existingConfig = (await emailStorage.get(feedConfigKey, {
description, type: "json",
language: "en", // We're defaulting to English })) as FeedConfig | null;
});
// Get existing feed config if (!existingConfig) {
const feedConfigKey = `feed:${feedId}:config`; return c.json({ error: "Feed not found" }, 404);
const existingConfig = (await emailStorage.get(feedConfigKey, { }
type: "json",
})) as FeedConfig | null;
if (!existingConfig) { // Update feed configuration
return c.json({ error: "Feed not found" }, 404); await emailStorage.put(
feedConfigKey,
JSON.stringify({
...existingConfig,
title: parsedData.title,
description: parsedData.description,
updated_at: Date.now(),
}),
);
// Update feed in the list of all feeds
await updateFeedInList(
emailStorage,
feedId,
parsedData.title,
parsedData.description,
);
// Return success response
return c.json({ success: true });
} catch (error) {
console.error("Error updating feed via API:", error);
return c.json({ error: "Error updating feed" }, 400);
} }
},
// Update feed configuration );
await emailStorage.put(
feedConfigKey,
JSON.stringify({
...existingConfig,
title: parsedData.title,
description: parsedData.description,
updated_at: Date.now(),
}),
);
// Update feed in the list of all feeds
await updateFeedInList(
emailStorage,
feedId,
parsedData.title,
parsedData.description,
);
// Return success response
return c.json({ success: true });
} catch (error) {
console.error("Error updating feed via API:", error);
return c.json({ error: "Error updating feed" }, 400);
}
});
// Export the Hono app // Export the Hono app
export const handle = app; export const handle = app;