mirror of
https://github.com/juherr/kill-the-news.git
synced 2026-06-20 22:03:48 +00:00
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:
@@ -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", () => {
|
||||
it("should prevent feed deletion without authentication", async () => {
|
||||
const res = await request("/admin/feeds/test-feed/delete", {
|
||||
|
||||
+53
-49
@@ -1,6 +1,7 @@
|
||||
import { Context, Hono } from "hono";
|
||||
import { deleteCookie, getSignedCookie, setSignedCookie } from "hono/cookie";
|
||||
import { html, raw } from "hono/html";
|
||||
import { zValidator } from "@hono/zod-validator";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
Env,
|
||||
@@ -3756,60 +3757,63 @@ async function removeFeedFromList(
|
||||
}
|
||||
|
||||
// Update feed via API (for in-place editing)
|
||||
app.post("/api/feeds/:feedId/update", 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");
|
||||
app.post(
|
||||
"/api/feeds/:feedId/update",
|
||||
zValidator(
|
||||
"json",
|
||||
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 {
|
||||
// Parse JSON data from request
|
||||
const data = await c.req.json();
|
||||
const { title, description } = data;
|
||||
try {
|
||||
const { title, description } = c.req.valid("json");
|
||||
const parsedData = { title, description, language: "en" as const };
|
||||
|
||||
// Validate inputs
|
||||
const parsedData = updateFeedSchema.parse({
|
||||
title,
|
||||
description,
|
||||
language: "en", // We're defaulting to English
|
||||
});
|
||||
// Get existing feed config
|
||||
const feedConfigKey = `feed:${feedId}:config`;
|
||||
const existingConfig = (await emailStorage.get(feedConfigKey, {
|
||||
type: "json",
|
||||
})) as FeedConfig | null;
|
||||
|
||||
// Get existing feed config
|
||||
const feedConfigKey = `feed:${feedId}:config`;
|
||||
const existingConfig = (await emailStorage.get(feedConfigKey, {
|
||||
type: "json",
|
||||
})) as FeedConfig | null;
|
||||
if (!existingConfig) {
|
||||
return c.json({ error: "Feed not found" }, 404);
|
||||
}
|
||||
|
||||
if (!existingConfig) {
|
||||
return c.json({ error: "Feed not found" }, 404);
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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 const handle = app;
|
||||
|
||||
Reference in New Issue
Block a user