phonograph/interim-server/src/routes/mod.rs

133 lines
5 KiB
Rust
Raw Normal View History

//! 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.
2025-08-13 18:52:37 -07:00
use std::net::SocketAddr;
2025-08-13 18:52:37 -07:00
use axum::{
Router,
extract::{ConnectInfo, State, WebSocketUpgrade, ws::WebSocket},
http::{HeaderValue, header::CACHE_CONTROL},
response::{Redirect, Response},
routing::{any, 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<Settings>| async move {
Redirect::to(&format!("{root_path}/workspaces/list/"))
},
),
)
.nest("/workspaces", workspaces_multi::new_router())
2025-09-25 14:51:09 -07:00
.nest("/w", workspaces_single::new_router())
.nest("/auth", auth::new_router())
.route("/__dev-healthz", any(dev_healthz_handler))
.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) })
}
}
/// Development endpoint helping to implement home-grown "hot" reloads.
async fn dev_healthz_handler(
ws: WebSocketUpgrade,
ConnectInfo(addr): ConnectInfo<SocketAddr>,
) -> Response {
tracing::info!("{addr} connected");
ws.on_upgrade(move |socket| handle_dev_healthz_socket(socket, addr))
}
async fn handle_dev_healthz_socket(mut socket: WebSocket, _: SocketAddr) {
// Keep socket open indefinitely until the entire server exits
while let Some(Ok(_)) = socket.recv().await {}
2025-08-13 18:52:37 -07:00
}