//! Request handlers. Each page is an askama template that embeds a [`Layout`]. pub mod archive; pub mod blame; pub mod blob; pub mod commit; pub mod error; pub mod feed; pub mod find; pub mod index; pub mod log; pub mod raw; pub mod refs; pub mod repo; pub mod summary; pub mod tree; use std::sync::Arc; use askama::Template; use axum::http::StatusCode; use axum::response::{Html, IntoResponse, Response}; use crate::config::Config; use crate::server::assets; use crate::state::AppState; /// Data shared by every page: site chrome, asset URLs and customization. pub struct Layout { /// `server.base_path`, always ending in `/`. pub base: String, /// `{base}-/static/{hash}/` pub static_base: String, pub site_title: String, /// Full `` text. pub page_title: String, /// Server default theme: `auto`, `dark` or `light`. pub theme: &'static str, /// Versioned URL of `/-/site.css`, when there is anything in it. pub site_css: Option<String>, pub header: Option<Arc<str>>, pub footer: Option<Arc<str>>, /// Favicon URL and media type: `site.favicon` or the built-in icon. pub favicon: (String, &'static str), /// Versioned URL of `site.logo`. pub logo: Option<String>, pub version: &'static str, /// Atom feed advertised in `<head>`. pub feed: Option<String>, } impl Layout { /// `title` is the page-specific part; the site title is appended. pub fn new(state: &AppState, config: &Config, title: Option<&str>) -> Self { let base = config.server.base_path.clone(); let site_title = config.site.title.clone(); let static_base = format!("{base}-/static/{}/", state.assets.hash()); Layout { favicon: match &config.custom.favicon { Some(icon) => ( format!("{base}-/favicon?v={}", icon.version), icon.content_type, ), None => (format!("{static_base}icons/favicon.svg"), "image/svg+xml"), }, logo: config .custom .logo .as_ref() .map(|logo| format!("{base}-/logo?v={}", logo.version)), static_base, site_css: assets::site_css_version(config).map(|v| format!("{base}-/site.css?v={v}")), page_title: match title { Some(t) => format!("{t} ยท {site_title}"), None => site_title.clone(), }, site_title, base, theme: config.ui.theme.as_str(), header: config.custom.header.clone(), footer: config.custom.footer.clone(), version: env!("CARGO_PKG_VERSION"), feed: None, } } } /// Render a template into an HTML response with the given status. pub fn render(status: StatusCode, page: &impl Template) -> Response { match page.render() { Ok(html) => (status, Html(html)).into_response(), Err(e) => { tracing::error!("template error: {e}"); (StatusCode::INTERNAL_SERVER_ERROR, "internal error\n").into_response() } } }