use axum::{
extract::State,
http::{StatusCode, Uri, header},
response::{IntoResponse, Response},
};
use rust_embed::Embed;
use super::AppState;
#[derive(Embed)]
#[folder = "web/dist/"]
struct WebAssets;
pub async fn handle_static(uri: Uri) -> Response {
let path = uri
.path()
.strip_prefix("/_app/")
.unwrap_or(uri.path())
.trim_start_matches('/');
serve_embedded_file(path)
}
pub async fn handle_spa_fallback(State(state): State<AppState>) -> Response {
let Some(content) = WebAssets::get("index.html") else {
return (
StatusCode::SERVICE_UNAVAILABLE,
"Web dashboard not available. Build it with: cd web && npm ci && npm run build",
)
.into_response();
};
let html = String::from_utf8_lossy(&content.data);
let html = if state.path_prefix.is_empty() {
html.into_owned()
} else {
let pfx = &state.path_prefix;
let json_pfx = serde_json::to_string(pfx).unwrap_or_else(|_| "\"\"".to_string());
let script = format!("<script>window.__ZEROCLAW_BASE__={json_pfx};</script>");
html.replace("/_app/", &format!("{pfx}/_app/"))
.replace("<head>", &format!("<head>{script}"))
};
(
StatusCode::OK,
[
(header::CONTENT_TYPE, "text/html; charset=utf-8".to_string()),
(header::CACHE_CONTROL, "no-cache".to_string()),
],
html,
)
.into_response()
}
fn serve_embedded_file(path: &str) -> Response {
match WebAssets::get(path) {
Some(content) => {
let mime = mime_guess::from_path(path)
.first_or_octet_stream()
.to_string();
(
StatusCode::OK,
[
(header::CONTENT_TYPE, mime),
(
header::CACHE_CONTROL,
if path.contains("assets/") {
"public, max-age=31536000, immutable".to_string()
} else {
"no-cache".to_string()
},
),
],
content.data.to_vec(),
)
.into_response()
}
None => (StatusCode::NOT_FOUND, "Not found").into_response(),
}
}