//! Hierarchical HTTP routing. //! //! Top level module establishes the overall //! [`axum::Router`], and submodules organize nested subrouters into manageable //! chunks. Pragmatically, the submodule tree should be kept fairly flat, lest //! file paths grow exceedingly long. Deeply nested routers may still be //! implemented, by use of the `super` keyword. use axum::{ Router, extract::State, http::{HeaderValue, header::CACHE_CONTROL}, response::Redirect, routing::get, }; use tower::ServiceBuilder; use tower_http::{ services::{ServeDir, ServeFile}, set_header::SetResponseHeaderLayer, }; use crate::auth; use crate::{app::App, settings::Settings}; mod relations_single; mod workspaces_multi; mod workspaces_single; /// Create the root [`Router`] for the application, including nesting according /// to the `root_path` [`crate::settings::Settings`] value, setting cache /// headers, setting up static file handling, and defining fallback handlers. pub(crate) fn new_router(app: App) -> Router<()> { let root_path = app.settings.root_path.clone(); let router = Router::new() .route( "/", get( |State(Settings { root_path, .. }): State| async move { Redirect::to(&format!("{root_path}/workspaces/list/")) }, ), ) .nest("/workspaces", workspaces_multi::new_router()) .nest("/w", workspaces_single::new_router()) .nest("/auth", auth::new_router()) .route("/__dev-healthz", get(|| async move { "ok" })) .layer(SetResponseHeaderLayer::if_not_present( CACHE_CONTROL, HeaderValue::from_static("no-cache"), )) .nest_service( "/js_dist", ServiceBuilder::new() .layer(SetResponseHeaderLayer::if_not_present( CACHE_CONTROL, // FIXME: restore production value // HeaderValue::from_static("max-age=21600, stale-while-revalidate=86400"), HeaderValue::from_static("no-cache"), )) .service( ServeDir::new("js_dist").not_found_service( ServiceBuilder::new() .layer(SetResponseHeaderLayer::if_not_present( CACHE_CONTROL, HeaderValue::from_static("no-cache"), )) .service(ServeFile::new("static/_404.html")), ), ), ) .nest_service( "/css_dist", ServiceBuilder::new() .layer(SetResponseHeaderLayer::if_not_present( CACHE_CONTROL, // FIXME: restore production value // HeaderValue::from_static("max-age=21600, stale-while-revalidate=86400"), HeaderValue::from_static("no-cache"), )) .service( ServeDir::new("css_dist").not_found_service( ServiceBuilder::new() .layer(SetResponseHeaderLayer::if_not_present( CACHE_CONTROL, HeaderValue::from_static("no-cache"), )) .service(ServeFile::new("static/_404.html")), ), ), ) .fallback_service( ServiceBuilder::new() .layer(SetResponseHeaderLayer::if_not_present( CACHE_CONTROL, HeaderValue::from_static("max-age=21600, stale-while-revalidate=86400"), )) .service( ServeDir::new("static").not_found_service( ServiceBuilder::new() .layer(SetResponseHeaderLayer::if_not_present( CACHE_CONTROL, HeaderValue::from_static("no-cache"), )) .service(ServeFile::new("static/_404.html")), ), ), ) .with_state(app); if root_path.is_empty() { router } else { Router::new() .nest(&root_path, router) .fallback(|| async move { Redirect::to(&root_path) }) } }