Miniflux v2 API Cutover Plan (Fever Removed, DB Source of Truth)#
Decisions (Locked In)#
- Remove Fever API immediately (no dual-stack period).
- Expose only Miniflux-compatible API endpoints.
- SQLite is the source of truth for categories/feeds/items.
slurp.tomlbecomes runtime config only (bind, db path, fetch interval, auth token).- Existing
[[groups]]/[[feeds]]inslurp.tomlare treated as legacy bootstrap input only (one-time import), then ignored/removed.
Current State Summary#
- Schema:
groups,feeds,items,favicons - API: Fever-only handler on
/,/fever,/fever/ - Auth: Fever-style md5 API key in POST body
- Config behavior:
db::sync_config()enforces config as source of truth (including deletions) - Gaps for Miniflux:
- no
is_starred - no Miniflux
/v1/*routes - favicon format stored as combined
"mime;base64,DATA"
- no
Step 1: Database Migrations#
1a) Add starred state#
File: migrations/003_starred.sql
Add:
items.is_starred INTEGER NOT NULL DEFAULT 0 CHECK(is_starred IN (0,1))- index on
items(is_starred)
1b) Add change tracking timestamp (recommended)#
File: migrations/004_changed_at.sql
Add:
items.changed_at INTEGER NOT NULL DEFAULT (unixepoch())- index on
items(changed_at)
changed_at should be updated whenever read/starred status changes.
Step 2: Move Source of Truth from Config to DB#
2a) Stop destructive config sync#
File: src/main.rs
- Remove call to
db::sync_config(&pool, &config)inserve. - Replace with one-time legacy bootstrap:
- if DB has zero groups/feeds and config still contains legacy sections, import them.
- otherwise do nothing.
2b) Update config model#
File: src/config.rs
- Keep runtime config fields:
server.bindserver.api_key(used as Miniflux auth token)database.pathfetcher.interval_minutes
- Mark
groups/feedsas deprecated legacy bootstrap input.
2c) Add bootstrap helper#
File: src/db.rs
Add bootstrap_from_legacy_config_if_empty(...):
- If DB empty, insert groups + feeds from config.
- Never delete DB rows based on config.
Step 3: Remove Fever Completely#
Delete/remove:
src/api/fever.rs- Fever routes in
src/server.rs - Fever test files:
tests/fever_test.rstests/fever_integration.rs
- Fever-specific comments/types in
src/db.rsnaming - md5-focused CLI behavior tied only to Fever compatibility
Result: No /fever and no Fever request handling.
Step 4: Auth Model for Miniflux#
Files:
src/api/miniflux/auth.rs(new)src/config.rs
Implement an auth extractor/middleware that accepts:
X-Auth-Token: <server.api_key>- HTTP Basic (
username:password) wherepassword == server.api_key
On failure, return:
401 Unauthorized- JSON:
{"error_message":"Access Unauthorized"}
Step 5: Miniflux API Types#
File: src/api/miniflux/types.rs (new)
Define serializable response structs for:
/v1/me- categories
- feeds (with nested category/icon)
- entries + entries envelope (
{ total, entries }) - icons
- counters (
{ reads: {feed_id: count}, unreads: {feed_id: count} }) - version payload
Use sane defaults for unsupported Miniflux fields ("", false, null, etc.).
Step 6: DB Query/Command Layer for Miniflux#
File: src/db.rs
Add/adjust functions:
Categories#
get_categories(...)get_category(...)create_category(title)update_category(id, title)delete_category(id)get_category_counts(...)(feed_count,total_unread)
Feeds#
get_feeds_with_categories(...)get_feed_with_category(id)create_feed(feed_url, category_id)update_feed(id, fields...)delete_feed(id)get_category_feeds(category_id)mark_all_feed_entries_read(feed_id)mark_all_category_entries_read(category_id)get_feed_counters()-> Miniflux counters shape
Entries#
get_entry(id)get_entries_filtered(filter) -> (total, Vec<Entry>)update_entries_status(entry_ids, status)toggle_entry_starred(id)
Icons#
get_icon_by_id(id)get_icon_by_feed_id(feed_id)
Query Builder constraints#
- Build dynamic filters with
sqlx::QueryBuilder - Whitelist
orderanddirection - Clamp
limitto a max (e.g. 500)
Step 7: Implement Miniflux Handlers#
File: src/api/miniflux/handlers.rs (new)
7a) User#
GET /v1/me
7b) Categories (now writable)#
GET /v1/categories(?counts=truesupport)POST /v1/categoriesPUT /v1/categories/:idDELETE /v1/categories/:idPUT /v1/categories/:id/mark-all-as-readGET /v1/categories/:id/entriesGET /v1/categories/:id/feeds
7c) Feeds (now writable)#
GET /v1/feedsGET /v1/feeds/:idPOST /v1/feeds(add feed through API)PUT /v1/feeds/:idDELETE /v1/feeds/:idGET /v1/feeds/:id/iconGET /v1/feeds/:id/entriesPUT /v1/feeds/:id/mark-all-as-readPUT /v1/feeds/:id/refreshPUT /v1/feeds/refresh
7d) Entries#
GET /v1/entriesGET /v1/entries/:idPUT /v1/entries({ entry_ids, status })PUT /v1/entries/:id/bookmark
7e) Icons#
GET /v1/icons/:id
7f) OPML#
GET /v1/export- (optional)
POST /v1/import
7g) Health/Version#
GET /healthcheck(root path)GET /livenessand/healthz(root paths)GET /readinessand/readyz(root paths)GET /v1/versionGET /v1/feeds/counters
Step 8: Router Wiring#
Files:
src/api/miniflux/mod.rs(new)src/api/mod.rssrc/server.rs
Target shape:
Router::new()
.nest("/v1", miniflux_router())
.route("/healthcheck", get(...))
.route("/liveness", get(...))
.route("/healthz", get(...))
.route("/readiness", get(...))
.route("/readyz", get(...))
.with_state(state)
No Fever routes are registered.
Step 9: Fetcher Trigger Integration#
Files: src/fetcher.rs, src/main.rs, handlers
- Keep periodic fetch loop.
- Add an internal trigger mechanism for:
PUT /v1/feeds/:id/refreshPUT /v1/feeds/refresh
- Ensure manual refresh does not race badly with periodic runs.
Step 10: CLI Cleanup#
File: src/main.rs
- Remove Fever-specific md5 auth expectations.
Authsubcommand should be removed or repurposed to generate a random token.Add/Importshould write to DB (or be removed if API-first is preferred).
Step 11: Testing#
- Remove Fever tests.
- Add Miniflux integration tests for:
- auth (
X-Auth-Token, Basic) - feed/category CRUD
- entries filtering and pagination
- status updates and bookmark toggle
- counters shape
- health/version endpoints
- auth (
- Verify with real client against
/v1.
File Layout After Cutover#
src/
api/
mod.rs ← only `pub mod miniflux;`
miniflux/
mod.rs
auth.rs
types.rs
handlers.rs
config.rs
db.rs
fetcher.rs
server.rs
main.rs
migrations/
001_initial.sql
002_read_status.sql
003_starred.sql
004_changed_at.sql
tests/
miniflux_*.rs
Suggested Implementation Order#
- Migrations (
003,004) - Remove Fever routes/files/tests
- Stop
sync_configas source of truth; add bootstrap-if-empty - Add Miniflux auth extractor
- Add Miniflux types + DB functions
- Implement handlers (user → categories → feeds → entries → icons → counters)
- Wire router + health/version endpoints
- Fetch trigger integration
- CLI cleanup
- Integration testing with real client
This delivers a clean cutover: Miniflux-only API with DB-owned feeds/categories and no Fever compatibility layer.