use crate::server::app_state::AppState;
use axum::Router;
use axum::extract::Path;
use axum::http::{StatusCode, header};
use axum::response::{IntoResponse, Response};
use axum::routing::get;
use axum_extra::headers::{HeaderMap, HeaderValue};
use include_dir::{Dir, include_dir};
use std::sync::Arc;
#[cfg(debug_assertions)]
static UI_DIST: Dir = include_dir!("$CARGO_MANIFEST_DIR/assets/development");
#[cfg(not(debug_assertions))]
static UI_DIST: Dir = include_dir!("$CARGO_MANIFEST_DIR/assets/production");
pub fn router() -> Router<Arc<AppState>> {
Router::new()
.route("/", get(index))
.route("/{*path}", get(serve))
}
async fn index() -> Response {
serve_index()
}
async fn serve(Path(raw): Path<String>) -> Response {
let path = raw.trim_start_matches('/');
if path.contains("..") {
return StatusCode::BAD_REQUEST.into_response();
}
if let Some(file) = UI_DIST.get_file(path) {
return respond_file(path, file.contents());
}
serve_index()
}
fn serve_index() -> Response {
match UI_DIST.get_file("index.html") {
Some(index) => respond_index(index.contents()),
None => (StatusCode::INTERNAL_SERVER_ERROR, "index.html missing").into_response(),
}
}
fn respond_file(path: &str, bytes: &'static [u8]) -> Response {
let mime = mime_guess::from_path(path).first_or_octet_stream();
let cache_control = if std::path::Path::new(path)
.file_name()
.and_then(|s| s.to_str())
.is_some_and(|name| name.eq_ignore_ascii_case("index.html"))
{
"no-cache, no-store, must-revalidate"
} else {
"public, max-age=31536000, immutable"
};
let mut headers = HeaderMap::with_capacity(2);
headers.insert(
header::CONTENT_TYPE,
HeaderValue::from_str(mime.as_ref()).unwrap_or(HeaderValue::from_static("application/octet-stream")),
);
headers.insert(header::CACHE_CONTROL, HeaderValue::from_static(cache_control));
(headers, bytes).into_response()
}
fn respond_index(bytes: &'static [u8]) -> Response {
let mut headers = HeaderMap::with_capacity(2);
headers.insert(
header::CONTENT_TYPE,
HeaderValue::from_static("text/html; charset=utf-8"),
);
headers.insert(
header::CACHE_CONTROL,
HeaderValue::from_static("no-cache, no-store, must-revalidate"),
);
(headers, bytes).into_response()
}