feat: complete Phase 2 tech debt remediation

- Extract shared RSS/Atom fetch logic into feed-fetcher utility (P1-3)
- Split email-processor into validateEmail/storeEmail functions (P1-6)
- Add stateless HMAC-SHA256 CSRF protection to admin forms (P2-8)
- Fix Hono<{ Bindings: Env }> type safety across all routes (P3-13)
- Add entries.test.ts and files.test.ts with full coverage (P1-7)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Julien Herr
2026-05-22 09:46:55 +02:00
parent f2981eec31
commit 7d375693b9
15 changed files with 485 additions and 152 deletions
+44
View File
@@ -0,0 +1,44 @@
import { Env, FeedConfig, FeedMetadata, EmailData } from "../types";
const MAX_FEED_ITEMS = 20;
export interface FeedData {
feedConfig: FeedConfig;
emails: EmailData[];
}
export async function fetchFeedData(
feedId: string,
env: Env,
feedPath: "rss" | "atom",
): Promise<FeedData | null> {
const storage = env.EMAIL_STORAGE;
const feedMetadata = (await storage.get(
`feed:${feedId}:metadata`,
"json",
)) as FeedMetadata | null;
if (!feedMetadata) return null;
const feedConfig = ((await storage.get(
`feed:${feedId}:config`,
"json",
)) as FeedConfig | null) ?? {
title: `Newsletter Feed ${feedId}`,
description: "Converted email newsletter",
site_url: `https://${env.DOMAIN}/${feedPath}/${feedId}`,
feed_url: `https://${env.DOMAIN}/${feedPath}/${feedId}`,
language: "en",
created_at: Date.now(),
};
const emailRefs = feedMetadata.emails.slice(0, MAX_FEED_ITEMS);
const emails: EmailData[] = [];
for (const ref of emailRefs) {
const data = (await storage.get(ref.key, "json")) as EmailData | null;
if (data) emails.push(data);
}
return { feedConfig, emails };
}