2025-03-20 09:22:31 +01:00
|
|
|
use crate::common::config::AppConfig;
|
2026-02-14 01:29:34 +01:00
|
|
|
use crate::common::di::AppState;
|
2026-02-17 19:29:42 +01:00
|
|
|
use axum::http::header::{CACHE_CONTROL, HeaderValue};
|
2026-02-14 01:29:34 +01:00
|
|
|
use axum::{Router, response::Html, routing::get};
|
2026-02-24 15:11:56 +01:00
|
|
|
use std::sync::Arc;
|
2026-02-17 19:29:42 +01:00
|
|
|
use tower_http::compression::CompressionLayer;
|
2026-02-14 01:29:34 +01:00
|
|
|
use tower_http::services::ServeDir;
|
2026-02-17 19:29:42 +01:00
|
|
|
use tower_http::set_header::SetResponseHeaderLayer;
|
2025-03-17 21:28:08 +01:00
|
|
|
|
|
|
|
|
/// Creates web routes for serving static files
|
2026-02-24 15:11:56 +01:00
|
|
|
pub fn create_web_routes() -> Router<Arc<AppState>> {
|
2025-03-20 09:22:31 +01:00
|
|
|
// Get config to access static path
|
|
|
|
|
let config = AppConfig::from_env();
|
2026-03-08 13:10:38 +01:00
|
|
|
|
2026-03-22 22:30:29 +01:00
|
|
|
// XXX do we prefer PROFILE or ENV ?
|
|
|
|
|
let is_dev = std::env::var("PROFILE").is_ok_and(|profile| profile == "dev");
|
|
|
|
|
|
2026-03-26 09:46:50 +01:00
|
|
|
let assets_dir = if is_dev {
|
|
|
|
|
// take directly the source (permits faster development)
|
|
|
|
|
"static"
|
|
|
|
|
} else {
|
|
|
|
|
// take the compiled assets
|
|
|
|
|
"static-dist"
|
|
|
|
|
};
|
2026-03-22 22:30:29 +01:00
|
|
|
|
2026-03-08 13:10:38 +01:00
|
|
|
// In release builds, serve from static-dist/ (processed assets).
|
|
|
|
|
// In debug builds, serve from the original static/ directory.
|
|
|
|
|
let static_path = if cfg!(not(debug_assertions)) {
|
|
|
|
|
let dist = config
|
|
|
|
|
.static_path
|
|
|
|
|
.parent()
|
|
|
|
|
.unwrap_or(std::path::Path::new("."))
|
2026-03-26 09:46:50 +01:00
|
|
|
.join(assets_dir);
|
2026-03-09 14:34:07 +01:00
|
|
|
if dist.exists() {
|
|
|
|
|
dist
|
|
|
|
|
} else {
|
|
|
|
|
config.static_path.clone()
|
|
|
|
|
}
|
2026-03-08 13:10:38 +01:00
|
|
|
} else {
|
|
|
|
|
config.static_path.clone()
|
|
|
|
|
};
|
2025-03-20 09:22:31 +01:00
|
|
|
|
2026-02-17 19:29:42 +01:00
|
|
|
// Static assets (JS, CSS, JSON, SVG, ICO) served via ServeDir
|
2026-02-17 22:58:56 -08:00
|
|
|
// with brotli + gzip compression and aggressive caching (7 days).
|
2026-02-17 19:29:42 +01:00
|
|
|
// HTML pages are served via explicit routes (include_str!) and
|
|
|
|
|
// do NOT pass through these layers.
|
2026-03-08 13:10:38 +01:00
|
|
|
let static_service = ServeDir::new(&static_path);
|
2026-02-17 19:29:42 +01:00
|
|
|
|
2026-03-22 22:30:29 +01:00
|
|
|
// By default assets are cached in release/production environement and no cache in dev
|
2026-03-26 09:46:50 +01:00
|
|
|
let cache_control_value = if is_dev {
|
|
|
|
|
"max-age=0, no-cache, no-store"
|
|
|
|
|
} else {
|
|
|
|
|
"public, max-age=604800, stale-while-revalidate=86400"
|
|
|
|
|
};
|
2026-03-22 22:30:29 +01:00
|
|
|
|
2025-03-17 21:28:08 +01:00
|
|
|
Router::new()
|
2026-02-13 22:44:20 +01:00
|
|
|
// Add specific routes for clean URLs (without .html)
|
2025-03-20 09:22:31 +01:00
|
|
|
.route("/login", get(serve_login_page))
|
2026-02-13 22:44:20 +01:00
|
|
|
.route("/profile", get(serve_profile_page))
|
|
|
|
|
.route("/admin", get(serve_admin_page))
|
2026-03-01 11:54:43 +01:00
|
|
|
.route("/device", get(serve_device_verify_page))
|
2026-04-12 01:51:30 +02:00
|
|
|
.route("/s/{token}", get(serve_share_page))
|
2026-02-17 19:29:42 +01:00
|
|
|
// Serve static files with compression + cache headers
|
|
|
|
|
.fallback_service(static_service)
|
2026-02-17 22:58:56 -08:00
|
|
|
.layer(CompressionLayer::new().br(true).gzip(true))
|
2026-02-17 19:29:42 +01:00
|
|
|
.layer(SetResponseHeaderLayer::if_not_present(
|
|
|
|
|
CACHE_CONTROL,
|
2026-03-26 09:46:50 +01:00
|
|
|
HeaderValue::from_static(cache_control_value),
|
2026-02-17 19:29:42 +01:00
|
|
|
))
|
2025-03-20 09:22:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Serve the login page
|
|
|
|
|
async fn serve_login_page() -> Html<&'static str> {
|
2026-03-08 13:10:38 +01:00
|
|
|
Html(include_str!(concat!(env!("OUT_DIR"), "/login.html")))
|
2026-02-13 22:44:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Serve the profile page
|
|
|
|
|
async fn serve_profile_page() -> Html<&'static str> {
|
2026-03-08 13:10:38 +01:00
|
|
|
Html(include_str!(concat!(env!("OUT_DIR"), "/profile.html")))
|
2026-02-13 22:44:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Serve the admin page
|
|
|
|
|
async fn serve_admin_page() -> Html<&'static str> {
|
2026-03-08 13:10:38 +01:00
|
|
|
Html(include_str!(concat!(env!("OUT_DIR"), "/admin.html")))
|
2026-02-13 22:44:20 +01:00
|
|
|
}
|
2026-03-01 11:54:43 +01:00
|
|
|
|
|
|
|
|
/// Serve the device verification page (RFC 8628 Device Authorization Grant)
|
|
|
|
|
async fn serve_device_verify_page() -> Html<&'static str> {
|
2026-03-08 13:10:38 +01:00
|
|
|
Html(include_str!(concat!(
|
|
|
|
|
env!("OUT_DIR"),
|
|
|
|
|
"/device-verify.html"
|
|
|
|
|
)))
|
2026-03-01 11:54:43 +01:00
|
|
|
}
|
2026-04-12 01:51:30 +02:00
|
|
|
|
|
|
|
|
/// Serve the public share page (unauthenticated)
|
|
|
|
|
async fn serve_share_page() -> Html<&'static str> {
|
|
|
|
|
Html(include_str!(concat!(env!("OUT_DIR"), "/share.html")))
|
|
|
|
|
}
|