Homebrew RSS reader server
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 320 lines 7.8 kB view raw view rendered
1# Miniflux v2 API Cutover Plan (Fever Removed, DB Source of Truth) 2 3## Decisions (Locked In) 4 51. **Remove Fever API immediately** (no dual-stack period). 62. **Expose only Miniflux-compatible API endpoints**. 73. **SQLite is the source of truth** for categories/feeds/items. 84. `slurp.toml` becomes **runtime config only** (bind, db path, fetch interval, auth token). 95. Existing `[[groups]]` / `[[feeds]]` in `slurp.toml` are treated as **legacy bootstrap input only** (one-time import), then ignored/removed. 10 11--- 12 13## Current State Summary 14 15- **Schema**: `groups`, `feeds`, `items`, `favicons` 16- **API**: Fever-only handler on `/`, `/fever`, `/fever/` 17- **Auth**: Fever-style md5 API key in POST body 18- **Config behavior**: `db::sync_config()` enforces config as source of truth (including deletions) 19- **Gaps for Miniflux**: 20 - no `is_starred` 21 - no Miniflux `/v1/*` routes 22 - favicon format stored as combined `"mime;base64,DATA"` 23 24--- 25 26## Step 1: Database Migrations 27 28### 1a) Add starred state 29 30**File**: `migrations/003_starred.sql` 31 32Add: 33- `items.is_starred INTEGER NOT NULL DEFAULT 0 CHECK(is_starred IN (0,1))` 34- index on `items(is_starred)` 35 36### 1b) Add change tracking timestamp (recommended) 37 38**File**: `migrations/004_changed_at.sql` 39 40Add: 41- `items.changed_at INTEGER NOT NULL DEFAULT (unixepoch())` 42- index on `items(changed_at)` 43 44`changed_at` should be updated whenever read/starred status changes. 45 46--- 47 48## Step 2: Move Source of Truth from Config to DB 49 50### 2a) Stop destructive config sync 51 52**File**: `src/main.rs` 53 54- Remove call to `db::sync_config(&pool, &config)` in `serve`. 55- Replace with one-time legacy bootstrap: 56 - if DB has zero groups/feeds and config still contains legacy sections, import them. 57 - otherwise do nothing. 58 59### 2b) Update config model 60 61**File**: `src/config.rs` 62 63- Keep runtime config fields: 64 - `server.bind` 65 - `server.api_key` (used as Miniflux auth token) 66 - `database.path` 67 - `fetcher.interval_minutes` 68- Mark `groups`/`feeds` as deprecated legacy bootstrap input. 69 70### 2c) Add bootstrap helper 71 72**File**: `src/db.rs` 73 74Add `bootstrap_from_legacy_config_if_empty(...)`: 75- If DB empty, insert groups + feeds from config. 76- Never delete DB rows based on config. 77 78--- 79 80## Step 3: Remove Fever Completely 81 82Delete/remove: 83- `src/api/fever.rs` 84- Fever routes in `src/server.rs` 85- Fever test files: 86 - `tests/fever_test.rs` 87 - `tests/fever_integration.rs` 88- Fever-specific comments/types in `src/db.rs` naming 89- md5-focused CLI behavior tied only to Fever compatibility 90 91**Result**: No `/fever` and no Fever request handling. 92 93--- 94 95## Step 4: Auth Model for Miniflux 96 97**Files**: 98- `src/api/miniflux/auth.rs` (new) 99- `src/config.rs` 100 101Implement an auth extractor/middleware that accepts: 102- `X-Auth-Token: <server.api_key>` 103- HTTP Basic (`username:password`) where `password == server.api_key` 104 105On failure, return: 106- `401 Unauthorized` 107- JSON: `{"error_message":"Access Unauthorized"}` 108 109--- 110 111## Step 5: Miniflux API Types 112 113**File**: `src/api/miniflux/types.rs` (new) 114 115Define serializable response structs for: 116- `/v1/me` 117- categories 118- feeds (with nested category/icon) 119- entries + entries envelope (`{ total, entries }`) 120- icons 121- counters (`{ reads: {feed_id: count}, unreads: {feed_id: count} }`) 122- version payload 123 124Use sane defaults for unsupported Miniflux fields (`""`, `false`, `null`, etc.). 125 126--- 127 128## Step 6: DB Query/Command Layer for Miniflux 129 130**File**: `src/db.rs` 131 132Add/adjust functions: 133 134### Categories 1351. `get_categories(...)` 1362. `get_category(...)` 1373. `create_category(title)` 1384. `update_category(id, title)` 1395. `delete_category(id)` 1406. `get_category_counts(...)` (`feed_count`, `total_unread`) 141 142### Feeds 1437. `get_feeds_with_categories(...)` 1448. `get_feed_with_category(id)` 1459. `create_feed(feed_url, category_id)` 14610. `update_feed(id, fields...)` 14711. `delete_feed(id)` 14812. `get_category_feeds(category_id)` 14913. `mark_all_feed_entries_read(feed_id)` 15014. `mark_all_category_entries_read(category_id)` 15115. `get_feed_counters()` -> Miniflux counters shape 152 153### Entries 15416. `get_entry(id)` 15517. `get_entries_filtered(filter) -> (total, Vec<Entry>)` 15618. `update_entries_status(entry_ids, status)` 15719. `toggle_entry_starred(id)` 158 159### Icons 16020. `get_icon_by_id(id)` 16121. `get_icon_by_feed_id(feed_id)` 162 163### Query Builder constraints 164- Build dynamic filters with `sqlx::QueryBuilder` 165- Whitelist `order` and `direction` 166- Clamp `limit` to a max (e.g. 500) 167 168--- 169 170## Step 7: Implement Miniflux Handlers 171 172**File**: `src/api/miniflux/handlers.rs` (new) 173 174### 7a) User 175- `GET /v1/me` 176 177### 7b) Categories (now writable) 178- `GET /v1/categories` (`?counts=true` support) 179- `POST /v1/categories` 180- `PUT /v1/categories/:id` 181- `DELETE /v1/categories/:id` 182- `PUT /v1/categories/:id/mark-all-as-read` 183- `GET /v1/categories/:id/entries` 184- `GET /v1/categories/:id/feeds` 185 186### 7c) Feeds (now writable) 187- `GET /v1/feeds` 188- `GET /v1/feeds/:id` 189- `POST /v1/feeds` (add feed through API) 190- `PUT /v1/feeds/:id` 191- `DELETE /v1/feeds/:id` 192- `GET /v1/feeds/:id/icon` 193- `GET /v1/feeds/:id/entries` 194- `PUT /v1/feeds/:id/mark-all-as-read` 195- `PUT /v1/feeds/:id/refresh` 196- `PUT /v1/feeds/refresh` 197 198### 7d) Entries 199- `GET /v1/entries` 200- `GET /v1/entries/:id` 201- `PUT /v1/entries` (`{ entry_ids, status }`) 202- `PUT /v1/entries/:id/bookmark` 203 204### 7e) Icons 205- `GET /v1/icons/:id` 206 207### 7f) OPML 208- `GET /v1/export` 209- (optional) `POST /v1/import` 210 211### 7g) Health/Version 212- `GET /healthcheck` (root path) 213- `GET /liveness` and `/healthz` (root paths) 214- `GET /readiness` and `/readyz` (root paths) 215- `GET /v1/version` 216- `GET /v1/feeds/counters` 217 218--- 219 220## Step 8: Router Wiring 221 222**Files**: 223- `src/api/miniflux/mod.rs` (new) 224- `src/api/mod.rs` 225- `src/server.rs` 226 227Target shape: 228 229```rust 230Router::new() 231 .nest("/v1", miniflux_router()) 232 .route("/healthcheck", get(...)) 233 .route("/liveness", get(...)) 234 .route("/healthz", get(...)) 235 .route("/readiness", get(...)) 236 .route("/readyz", get(...)) 237 .with_state(state) 238``` 239 240No Fever routes are registered. 241 242--- 243 244## Step 9: Fetcher Trigger Integration 245 246**Files**: `src/fetcher.rs`, `src/main.rs`, handlers 247 248- Keep periodic fetch loop. 249- Add an internal trigger mechanism for: 250 - `PUT /v1/feeds/:id/refresh` 251 - `PUT /v1/feeds/refresh` 252- Ensure manual refresh does not race badly with periodic runs. 253 254--- 255 256## Step 10: CLI Cleanup 257 258**File**: `src/main.rs` 259 260- Remove Fever-specific md5 auth expectations. 261- `Auth` subcommand should be removed or repurposed to generate a random token. 262- `Add`/`Import` should write to DB (or be removed if API-first is preferred). 263 264--- 265 266## Step 11: Testing 267 2681. Remove Fever tests. 2692. Add Miniflux integration tests for: 270 - auth (`X-Auth-Token`, Basic) 271 - feed/category CRUD 272 - entries filtering and pagination 273 - status updates and bookmark toggle 274 - counters shape 275 - health/version endpoints 2763. Verify with real client against `/v1`. 277 278--- 279 280## File Layout After Cutover 281 282``` 283src/ 284 api/ 285 mod.rs ← only `pub mod miniflux;` 286 miniflux/ 287 mod.rs 288 auth.rs 289 types.rs 290 handlers.rs 291 config.rs 292 db.rs 293 fetcher.rs 294 server.rs 295 main.rs 296migrations/ 297 001_initial.sql 298 002_read_status.sql 299 003_starred.sql 300 004_changed_at.sql 301tests/ 302 miniflux_*.rs 303``` 304 305--- 306 307## Suggested Implementation Order 308 3091. Migrations (`003`, `004`) 3102. Remove Fever routes/files/tests 3113. Stop `sync_config` as source of truth; add bootstrap-if-empty 3124. Add Miniflux auth extractor 3135. Add Miniflux types + DB functions 3146. Implement handlers (user → categories → feeds → entries → icons → counters) 3157. Wire router + health/version endpoints 3168. Fetch trigger integration 3179. CLI cleanup 31810. Integration testing with real client 319 320This delivers a clean cutover: **Miniflux-only API with DB-owned feeds/categories and no Fever compatibility layer**.