Implement frontend routing and API fallback handling; add dependencies for include_dir and mime_guess
This commit is contained in:
57
Cargo.lock
generated
57
Cargo.lock
generated
@@ -1135,6 +1135,25 @@ version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http",
|
||||
"indexmap 2.12.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
@@ -1277,6 +1296,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
@@ -1488,6 +1508,25 @@ dependencies = [
|
||||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "include_dir"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd"
|
||||
dependencies = [
|
||||
"include_dir_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "include_dir_macros"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
@@ -1690,6 +1729,16 @@ version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "mime_guess"
|
||||
version = "2.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
|
||||
dependencies = [
|
||||
"mime",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.1.0"
|
||||
@@ -3696,6 +3745,12 @@ version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.18"
|
||||
@@ -4275,7 +4330,9 @@ dependencies = [
|
||||
"chrono",
|
||||
"config",
|
||||
"database",
|
||||
"include_dir",
|
||||
"migration",
|
||||
"mime_guess",
|
||||
"sea-orm",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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()));
|
||||
|
||||
11
apps/api/src/routes/api.rs
Normal file
11
apps/api/src/routes/api.rs
Normal 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()
|
||||
}
|
||||
71
apps/api/src/routes/view.rs
Normal file
71
apps/api/src/routes/view.rs
Normal 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(),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user