From e2d364722b00f6b47f72c32c16f9afe8e463b989 Mon Sep 17 00:00:00 2001 From: GW_MC <72297530+GWMCwing@users.noreply.github.com> Date: Sat, 18 Apr 2026 07:30:37 +0000 Subject: [PATCH] feat: Add frontend routing and asset handling with Axum --- apps/nxmesh-master/src/main.rs | 1 + apps/nxmesh-master/src/routes/frontend/mod.rs | 71 +++++++++++++++++++ apps/nxmesh-master/src/routes/mod.rs | 9 +++ 3 files changed, 81 insertions(+) create mode 100644 apps/nxmesh-master/src/routes/frontend/mod.rs create mode 100644 apps/nxmesh-master/src/routes/mod.rs diff --git a/apps/nxmesh-master/src/main.rs b/apps/nxmesh-master/src/main.rs index 5aad5d4..d4a7896 100644 --- a/apps/nxmesh-master/src/main.rs +++ b/apps/nxmesh-master/src/main.rs @@ -14,6 +14,7 @@ mod cli; mod config; mod connector; mod db; +mod routes; mod service; #[tokio::main] diff --git a/apps/nxmesh-master/src/routes/frontend/mod.rs b/apps/nxmesh-master/src/routes/frontend/mod.rs new file mode 100644 index 0000000..84baf96 --- /dev/null +++ b/apps/nxmesh-master/src/routes/frontend/mod.rs @@ -0,0 +1,71 @@ +use axum::{Router, response::IntoResponse}; +use tracing::error; + +// In development, build the frontend from the source directory, the soft link will handle the path resolution +// In deployment, pre-build the frontend and replace the frontend-dist folder with the built assets, the rust-embed will handle the embedding and path resolution +#[derive(rust_embed::Embed)] +#[folder = "./frontend-dist/"] +struct FrontendAssets; + +const INDEX_HTML: &str = "index.html"; + +pub async fn get_router() -> Router { + Router::new() + .route( + "/", + axum::routing::get(get_fallback_handler) + .head(get_fallback_handler) + .options(get_fallback_handler), + ) + .route( + "/{*path}", + axum::routing::get(get_file_handler) + .head(get_file_handler) + .options(get_file_handler), + ) + // + .fallback(get_fallback_handler().await) +} + +#[cfg_attr(debug_assertions, axum::debug_handler)] +pub async fn get_fallback_handler() -> Result>, axum::http::StatusCode> +{ + let index_html = get_index_html(); + match index_html { + Some(html) => Ok(axum::response::Html(html)), + None => Err(axum::http::StatusCode::NOT_FOUND), + } +} + +fn get_index_html() -> Option> { + FrontendAssets::get(INDEX_HTML).map(|asset| asset.data.as_ref().to_owned()) +} + +#[cfg_attr(debug_assertions, axum::debug_handler)] +async fn get_file_handler( + axum::extract::Path(path): axum::extract::Path, +) -> Result { + let file_path = if path.is_empty() { + INDEX_HTML.to_string() + } else { + path + }; + + match FrontendAssets::get(&file_path) { + Some(asset) => { + let content_type = mime_guess::from_path(&file_path).first_or_octet_stream(); + let response = axum::response::Response::builder() + .header(axum::http::header::CONTENT_TYPE, content_type.as_ref()) + .body(asset.data.into_owned().into()) + .map_err(|e| { + error!("Failed to build response for {}: {}", file_path, e); + axum::http::StatusCode::INTERNAL_SERVER_ERROR + })?; + Ok(response) + } + // return index.html for any file not found to support client-side routing in the frontend + None => get_fallback_handler() + .await + .map(|html| html.into_response()), + } +} diff --git a/apps/nxmesh-master/src/routes/mod.rs b/apps/nxmesh-master/src/routes/mod.rs new file mode 100644 index 0000000..80164a8 --- /dev/null +++ b/apps/nxmesh-master/src/routes/mod.rs @@ -0,0 +1,9 @@ +use axum::Router; + +mod frontend; + +pub async fn get_root_router() -> Router { + Router::new() + .merge(frontend::get_router().await) + .fallback(frontend::get_fallback_handler().await) +}