Single-binary self-hosted Markdown blog on Rust axum: no database, live search, Typst PDF export, and strong SEO.
axumblogdockermarkdownminijinjarustself-hostedtypstvite
1use axum::http::{header, HeaderMap, Uri};
2use axum::response::Html;
3use chrono::{Datelike, Local};
4use minijinja::context;
5use serde::Serialize;
6
7use crate::app::AppState;
8use crate::error::AppError;
9use crate::posts::{self, collect_tags};
10use crate::templates::RequestCtx;
11
12#[derive(Debug, Clone, Serialize)]
13pub struct Crumb {
14 pub title: String,
15 pub url: String,
16}
17
18#[derive(Debug, Serialize)]
19struct NowCtx {
20 year: i32,
21}
22
23pub fn build_request(uri: &Uri, headers: &HeaderMap) -> RequestCtx {
24 let host = headers
25 .get(header::HOST)
26 .and_then(|v| v.to_str().ok())
27 .unwrap_or("localhost");
28 let scheme = headers
29 .get("x-forwarded-proto")
30 .and_then(|v| v.to_str().ok())
31 .unwrap_or("http");
32 let url_root = format!("{scheme}://{host}/");
33 let path_and_query = uri.path_and_query().map(|p| p.as_str()).unwrap_or("/");
34 let url = format!("{scheme}://{host}{path_and_query}");
35 let base_url = format!("{scheme}://{host}{}", uri.path());
36 RequestCtx {
37 url,
38 url_root,
39 base_url,
40 }
41}
42
43pub fn render_html(
44 state: &AppState,
45 template: &str,
46 extra: minijinja::Value,
47 request: &RequestCtx,
48) -> Result<Html<String>, AppError> {
49 let published = posts::published(&state.posts);
50 let nav_items = collect_tags(&published);
51 let now = NowCtx {
52 year: Local::now().year(),
53 };
54 let tmpl = state.env.get_template(template)?;
55 let ctx = context! {
56 nav_items => nav_items,
57 now => now,
58 debug => cfg!(debug_assertions),
59 request => request,
60 ..extra
61 };
62 let body = tmpl.render(ctx)?;
63 Ok(Html(body))
64}