Implement entry and feed pagination
This commit is contained in:
@@ -1,9 +1,27 @@
|
||||
use axum::{extract::State, Json};
|
||||
use axum::extract::Query;
|
||||
use axum::extract::State;
|
||||
use axum::response::IntoResponse;
|
||||
use axum::TypedHeader;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::api_response::ApiResponse;
|
||||
use crate::error::Error;
|
||||
use crate::models::entry::Entry;
|
||||
use crate::headers::Accept;
|
||||
use crate::models::entry::{Entry, GetEntriesOptions};
|
||||
use crate::partials::entry_list::entry_list;
|
||||
|
||||
pub async fn get(State(pool): State<PgPool>) -> Result<Json<Vec<Entry>>, Error> {
|
||||
Ok(Json(Entry::get_all(&pool, Default::default()).await?))
|
||||
pub async fn get(
|
||||
Query(options): Query<GetEntriesOptions>,
|
||||
accept: Option<TypedHeader<Accept>>,
|
||||
State(pool): State<PgPool>,
|
||||
) -> Result<impl IntoResponse, impl IntoResponse> {
|
||||
let entries = Entry::get_all(&pool, &options).await.map_err(Error::from)?;
|
||||
if let Some(TypedHeader(accept)) = accept {
|
||||
if accept == Accept::ApplicationJson {
|
||||
return Ok::<ApiResponse<Vec<Entry>>, Error>(ApiResponse::Json(entries));
|
||||
}
|
||||
}
|
||||
Ok(ApiResponse::Html(
|
||||
entry_list(entries, &options).into_string(),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -1,10 +1,27 @@
|
||||
use axum::{extract::State, Json};
|
||||
use axum::TypedHeader;
|
||||
use axum::extract::Query;
|
||||
use axum::response::IntoResponse;
|
||||
use axum::extract::State;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::api_response::ApiResponse;
|
||||
use crate::error::Error;
|
||||
use crate::models::feed::Feed;
|
||||
use crate::headers::Accept;
|
||||
use crate::models::feed::{Feed, GetFeedsOptions};
|
||||
use crate::partials::feed_list::feed_list;
|
||||
|
||||
pub async fn get(State(pool): State<PgPool>) -> Result<Json<Vec<Feed>>, Error> {
|
||||
// TODO: pagination
|
||||
Ok(Json(Feed::get_all(&pool, Default::default()).await?))
|
||||
pub async fn get(
|
||||
Query(options): Query<GetFeedsOptions>,
|
||||
accept: Option<TypedHeader<Accept>>,
|
||||
State(pool): State<PgPool>,
|
||||
) -> Result<impl IntoResponse, impl IntoResponse> {
|
||||
let feeds = Feed::get_all(&pool, &options).await.map_err(Error::from)?;
|
||||
if let Some(TypedHeader(accept)) = accept {
|
||||
if accept == Accept::ApplicationJson {
|
||||
return Ok::<ApiResponse<Vec<Feed>>, Error>(ApiResponse::Json(feeds));
|
||||
}
|
||||
}
|
||||
Ok(ApiResponse::Html(
|
||||
feed_list(feeds, &options).into_string(),
|
||||
))
|
||||
}
|
||||
|
||||
15
src/handlers/entries.rs
Normal file
15
src/handlers/entries.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use axum::extract::{Query, State};
|
||||
use maud::Markup;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::models::entry::{Entry, GetEntriesOptions};
|
||||
use crate::partials::entry_list::entry_list;
|
||||
|
||||
pub async fn get(
|
||||
Query(options): Query<GetEntriesOptions>,
|
||||
State(pool): State<PgPool>,
|
||||
) -> Result<Markup> {
|
||||
let entries = Entry::get_all(&pool, &options).await?;
|
||||
Ok(entry_list(entries, &options))
|
||||
}
|
||||
@@ -20,10 +20,12 @@ pub async fn get(
|
||||
let entry = Entry::get(&pool, id.as_uuid()).await?;
|
||||
let content_dir = std::path::Path::new(&config.content_dir);
|
||||
let content_path = content_dir.join(format!("{}.html", entry.entry_id));
|
||||
let title = entry.title.unwrap_or_else(|| "Untitled".to_string());
|
||||
let published_at = entry.published_at.to_rfc3339_opts(chrono::SecondsFormat::Millis, true);
|
||||
let title = entry.title.unwrap_or_else(|| "Untitled Entry".to_string());
|
||||
let published_at = entry
|
||||
.published_at
|
||||
.to_rfc3339_opts(chrono::SecondsFormat::Millis, true);
|
||||
let content = fs::read_to_string(content_path).unwrap_or_else(|_| "No content".to_string());
|
||||
Ok(layout.render(html! {
|
||||
Ok(layout.with_subtitle(&title).render(html! {
|
||||
article {
|
||||
h2 class="title" { a href=(entry.url) { (title) } }
|
||||
div {
|
||||
|
||||
@@ -16,7 +16,7 @@ use tokio_stream::StreamExt;
|
||||
use crate::actors::crawl_scheduler::{CrawlSchedulerHandle, CrawlSchedulerHandleMessage};
|
||||
use crate::actors::feed_crawler::FeedCrawlerHandleMessage;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::models::entry::Entry;
|
||||
use crate::models::entry::{Entry, GetEntriesOptions};
|
||||
use crate::models::feed::{CreateFeed, Feed};
|
||||
use crate::partials::add_feed_form::add_feed_form;
|
||||
use crate::partials::entry_link::entry_link;
|
||||
@@ -30,11 +30,16 @@ pub async fn get(
|
||||
layout: Layout,
|
||||
) -> Result<Response> {
|
||||
let feed = Feed::get(&pool, id.as_uuid()).await?;
|
||||
let entries = Entry::get_all_for_feed(&pool, feed.feed_id, Default::default()).await?;
|
||||
let options = GetEntriesOptions {
|
||||
feed_id: Some(feed.feed_id),
|
||||
..Default::default()
|
||||
};
|
||||
let title = feed.title.unwrap_or_else(|| "Untitled Feed".to_string());
|
||||
let entries = Entry::get_all(&pool, &options).await?;
|
||||
let delete_url = format!("/feed/{}/delete", id);
|
||||
Ok(layout.render(html! {
|
||||
Ok(layout.with_subtitle(&title).render(html! {
|
||||
header class="feed-header" {
|
||||
h2 { (feed.title.unwrap_or_else(|| "Untitled Feed".to_string())) }
|
||||
h2 { (title) }
|
||||
button class="edit-feed" { "✏️ Edit feed" }
|
||||
form action=(delete_url) method="post" {
|
||||
button type="submit" class="remove-feed" data-controller="remove-feed" { "❌ Remove feed" }
|
||||
@@ -43,7 +48,7 @@ pub async fn get(
|
||||
@if let Some(description) = feed.description {
|
||||
p { (description) }
|
||||
}
|
||||
(entry_list(entries))
|
||||
(entry_list(entries, &options))
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -178,7 +183,7 @@ pub async fn stream(
|
||||
entry,
|
||||
)))) => Ok(Event::default().data(
|
||||
html! {
|
||||
li { "Crawled entry: " (entry_link(entry)) }
|
||||
li { "Crawled entry: " (entry_link(&entry)) }
|
||||
}
|
||||
.into_string(),
|
||||
)),
|
||||
|
||||
@@ -7,21 +7,23 @@ use crate::error::Result;
|
||||
use crate::models::feed::{Feed, GetFeedsOptions};
|
||||
use crate::partials::add_feed_form::add_feed_form;
|
||||
use crate::partials::feed_list::feed_list;
|
||||
use crate::partials::opml_import_form::opml_import_form;
|
||||
use crate::partials::layout::Layout;
|
||||
use crate::partials::opml_import_form::opml_import_form;
|
||||
|
||||
pub async fn get(State(pool): State<PgPool>, layout: Layout) -> Result<Response> {
|
||||
let options = GetFeedsOptions::default();
|
||||
let feeds = Feed::get_all(&pool, options.clone()).await?;
|
||||
Ok(layout.render(html! {
|
||||
let feeds = Feed::get_all(&pool, &options).await?;
|
||||
Ok(layout.with_subtitle("feeds").render(html! {
|
||||
h2 { "Feeds" }
|
||||
div class="feeds" {
|
||||
(feed_list(feeds, options))
|
||||
ul id="feeds" {
|
||||
(feed_list(feeds, &options))
|
||||
}
|
||||
div class="add-feed" {
|
||||
h3 { "Add Feed" }
|
||||
(add_feed_form())
|
||||
(opml_import_form())
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use axum::extract::State;
|
||||
use axum::response::Response;
|
||||
use maud::html;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::error::Result;
|
||||
@@ -7,6 +8,11 @@ use crate::models::entry::Entry;
|
||||
use crate::partials::{layout::Layout, entry_list::entry_list};
|
||||
|
||||
pub async fn get(State(pool): State<PgPool>, layout: Layout) -> Result<Response> {
|
||||
let entries = Entry::get_all(&pool, Default::default()).await?;
|
||||
Ok(layout.render(entry_list(entries)))
|
||||
let options = Default::default();
|
||||
let entries = Entry::get_all(&pool, &options).await?;
|
||||
Ok(layout.render(html! {
|
||||
ul class="entries" {
|
||||
(entry_list(entries, &options))
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ pub async fn stream(
|
||||
))) => Ok::<Event, String>(
|
||||
Event::default().data(
|
||||
html! {
|
||||
li { "Crawled entry: " (entry_link(entry)) }
|
||||
li { "Crawled entry: " (entry_link(&entry)) }
|
||||
}
|
||||
.into_string(),
|
||||
),
|
||||
|
||||
@@ -22,7 +22,7 @@ use crate::partials::layout::Layout;
|
||||
|
||||
pub async fn get(layout: Layout) -> Result<Response> {
|
||||
let mem_buf = MEM_LOG.lock().unwrap();
|
||||
Ok(layout.render(html! {
|
||||
Ok(layout.with_subtitle("log").render(html! {
|
||||
pre id="log" hx-sse="connect:/log/stream swap:message" hx-swap="beforeend" {
|
||||
(PreEscaped(convert_escaped(from_utf8(mem_buf.as_slices().0).unwrap()).unwrap()))
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod api;
|
||||
pub mod entries;
|
||||
pub mod entry;
|
||||
pub mod home;
|
||||
pub mod import;
|
||||
|
||||
Reference in New Issue
Block a user