diff --git a/src/handlers/feed.rs b/src/handlers/feed.rs new file mode 100644 index 0000000..83a9a2e --- /dev/null +++ b/src/handlers/feed.rs @@ -0,0 +1,23 @@ +use axum::extract::{Path, State}; +use axum::response::Response; +use maud::html; +use sqlx::PgPool; + +use crate::error::Result; +use crate::models::entry::get_entries_for_feed; +use crate::models::feed::get_feed; +use crate::partials::{entry_list::entry_list, layout::Layout}; +use crate::uuid::Base62Uuid; + +pub async fn get( + Path(id): Path, + State(pool): State, + layout: Layout, +) -> Result { + let feed = get_feed(&pool, id.as_uuid()).await?; + let entries = get_entries_for_feed(&pool, feed.feed_id, Default::default()).await?; + Ok(layout.render(html! { + h1 { (feed.title.unwrap_or_else(|| "Untitled Feed".to_string())) } + (entry_list(entries)) + })) +} diff --git a/src/handlers/feeds.rs b/src/handlers/feeds.rs index 76a797b..9b90b79 100644 --- a/src/handlers/feeds.rs +++ b/src/handlers/feeds.rs @@ -6,6 +6,7 @@ use sqlx::PgPool; use crate::error::Result; use crate::models::feed::get_feeds; use crate::partials::layout::Layout; +use crate::uuid::Base62Uuid; pub async fn get(State(pool): State, layout: Layout) -> Result { let feeds = get_feeds(&pool).await?; @@ -13,7 +14,8 @@ pub async fn get(State(pool): State, layout: Layout) -> Result ul { @for feed in feeds { @let title = feed.title.unwrap_or_else(|| "Untitled Feed".to_string()); - li { (title) } + @let feed_url = format!("/feed/{}", Base62Uuid::from(feed.feed_id)); + li { a href=(feed_url) { (title) } } } } })) diff --git a/src/handlers/home.rs b/src/handlers/home.rs index 5181d8c..247d3e7 100644 --- a/src/handlers/home.rs +++ b/src/handlers/home.rs @@ -1,24 +1,12 @@ use axum::extract::State; use axum::response::Response; -use maud::html; use sqlx::PgPool; use crate::error::Result; use crate::models::entry::{get_entries, GetEntriesOptions}; -use crate::partials::layout::Layout; -use crate::utils::get_domain; -use crate::uuid::Base62Uuid; +use crate::partials::{layout::Layout, entry_list::entry_list}; pub async fn get(State(pool): State, layout: Layout) -> Result { let entries = get_entries(&pool, GetEntriesOptions::default()).await?; - Ok(layout.render(html! { - ul class="entries" { - @for entry in entries { - @let title = entry.title.unwrap_or_else(|| "Untitled".to_string()); - @let url = format!("/entry/{}", Base62Uuid::from(entry.entry_id)); - @let domain = get_domain(&entry.url).unwrap_or_default(); - li { a href=(url) { (title) } em class="domain" { (domain) }} - } - } - })) + Ok(layout.render(entry_list(entries))) } diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index a6d958b..4e14cf5 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -1,5 +1,6 @@ pub mod api; pub mod entry; pub mod home; +pub mod feed; pub mod feeds; pub mod log; diff --git a/src/main.rs b/src/main.rs index 65f01e1..6df0a49 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,6 +55,7 @@ async fn main() -> Result<()> { .route("/api/v1/entry/:id", get(handlers::api::entry::get)) .route("/", get(handlers::home::get)) .route("/feeds", get(handlers::feeds::get)) + .route("/feed/:id", get(handlers::feed::get)) .route("/entry/:id", get(handlers::entry::get)) .route("/log", get(handlers::log::get)) .route("/log/stream", get(handlers::log::stream)) diff --git a/src/models/entry.rs b/src/models/entry.rs index 7f6dbdb..90015d5 100644 --- a/src/models/entry.rs +++ b/src/models/entry.rs @@ -85,6 +85,45 @@ pub async fn get_entries( } } +pub async fn get_entries_for_feed( + pool: &PgPool, + feed_id: Uuid, + options: GetEntriesOptions, +) -> sqlx::Result> { + if let Some(published_before) = options.published_before { + sqlx::query_as!( + Entry, + "select * from entry + where deleted_at is null + and feed_id = $1 + and published_at < $2 + order by published_at desc + limit $3 + ", + feed_id, + published_before, + options.limit.unwrap_or(DEFAULT_ENTRIES_PAGE_SIZE) + ) + .fetch_all(pool) + .await + } else { + sqlx::query_as!( + Entry, + "select * from entry + where deleted_at is null + and feed_id = $1 + order by published_at desc + limit $2 + ", + feed_id, + options.limit.unwrap_or(DEFAULT_ENTRIES_PAGE_SIZE) + ) + .fetch_all(pool) + .await + + } +} + pub async fn create_entry(pool: &PgPool, payload: CreateEntry) -> Result { payload.validate()?; sqlx::query_as!( diff --git a/src/partials/entry_list.rs b/src/partials/entry_list.rs new file mode 100644 index 0000000..3c7cac0 --- /dev/null +++ b/src/partials/entry_list.rs @@ -0,0 +1,18 @@ +use maud::{html, Markup}; + +use crate::models::entry::Entry; +use crate::utils::get_domain; +use crate::uuid::Base62Uuid; + +pub fn entry_list(entries: Vec) -> Markup { + html! { + ul class="entries" { + @for entry in entries { + @let title = entry.title.unwrap_or_else(|| "Untitled".to_string()); + @let url = format!("/entry/{}", Base62Uuid::from(entry.entry_id)); + @let domain = get_domain(&entry.url).unwrap_or_default(); + li { a href=(url) { (title) } em class="domain" { (domain) }} + } + } + } +} diff --git a/src/partials/mod.rs b/src/partials/mod.rs index 8bb0028..040bf02 100644 --- a/src/partials/mod.rs +++ b/src/partials/mod.rs @@ -1,2 +1,3 @@ +pub mod entry_list; pub mod header; pub mod layout;