61 lines
3.1 KiB
Markdown
61 lines
3.1 KiB
Markdown
# Plan — RSS provider
|
||
|
||
Executes `architecture/rss-provider.md` against `quality/rss-provider.md`.
|
||
Two commits: **(A)** the `rssdy` crate, **(B)** wiring (server feature,
|
||
clients, docs).
|
||
|
||
## A — The `rssdy` crate
|
||
|
||
- [x] **A1 — `api::Episode::key_for`** — `blake3(guid)` truncated to 16 hex
|
||
chars. *Verifies:* G7.
|
||
- [x] **A2 — `api::FeedFetcher`** — `reqwest` client with the timeout,
|
||
streaming the body while counting bytes so `max_feed_bytes` rejects
|
||
before buffering it whole; parse with `feed-rs`; map entries to
|
||
`Episode` (guid → key, title, author, published, `itunes:duration` where
|
||
present, first audio enclosure); skip entries with no enclosure; sort
|
||
newest first; truncate to `limit`. Errors carry no URL. *Verifies:* G1,
|
||
G15, G16, G17.
|
||
- [x] **A3 — `slugify` + de-duplication** at load and on rename/subscribe.
|
||
*Verifies:* G8.
|
||
- [x] **A4 — `parse_path`** for root/feed/episode, rejecting anything else.
|
||
*Verifies:* G10.
|
||
- [x] **A5 — `Client::with_api` / `init`** — resolve subscriptions with
|
||
slugs, drop entries without a url (warning), keep the settings for
|
||
write-back. No feeds still mounts. *Verifies:* G18.
|
||
- [x] **A6 — Listings** — `get_lib_root` lists subscriptions and is
|
||
creatable; `get_lib_node("/rss/<slug>")` fetches, replaces the memo, and
|
||
returns episodes as tracks (queueable, downloadable). *Verifies:* G3, G9.
|
||
- [x] **A7 — Memo** — bounded push/replace, listing-written, read by
|
||
`get_urls_for_track` / `get_metadata_for_track` /
|
||
`resolve_tracks_into`. *Verifies:* G4, G5.
|
||
- [x] **A8 — Subscription CRUD** — `create_lib_node` (URL → fetch → name
|
||
from feed title → add), `rename_lib_node`, `delete_lib_node`, and
|
||
`settings()` reserializing the live list. *Verifies:* G11–G14.
|
||
- [x] **A9 — `rssdy/src/tests.rs`** over a `FakeFeeds` fixture backend
|
||
covering every gate above.
|
||
- [x] **A10 — Commit A**: crate tests, clippy, fmt clean.
|
||
|
||
## B — Wiring
|
||
|
||
- [x] **B1 — Server feature `rss`** — optional `dep:rssdy`, `all-providers`
|
||
membership, `ALL_PROVIDERS` + `BUILT_IN_PROVIDERS` entries,
|
||
`ProviderToggles.rss`, `ProviderToggles::all`, and the mount
|
||
registration through `mount_from_config`. *Verifies:* G19.
|
||
- [x] **B2 — `check-features`** gains an `rss`-alone row. *Verifies:* G19.
|
||
- [x] **B3 — Clients** — `/rss` into `MUTABLE_ROOTS` in `cbd-tui/src/rpc.rs`
|
||
and `cbd-web/src/state.rs`, with their cacheability tests extended.
|
||
*Verifies:* G6.
|
||
- [x] **B4 — Docs** — `docs/src/providers/rss.md`, `rssdy/README.md`,
|
||
links from `docs/src/providers.md`, `SUMMARY.md`, `config.md`, and the
|
||
root README (tree + config table). Say plainly that a premium URL is a
|
||
credential, that listings are never cached, and that bookmarks depend on
|
||
publisher guids. *Verifies:* G20.
|
||
- [x] **B5 — `plan/summary.md`** entry, then commit B.
|
||
|
||
## Deferred (recorded, not dropped)
|
||
|
||
- OPML import/export, per-episode played state, background polling, and
|
||
artwork (`itunes:image`) — the library model has no field for it.
|
||
- Sharing the episode→`Track` mapping with `fyyd`; worth extracting only
|
||
if a third podcast source lands.
|