Homebrew RSS reader server
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**.