From a9173159672a4bf7bd8a9d2dfb7fcfef2a518685 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 13 Sep 2025 12:23:27 -0500 Subject: [PATCH] fix: simplify asset serving, use fallback primarily --- src/main.rs | 12 +++++++----- src/web/assets.rs | 31 +------------------------------ src/web/routes.rs | 42 +++++++++++++++++++++++++++--------------- 3 files changed, 35 insertions(+), 50 deletions(-) diff --git a/src/main.rs b/src/main.rs index cd1fca6..fb29c1c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -50,15 +50,15 @@ struct Args { tracing: TracingFormat, /// Services to run (comma-separated). Default: all services - /// + /// /// Examples: /// --services bot,web # Run only bot and web services /// --services scraper # Run only the scraper service #[arg(long, value_delimiter = ',', conflicts_with = "disable_services")] services: Option>, - + /// Services to disable (comma-separated) - /// + /// /// Examples: /// --disable-services bot # Run web and scraper only /// --disable-services bot,web # Run only the scraper service @@ -89,7 +89,7 @@ impl ServiceName { fn all() -> Vec { vec![ServiceName::Bot, ServiceName::Web, ServiceName::Scraper] } - + /// Convert to string for service registration fn as_str(&self) -> &'static str { match self { @@ -121,7 +121,9 @@ fn determine_enabled_services(args: &Args) -> Result, anyhow::E } (Some(_), Some(_)) => { // This should be prevented by clap's conflicts_with, but just in case - Err(anyhow::anyhow!("Cannot specify both --services and --disable-services")) + Err(anyhow::anyhow!( + "Cannot specify both --services and --disable-services" + )) } } } diff --git a/src/web/assets.rs b/src/web/assets.rs index f0674c8..ca5685a 100644 --- a/src/web/assets.rs +++ b/src/web/assets.rs @@ -9,6 +9,7 @@ use axum::{ response::{Html, IntoResponse, Response}, }; use rust_embed::RustEmbed; +use tracing::debug; /// Embedded web assets from the dist directory #[derive(RustEmbed)] @@ -17,36 +18,6 @@ use rust_embed::RustEmbed; #[exclude = "*.map"] pub struct WebAssets; -/// Serve embedded static assets -pub async fn serve_asset(Path(path): Path) -> Response { - let path = path.trim_start_matches('/'); - - match WebAssets::get(path) { - Some(content) => { - let mime_type = mime_guess::from_path(path).first_or_text_plain(); - let data = content.data.to_vec(); - ([(header::CONTENT_TYPE, mime_type.as_ref())], data).into_response() - } - None => (StatusCode::NOT_FOUND, "Asset not found").into_response(), - } -} - -/// Serve the main SPA index.html for client-side routing -pub async fn serve_spa_index() -> Response { - match WebAssets::get("index.html") { - Some(content) => { - let data = content.data.to_vec(); - let html_content = String::from_utf8_lossy(&data).to_string(); - Html(html_content).into_response() - } - None => ( - StatusCode::INTERNAL_SERVER_ERROR, - "Failed to load index.html", - ) - .into_response(), - } -} - const ASSET_EXTENSIONS: &[&str] = &[ "js", "css", "png", "jpg", "jpeg", "gif", "svg", "ico", "woff", "woff2", "ttf", "eot", ]; diff --git a/src/web/routes.rs b/src/web/routes.rs index e4a427b..9559ed8 100644 --- a/src/web/routes.rs +++ b/src/web/routes.rs @@ -4,18 +4,19 @@ use axum::{ Router, extract::State, http::{StatusCode, Uri}, - response::{IntoResponse, Json, Response}, - routing::{any, get}, + response::{Html, IntoResponse, Json, Response}, + routing::get, }; +use http::header; use serde_json::{Value, json}; use std::sync::Arc; use tower_http::{ cors::{Any, CorsLayer}, trace::TraceLayer, }; -use tracing::info; +use tracing::{debug, info}; -use crate::web::assets::{is_asset_path, serve_asset, serve_spa_index}; +use crate::web::assets::WebAssets; use crate::banner::BannerApi; @@ -50,7 +51,6 @@ pub fn create_router(state: BannerState) -> Router { Router::new() .route("/", get(root)) .nest("/api", api_router) - .route("/assets/{*path}", any(serve_asset)) .fallback(handle_spa_fallback) .layer(TraceLayer::new_for_http()) } @@ -79,20 +79,32 @@ async fn root() -> Response { /// Handles SPA routing by serving index.html for non-API, non-asset requests async fn handle_spa_fallback(uri: Uri) -> Response { - let path = uri.path(); + let path = uri.path().trim_start_matches('/'); - // Don't serve index.html for API routes or asset requests - if path.starts_with("/api/") || is_asset_path(path) { - return (StatusCode::NOT_FOUND, "Not Found").into_response(); + if let Some(content) = WebAssets::get(path) { + let mime_type = mime_guess::from_path(path).first_or_text_plain(); + let data = content.data.to_vec(); + return ([(header::CONTENT_TYPE, mime_type.as_ref())], data).into_response(); + } else { + // Any assets that are not found should be treated as a 404, not falling back to the SPA index.html + if path.starts_with("assets/") { + return (StatusCode::NOT_FOUND, "Asset not found").into_response(); + } } - // In production, serve embedded index.html for SPA routing - if cfg!(not(debug_assertions)) { - return serve_spa_index().await; + // Fall back to the SPA index.html + match WebAssets::get("index.html") { + Some(content) => { + let data = content.data.to_vec(); + let html_content = String::from_utf8_lossy(&data).to_string(); + Html(html_content).into_response() + } + None => ( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed to load index.html", + ) + .into_response(), } - - // Development fallback (shouldn't reach here in production) - (StatusCode::NOT_FOUND, "Not Found").into_response() } /// Health check endpoint