heartwood every commit a ring
2.6 KB raw
use axum::{http::header, middleware as axum_middleware, Router};
use minijinja::Environment;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use tower_http::services::ServeDir;
use tower_http::set_header::SetResponseHeaderLayer;

use crate::middleware::log_requests;
use crate::pdf::PdfRenderer;
use crate::posts::{self, Post};
use crate::routes;
use crate::templates;

#[derive(Clone)]
pub struct AppState {
    pub env: Arc<Environment<'static>>,
    pub posts: Arc<Vec<Post>>,
    pub posts_by_slug: Arc<HashMap<String, usize>>,
    pub content_dir: PathBuf,
    pub dist_dir: PathBuf,
    pub pdf_renderer: Arc<PdfRenderer>,
}

impl AppState {
    pub fn from_env() -> Self {
        let project_root: PathBuf = std::env::var("BLOG_ROOT")
            .map(PathBuf::from)
            .unwrap_or_else(|_| PathBuf::from("."));

        let templates_dir = project_root.join("templates");
        let dist_dir = project_root.join("dist");
        let content_dir = project_root.join("content");
        let manifest_path = dist_dir.join(".vite/manifest.json");

        let env = templates::build_env(&templates_dir, &manifest_path);
        let loaded = posts::load_posts(&content_dir);
        let posts_by_slug: HashMap<String, usize> = loaded
            .iter()
            .enumerate()
            .map(|(i, p)| (p.slug.clone(), i))
            .collect();
        let pdf_renderer = Arc::new(PdfRenderer::new(project_root));

        Self {
            env: Arc::new(env),
            posts: Arc::new(loaded),
            posts_by_slug: Arc::new(posts_by_slug),
            content_dir,
            dist_dir,
            pdf_renderer,
        }
    }
}

pub fn router(state: AppState) -> Router {
    let cache_static = || {
        SetResponseHeaderLayer::if_not_present(
            header::CACHE_CONTROL,
            header::HeaderValue::from_static("public, max-age=31536000"),
        )
    };
    let static_files = tower::ServiceBuilder::new()
        .layer(cache_static())
        .service(ServeDir::new(&state.dist_dir));
    let images = tower::ServiceBuilder::new()
        .layer(cache_static())
        .service(ServeDir::new(state.content_dir.join("images")));

    Router::new()
        .merge(routes::home::router())
        .merge(routes::blog::router())
        .merge(routes::post::router())
        .merge(routes::search::router())
        .merge(routes::seo::router())
        .nest_service("/static", static_files)
        .nest_service("/content/images", images)
        .fallback(routes::errors::not_found)
        .layer(axum_middleware::from_fn(log_requests))
        .with_state(state)
}