Implement frontend routing and API fallback handling; add dependencies for include_dir and mime_guess
Some checks failed
Test / verify-generated-code (pull_request) Successful in 7m59s
Test / test (pull_request) Failing after 1m12s
Test / lint (pull_request) Failing after 1m11s

This commit is contained in:
GW_MC
2025-12-02 19:25:46 +08:00
parent 27173c01da
commit edbcdaeff4
5 changed files with 149 additions and 1 deletions

View File

@@ -7,7 +7,7 @@ edition = "2024"
database = { path = "../../public/database" }
migration = { path = "../../public/migration" }
axum = { version = "0.8.7", features = ["form", "http1", "json", "matched-path", "original-uri", "query", "tokio", "tower-log", "tracing", "macros"]}
axum = { version = "0.8.7", features = ["form", "http1", "http2", "json", "matched-path", "original-uri", "query", "tokio", "tower-log", "tracing", "macros"]}
async-trait = { version = "0.1.89" }
chrono = { version = "0.4.42", features = ["clock", "std", "oldtime", "wasmbind", "serde"] }
config = { version = "0.15.19", features = ["toml", "json", "yaml", "ini", "ron", "json5", "convert-case", "async"] }
@@ -18,3 +18,5 @@ tracing-subscriber = { version = "0.3.20", features = ["smallvec", "fmt", "ansi"
serde_json = { version = "1.0.145", features = ["std"] }
serde = { version = "1.0.228", features = ["std", "derive"] }
sea-orm = { workspace = true }
include_dir = { version = "0.7.4" }
mime_guess = { version = "2.0.5" }

View File

@@ -1,3 +1,6 @@
mod api;
mod view;
use std::sync::Arc;
use axum::{Extension, Router};
@@ -25,6 +28,10 @@ pub struct AppService {
pub fn get_root_router(state: impl Into<Arc<AppState>>) -> Router {
let mut router = Router::new();
router = router
.nest("/api", api::get_api_router())
.merge(view::get_view_router());
router = middlewares::apply_root_middleware(router);
router = router.layer(Extension(state.into()));

View File

@@ -0,0 +1,11 @@
use axum::{Router, response::IntoResponse, routing::any};
pub fn get_api_router() -> Router {
Router::new()
// explicit fallback for unmatched API routes
.route("/{*wildcard}", any(api_fallback_handler))
}
async fn api_fallback_handler() -> impl IntoResponse {
(axum::http::StatusCode::NOT_FOUND, "API route not found").into_response()
}

View File

@@ -0,0 +1,71 @@
use axum::{
Router,
body::Bytes,
extract::Path,
http::{StatusCode, header},
response::IntoResponse,
routing::{MethodRouter, get},
};
use include_dir::{Dir, include_dir};
use mime_guess::from_path;
static DIST_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/../frontend/build/client");
const INDEX_HTML_PATH: &str = "index.html";
const INDEX_FILE_NOT_FOUND_HTML: &str = r#"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>404 Not Found</title>
</head>
<body>
<h1>404 Not Found</h1>
<p>The requested resource was not found on this server. Possibly the frontend build is missing or corrupted.</p>
</body>
</html>
"#;
pub fn get_view_router() -> Router {
Router::new()
// Serve the root index.html
.route("/", get(root_view_handler))
.route(
"/{*wildcard}",
MethodRouter::new()
.get(|Path(path): Path<String>| async move { view_handler(Some(path)).await }),
)
}
async fn root_view_handler() -> impl IntoResponse {
view_handler(None).await
}
async fn view_handler(path: Option<String>) -> impl IntoResponse {
// If path is empty, serve index.html
let incoming_path = if let Some(p) = path {
p.trim_start_matches('/').to_string()
} else {
INDEX_HTML_PATH.to_string()
};
let path = match DIST_DIR.get_file(&incoming_path) {
Some(_) => incoming_path,
None => INDEX_HTML_PATH.to_string(),
};
match DIST_DIR.get_file(&path) {
Some(file) => {
let mime = from_path(&path).first_or_octet_stream();
let body: Bytes = Bytes::copy_from_slice(file.contents());
([(header::CONTENT_TYPE, mime.as_ref())], body).into_response()
}
// This should never happen, but just in case...
None => (
StatusCode::NOT_FOUND,
[(header::CONTENT_TYPE, "text/plain")],
Bytes::from(INDEX_FILE_NOT_FOUND_HTML),
)
.into_response(),
}
}