use std::sync::Arc; use axum::{extract::Path, response::Html, routing::get, Extension, Router}; use serde_derive::Serialize; use tracing::{instrument, log::*}; use crate::{ post::{render_post, Post}, State, WebsiteError, }; use super::HIT_COUNTER; pub fn router() -> Router { Router::new() .route("/", get(index)) .route("/:slug/", get(view)) .fallback_service(tower_http::services::ServeDir::new("./posts")) } #[instrument(skip(state))] pub async fn view( Path(slug): Path, Extension(state): Extension>, ) -> Result, WebsiteError> { debug!("viewing post: {slug}"); let post = state.posts.get(&slug).ok_or(WebsiteError::NotFound)?; if !post.is_published() { warn!("attempted to view post before it has been published!"); return Err(WebsiteError::NotFound); } let res = render_post(&state.tera, post).await?; HIT_COUNTER .with_label_values(&[&format!("/posts/{}/", slug)]) .inc(); Ok(Html(res)) } #[derive(Serialize)] struct IndexContext<'a> { title: &'a str, posts: Vec<&'a Post>, } #[instrument(skip(state))] pub async fn index(Extension(state): Extension>) -> Result, WebsiteError> { let mut posts = state .posts .values() .filter(|p| p.is_published()) .collect::>(); posts.sort_by_key(|p| &p.date); posts.reverse(); let ctx = IndexContext { title: "Posts", posts, }; let res = match state .tera .render("posts_index.html", &tera::Context::from_serialize(ctx)?) { Ok(r) => r, Err(e) => { error!("failed to render posts index: {}", e); return Err(e.into()); } }; HIT_COUNTER.with_label_values(&["/posts/"]).inc(); Ok(Html(res)) }