feat: reader-compat batch — JSON Feed, OPML export, conditional GET, dedup

Batch of four reader-facing improvements (TODO "Compat lecteurs + dedup"):

- JSON Feed at /json/:feedId (feed lib .json1()); all formats cross-link
- OPML export at /admin/opml (admin-protected; the registry lists every
  feed URL, so it must not be public)
- Conditional GET on /rss + /atom: strong ETag + Last-Modified, 304 on
  If-None-Match/If-Modified-Since, validators shared via http-cache.ts
- Duplicate-send dedup in ingestion: match by Message-ID, fall back to a
  SHA-256 of normalized subject+content; a duplicate is a no-op and bumps
  the new emails_deduplicated counter (status page + /api/v1/stats)

429 tests green, tsc clean, build dry-run OK. Docs (README/CLAUDE/TODO +
landing cards) updated.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Julien Herr
2026-05-24 20:47:54 +02:00
parent 334713fbd9
commit 0abd5f306c
23 changed files with 1015 additions and 11 deletions
+16
View File
@@ -858,6 +858,22 @@
<p>Automate feeds and emails through a versioned REST API, documented with an OpenAPI 3.1 spec and a <a href="https://demo.kill-the.news/api/docs" target="_blank" rel="noopener" style="color:var(--accent)">live interactive reference</a>.</p>
</div>
<div class="feature-card">
<div class="feature-icon">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 11a9 9 0 0 1 9 9"/><path d="M4 4a16 16 0 0 1 16 16"/><circle cx="5" cy="19" r="1"/></svg>
</div>
<h3>RSS, Atom &amp; JSON Feed</h3>
<p>Every feed is served in all three formats — RSS 2.0, Atom, and JSON Feed — so it just works in NetNewsWire, Reeder, Feedly, NewsBlur and any other reader. Conditional requests (ETag / 304) keep polling cheap.</p>
</div>
<div class="feature-card">
<div class="feature-icon">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="8" y1="13" x2="16" y2="13"/><line x1="8" y1="17" x2="16" y2="17"/></svg>
</div>
<h3>One-Click OPML Export</h3>
<p>Export all your feeds as an OPML file and bulk-import them into any reader in one shot — easy onboarding, and no lock-in if you ever want to move.</p>
</div>
</div>
</section>