refactor(routes): extract form option helpers and fragment renderer macro
- Add load_roaster_options, load_roast_options, load_cafe_options helpers to support.rs, replacing duplicated inline loading in roasts.rs, bags.rs, and cups.rs - Add define_list_fragment_renderer! macro for the 5 structurally identical render_*_list_fragment functions - Fix gear.rs to return navigator from load_gear_page like all other loaders instead of discarding and reconstructing it
This commit is contained in:
parent
4b55d1b65c
commit
714713827d
8 changed files with 132 additions and 135 deletions
|
|
@ -9,16 +9,15 @@ use crate::application::auth::AuthenticatedUser;
|
||||||
use crate::application::errors::{ApiError, AppError, map_app_error};
|
use crate::application::errors::{ApiError, AppError, map_app_error};
|
||||||
use crate::application::routes::render_html;
|
use crate::application::routes::render_html;
|
||||||
use crate::application::routes::support::{
|
use crate::application::routes::support::{
|
||||||
FlexiblePayload, ListQuery, PayloadSource, is_datastar_request,
|
FlexiblePayload, ListQuery, PayloadSource, is_datastar_request, load_roaster_options,
|
||||||
};
|
};
|
||||||
use crate::application::server::AppState;
|
use crate::application::server::AppState;
|
||||||
use crate::domain::bags::{BagFilter, BagSortKey, BagWithRoast, NewBag, UpdateBag};
|
use crate::domain::bags::{BagFilter, BagSortKey, BagWithRoast, NewBag, UpdateBag};
|
||||||
use crate::domain::ids::{BagId, RoastId};
|
use crate::domain::ids::{BagId, RoastId};
|
||||||
use crate::domain::listing::{ListRequest, SortDirection};
|
use crate::domain::listing::{ListRequest, SortDirection};
|
||||||
use crate::domain::roasters::RoasterSortKey;
|
|
||||||
use crate::domain::timeline::{NewTimelineEvent, TimelineEventDetail};
|
use crate::domain::timeline::{NewTimelineEvent, TimelineEventDetail};
|
||||||
use crate::presentation::web::templates::{BagListTemplate, BagsTemplate};
|
use crate::presentation::web::templates::{BagListTemplate, BagsTemplate};
|
||||||
use crate::presentation::web::views::{BagView, ListNavigator, Paginated, RoasterOptionView};
|
use crate::presentation::web::views::{BagView, ListNavigator, Paginated};
|
||||||
|
|
||||||
const BAG_PAGE_PATH: &str = "/bags";
|
const BAG_PAGE_PATH: &str = "/bags";
|
||||||
const BAG_FRAGMENT_PATH: &str = "/bags#bag-list";
|
const BAG_FRAGMENT_PATH: &str = "/bags#bag-list";
|
||||||
|
|
@ -85,13 +84,7 @@ pub(crate) async fn bags_page(
|
||||||
.map_err(map_app_error);
|
.map_err(map_app_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
let roasters = state
|
let roaster_options = load_roaster_options(&state).await.map_err(map_app_error)?;
|
||||||
.roaster_repo
|
|
||||||
.list_all_sorted(RoasterSortKey::Name, SortDirection::Asc)
|
|
||||||
.await
|
|
||||||
.map_err(|err| map_app_error(AppError::from(err)))?;
|
|
||||||
|
|
||||||
let roaster_options = roasters.into_iter().map(RoasterOptionView::from).collect();
|
|
||||||
|
|
||||||
let BagPageData {
|
let BagPageData {
|
||||||
open_bags,
|
open_bags,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use axum::http::{HeaderMap, StatusCode};
|
||||||
use axum::response::{IntoResponse, Redirect, Response};
|
use axum::response::{IntoResponse, Redirect, Response};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use super::macros::{define_delete_handler, define_get_handler};
|
use super::macros::{define_delete_handler, define_get_handler, define_list_fragment_renderer};
|
||||||
use crate::application::auth::AuthenticatedUser;
|
use crate::application::auth::AuthenticatedUser;
|
||||||
use crate::application::errors::{ApiError, AppError, map_app_error};
|
use crate::application::errors::{ApiError, AppError, map_app_error};
|
||||||
use crate::application::routes::render_html;
|
use crate::application::routes::render_html;
|
||||||
|
|
@ -177,22 +177,13 @@ define_delete_handler!(
|
||||||
render_cafe_list_fragment
|
render_cafe_list_fragment
|
||||||
);
|
);
|
||||||
|
|
||||||
async fn render_cafe_list_fragment(
|
define_list_fragment_renderer!(
|
||||||
state: AppState,
|
render_cafe_list_fragment,
|
||||||
request: ListRequest<CafeSortKey>,
|
CafeSortKey,
|
||||||
search: Option<String>,
|
load_cafe_page,
|
||||||
is_authenticated: bool,
|
CafeListTemplate { cafes },
|
||||||
) -> Result<Response, AppError> {
|
"#cafe-list"
|
||||||
let (cafes, navigator) = load_cafe_page(&state, request, search.as_deref()).await?;
|
);
|
||||||
|
|
||||||
let template = CafeListTemplate {
|
|
||||||
is_authenticated,
|
|
||||||
cafes,
|
|
||||||
navigator,
|
|
||||||
};
|
|
||||||
|
|
||||||
crate::application::routes::support::render_fragment(template, "#cafe-list")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct NearbyQuery {
|
pub struct NearbyQuery {
|
||||||
|
|
|
||||||
|
|
@ -3,22 +3,22 @@ use axum::extract::{Path, Query, State};
|
||||||
use axum::http::{HeaderMap, StatusCode};
|
use axum::http::{HeaderMap, StatusCode};
|
||||||
use axum::response::{IntoResponse, Redirect, Response};
|
use axum::response::{IntoResponse, Redirect, Response};
|
||||||
|
|
||||||
use super::macros::{define_delete_handler, define_enriched_get_handler};
|
use super::macros::{
|
||||||
|
define_delete_handler, define_enriched_get_handler, define_list_fragment_renderer,
|
||||||
|
};
|
||||||
use crate::application::auth::AuthenticatedUser;
|
use crate::application::auth::AuthenticatedUser;
|
||||||
use crate::application::errors::{ApiError, AppError, map_app_error};
|
use crate::application::errors::{ApiError, AppError, map_app_error};
|
||||||
use crate::application::routes::render_html;
|
use crate::application::routes::render_html;
|
||||||
use crate::application::routes::support::{
|
use crate::application::routes::support::{
|
||||||
FlexiblePayload, ListQuery, PayloadSource, is_datastar_request,
|
FlexiblePayload, ListQuery, PayloadSource, is_datastar_request, load_cafe_options,
|
||||||
|
load_roast_options,
|
||||||
};
|
};
|
||||||
use crate::application::server::AppState;
|
use crate::application::server::AppState;
|
||||||
use crate::domain::cafes::CafeSortKey;
|
|
||||||
use crate::domain::cups::{Cup, CupFilter, CupSortKey, CupWithDetails, NewCup, UpdateCup};
|
use crate::domain::cups::{Cup, CupFilter, CupSortKey, CupWithDetails, NewCup, UpdateCup};
|
||||||
use crate::domain::ids::CupId;
|
use crate::domain::ids::CupId;
|
||||||
use crate::domain::listing::{ListRequest, SortDirection};
|
use crate::domain::listing::{ListRequest, SortDirection};
|
||||||
use crate::presentation::web::templates::{CupListTemplate, CupsTemplate};
|
use crate::presentation::web::templates::{CupListTemplate, CupsTemplate};
|
||||||
use crate::presentation::web::views::{
|
use crate::presentation::web::views::{CupView, ListNavigator, Paginated};
|
||||||
CafeOptionView, CupView, ListNavigator, Paginated, RoastOptionView,
|
|
||||||
};
|
|
||||||
|
|
||||||
const CUP_PAGE_PATH: &str = "/cups";
|
const CUP_PAGE_PATH: &str = "/cups";
|
||||||
const CUP_FRAGMENT_PATH: &str = "/cups#cup-list";
|
const CUP_FRAGMENT_PATH: &str = "/cups#cup-list";
|
||||||
|
|
@ -67,23 +67,9 @@ pub(crate) async fn cups_page(
|
||||||
|
|
||||||
let is_authenticated = super::is_authenticated(&state, &cookies).await;
|
let is_authenticated = super::is_authenticated(&state, &cookies).await;
|
||||||
|
|
||||||
let roast_options: Vec<RoastOptionView> = state
|
let roast_options = load_roast_options(&state).await.map_err(map_app_error)?;
|
||||||
.roast_repo
|
|
||||||
.list_all()
|
|
||||||
.await
|
|
||||||
.map_err(|e| map_app_error(AppError::from(e)))?
|
|
||||||
.into_iter()
|
|
||||||
.map(RoastOptionView::from)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let cafe_options: Vec<CafeOptionView> = state
|
let cafe_options = load_cafe_options(&state).await.map_err(map_app_error)?;
|
||||||
.cafe_repo
|
|
||||||
.list_all_sorted(CafeSortKey::Name, SortDirection::Asc)
|
|
||||||
.await
|
|
||||||
.map_err(|e| map_app_error(AppError::from(e)))?
|
|
||||||
.into_iter()
|
|
||||||
.map(CafeOptionView::from)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let template = CupsTemplate {
|
let template = CupsTemplate {
|
||||||
nav_active: "cups",
|
nav_active: "cups",
|
||||||
|
|
@ -183,19 +169,10 @@ define_delete_handler!(
|
||||||
render_cup_list_fragment
|
render_cup_list_fragment
|
||||||
);
|
);
|
||||||
|
|
||||||
async fn render_cup_list_fragment(
|
define_list_fragment_renderer!(
|
||||||
state: AppState,
|
render_cup_list_fragment,
|
||||||
request: ListRequest<CupSortKey>,
|
CupSortKey,
|
||||||
search: Option<String>,
|
load_cup_page,
|
||||||
is_authenticated: bool,
|
CupListTemplate { cups },
|
||||||
) -> Result<Response, AppError> {
|
"#cup-list"
|
||||||
let (cups, navigator) = load_cup_page(&state, request, search.as_deref()).await?;
|
);
|
||||||
|
|
||||||
let template = CupListTemplate {
|
|
||||||
is_authenticated,
|
|
||||||
cups,
|
|
||||||
navigator,
|
|
||||||
};
|
|
||||||
|
|
||||||
crate::application::routes::support::render_fragment(template, "#cup-list")
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use axum::http::{HeaderMap, StatusCode};
|
||||||
use axum::response::{IntoResponse, Redirect, Response};
|
use axum::response::{IntoResponse, Redirect, Response};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use super::macros::{define_delete_handler, define_get_handler};
|
use super::macros::{define_delete_handler, define_get_handler, define_list_fragment_renderer};
|
||||||
use crate::application::auth::AuthenticatedUser;
|
use crate::application::auth::AuthenticatedUser;
|
||||||
use crate::application::errors::{ApiError, AppError, map_app_error};
|
use crate::application::errors::{ApiError, AppError, map_app_error};
|
||||||
use crate::application::routes::render_html;
|
use crate::application::routes::render_html;
|
||||||
|
|
@ -29,23 +29,21 @@ async fn load_gear_page(
|
||||||
state: &AppState,
|
state: &AppState,
|
||||||
request: ListRequest<GearSortKey>,
|
request: ListRequest<GearSortKey>,
|
||||||
search: Option<&str>,
|
search: Option<&str>,
|
||||||
) -> Result<Paginated<GearView>, AppError> {
|
) -> Result<(Paginated<GearView>, ListNavigator<GearSortKey>), AppError> {
|
||||||
let page = state
|
let page = state
|
||||||
.gear_repo
|
.gear_repo
|
||||||
.list(GearFilter::all(), &request, search)
|
.list(GearFilter::all(), &request, search)
|
||||||
.await
|
.await
|
||||||
.map_err(AppError::from)?;
|
.map_err(AppError::from)?;
|
||||||
|
|
||||||
let (gear, _navigator) = crate::application::routes::support::build_page_view(
|
Ok(crate::application::routes::support::build_page_view(
|
||||||
page,
|
page,
|
||||||
request,
|
request,
|
||||||
GearView::from_domain,
|
GearView::from_domain,
|
||||||
GEAR_PAGE_PATH,
|
GEAR_PAGE_PATH,
|
||||||
GEAR_FRAGMENT_PATH,
|
GEAR_FRAGMENT_PATH,
|
||||||
search.map(String::from),
|
search.map(String::from),
|
||||||
);
|
))
|
||||||
|
|
||||||
Ok(gear)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(state, cookies, headers, query))]
|
#[tracing::instrument(skip(state, cookies, headers, query))]
|
||||||
|
|
@ -64,12 +62,11 @@ pub(crate) async fn gear_page(
|
||||||
.map_err(map_app_error);
|
.map_err(map_app_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
let gear = load_gear_page(&state, request, search.as_deref())
|
let (gear, navigator) = load_gear_page(&state, request, search.as_deref())
|
||||||
.await
|
.await
|
||||||
.map_err(map_app_error)?;
|
.map_err(map_app_error)?;
|
||||||
|
|
||||||
let is_authenticated = super::is_authenticated(&state, &cookies).await;
|
let is_authenticated = super::is_authenticated(&state, &cookies).await;
|
||||||
let navigator = ListNavigator::new(GEAR_PAGE_PATH, GEAR_FRAGMENT_PATH, request, search);
|
|
||||||
|
|
||||||
let template = GearTemplate {
|
let template = GearTemplate {
|
||||||
nav_active: "gear",
|
nav_active: "gear",
|
||||||
|
|
@ -231,20 +228,10 @@ impl NewGearSubmission {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn render_gear_list_fragment(
|
define_list_fragment_renderer!(
|
||||||
state: AppState,
|
render_gear_list_fragment,
|
||||||
request: ListRequest<GearSortKey>,
|
GearSortKey,
|
||||||
search: Option<String>,
|
load_gear_page,
|
||||||
is_authenticated: bool,
|
GearListTemplate { gear },
|
||||||
) -> Result<Response, AppError> {
|
"#gear-list"
|
||||||
let gear = load_gear_page(&state, request, search.as_deref()).await?;
|
);
|
||||||
let navigator = ListNavigator::new(GEAR_PAGE_PATH, GEAR_FRAGMENT_PATH, request, search);
|
|
||||||
|
|
||||||
let template = GearListTemplate {
|
|
||||||
is_authenticated,
|
|
||||||
gear,
|
|
||||||
navigator,
|
|
||||||
};
|
|
||||||
|
|
||||||
crate::application::routes::support::render_fragment(template, "#gear-list")
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,48 @@ macro_rules! define_delete_handler {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates a list-fragment renderer for Datastar partial updates.
|
||||||
|
///
|
||||||
|
/// Produces a function that loads a page via `$loader`, builds the given list
|
||||||
|
/// template, and returns it as a Datastar fragment targeting `$selector`.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `$fn_name` - Name of the generated function
|
||||||
|
/// * `$sort_key` - Sort key type (e.g., `RoasterSortKey`)
|
||||||
|
/// * `$loader` - Page-loader function returning `(Paginated<V>, ListNavigator<K>)`
|
||||||
|
/// * `$template { $field }` - List template type and its items field name
|
||||||
|
/// * `$selector` - CSS selector for Datastar patching (e.g., `"#roaster-list"`)
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```ignore
|
||||||
|
/// define_list_fragment_renderer!(
|
||||||
|
/// render_roaster_list_fragment,
|
||||||
|
/// RoasterSortKey,
|
||||||
|
/// load_roaster_page,
|
||||||
|
/// RoasterListTemplate { roasters },
|
||||||
|
/// "#roaster-list"
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
macro_rules! define_list_fragment_renderer {
|
||||||
|
($fn_name:ident, $sort_key:ty, $loader:ident, $template:ident { $field:ident }, $selector:literal) => {
|
||||||
|
async fn $fn_name(
|
||||||
|
state: crate::application::server::AppState,
|
||||||
|
request: crate::domain::listing::ListRequest<$sort_key>,
|
||||||
|
search: Option<String>,
|
||||||
|
is_authenticated: bool,
|
||||||
|
) -> Result<axum::response::Response, crate::application::errors::AppError> {
|
||||||
|
let (items, navigator) = $loader(&state, request, search.as_deref()).await?;
|
||||||
|
let template = $template {
|
||||||
|
is_authenticated,
|
||||||
|
$field: items,
|
||||||
|
navigator,
|
||||||
|
};
|
||||||
|
crate::application::routes::support::render_fragment(template, $selector)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) use define_delete_handler;
|
pub(super) use define_delete_handler;
|
||||||
pub(super) use define_enriched_get_handler;
|
pub(super) use define_enriched_get_handler;
|
||||||
pub(super) use define_get_handler;
|
pub(super) use define_get_handler;
|
||||||
|
pub(super) use define_list_fragment_renderer;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use axum::extract::{Path, Query, State};
|
||||||
use axum::http::{HeaderMap, StatusCode};
|
use axum::http::{HeaderMap, StatusCode};
|
||||||
use axum::response::{Html, IntoResponse, Redirect, Response};
|
use axum::response::{Html, IntoResponse, Redirect, Response};
|
||||||
|
|
||||||
use super::macros::{define_delete_handler, define_get_handler};
|
use super::macros::{define_delete_handler, define_get_handler, define_list_fragment_renderer};
|
||||||
use crate::application::auth::AuthenticatedUser;
|
use crate::application::auth::AuthenticatedUser;
|
||||||
use crate::application::errors::{ApiError, AppError, map_app_error};
|
use crate::application::errors::{ApiError, AppError, map_app_error};
|
||||||
use crate::application::routes::render_html;
|
use crate::application::routes::render_html;
|
||||||
|
|
@ -183,19 +183,10 @@ define_delete_handler!(
|
||||||
render_roaster_list_fragment
|
render_roaster_list_fragment
|
||||||
);
|
);
|
||||||
|
|
||||||
async fn render_roaster_list_fragment(
|
define_list_fragment_renderer!(
|
||||||
state: AppState,
|
render_roaster_list_fragment,
|
||||||
request: ListRequest<RoasterSortKey>,
|
RoasterSortKey,
|
||||||
search: Option<String>,
|
load_roaster_page,
|
||||||
is_authenticated: bool,
|
RoasterListTemplate { roasters },
|
||||||
) -> Result<Response, AppError> {
|
"#roaster-list"
|
||||||
let (roasters, navigator) = load_roaster_page(&state, request, search.as_deref()).await?;
|
);
|
||||||
|
|
||||||
let template = RoasterListTemplate {
|
|
||||||
is_authenticated,
|
|
||||||
roasters,
|
|
||||||
navigator,
|
|
||||||
};
|
|
||||||
|
|
||||||
crate::application::routes::support::render_fragment(template, "#roaster-list")
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -4,23 +4,24 @@ use axum::http::{HeaderMap, StatusCode};
|
||||||
use axum::response::{Html, IntoResponse, Redirect, Response};
|
use axum::response::{Html, IntoResponse, Redirect, Response};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use super::macros::{define_delete_handler, define_enriched_get_handler};
|
use super::macros::{
|
||||||
|
define_delete_handler, define_enriched_get_handler, define_list_fragment_renderer,
|
||||||
|
};
|
||||||
use crate::application::auth::AuthenticatedUser;
|
use crate::application::auth::AuthenticatedUser;
|
||||||
use crate::application::errors::{ApiError, AppError, map_app_error};
|
use crate::application::errors::{ApiError, AppError, map_app_error};
|
||||||
use crate::application::routes::render_html;
|
use crate::application::routes::render_html;
|
||||||
use crate::application::routes::support::{
|
use crate::application::routes::support::{
|
||||||
FlexiblePayload, ListQuery, PayloadSource, is_datastar_request,
|
FlexiblePayload, ListQuery, PayloadSource, is_datastar_request, load_roaster_options,
|
||||||
};
|
};
|
||||||
use crate::application::server::AppState;
|
use crate::application::server::AppState;
|
||||||
use crate::domain::bags::{BagFilter, BagSortKey};
|
use crate::domain::bags::{BagFilter, BagSortKey};
|
||||||
use crate::domain::ids::{RoastId, RoasterId};
|
use crate::domain::ids::{RoastId, RoasterId};
|
||||||
use crate::domain::listing::{ListRequest, SortDirection};
|
use crate::domain::listing::{ListRequest, SortDirection};
|
||||||
use crate::domain::roasters::RoasterSortKey;
|
|
||||||
use crate::domain::roasts::{NewRoast, RoastSortKey, RoastWithRoaster, UpdateRoast};
|
use crate::domain::roasts::{NewRoast, RoastSortKey, RoastWithRoaster, UpdateRoast};
|
||||||
use crate::presentation::web::templates::{
|
use crate::presentation::web::templates::{
|
||||||
RoastDetailTemplate, RoastListTemplate, RoastOptionsTemplate, RoastsTemplate,
|
RoastDetailTemplate, RoastListTemplate, RoastOptionsTemplate, RoastsTemplate,
|
||||||
};
|
};
|
||||||
use crate::presentation::web::views::{ListNavigator, Paginated, RoastView, RoasterOptionView};
|
use crate::presentation::web::views::{ListNavigator, Paginated, RoastView};
|
||||||
|
|
||||||
const ROAST_PAGE_PATH: &str = "/roasts";
|
const ROAST_PAGE_PATH: &str = "/roasts";
|
||||||
const ROAST_FRAGMENT_PATH: &str = "/roasts#roast-list";
|
const ROAST_FRAGMENT_PATH: &str = "/roasts#roast-list";
|
||||||
|
|
@ -63,13 +64,7 @@ pub(crate) async fn roasts_page(
|
||||||
.map_err(map_app_error);
|
.map_err(map_app_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
let roasters = state
|
let roaster_options = load_roaster_options(&state).await.map_err(map_app_error)?;
|
||||||
.roaster_repo
|
|
||||||
.list_all_sorted(RoasterSortKey::Name, SortDirection::Asc)
|
|
||||||
.await
|
|
||||||
.map_err(|err| map_app_error(AppError::from(err)))?;
|
|
||||||
|
|
||||||
let roaster_options = roasters.into_iter().map(RoasterOptionView::from).collect();
|
|
||||||
|
|
||||||
let (roasts, navigator) = load_roast_page(&state, request, search.as_deref())
|
let (roasts, navigator) = load_roast_page(&state, request, search.as_deref())
|
||||||
.await
|
.await
|
||||||
|
|
@ -343,19 +338,10 @@ impl TastingNotesInput {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn render_roast_list_fragment(
|
define_list_fragment_renderer!(
|
||||||
state: AppState,
|
render_roast_list_fragment,
|
||||||
request: ListRequest<RoastSortKey>,
|
RoastSortKey,
|
||||||
search: Option<String>,
|
load_roast_page,
|
||||||
is_authenticated: bool,
|
RoastListTemplate { roasts },
|
||||||
) -> Result<Response, AppError> {
|
"#roast-list"
|
||||||
let (roasts, navigator) = load_roast_page(&state, request, search.as_deref()).await?;
|
);
|
||||||
|
|
||||||
let template = RoastListTemplate {
|
|
||||||
is_authenticated,
|
|
||||||
roasts,
|
|
||||||
navigator,
|
|
||||||
};
|
|
||||||
|
|
||||||
crate::application::routes::support::render_fragment(template, "#roast-list")
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,13 @@ use axum::response::{Html, IntoResponse, Response};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::application::errors::{ApiError, AppError};
|
use crate::application::errors::{ApiError, AppError};
|
||||||
|
use crate::application::server::AppState;
|
||||||
use crate::domain::listing::{
|
use crate::domain::listing::{
|
||||||
DEFAULT_PAGE_SIZE, ListRequest, Page, PageSize, SortDirection, SortKey,
|
DEFAULT_PAGE_SIZE, ListRequest, Page, PageSize, SortDirection, SortKey,
|
||||||
};
|
};
|
||||||
use crate::presentation::web::views::{ListNavigator, Paginated};
|
use crate::presentation::web::views::{
|
||||||
|
CafeOptionView, ListNavigator, Paginated, RoastOptionView, RoasterOptionView,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub enum PayloadSource {
|
pub enum PayloadSource {
|
||||||
|
|
@ -202,6 +205,33 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) async fn load_roaster_options(
|
||||||
|
state: &AppState,
|
||||||
|
) -> Result<Vec<RoasterOptionView>, AppError> {
|
||||||
|
use crate::domain::roasters::RoasterSortKey;
|
||||||
|
let roasters = state
|
||||||
|
.roaster_repo
|
||||||
|
.list_all_sorted(RoasterSortKey::Name, SortDirection::Asc)
|
||||||
|
.await
|
||||||
|
.map_err(AppError::from)?;
|
||||||
|
Ok(roasters.into_iter().map(RoasterOptionView::from).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn load_roast_options(state: &AppState) -> Result<Vec<RoastOptionView>, AppError> {
|
||||||
|
let roasts = state.roast_repo.list_all().await.map_err(AppError::from)?;
|
||||||
|
Ok(roasts.into_iter().map(RoastOptionView::from).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn load_cafe_options(state: &AppState) -> Result<Vec<CafeOptionView>, AppError> {
|
||||||
|
use crate::domain::cafes::CafeSortKey;
|
||||||
|
let cafes = state
|
||||||
|
.cafe_repo
|
||||||
|
.list_all_sorted(CafeSortKey::Name, SortDirection::Asc)
|
||||||
|
.await
|
||||||
|
.map_err(AppError::from)?;
|
||||||
|
Ok(cafes.into_iter().map(CafeOptionView::from).collect())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_datastar_request(headers: &HeaderMap) -> bool {
|
pub fn is_datastar_request(headers: &HeaderMap) -> bool {
|
||||||
headers
|
headers
|
||||||
.get("datastar-request")
|
.get("datastar-request")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue