Commit Graph

161 Commits

Author SHA1 Message Date
Julien Herr ba7add5f53 fix(demo): correct DOMAIN env var to demo.kill-the.news
The [env.demo] section pointed DOMAIN to kill-the.news while the
custom_domain route was demo.kill-the.news, causing feed/email URLs
to show the wrong domain in the admin UI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:21:26 +02:00
Julien Herr 3ccbd876b6 build(deps-dev): bump dev-dependencies group with 8 updates
Includes TypeScript 5→6 migration: moduleResolution node→bundler.
2026-05-22 17:35:01 +02:00
Julien Herr 03d069bfa3 build(deps): bump hono from 4.11.7 to 4.12.22
Fix TypeScript errors from stricter c.req.param() types (string | undefined).
2026-05-22 17:33:47 +02:00
Julien Herr ea400883dd build(deps): bump zod from 4.3.6 to 4.4.3 2026-05-22 17:32:50 +02:00
Julien Herr 7dd1225cde build(deps): bump feed from 5.2.0 to 5.2.1 2026-05-22 17:32:37 +02:00
Julien Herr 3b4a1ce98c build(deps): bump actions/setup-node from v4 to v6.4.0 2026-05-22 17:32:19 +02:00
Julien Herr 3fa17228a3 build(deps): bump actions/checkout from v4 to v6.0.2 2026-05-22 17:32:04 +02:00
Julien Herr ebfb8d4a60 fix(config): silence CSS module rule fallthrough warning 2026-05-22 17:28:02 +02:00
Julien Herr d657875322 feat(ci): auto-deploy demo instance on main push 2026-05-22 17:20:46 +02:00
Julien Herr 4d9418057e feat(deploy): add demo environment to wrangler config 2026-05-22 17:20:08 +02:00
Julien Herr edc1183e61 docs: rewrite CLAUDE.md to match actual codebase, remove .cursor
CLAUDE.md now reflects the real route set (atom, entries, files, hub,
email handler), src/lib/ layout, admin sub-modules, client script
pipeline, full Env bindings, and WebSub KV schema.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 15:53:22 +02:00
Julien Herr 4316354ee5 refactor(styles): migrate TS template literal styles to real CSS files
Extract CSS from TypeScript template literals into standalone .css files
(variables.css, layout.css, components.css, utilities.css) and update
src/routes/admin/ui.tsx to import them directly via Wrangler text imports,
concatenating the strings at runtime for the inline <style> tag.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 15:44:08 +02:00
Julien Herr 5d75682702 build: add Wrangler text rule for CSS imports
Add [[rules]] type = "Text" globs = ["**/*.css"] to wrangler-example.toml
so Wrangler bundles .css files as raw text strings importable in TypeScript.
Add src/types/css.d.ts to provide the module declaration for `import css from "*.css"`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 15:43:49 +02:00
Julien Herr d64f703820 fix(ui): replace children cast to any with hono/jsx Child type
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-05-22 15:35:10 +02:00
Julien Herr 0e1592f8c6 build: gitignore generated client scripts, rebuild on npm install
Move src/scripts/generated/ to .gitignore — files are deterministic
build artefacts and don't belong in version control. Wire build:client
into prepare so they're regenerated automatically after npm install.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-05-22 13:53:33 +02:00
Julien Herr 0f6670d0e9 refactor(admin): extract emailsScript to src/scripts/client/emails-page.ts
Moves the inline JS template literal from emails.tsx into a typed
TypeScript source file. The dynamic feedId value (previously interpolated
directly) is now passed via a window.__APP_CONFIG__ bootstrap script
injected immediately before the compiled static script in the HTML.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 13:50:54 +02:00
Julien Herr 40f4b42cd5 refactor(admin): extract dashboardScript to src/scripts/client/dashboard.ts
Moves the 650-line inline JS template literal from admin.tsx into a
proper TypeScript source file with full type annotations. esbuild
compiles it to a minified IIFE committed in src/scripts/generated/,
which is imported and inlined into the HTML response as before.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 13:50:47 +02:00
Julien Herr 0663861471 build: add client-side TypeScript compilation pipeline
Adds scripts/build-client.mjs which uses esbuild to compile TypeScript
files in src/scripts/client/ into minified IIFE bundles, then writes
them as TypeScript string-constant modules in src/scripts/generated/.

- Adds build:client npm script; wires it as prebuild and predev hooks
- Adds src/scripts/client/tsconfig.json with DOM lib for IDE support
- Excludes src/scripts/client/ from the root Worker tsconfig to avoid
  DOM type conflicts with the Cloudflare Workers runtime types

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 13:50:41 +02:00
Julien Herr 671179acc7 build: add esbuild dev dependency
Installs esbuild to support compiling client-side TypeScript into
minified IIFE bundles that can be inlined into Worker HTML responses.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 13:50:31 +02:00
Julien Herr 9bfebf4aa4 refactor(admin): migrate admin.ts to admin.tsx with JSX login and dashboard
Convert login page and dashboard GET routes from hono/html tagged template
literals to typed JSX using the <Layout> component. Extracts reusable
CopyIcon, CheckIcon, and CopyFieldInline components. Dashboard inline
script (~650 lines) preserved exactly via dangerouslySetInnerHTML constant.
All auth logic, CSRF middleware, and API routes are unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 13:17:46 +02:00
Julien Herr c9ab3839e4 refactor(admin): migrate emails.ts to emails.tsx with JSX rendering
Convert feed emails list and single email view GET routes from hono/html
tagged template literals to typed JSX. Extracts reusable CopyField and
SVG icon components. Inline page scripts are preserved verbatim via
dangerouslySetInnerHTML. Raw HTML display in single email view uses
dangerouslySetInnerHTML to avoid double-escaping pre-escaped content.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 13:17:38 +02:00
Julien Herr ecb85730e0 refactor(admin): migrate feeds.ts to feeds.tsx with JSX rendering
Convert the edit feed GET route from hono/html tagged template literals
to typed JSX using the <Layout> component. All CRUD routes and business
logic are preserved unchanged. textarea placeholder special characters
are now handled via JSX attribute escaping rather than &#10; entities.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 13:17:31 +02:00
Julien Herr 996aa5e211 refactor(admin): migrate ui.ts to ui.tsx with JSX Layout component
Replace hono/html tagged template layout() function with a typed JSX
<Layout> component. CSS and interactive scripts are injected via
dangerouslySetInnerHTML to preserve exact output. clampText() is
preserved and re-exported for consumers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 13:17:24 +02:00
Julien Herr f5bac1bd7d build: add hono/jsx support to tsconfig
Add jsx and jsxImportSource compiler options to enable hono/jsx server-side
rendering in .tsx files without per-file JSX pragmas.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 13:17:16 +02:00
Julien Herr d82344ac67 chore: remove TECH_DEBT.md — all tasks completed
All three phases fully remediated. P1-5 (WAF rate limiting) is
infrastructure-only and tracked in TODO.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 12:01:42 +02:00
Julien Herr 427eac973c docs: mark Phase 3 tasks done; document WAF rate limiting in TODO
P1-4, P2-10, P2-11 marked DONE in TECH_DEBT.md.
P1-5 (rate limiting) is infrastructure-only — documented in TODO.md with
the recommended Cloudflare WAF rule targets and thresholds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 10:56:55 +02:00
Julien Herr 205d4ef5bb refactor: split admin.ts into sub-modules (P2-11)
Extracts 3833-line admin.ts into focused modules:
- src/routes/admin/ui.ts       — layout() and clampText() helpers
- src/routes/admin/helpers.ts  — KV list helpers (listAllFeeds, addFeedToList, etc.)
- src/routes/admin/feeds.ts    — feed CRUD routes (feedsRouter)
- src/routes/admin/emails.ts   — email view/delete routes (emailsRouter)

admin.ts now mounts the sub-routers and retains only auth middleware,
dashboard, login/logout, and the in-place feed API update route.
All 163 tests continue to pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 10:56:20 +02:00
Julien Herr a9501d6e44 feat: add structured JSON logger and worker utility (P1-4)
Introduces src/lib/logger.ts emitting JSON lines (level, message, data)
compatible with Cloudflare Logpush. Replaces all console.log/warn/error
calls in email-processor.ts, index.ts, and hub.ts with structured logger
calls. Extracts waitUntilSafe into src/utils/worker.ts to avoid duplicating
the executionCtx guard across routes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 10:55:15 +02:00
Julien Herr 0c0669c473 feat: extract constants module (P2-10)
Centralises magic numbers and string literals into src/config/constants.ts
(FEED_MAX_BYTES, MAX_FEED_ITEMS, MAX_METADATA_EMAILS, ADMIN_COOKIE_MAX_AGE,
FORWARD_EMAIL_IPS_CACHE_TTL_MS, DEFAULT_LEASE_SECONDS, MAX_LEASE_SECONDS,
FEEDS_LIST_KEY). Consumers updated in storage.ts and feed-fetcher.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 10:55:01 +02:00
Julien Herr 582108a8e3 docs: move Durable Objects migration from TECH_DEBT to TODO
The race condition is an accepted limitation of KV; the DO migration
is a future feature rather than active tech debt to remediate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 10:31:41 +02:00
Julien Herr a0415cdc41 refactor: replace custom HMAC CSRF with hono/csrf middleware
Removes 38-line hand-rolled HMAC-SHA256 implementation in favour of
the built-in hono/csrf, which validates the Origin header natively.

- Delete src/utils/csrf.ts
- Replace custom CSRF middleware with hono/csrf (Origin-header check)
- Remove csrfToken from ContextVariableMap, layout(), forms, and JS fetch() calls
- Update admin tests: swap X-CSRF-Token for Origin header

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 10:28:26 +02:00
Julien Herr 7d375693b9 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>
2026-05-22 09:46:55 +02:00
Julien Herr f2981eec31 test: add inbound route tests covering IP auth, validation, and R2 upload 2026-05-22 07:39:48 +02:00
Julien Herr e874906291 docs: inline Durable Objects migration TODO in email-processor 2026-05-22 07:39:43 +02:00
Julien Herr 10404efffa fix: delete orphaned KV entries when trimming feed metadata 2026-05-22 07:39:37 +02:00
Julien Herr dae5db2524 feat: add GET /health endpoint 2026-05-22 07:39:24 +02:00
Julien Herr bde06dd3e4 fix(websub): add missing WebSub Link header to Atom feed
The RSS feed already advertised hub and self via the Link response
header, but the Atom feed was missing it entirely. Subscribers using
Atom had no way to discover the hub for real-time push notifications.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 23:46:51 +02:00
Julien Herr 5723fd36f9 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>
2026-05-21 23:46:51 +02:00
Julien Herr 57e0cc5413 refactor(cors): replace manual CORS middleware with hono/cors
Fixes a bug where routes returning raw `new Response()` (RSS, Atom,
entries) were not receiving CORS headers — hono/cors applies headers
after next(), covering all response paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 23:46:51 +02:00
Julien Herr ece237c0af chore: ignore coverage/ directory 2026-05-21 23:46:51 +02:00
Julien Herr 68151cbb5f fix(websub): require feed existence for subscriptions, remove atom hub header, simplify router mounting
- Add KV feed existence check in hub.ts to prevent SSRF via non-existent feeds (returns 404)
- Treat empty string hub.secret as absent (|| instead of ??)
- Remove misleading hub Link header from atom.ts (hub only supports RSS topics)
- Simplify double-layered hub router in index.ts (direct app.route instead of nested Hono)
- Update hub.test.ts to seed KV with feed config for tests requiring valid subscribe/unsubscribe

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 23:46:50 +02:00
Julien Herr 0d00e003d4 test(websub): add ctx.waitUntil coverage for processEmail notification wiring
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 23:46:50 +02:00
Julien Herr d0764ddd8e feat(websub): wire real-time push notifications on email ingest
Pass ExecutionContext through the email processing chain so notifySubscribers
is called via ctx.waitUntil after a new email is stored.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 23:46:50 +02:00
Julien Herr 6d221a07dd feat(websub): add hub discovery Link headers to RSS and Atom feeds 2026-05-21 23:46:50 +02:00
Julien Herr aa41337c6b feat(websub): mount /hub route 2026-05-21 23:46:50 +02:00
Julien Herr 09db52bb4d test(websub): add hub route tests
Add comprehensive tests for POST /hub validation (missing fields, unknown mode, non-HTTPS callback, invalid URL, wrong domain, secret > 200 bytes) and happy-path subscribe/unsubscribe (202). Also fix hub.ts to use a waitUntilSafe wrapper so executionCtx.waitUntil doesn't throw in Node test environments.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 23:46:50 +02:00
Julien Herr 4165774667 fix(websub): validate callback URL (HTTPS), fix domain regex, enforce secret length 2026-05-21 23:46:50 +02:00
Julien Herr ee4b9d8fdc feat(websub): add hub route (subscribe/unsubscribe) 2026-05-21 23:46:49 +02:00
Julien Herr e8f5af8b87 test(websub): add missing error-path tests for verify functions 2026-05-21 23:46:49 +02:00
Julien Herr c6785554d4 test(websub): add unit tests for WebSub utilities 2026-05-21 23:46:49 +02:00