Default view stays the all-feeds joined article list, now with a sidebar listing every subscribed feed so a reader can pin down to one feed's articles. Filtering happens server-side (list_ranked_articles now takes an optional feed_id). Pulled in the sidebar/badge/scroll_area (plus their sheet/skeleton/ tooltip/separator dependencies) components via `dx components add` instead of hand-rolling nav/tag/scroll markup, matching this project's existing pattern of using the Dioxus component library over raw elements (see button/input). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PqTJmazHBQK878vjQ4JnnF
48 lines
1.7 KiB
Rust
48 lines
1.7 KiB
Rust
use crate::api::FeedView;
|
|
use anyhow::Result;
|
|
use feedsignal_db::Db;
|
|
use uuid::Uuid;
|
|
|
|
/// Subscribes to a feed by URL: registers it (or updates its title if
|
|
/// already subscribed) and does an immediate first fetch so the reader
|
|
/// isn't empty until the next scheduled poll. Returns the number of
|
|
/// articles pulled in on this fetch.
|
|
///
|
|
/// Fetches under a throwaway id before writing anything, so there's exactly
|
|
/// one write to the `feeds` table (`upsert_feed` is the single source of
|
|
/// truth for the real id — the existing one on a re-subscribe, a fresh one
|
|
/// otherwise) and no half-registered row left behind if the fetch fails
|
|
/// (e.g. the URL is well-formed but points at nothing, or the feed no
|
|
/// longer exists — `fetch_feed` surfaces both as an `Err` via
|
|
/// `error_for_status`/feed-rs parse failure, which becomes the error
|
|
/// message shown in the subscribe form).
|
|
pub async fn subscribe(db: Db, url: String) -> Result<usize> {
|
|
anyhow::ensure!(!url.trim().is_empty(), "feed URL is required");
|
|
let url = url.trim();
|
|
|
|
let (title, mut articles) = feedsignal_feeds::fetch_feed(url, Uuid::new_v4()).await?;
|
|
let feed_id = db.upsert_feed(url, &title).await?;
|
|
for article in &mut articles {
|
|
article.feed_id = feed_id;
|
|
}
|
|
|
|
let count = articles.len();
|
|
for article in &articles {
|
|
db.insert_article(article).await?;
|
|
}
|
|
Ok(count)
|
|
}
|
|
|
|
/// Lists every subscribed feed, for the sidebar's feed-navigation list.
|
|
pub async fn list(db: Db) -> Result<Vec<FeedView>> {
|
|
Ok(db
|
|
.list_feeds_with_titles()
|
|
.await?
|
|
.into_iter()
|
|
.map(|(id, title, url)| FeedView {
|
|
id: id.to_string(),
|
|
title,
|
|
url,
|
|
})
|
|
.collect())
|
|
}
|