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.
This commit is contained in:
parent
0e620dcb2a
commit
9cc0b30cca
12 changed files with 29 additions and 101 deletions
|
|
@ -22,13 +22,13 @@ use crate::presentation::web::views::{BagView, ListNavigator, Paginated};
|
||||||
const BAG_PAGE_PATH: &str = "/data?type=bags";
|
const BAG_PAGE_PATH: &str = "/data?type=bags";
|
||||||
const BAG_FRAGMENT_PATH: &str = "/data?type=bags#bag-list";
|
const BAG_FRAGMENT_PATH: &str = "/data?type=bags#bag-list";
|
||||||
|
|
||||||
pub(super) struct BagPageData {
|
pub(in crate::application::routes) struct BagPageData {
|
||||||
pub(super) bags: Paginated<BagView>,
|
pub(in crate::application::routes) bags: Paginated<BagView>,
|
||||||
pub(super) navigator: ListNavigator<BagSortKey>,
|
pub(in crate::application::routes) navigator: ListNavigator<BagSortKey>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(state))]
|
#[tracing::instrument(skip(state))]
|
||||||
pub(super) async fn load_bag_page(
|
pub(in crate::application::routes) async fn load_bag_page(
|
||||||
state: &AppState,
|
state: &AppState,
|
||||||
request: ListRequest<BagSortKey>,
|
request: ListRequest<BagSortKey>,
|
||||||
search: Option<&str>,
|
search: Option<&str>,
|
||||||
|
|
@ -26,20 +26,22 @@ use crate::presentation::web::views::{
|
||||||
const BREW_PAGE_PATH: &str = "/data?type=brews";
|
const BREW_PAGE_PATH: &str = "/data?type=brews";
|
||||||
const BREW_FRAGMENT_PATH: &str = "/data?type=brews#brew-list";
|
const BREW_FRAGMENT_PATH: &str = "/data?type=brews#brew-list";
|
||||||
|
|
||||||
pub(super) struct BrewPageData {
|
pub(in crate::application::routes) struct BrewPageData {
|
||||||
pub(super) brews: Paginated<BrewView>,
|
pub(in crate::application::routes) brews: Paginated<BrewView>,
|
||||||
pub(super) navigator: ListNavigator<BrewSortKey>,
|
pub(in crate::application::routes) navigator: ListNavigator<BrewSortKey>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) struct BrewFormData {
|
pub(in crate::application::routes) struct BrewFormData {
|
||||||
pub(super) bag_options: Vec<BagOptionView>,
|
pub(in crate::application::routes) bag_options: Vec<BagOptionView>,
|
||||||
pub(super) grinder_options: Vec<GearOptionView>,
|
pub(in crate::application::routes) grinder_options: Vec<GearOptionView>,
|
||||||
pub(super) brewer_options: Vec<GearOptionView>,
|
pub(in crate::application::routes) brewer_options: Vec<GearOptionView>,
|
||||||
pub(super) filter_paper_options: Vec<GearOptionView>,
|
pub(in crate::application::routes) filter_paper_options: Vec<GearOptionView>,
|
||||||
pub(super) defaults: BrewDefaultsView,
|
pub(in crate::application::routes) defaults: BrewDefaultsView,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn load_brew_form_data(state: &AppState) -> Result<BrewFormData, AppError> {
|
pub(in crate::application::routes) async fn load_brew_form_data(
|
||||||
|
state: &AppState,
|
||||||
|
) -> Result<BrewFormData, AppError> {
|
||||||
let open_bags_request = ListRequest::show_all(
|
let open_bags_request = ListRequest::show_all(
|
||||||
crate::domain::bags::BagSortKey::RoastDate,
|
crate::domain::bags::BagSortKey::RoastDate,
|
||||||
SortDirection::Desc,
|
SortDirection::Desc,
|
||||||
|
|
@ -89,7 +91,7 @@ pub(super) async fn load_brew_form_data(state: &AppState) -> Result<BrewFormData
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn load_gear_options(
|
pub(in crate::application::routes) async fn load_gear_options(
|
||||||
state: &AppState,
|
state: &AppState,
|
||||||
category: GearCategory,
|
category: GearCategory,
|
||||||
request: &ListRequest<GearSortKey>,
|
request: &ListRequest<GearSortKey>,
|
||||||
|
|
@ -103,7 +105,7 @@ pub(super) async fn load_gear_options(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(state))]
|
#[tracing::instrument(skip(state))]
|
||||||
pub(super) async fn load_brew_page(
|
pub(in crate::application::routes) async fn load_brew_page(
|
||||||
state: &AppState,
|
state: &AppState,
|
||||||
request: ListRequest<BrewSortKey>,
|
request: ListRequest<BrewSortKey>,
|
||||||
search: Option<&str>,
|
search: Option<&str>,
|
||||||
|
|
@ -23,7 +23,7 @@ const CAFE_PAGE_PATH: &str = "/data?type=cafes";
|
||||||
const CAFE_FRAGMENT_PATH: &str = "/data?type=cafes#cafe-list";
|
const CAFE_FRAGMENT_PATH: &str = "/data?type=cafes#cafe-list";
|
||||||
|
|
||||||
#[tracing::instrument(skip(state))]
|
#[tracing::instrument(skip(state))]
|
||||||
pub(super) async fn load_cafe_page(
|
pub(in crate::application::routes) async fn load_cafe_page(
|
||||||
state: &AppState,
|
state: &AppState,
|
||||||
request: ListRequest<CafeSortKey>,
|
request: ListRequest<CafeSortKey>,
|
||||||
search: Option<&str>,
|
search: Option<&str>,
|
||||||
|
|
@ -23,7 +23,7 @@ const CUP_PAGE_PATH: &str = "/data?type=cups";
|
||||||
const CUP_FRAGMENT_PATH: &str = "/data?type=cups#cup-list";
|
const CUP_FRAGMENT_PATH: &str = "/data?type=cups#cup-list";
|
||||||
|
|
||||||
#[tracing::instrument(skip(state))]
|
#[tracing::instrument(skip(state))]
|
||||||
pub(super) async fn load_cup_page(
|
pub(in crate::application::routes) async fn load_cup_page(
|
||||||
state: &AppState,
|
state: &AppState,
|
||||||
request: ListRequest<CupSortKey>,
|
request: ListRequest<CupSortKey>,
|
||||||
search: Option<&str>,
|
search: Option<&str>,
|
||||||
|
|
@ -25,7 +25,7 @@ const GEAR_PAGE_PATH: &str = "/data?type=gear";
|
||||||
const GEAR_FRAGMENT_PATH: &str = "/data?type=gear#gear-list";
|
const GEAR_FRAGMENT_PATH: &str = "/data?type=gear#gear-list";
|
||||||
|
|
||||||
#[tracing::instrument(skip(state))]
|
#[tracing::instrument(skip(state))]
|
||||||
pub(super) async fn load_gear_page(
|
pub(in crate::application::routes) async fn load_gear_page(
|
||||||
state: &AppState,
|
state: &AppState,
|
||||||
request: ListRequest<GearSortKey>,
|
request: ListRequest<GearSortKey>,
|
||||||
search: Option<&str>,
|
search: Option<&str>,
|
||||||
|
|
@ -22,7 +22,7 @@ const ROASTER_PAGE_PATH: &str = "/data?type=roasters";
|
||||||
const ROASTER_FRAGMENT_PATH: &str = "/data?type=roasters#roaster-list";
|
const ROASTER_FRAGMENT_PATH: &str = "/data?type=roasters#roaster-list";
|
||||||
|
|
||||||
#[tracing::instrument(skip(state))]
|
#[tracing::instrument(skip(state))]
|
||||||
pub(super) async fn load_roaster_page(
|
pub(in crate::application::routes) async fn load_roaster_page(
|
||||||
state: &AppState,
|
state: &AppState,
|
||||||
request: ListRequest<RoasterSortKey>,
|
request: ListRequest<RoasterSortKey>,
|
||||||
search: Option<&str>,
|
search: Option<&str>,
|
||||||
|
|
@ -139,7 +139,7 @@ pub(crate) async fn extract_roaster(
|
||||||
.await
|
.await
|
||||||
.map_err(ApiError::from)?;
|
.map_err(ApiError::from)?;
|
||||||
|
|
||||||
super::support::record_ai_usage(
|
crate::application::routes::support::record_ai_usage(
|
||||||
state.ai_usage_repo.clone(),
|
state.ai_usage_repo.clone(),
|
||||||
auth_user.0.id,
|
auth_user.0.id,
|
||||||
&state.openrouter_model,
|
&state.openrouter_model,
|
||||||
|
|
@ -25,7 +25,7 @@ const ROAST_PAGE_PATH: &str = "/data?type=roasts";
|
||||||
const ROAST_FRAGMENT_PATH: &str = "/data?type=roasts#roast-list";
|
const ROAST_FRAGMENT_PATH: &str = "/data?type=roasts#roast-list";
|
||||||
|
|
||||||
#[tracing::instrument(skip(state))]
|
#[tracing::instrument(skip(state))]
|
||||||
pub(super) async fn load_roast_page(
|
pub(in crate::application::routes) async fn load_roast_page(
|
||||||
state: &AppState,
|
state: &AppState,
|
||||||
request: ListRequest<RoastSortKey>,
|
request: ListRequest<RoastSortKey>,
|
||||||
search: Option<&str>,
|
search: Option<&str>,
|
||||||
|
|
@ -279,7 +279,7 @@ pub(crate) async fn extract_roast_info(
|
||||||
.await
|
.await
|
||||||
.map_err(ApiError::from)?;
|
.map_err(ApiError::from)?;
|
||||||
|
|
||||||
super::support::record_ai_usage(
|
crate::application::routes::support::record_ai_usage(
|
||||||
state.ai_usage_repo.clone(),
|
state.ai_usage_repo.clone(),
|
||||||
auth_user.0.id,
|
auth_user.0.id,
|
||||||
&state.openrouter_model,
|
&state.openrouter_model,
|
||||||
|
|
@ -5,9 +5,9 @@ use axum::response::{IntoResponse, Response};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
|
||||||
|
use super::roasts::TastingNotesInput;
|
||||||
use crate::application::auth::AuthenticatedUser;
|
use crate::application::auth::AuthenticatedUser;
|
||||||
use crate::application::errors::{ApiError, AppError};
|
use crate::application::errors::{ApiError, AppError};
|
||||||
use crate::application::routes::roasts::TastingNotesInput;
|
|
||||||
use crate::application::routes::support::{FlexiblePayload, is_datastar_request};
|
use crate::application::routes::support::{FlexiblePayload, is_datastar_request};
|
||||||
use crate::application::server::AppState;
|
use crate::application::server::AppState;
|
||||||
use crate::domain::bags::NewBag;
|
use crate::domain::bags::NewBag;
|
||||||
|
|
@ -34,7 +34,7 @@ pub(crate) async fn extract_bag_scan(
|
||||||
.await
|
.await
|
||||||
.map_err(ApiError::from)?;
|
.map_err(ApiError::from)?;
|
||||||
|
|
||||||
super::support::record_ai_usage(
|
crate::application::routes::support::record_ai_usage(
|
||||||
state.ai_usage_repo.clone(),
|
state.ai_usage_repo.clone(),
|
||||||
auth_user.0.id,
|
auth_user.0.id,
|
||||||
&state.openrouter_model,
|
&state.openrouter_model,
|
||||||
|
|
@ -207,7 +207,7 @@ pub(crate) async fn submit_scan(
|
||||||
|
|
||||||
if has_raw_input {
|
if has_raw_input {
|
||||||
let usage = extract_into_submission(&state, &mut submission).await?;
|
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(),
|
state.ai_usage_repo.clone(),
|
||||||
auth_user.0.id,
|
auth_user.0.id,
|
||||||
&state.openrouter_model,
|
&state.openrouter_model,
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
use askama::Template;
|
|
||||||
use axum::Json;
|
use axum::Json;
|
||||||
use axum::extract::{Path, Query, State};
|
use axum::extract::{Query, State};
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
use axum::response::{IntoResponse, Response};
|
|
||||||
use chrono::{Duration, Utc};
|
use chrono::{Duration, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tower_cookies::{Cookie, Cookies};
|
use tower_cookies::{Cookie, Cookies};
|
||||||
|
|
@ -11,7 +9,6 @@ use uuid::Uuid;
|
||||||
use webauthn_rs::prelude::*;
|
use webauthn_rs::prelude::*;
|
||||||
|
|
||||||
use crate::application::auth::AuthenticatedUser;
|
use crate::application::auth::AuthenticatedUser;
|
||||||
use crate::application::routes::render_html;
|
|
||||||
use crate::application::server::AppState;
|
use crate::application::server::AppState;
|
||||||
use crate::domain::passkey_credentials::NewPasskeyCredential;
|
use crate::domain::passkey_credentials::NewPasskeyCredential;
|
||||||
use crate::domain::sessions::NewSession;
|
use crate::domain::sessions::NewSession;
|
||||||
|
|
@ -22,27 +19,6 @@ use crate::infrastructure::webauthn::CliCallbackInfo;
|
||||||
|
|
||||||
const SESSION_COOKIE_NAME: &str = "brewlog_session";
|
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<String>,
|
|
||||||
error: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Request/Response types ---
|
// --- Request/Response types ---
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
|
@ -89,42 +65,6 @@ pub struct AuthFinishResponse {
|
||||||
pub redirect: Option<String>,
|
pub redirect: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Registration page (bootstrap flow) ---
|
|
||||||
|
|
||||||
#[tracing::instrument(skip(state))]
|
|
||||||
pub(crate) async fn register_page(
|
|
||||||
State(state): State<AppState>,
|
|
||||||
Path(token): Path<String>,
|
|
||||||
) -> Result<Response, StatusCode> {
|
|
||||||
// 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) ---
|
// --- Registration start (creates user + begins ceremony) ---
|
||||||
|
|
||||||
#[tracing::instrument(skip(state, payload), fields(display_name = %payload.display_name))]
|
#[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)
|
Ok(StatusCode::OK)
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- CLI callback page ---
|
|
||||||
|
|
||||||
pub(crate) async fn cli_callback_page() -> Result<Response, StatusCode> {
|
|
||||||
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 ---
|
// --- Helpers ---
|
||||||
|
|
||||||
fn create_session(state: &AppState, cookies: &Cookies, user_id: crate::domain::ids::UserId) {
|
fn create_session(state: &AppState, cookies: &Cookies, user_id: crate::domain::ids::UserId) {
|
||||||
Loading…
Reference in a new issue