From 9cc0b30ccad2d8778bfe01266a91b67fda73bec3 Mon Sep 17 00:00:00 2001 From: Jon Seager Date: Thu, 5 Feb 2026 19:04:21 +0000 Subject: [PATCH] refactor(routes): move API handlers to routes/api/ subdirectory Move entity CRUD modules (roasters, roasts, bags, brews, gear, cafes, cups), pure API modules (tokens, backup, scan), macros, and webauthn to routes/api/. Remove page handlers from api/webauthn.rs. Update import paths (super::support:: to crate::, scan's TastingNotesInput reference to super::roasts::). Widen pub(super) to pub(in crate::application::routes) on page loaders and data structs for cross-subdirectory access. --- src/application/routes/{ => api}/backup.rs | 0 src/application/routes/{ => api}/bags.rs | 8 +-- src/application/routes/{ => api}/brews.rs | 26 +++---- src/application/routes/{ => api}/cafes.rs | 2 +- src/application/routes/{ => api}/cups.rs | 2 +- src/application/routes/{ => api}/gear.rs | 2 +- src/application/routes/{ => api}/macros.rs | 0 src/application/routes/{ => api}/roasters.rs | 4 +- src/application/routes/{ => api}/roasts.rs | 4 +- src/application/routes/{ => api}/scan.rs | 6 +- src/application/routes/{ => api}/tokens.rs | 0 src/application/routes/{ => api}/webauthn.rs | 76 +------------------- 12 files changed, 29 insertions(+), 101 deletions(-) rename src/application/routes/{ => api}/backup.rs (100%) rename src/application/routes/{ => api}/bags.rs (97%) rename src/application/routes/{ => api}/brews.rs (93%) rename src/application/routes/{ => api}/cafes.rs (99%) rename src/application/routes/{ => api}/cups.rs (98%) rename src/application/routes/{ => api}/gear.rs (99%) rename src/application/routes/{ => api}/macros.rs (100%) rename src/application/routes/{ => api}/roasters.rs (97%) rename src/application/routes/{ => api}/roasts.rs (98%) rename src/application/routes/{ => api}/scan.rs (98%) rename src/application/routes/{ => api}/tokens.rs (100%) rename src/application/routes/{ => api}/webauthn.rs (89%) diff --git a/src/application/routes/backup.rs b/src/application/routes/api/backup.rs similarity index 100% rename from src/application/routes/backup.rs rename to src/application/routes/api/backup.rs diff --git a/src/application/routes/bags.rs b/src/application/routes/api/bags.rs similarity index 97% rename from src/application/routes/bags.rs rename to src/application/routes/api/bags.rs index 620e56b..1f55210 100644 --- a/src/application/routes/bags.rs +++ b/src/application/routes/api/bags.rs @@ -22,13 +22,13 @@ use crate::presentation::web::views::{BagView, ListNavigator, Paginated}; const BAG_PAGE_PATH: &str = "/data?type=bags"; const BAG_FRAGMENT_PATH: &str = "/data?type=bags#bag-list"; -pub(super) struct BagPageData { - pub(super) bags: Paginated, - pub(super) navigator: ListNavigator, +pub(in crate::application::routes) struct BagPageData { + pub(in crate::application::routes) bags: Paginated, + pub(in crate::application::routes) navigator: ListNavigator, } #[tracing::instrument(skip(state))] -pub(super) async fn load_bag_page( +pub(in crate::application::routes) async fn load_bag_page( state: &AppState, request: ListRequest, search: Option<&str>, diff --git a/src/application/routes/brews.rs b/src/application/routes/api/brews.rs similarity index 93% rename from src/application/routes/brews.rs rename to src/application/routes/api/brews.rs index 4cb5d72..7857b54 100644 --- a/src/application/routes/brews.rs +++ b/src/application/routes/api/brews.rs @@ -26,20 +26,22 @@ use crate::presentation::web::views::{ const BREW_PAGE_PATH: &str = "/data?type=brews"; const BREW_FRAGMENT_PATH: &str = "/data?type=brews#brew-list"; -pub(super) struct BrewPageData { - pub(super) brews: Paginated, - pub(super) navigator: ListNavigator, +pub(in crate::application::routes) struct BrewPageData { + pub(in crate::application::routes) brews: Paginated, + pub(in crate::application::routes) navigator: ListNavigator, } -pub(super) struct BrewFormData { - pub(super) bag_options: Vec, - pub(super) grinder_options: Vec, - pub(super) brewer_options: Vec, - pub(super) filter_paper_options: Vec, - pub(super) defaults: BrewDefaultsView, +pub(in crate::application::routes) struct BrewFormData { + pub(in crate::application::routes) bag_options: Vec, + pub(in crate::application::routes) grinder_options: Vec, + pub(in crate::application::routes) brewer_options: Vec, + pub(in crate::application::routes) filter_paper_options: Vec, + pub(in crate::application::routes) defaults: BrewDefaultsView, } -pub(super) async fn load_brew_form_data(state: &AppState) -> Result { +pub(in crate::application::routes) async fn load_brew_form_data( + state: &AppState, +) -> Result { let open_bags_request = ListRequest::show_all( crate::domain::bags::BagSortKey::RoastDate, SortDirection::Desc, @@ -89,7 +91,7 @@ pub(super) async fn load_brew_form_data(state: &AppState) -> Result, @@ -103,7 +105,7 @@ pub(super) async fn load_gear_options( } #[tracing::instrument(skip(state))] -pub(super) async fn load_brew_page( +pub(in crate::application::routes) async fn load_brew_page( state: &AppState, request: ListRequest, search: Option<&str>, diff --git a/src/application/routes/cafes.rs b/src/application/routes/api/cafes.rs similarity index 99% rename from src/application/routes/cafes.rs rename to src/application/routes/api/cafes.rs index 607ba13..31b6ff6 100644 --- a/src/application/routes/cafes.rs +++ b/src/application/routes/api/cafes.rs @@ -23,7 +23,7 @@ const CAFE_PAGE_PATH: &str = "/data?type=cafes"; const CAFE_FRAGMENT_PATH: &str = "/data?type=cafes#cafe-list"; #[tracing::instrument(skip(state))] -pub(super) async fn load_cafe_page( +pub(in crate::application::routes) async fn load_cafe_page( state: &AppState, request: ListRequest, search: Option<&str>, diff --git a/src/application/routes/cups.rs b/src/application/routes/api/cups.rs similarity index 98% rename from src/application/routes/cups.rs rename to src/application/routes/api/cups.rs index 79bd362..ed59a78 100644 --- a/src/application/routes/cups.rs +++ b/src/application/routes/api/cups.rs @@ -23,7 +23,7 @@ const CUP_PAGE_PATH: &str = "/data?type=cups"; const CUP_FRAGMENT_PATH: &str = "/data?type=cups#cup-list"; #[tracing::instrument(skip(state))] -pub(super) async fn load_cup_page( +pub(in crate::application::routes) async fn load_cup_page( state: &AppState, request: ListRequest, search: Option<&str>, diff --git a/src/application/routes/gear.rs b/src/application/routes/api/gear.rs similarity index 99% rename from src/application/routes/gear.rs rename to src/application/routes/api/gear.rs index f1d836c..dc31be4 100644 --- a/src/application/routes/gear.rs +++ b/src/application/routes/api/gear.rs @@ -25,7 +25,7 @@ const GEAR_PAGE_PATH: &str = "/data?type=gear"; const GEAR_FRAGMENT_PATH: &str = "/data?type=gear#gear-list"; #[tracing::instrument(skip(state))] -pub(super) async fn load_gear_page( +pub(in crate::application::routes) async fn load_gear_page( state: &AppState, request: ListRequest, search: Option<&str>, diff --git a/src/application/routes/macros.rs b/src/application/routes/api/macros.rs similarity index 100% rename from src/application/routes/macros.rs rename to src/application/routes/api/macros.rs diff --git a/src/application/routes/roasters.rs b/src/application/routes/api/roasters.rs similarity index 97% rename from src/application/routes/roasters.rs rename to src/application/routes/api/roasters.rs index e12e7de..b6c1ff0 100644 --- a/src/application/routes/roasters.rs +++ b/src/application/routes/api/roasters.rs @@ -22,7 +22,7 @@ const ROASTER_PAGE_PATH: &str = "/data?type=roasters"; const ROASTER_FRAGMENT_PATH: &str = "/data?type=roasters#roaster-list"; #[tracing::instrument(skip(state))] -pub(super) async fn load_roaster_page( +pub(in crate::application::routes) async fn load_roaster_page( state: &AppState, request: ListRequest, search: Option<&str>, @@ -139,7 +139,7 @@ pub(crate) async fn extract_roaster( .await .map_err(ApiError::from)?; - super::support::record_ai_usage( + crate::application::routes::support::record_ai_usage( state.ai_usage_repo.clone(), auth_user.0.id, &state.openrouter_model, diff --git a/src/application/routes/roasts.rs b/src/application/routes/api/roasts.rs similarity index 98% rename from src/application/routes/roasts.rs rename to src/application/routes/api/roasts.rs index 2e374ee..cc50972 100644 --- a/src/application/routes/roasts.rs +++ b/src/application/routes/api/roasts.rs @@ -25,7 +25,7 @@ const ROAST_PAGE_PATH: &str = "/data?type=roasts"; const ROAST_FRAGMENT_PATH: &str = "/data?type=roasts#roast-list"; #[tracing::instrument(skip(state))] -pub(super) async fn load_roast_page( +pub(in crate::application::routes) async fn load_roast_page( state: &AppState, request: ListRequest, search: Option<&str>, @@ -279,7 +279,7 @@ pub(crate) async fn extract_roast_info( .await .map_err(ApiError::from)?; - super::support::record_ai_usage( + crate::application::routes::support::record_ai_usage( state.ai_usage_repo.clone(), auth_user.0.id, &state.openrouter_model, diff --git a/src/application/routes/scan.rs b/src/application/routes/api/scan.rs similarity index 98% rename from src/application/routes/scan.rs rename to src/application/routes/api/scan.rs index 623abd2..698de61 100644 --- a/src/application/routes/scan.rs +++ b/src/application/routes/api/scan.rs @@ -5,9 +5,9 @@ use axum::response::{IntoResponse, Response}; use serde::{Deserialize, Serialize}; use tracing::{info, warn}; +use super::roasts::TastingNotesInput; use crate::application::auth::AuthenticatedUser; use crate::application::errors::{ApiError, AppError}; -use crate::application::routes::roasts::TastingNotesInput; use crate::application::routes::support::{FlexiblePayload, is_datastar_request}; use crate::application::server::AppState; use crate::domain::bags::NewBag; @@ -34,7 +34,7 @@ pub(crate) async fn extract_bag_scan( .await .map_err(ApiError::from)?; - super::support::record_ai_usage( + crate::application::routes::support::record_ai_usage( state.ai_usage_repo.clone(), auth_user.0.id, &state.openrouter_model, @@ -207,7 +207,7 @@ pub(crate) async fn submit_scan( if has_raw_input { let usage = extract_into_submission(&state, &mut submission).await?; - super::support::record_ai_usage( + crate::application::routes::support::record_ai_usage( state.ai_usage_repo.clone(), auth_user.0.id, &state.openrouter_model, diff --git a/src/application/routes/tokens.rs b/src/application/routes/api/tokens.rs similarity index 100% rename from src/application/routes/tokens.rs rename to src/application/routes/api/tokens.rs diff --git a/src/application/routes/webauthn.rs b/src/application/routes/api/webauthn.rs similarity index 89% rename from src/application/routes/webauthn.rs rename to src/application/routes/api/webauthn.rs index e0a6f72..e205c81 100644 --- a/src/application/routes/webauthn.rs +++ b/src/application/routes/api/webauthn.rs @@ -1,8 +1,6 @@ -use askama::Template; use axum::Json; -use axum::extract::{Path, Query, State}; +use axum::extract::{Query, State}; use axum::http::StatusCode; -use axum::response::{IntoResponse, Response}; use chrono::{Duration, Utc}; use serde::{Deserialize, Serialize}; use tower_cookies::{Cookie, Cookies}; @@ -11,7 +9,6 @@ use uuid::Uuid; use webauthn_rs::prelude::*; use crate::application::auth::AuthenticatedUser; -use crate::application::routes::render_html; use crate::application::server::AppState; use crate::domain::passkey_credentials::NewPasskeyCredential; use crate::domain::sessions::NewSession; @@ -22,27 +19,6 @@ use crate::infrastructure::webauthn::CliCallbackInfo; const SESSION_COOKIE_NAME: &str = "brewlog_session"; -// --- Templates --- - -#[derive(Template)] -#[template(path = "pages/register.html")] -struct RegisterTemplate { - nav_active: &'static str, - is_authenticated: bool, - version_info: &'static crate::VersionInfo, - token: String, -} - -#[derive(Template)] -#[template(path = "pages/cli_callback.html")] -struct CliCallbackTemplate { - nav_active: &'static str, - is_authenticated: bool, - version_info: &'static crate::VersionInfo, - token: Option, - error: Option, -} - // --- Request/Response types --- #[derive(Deserialize)] @@ -89,42 +65,6 @@ pub struct AuthFinishResponse { pub redirect: Option, } -// --- Registration page (bootstrap flow) --- - -#[tracing::instrument(skip(state))] -pub(crate) async fn register_page( - State(state): State, - Path(token): Path, -) -> Result { - // Validate the token exists and is usable - let token_hash = hash_token(&token); - let reg_token = state - .registration_token_repo - .get_by_token_hash(&token_hash) - .await - .map_err(|err| { - warn!( - %err, - token_hash_prefix = &token_hash[..8], - "registration token lookup failed" - ); - StatusCode::NOT_FOUND - })?; - - if !reg_token.is_valid() { - return Err(StatusCode::GONE); - } - - let template = RegisterTemplate { - nav_active: "", - is_authenticated: false, - version_info: &crate::VERSION_INFO, - token, - }; - - render_html(template).map(IntoResponse::into_response) -} - // --- Registration start (creates user + begins ceremony) --- #[tracing::instrument(skip(state, payload), fields(display_name = %payload.display_name))] @@ -541,20 +481,6 @@ pub(crate) async fn passkey_add_finish( Ok(StatusCode::OK) } -// --- CLI callback page --- - -pub(crate) async fn cli_callback_page() -> Result { - let template = CliCallbackTemplate { - nav_active: "", - is_authenticated: false, - version_info: &crate::VERSION_INFO, - token: None, - error: None, - }; - - render_html(template).map(IntoResponse::into_response) -} - // --- Helpers --- fn create_session(state: &AppState, cookies: &Cookies, user_id: crate::domain::ids::UserId) {