From 5c103507c1ffa85ba476e064feae49626b3fbbaa Mon Sep 17 00:00:00 2001 From: Greg Shuflin Date: Sun, 2 Feb 2025 15:24:33 -0800 Subject: [PATCH] Feed polling --- Cargo.lock | 2 ++ Cargo.toml | 2 +- src/feed_utils.rs | 31 +++++++++++++++++++++++++++++++ src/feeds.rs | 31 +------------------------------ src/main.rs | 3 +++ src/poll.rs | 35 +++++++++++++++++++++++++++++++++++ static/js/app.js | 17 ++++++++++++++--- 7 files changed, 87 insertions(+), 34 deletions(-) create mode 100644 src/feed_utils.rs create mode 100644 src/poll.rs diff --git a/Cargo.lock b/Cargo.lock index 89ce5a3..d9b03e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2432,6 +2432,7 @@ dependencies = [ "tokio-stream", "tokio-util", "ubyte", + "uuid", "version_check", "yansi", ] @@ -2513,6 +2514,7 @@ dependencies = [ "time", "tokio", "uncased", + "uuid", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 7a40ad4..7ac8c63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" argon2 = "0.5.3" atom_syndication = "0.12.6" chrono = { version = "0.4.34", features = ["serde"] } -rocket = { version = "0.5.1", features = ["json", "secrets"] } +rocket = { version = "0.5.1", features = ["json", "secrets", "uuid"] } rocket_db_pools = { version = "0.2.0", features = ["sqlx_sqlite"] } rocket_dyn_templates = { version = "0.2.0", features = ["tera"] } rss = "2.0.11" diff --git a/src/feed_utils.rs b/src/feed_utils.rs new file mode 100644 index 0000000..5da4377 --- /dev/null +++ b/src/feed_utils.rs @@ -0,0 +1,31 @@ +use feed_rs; +use url::Url; + +#[derive(Debug)] +pub struct FeedError; + +pub async fn fetch_feed(url: &Url) -> Result { + // Fetch the feed content + let response = reqwest::get(url.as_ref()).await.map_err(|e| { + eprintln!("Failed to fetch feed: {}", e); + FeedError + })?; + + let content = response.text().await.map_err(|e| { + eprintln!("Failed to read response body: {}", e); + FeedError + })?; + + // Parse the feed + let feed_data = feed_rs::parser::parse(content.as_bytes()).map_err(|e| { + eprintln!("Failed to parse feed content: {}", e); + FeedError + })?; + + println!("Fetched feed: {}", url.as_ref()); + for item in &feed_data.entries { + println!("{:?}", item); + } + + Ok(feed_data) +} diff --git a/src/feeds.rs b/src/feeds.rs index 112f002..d98a9de 100644 --- a/src/feeds.rs +++ b/src/feeds.rs @@ -5,6 +5,7 @@ use sqlx::types::JsonValue; use url::Url; use uuid::Uuid; +use crate::feed_utils::fetch_feed; use crate::user::AuthenticatedUser; use crate::Db; @@ -43,36 +44,6 @@ pub struct NewFeed { pub categorization: Vec, } -#[derive(Debug)] -struct FeedError; - -async fn fetch_feed(url: &Url) -> Result { - // Fetch the feed content - let response = reqwest::get(url.as_ref()).await.map_err(|e| { - eprintln!("Failed to fetch feed: {}", e); - FeedError - })?; - - let content = response.text().await.map_err(|e| { - eprintln!("Failed to read response body: {}", e); - FeedError - })?; - - // Parse the feed - let feed_data = feed_rs::parser::parse(content.as_bytes()).map_err(|e| { - eprintln!("Failed to parse feed content: {}", e); - FeedError - })?; - - println!("Fetched feed: {}", url.as_ref()); - for item in &feed_data.entries { - println!("{:?}", item); - - } - - Ok(feed_data) -} - #[post("/feeds", data = "")] pub async fn create_feed( mut db: Connection, diff --git a/src/main.rs b/src/main.rs index 097a2e2..49e9a85 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,9 @@ #[macro_use] extern crate rocket; +mod feed_utils; mod feeds; +mod poll; mod user; use rocket::fs::FileServer; @@ -64,6 +66,7 @@ fn rocket() -> _ { feeds::get_feed, feeds::list_feeds, feeds::delete_feed, + poll::poll_feed, ], ) .mount("/static", FileServer::from("static")) diff --git a/src/poll.rs b/src/poll.rs new file mode 100644 index 0000000..e726836 --- /dev/null +++ b/src/poll.rs @@ -0,0 +1,35 @@ +use crate::{feed_utils::fetch_feed, Db}; +use rocket::http::Status; +use rocket::serde::uuid::Uuid; +use rocket::serde::{json::Json, Serialize}; +use rocket_db_pools::Connection; + +#[derive(Debug, Serialize)] +#[serde(crate = "rocket::serde")] +pub struct FeedPollResponse { + count: usize, +} + +#[post("/poll/")] +pub async fn poll_feed( + mut db: Connection, + feed_id: Uuid, +) -> Result, Status> { + let feed_id = feed_id.to_string(); + // Get the feed URL from the database + let feed_url = sqlx::query!("SELECT url FROM feeds WHERE feed_id = ?", feed_id) + .fetch_optional(&mut **db) + .await + .map_err(|_| Status::InternalServerError)? + .ok_or(Status::NotFound)? + .url; + + // Parse the URL + let url = url::Url::parse(&feed_url).map_err(|_| Status::InternalServerError)?; + + let feed_data = fetch_feed(&url).await.map_err(|_| Status::BadGateway)?; + + Ok(Json(FeedPollResponse { + count: feed_data.entries.len(), + })) +} diff --git a/static/js/app.js b/static/js/app.js index f415a15..ecd733a 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -33,9 +33,20 @@ function renderFeedItem(feed) { const name = document.createElement('span'); name.className = 'feed-name'; name.textContent = feed.name; - name.onclick = () => { - // TODO: Handle feed click - console.log('Feed clicked:', feed); + name.onclick = async () => { + try { + const response = await fetch(`/poll/${feed.feed_id}`, { + method: 'POST' + }); + if (response.ok) { + const data = await response.json(); + console.log('Feed poll response:', data); + } else { + console.error('Failed to poll feed:', response.status); + } + } catch (error) { + console.error('Error polling feed:', error); + } }; const menuButton = document.createElement('button');