chore: remove duplicated code for pagination
This commit is contained in:
parent
d41cf7a86c
commit
dd0f716437
10 changed files with 172 additions and 245 deletions
|
|
@ -13,6 +13,8 @@ pub type DatabasePool = sqlx::SqlitePool;
|
|||
type PoolOptions = sqlx::sqlite::SqlitePoolOptions;
|
||||
#[cfg(feature = "sqlite")]
|
||||
pub type DatabaseTransaction<'a> = sqlx::Transaction<'a, sqlx::Sqlite>;
|
||||
#[cfg(feature = "sqlite")]
|
||||
pub type DatabaseRow = sqlx::sqlite::SqliteRow;
|
||||
|
||||
#[cfg(feature = "postgres")]
|
||||
pub type DatabasePool = sqlx::PgPool;
|
||||
|
|
@ -20,6 +22,8 @@ pub type DatabasePool = sqlx::PgPool;
|
|||
type PoolOptions = sqlx::postgres::PgPoolOptions;
|
||||
#[cfg(feature = "postgres")]
|
||||
pub type DatabaseTransaction<'a> = sqlx::Transaction<'a, sqlx::Postgres>;
|
||||
#[cfg(feature = "postgres")]
|
||||
pub type DatabaseRow = sqlx::postgres::PgRow;
|
||||
|
||||
pub struct Database {
|
||||
pool: DatabasePool,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
pub mod pagination;
|
||||
pub mod roasters;
|
||||
pub mod roasts;
|
||||
pub mod sessions;
|
||||
|
|
|
|||
76
src/infrastructure/repositories/pagination.rs
Normal file
76
src/infrastructure/repositories/pagination.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
use sqlx::{FromRow, query_as, query_scalar};
|
||||
|
||||
use crate::domain::RepositoryError;
|
||||
use crate::domain::listing::{ListRequest, Page, PageSize, SortKey};
|
||||
use crate::infrastructure::database::{DatabasePool, DatabaseRow};
|
||||
|
||||
pub async fn paginate<K, R, T, MapFn>(
|
||||
pool: &DatabasePool,
|
||||
request: &ListRequest<K>,
|
||||
base_query: &str,
|
||||
count_query: &str,
|
||||
order_clause: &str,
|
||||
map_fn: MapFn,
|
||||
) -> Result<Page<T>, RepositoryError>
|
||||
where
|
||||
K: SortKey,
|
||||
R: for<'r> FromRow<'r, DatabaseRow> + Send + Unpin,
|
||||
T: Send,
|
||||
MapFn: Fn(R) -> Result<T, RepositoryError> + Send + Sync,
|
||||
{
|
||||
match request.page_size() {
|
||||
PageSize::All => {
|
||||
let query = format!("{} ORDER BY {}", base_query, order_clause);
|
||||
let records = query_as::<_, R>(&query)
|
||||
.fetch_all(pool)
|
||||
.await
|
||||
.map_err(|err| RepositoryError::unexpected(err.to_string()))?;
|
||||
|
||||
let mut items = Vec::with_capacity(records.len());
|
||||
for record in records {
|
||||
items.push(map_fn(record)?);
|
||||
}
|
||||
let total = items.len() as u64;
|
||||
let page_size = total.min(u64::from(u32::MAX)) as u32;
|
||||
Ok(Page::new(items, 1, page_size.max(1), total, true))
|
||||
}
|
||||
PageSize::Limited(page_size) => {
|
||||
let limit = page_size as i64;
|
||||
let mut page = request.page();
|
||||
let offset = ((page - 1) as i64).saturating_mul(limit);
|
||||
|
||||
let query_sql = format!("{} ORDER BY {} LIMIT ? OFFSET ?", base_query, order_clause);
|
||||
|
||||
let mut records = query_as::<_, R>(&query_sql)
|
||||
.bind(limit)
|
||||
.bind(offset)
|
||||
.fetch_all(pool)
|
||||
.await
|
||||
.map_err(|err| RepositoryError::unexpected(err.to_string()))?;
|
||||
|
||||
let total: i64 = query_scalar(count_query)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.map_err(|err| RepositoryError::unexpected(err.to_string()))?;
|
||||
|
||||
if page > 1 && records.is_empty() && total > 0 {
|
||||
let last_page = ((total + limit - 1) / limit) as u32;
|
||||
page = last_page.max(1);
|
||||
let offset = ((page - 1) as i64).saturating_mul(limit);
|
||||
records = query_as::<_, R>(&query_sql)
|
||||
.bind(limit)
|
||||
.bind(offset)
|
||||
.fetch_all(pool)
|
||||
.await
|
||||
.map_err(|err| RepositoryError::unexpected(err.to_string()))?;
|
||||
}
|
||||
|
||||
let mut items = Vec::with_capacity(records.len());
|
||||
for record in records {
|
||||
items.push(map_fn(record)?);
|
||||
}
|
||||
|
||||
Ok(Page::new(items, page, page_size, total as u64, false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
use async_trait::async_trait;
|
||||
use chrono::{DateTime, Utc};
|
||||
use sqlx::{QueryBuilder, query, query_as, query_scalar};
|
||||
use sqlx::{QueryBuilder, query, query_as};
|
||||
|
||||
use crate::domain::RepositoryError;
|
||||
use crate::domain::ids::RoasterId;
|
||||
use crate::domain::listing::{ListRequest, Page, PageSize, SortDirection};
|
||||
use crate::domain::listing::{ListRequest, Page, SortDirection};
|
||||
use crate::domain::repositories::RoasterRepository;
|
||||
use crate::domain::roasters::{NewRoaster, Roaster, RoasterSortKey, UpdateRoaster};
|
||||
use crate::domain::timeline::TimelineEventDetail;
|
||||
|
|
@ -159,77 +159,19 @@ impl RoasterRepository for SqlRoasterRepository {
|
|||
request: &ListRequest<RoasterSortKey>,
|
||||
) -> Result<Page<Roaster>, RepositoryError> {
|
||||
let order_clause = Self::sort_clause(request);
|
||||
let base_query =
|
||||
"SELECT id, name, country, city, homepage, notes, created_at FROM roasters";
|
||||
let count_query = "SELECT COUNT(*) FROM roasters";
|
||||
|
||||
match request.page_size() {
|
||||
PageSize::All => {
|
||||
let query = format!(
|
||||
"SELECT id, name, country, city, homepage, notes, created_at FROM roasters ORDER BY {}",
|
||||
order_clause
|
||||
);
|
||||
|
||||
let records = query_as::<_, RoasterRecord>(&query)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(|err| RepositoryError::unexpected(err.to_string()))?;
|
||||
|
||||
let items = records
|
||||
.into_iter()
|
||||
.map(Self::into_domain)
|
||||
.collect::<Vec<_>>();
|
||||
let total = items.len() as u64;
|
||||
let page_size = total.min(u64::from(u32::MAX)) as u32;
|
||||
|
||||
Ok(Page::new(items, 1, page_size.max(1), total, true))
|
||||
}
|
||||
PageSize::Limited(page_size) => {
|
||||
let limit = page_size as i64;
|
||||
let mut page_number = request.page();
|
||||
let offset = ((page_number - 1) as i64).saturating_mul(limit);
|
||||
|
||||
let query = format!(
|
||||
"SELECT id, name, country, city, homepage, notes, created_at FROM roasters ORDER BY {} LIMIT ? OFFSET ?",
|
||||
order_clause
|
||||
);
|
||||
|
||||
let mut records = query_as::<_, RoasterRecord>(&query)
|
||||
.bind(limit)
|
||||
.bind(offset)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(|err| RepositoryError::unexpected(err.to_string()))?;
|
||||
|
||||
let total: i64 = query_scalar("SELECT COUNT(*) FROM roasters")
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_err(|err| RepositoryError::unexpected(err.to_string()))?;
|
||||
|
||||
if page_number > 1 && records.is_empty() && total > 0 {
|
||||
let last_page = ((total + limit - 1) / limit) as u32;
|
||||
page_number = last_page.max(1);
|
||||
let offset = ((page_number - 1) as i64).saturating_mul(limit);
|
||||
|
||||
records = query_as::<_, RoasterRecord>(&query)
|
||||
.bind(limit)
|
||||
.bind(offset)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(|err| RepositoryError::unexpected(err.to_string()))?;
|
||||
}
|
||||
|
||||
let items = records
|
||||
.into_iter()
|
||||
.map(Self::into_domain)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(Page::new(
|
||||
items,
|
||||
page_number,
|
||||
page_size,
|
||||
total as u64,
|
||||
false,
|
||||
))
|
||||
}
|
||||
}
|
||||
crate::infrastructure::repositories::pagination::paginate(
|
||||
&self.pool,
|
||||
request,
|
||||
base_query,
|
||||
count_query,
|
||||
&order_clause,
|
||||
|record| Ok(Self::into_domain(record)),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update(
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use sqlx::{Error as SqlxError, QueryBuilder, query, query_as, query_scalar};
|
|||
|
||||
use crate::domain::RepositoryError;
|
||||
use crate::domain::ids::{RoastId, RoasterId};
|
||||
use crate::domain::listing::{ListRequest, Page, PageSize, SortDirection};
|
||||
use crate::domain::listing::{ListRequest, Page, SortDirection};
|
||||
use crate::domain::repositories::RoastRepository;
|
||||
use crate::domain::roasts::{NewRoast, Roast, RoastSortKey, RoastWithRoaster, UpdateRoast};
|
||||
use crate::domain::timeline::TimelineEventDetail;
|
||||
|
|
@ -195,77 +195,18 @@ impl RoastRepository for SqlRoastRepository {
|
|||
request: &ListRequest<RoastSortKey>,
|
||||
) -> Result<Page<RoastWithRoaster>, RepositoryError> {
|
||||
let order_clause = Self::order_clause(request);
|
||||
let base_query = "SELECT r.id, r.roaster_id, r.name, r.origin, r.region, r.producer, r.process, r.tasting_notes, r.created_at, ro.name AS roaster_name \n FROM roasts r \n JOIN roasters ro ON ro.id = r.roaster_id";
|
||||
let count_query = "SELECT COUNT(*) FROM roasts";
|
||||
|
||||
match request.page_size() {
|
||||
PageSize::All => {
|
||||
let query = format!(
|
||||
"SELECT r.id, r.roaster_id, r.name, r.origin, r.region, r.producer, r.process, r.tasting_notes, r.created_at, ro.name AS roaster_name \n FROM roasts r \n JOIN roasters ro ON ro.id = r.roaster_id \n ORDER BY {}",
|
||||
order_clause
|
||||
);
|
||||
|
||||
let records = query_as::<_, RoastWithRoasterRecord>(&query)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(|err| RepositoryError::unexpected(err.to_string()))?;
|
||||
|
||||
let items = records
|
||||
.into_iter()
|
||||
.map(|record| record.into_with_roaster())
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let total = items.len() as u64;
|
||||
let page_size = total.min(u64::from(u32::MAX)) as u32;
|
||||
Ok(Page::new(items, 1, page_size.max(1), total, true))
|
||||
}
|
||||
PageSize::Limited(page_size) => {
|
||||
let limit = page_size as i64;
|
||||
let mut page_number = request.page();
|
||||
let offset = ((page_number - 1) as i64).saturating_mul(limit);
|
||||
|
||||
let query = format!(
|
||||
"SELECT r.id, r.roaster_id, r.name, r.origin, r.region, r.producer, r.process, r.tasting_notes, r.created_at, ro.name AS roaster_name \n FROM roasts r \n JOIN roasters ro ON ro.id = r.roaster_id \n ORDER BY {} \n LIMIT ? OFFSET ?",
|
||||
order_clause
|
||||
);
|
||||
|
||||
let mut records = query_as::<_, RoastWithRoasterRecord>(&query)
|
||||
.bind(limit)
|
||||
.bind(offset)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(|err| RepositoryError::unexpected(err.to_string()))?;
|
||||
|
||||
let total: i64 = query_scalar("SELECT COUNT(*) FROM roasts")
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_err(|err| RepositoryError::unexpected(err.to_string()))?;
|
||||
|
||||
if page_number > 1 && records.is_empty() && total > 0 {
|
||||
let last_page = ((total + limit - 1) / limit) as u32;
|
||||
page_number = last_page.max(1);
|
||||
let offset = ((page_number - 1) as i64).saturating_mul(limit);
|
||||
|
||||
records = query_as::<_, RoastWithRoasterRecord>(&query)
|
||||
.bind(limit)
|
||||
.bind(offset)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(|err| RepositoryError::unexpected(err.to_string()))?;
|
||||
}
|
||||
|
||||
let items = records
|
||||
.into_iter()
|
||||
.map(|record| record.into_with_roaster())
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(Page::new(
|
||||
items,
|
||||
page_number,
|
||||
page_size,
|
||||
total as u64,
|
||||
false,
|
||||
))
|
||||
}
|
||||
}
|
||||
crate::infrastructure::repositories::pagination::paginate(
|
||||
&self.pool,
|
||||
request,
|
||||
base_query,
|
||||
count_query,
|
||||
&order_clause,
|
||||
|record: RoastWithRoasterRecord| record.into_with_roaster(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn list_by_roaster(
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
use async_trait::async_trait;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde_json::from_str;
|
||||
use sqlx::{query_as, query_scalar};
|
||||
|
||||
use crate::domain::RepositoryError;
|
||||
use crate::domain::ids::TimelineEventId;
|
||||
use crate::domain::listing::{ListRequest, Page, PageSize, SortDirection};
|
||||
use crate::domain::listing::{ListRequest, Page, SortDirection};
|
||||
use crate::domain::repositories::TimelineEventRepository;
|
||||
use crate::domain::timeline::{TimelineEvent, TimelineEventDetail, TimelineSortKey};
|
||||
use crate::infrastructure::database::DatabasePool;
|
||||
|
|
@ -33,73 +31,19 @@ impl TimelineEventRepository for SqlTimelineEventRepository {
|
|||
};
|
||||
|
||||
let order_clause = format!("occurred_at {direction_sql}, id DESC");
|
||||
let base_query = "SELECT id, entity_type, entity_id, occurred_at, title, details_json, tasting_notes_json \
|
||||
FROM timeline_events";
|
||||
let count_query = "SELECT COUNT(*) FROM timeline_events";
|
||||
|
||||
match request.page_size() {
|
||||
PageSize::All => {
|
||||
let query = format!(
|
||||
"SELECT id, entity_type, entity_id, occurred_at, title, details_json, tasting_notes_json \
|
||||
FROM timeline_events \
|
||||
ORDER BY {order_clause}"
|
||||
);
|
||||
|
||||
let records = query_as::<_, TimelineEventRecord>(&query)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(|err| RepositoryError::unexpected(err.to_string()))?;
|
||||
|
||||
let mut events = Vec::with_capacity(records.len());
|
||||
for record in records {
|
||||
events.push(record.into_domain()?);
|
||||
}
|
||||
|
||||
let total = events.len() as u64;
|
||||
let page_size = total.min(u64::from(u32::MAX)) as u32;
|
||||
Ok(Page::new(events, 1, page_size.max(1), total, true))
|
||||
}
|
||||
PageSize::Limited(page_size) => {
|
||||
let limit = page_size as i64;
|
||||
let mut page = request.page();
|
||||
let offset = ((page - 1) as i64).saturating_mul(limit);
|
||||
|
||||
let query = format!(
|
||||
"SELECT id, entity_type, entity_id, occurred_at, title, details_json, tasting_notes_json \
|
||||
FROM timeline_events \
|
||||
ORDER BY {order_clause} \
|
||||
LIMIT ? OFFSET ?"
|
||||
);
|
||||
|
||||
let mut records = query_as::<_, TimelineEventRecord>(&query)
|
||||
.bind(limit)
|
||||
.bind(offset)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(|err| RepositoryError::unexpected(err.to_string()))?;
|
||||
|
||||
let total: i64 = query_scalar("SELECT COUNT(*) FROM timeline_events")
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_err(|err| RepositoryError::unexpected(err.to_string()))?;
|
||||
|
||||
if page > 1 && records.is_empty() && total > 0 {
|
||||
let last_page = ((total + limit - 1) / limit) as u32;
|
||||
page = last_page.max(1);
|
||||
let offset = ((page - 1) as i64).saturating_mul(limit);
|
||||
records = query_as::<_, TimelineEventRecord>(&query)
|
||||
.bind(limit)
|
||||
.bind(offset)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(|err| RepositoryError::unexpected(err.to_string()))?;
|
||||
}
|
||||
|
||||
let mut events = Vec::with_capacity(records.len());
|
||||
for record in records {
|
||||
events.push(record.into_domain()?);
|
||||
}
|
||||
|
||||
Ok(Page::new(events, page, page_size, total as u64, false))
|
||||
}
|
||||
}
|
||||
crate::infrastructure::repositories::pagination::paginate(
|
||||
&self.pool,
|
||||
request,
|
||||
base_query,
|
||||
count_query,
|
||||
&order_clause,
|
||||
|record: TimelineEventRecord| record.into_domain(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use crate::server::auth::AuthenticatedUser;
|
|||
use crate::server::errors::{ApiError, AppError, map_app_error};
|
||||
use crate::server::routes::render_html;
|
||||
use crate::server::routes::support::{
|
||||
FlexiblePayload, ListQuery, PayloadSource, is_datastar_request, set_datastar_patch_headers,
|
||||
FlexiblePayload, ListQuery, PayloadSource, is_datastar_request,
|
||||
};
|
||||
use crate::server::server::AppState;
|
||||
|
||||
|
|
@ -31,12 +31,13 @@ async fn load_roaster_page(
|
|||
.await
|
||||
.map_err(AppError::from)?;
|
||||
|
||||
let normalized_request = crate::server::routes::support::normalize_request(request, &page);
|
||||
let roasters = Paginated::from_page(page, RoasterView::from);
|
||||
let navigator =
|
||||
ListNavigator::new(ROASTER_PAGE_PATH, ROASTER_FRAGMENT_PATH, normalized_request);
|
||||
|
||||
Ok((roasters, navigator))
|
||||
Ok(crate::server::routes::support::build_page_view(
|
||||
page,
|
||||
request,
|
||||
RoasterView::from,
|
||||
ROASTER_PAGE_PATH,
|
||||
ROASTER_FRAGMENT_PATH,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) async fn roasters_page(
|
||||
|
|
@ -204,10 +205,5 @@ async fn render_roaster_list_fragment(
|
|||
navigator,
|
||||
};
|
||||
|
||||
let html = crate::presentation::templates::render_template(template)
|
||||
.map_err(|err| AppError::unexpected(format!("failed to render roaster list: {err}")))?;
|
||||
|
||||
let mut response = Html(html).into_response();
|
||||
set_datastar_patch_headers(response.headers_mut(), "#roaster-list");
|
||||
Ok(response)
|
||||
crate::server::routes::support::render_fragment(template, "#roaster-list")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use crate::server::auth::AuthenticatedUser;
|
|||
use crate::server::errors::{ApiError, AppError, map_app_error};
|
||||
use crate::server::routes::render_html;
|
||||
use crate::server::routes::support::{
|
||||
FlexiblePayload, ListQuery, PayloadSource, is_datastar_request, set_datastar_patch_headers,
|
||||
FlexiblePayload, ListQuery, PayloadSource, is_datastar_request,
|
||||
};
|
||||
use crate::server::server::AppState;
|
||||
|
||||
|
|
@ -31,11 +31,13 @@ async fn load_roast_page(
|
|||
.await
|
||||
.map_err(AppError::from)?;
|
||||
|
||||
let normalized_request = crate::server::routes::support::normalize_request(request, &page);
|
||||
let roasts = Paginated::from_page(page, RoastView::from_list_item);
|
||||
let navigator = ListNavigator::new(ROAST_PAGE_PATH, ROAST_FRAGMENT_PATH, normalized_request);
|
||||
|
||||
Ok((roasts, navigator))
|
||||
Ok(crate::server::routes::support::build_page_view(
|
||||
page,
|
||||
request,
|
||||
RoastView::from_list_item,
|
||||
ROAST_PAGE_PATH,
|
||||
ROAST_FRAGMENT_PATH,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) async fn roasts_page(
|
||||
|
|
@ -268,10 +270,5 @@ async fn render_roast_list_fragment(
|
|||
|
||||
let template = RoastListTemplate { roasts, navigator };
|
||||
|
||||
let html = crate::presentation::templates::render_template(template)
|
||||
.map_err(|err| AppError::unexpected(format!("failed to render roast list: {err}")))?;
|
||||
|
||||
let mut response = Html(html).into_response();
|
||||
set_datastar_patch_headers(response.headers_mut(), "#roast-list");
|
||||
Ok(response)
|
||||
crate::server::routes::support::render_fragment(template, "#roast-list")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
use axum::async_trait;
|
||||
use axum::extract::{Form, FromRequest, Json as JsonPayload, Request};
|
||||
use axum::http::{HeaderMap, HeaderValue, header::CONTENT_TYPE};
|
||||
use axum::response::{Html, IntoResponse, Response};
|
||||
use askama::Template;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::domain::listing::{
|
||||
DEFAULT_PAGE_SIZE, ListRequest, Page, PageSize, SortDirection, SortKey,
|
||||
};
|
||||
use crate::presentation::views::{ListNavigator, Paginated};
|
||||
use crate::server::errors::{ApiError, AppError};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
|
|
@ -90,6 +93,34 @@ where
|
|||
)
|
||||
}
|
||||
|
||||
pub fn build_page_view<K, T, V>(
|
||||
page: Page<T>,
|
||||
request: ListRequest<K>,
|
||||
view_mapper: impl FnMut(T) -> V,
|
||||
base_path: &'static str,
|
||||
fragment_path: &'static str,
|
||||
) -> (Paginated<V>, ListNavigator<K>)
|
||||
where
|
||||
K: SortKey,
|
||||
{
|
||||
let normalized_request = normalize_request(request, &page);
|
||||
let view_page = Paginated::from_page(page, view_mapper);
|
||||
let navigator = ListNavigator::new(base_path, fragment_path, normalized_request);
|
||||
(view_page, navigator)
|
||||
}
|
||||
|
||||
pub fn render_fragment<T: Template>(
|
||||
template: T,
|
||||
selector: &'static str,
|
||||
) -> Result<Response, AppError> {
|
||||
let html = crate::presentation::templates::render_template(template)
|
||||
.map_err(|err| AppError::unexpected(format!("failed to render fragment: {err}")))?;
|
||||
|
||||
let mut response = Html(html).into_response();
|
||||
set_datastar_patch_headers(response.headers_mut(), selector);
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn page_size_from_text(value: &str) -> PageSize {
|
||||
if value.eq_ignore_ascii_case("all") {
|
||||
PageSize::All
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use axum::extract::{Query, State};
|
||||
use axum::http::HeaderMap;
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::{Html, IntoResponse, Response};
|
||||
use axum::response::{IntoResponse, Response};
|
||||
|
||||
use crate::domain::listing::ListRequest;
|
||||
use crate::domain::timeline::{TimelineEvent, TimelineSortKey};
|
||||
|
|
@ -10,7 +10,7 @@ use crate::presentation::views::{ListNavigator, Paginated, TimelineEventView, Ti
|
|||
use crate::server::errors::{AppError, map_app_error};
|
||||
use crate::server::routes::render_html;
|
||||
use crate::server::routes::support::{
|
||||
ListQuery, is_datastar_request, normalize_request, set_datastar_patch_headers,
|
||||
ListQuery, is_datastar_request, normalize_request,
|
||||
};
|
||||
use crate::server::server::AppState;
|
||||
|
||||
|
|
@ -65,12 +65,7 @@ async fn render_timeline_chunk(
|
|||
months: data.months,
|
||||
};
|
||||
|
||||
let html = crate::presentation::templates::render_template(template)
|
||||
.map_err(|err| AppError::unexpected(format!("failed to render timeline chunk: {err}")))?;
|
||||
|
||||
let mut response = Html(html).into_response();
|
||||
set_datastar_patch_headers(response.headers_mut(), "#timeline-loader");
|
||||
Ok(response)
|
||||
crate::server::routes::support::render_fragment(template, "#timeline-loader")
|
||||
}
|
||||
|
||||
struct TimelinePageData {
|
||||
|
|
|
|||
Loading…
Reference in a new issue