use std::sync::Arc; use axum::{ extract::{Path, State}, response::{Html, Redirect, IntoResponse}, routing::get, Router, }; use serde_derive::Serialize; use tracing::{instrument, log::*}; use crate::{ post::{render_post, Post}, AppState, WebsiteError, }; pub fn router() -> Router> { Router::new() .route("/posts", get(|| async { Redirect::permanent("/") })) .route("/posts/", get(index)) .route("/posts/:slug", get(redirect)) .route("/posts/:slug/", get(view)) .route("/posts/:slug/index.md", get(super::not_found)) } pub fn alias_router<'a>(posts: impl IntoIterator) -> Router> { let mut router = Router::new(); for post in posts { for alias in &post.aliases { let path = post.absolute_path.to_owned(); router = router.route( alias, get(move || async { let path = path; Redirect::permanent(&path) }), ); } } router } #[derive(Serialize, Debug)] struct PageContext<'a> { title: &'a str, } #[instrument(skip(state))] pub async fn index(State(state): State>) -> Result, WebsiteError> { let mut posts: Vec<&Post> = state.posts.values().filter(|p| p.is_published()).collect(); posts.sort_by_key(|p| &p.date); posts.reverse(); let ctx = PageContext { title: "Posts" }; let mut c = tera::Context::new(); c.insert("page", &ctx); c.insert("posts", &posts); let res = state.tera.render("posts_index.html", &c)?; Ok(Html(res)) } #[instrument(skip(state))] pub async fn view( Path(slug): Path, State(state): State>, ) -> Result, WebsiteError> { 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?; Ok(Html(res)) } #[instrument(skip(state))] pub async fn redirect( Path(slug): Path, State(state): State>, ) -> Result { if state.posts.contains_key(&slug) { Ok(Redirect::permanent(&format!("/posts/{slug}/"))) } else { Err(WebsiteError::NotFound) } } #[cfg(test)] mod tests { use chrono::DateTime; use crate::post::Post; use super::PageContext; #[test] fn render_index() { let posts = vec![ Post { title: "test".into(), slug: "test".into(), date: Some(DateTime::parse_from_rfc3339("2023-03-26T13:04:01+02:00").unwrap()), ..Default::default() }, Post { title: "test2".into(), slug: "test2".into(), date: None, ..Default::default() }, ]; let page = PageContext { title: "Posts" }; let mut ctx = tera::Context::new(); ctx.insert("page", &page); ctx.insert("posts", &posts); let tera = tera::Tera::new("templates/**/*").unwrap(); let _res = tera.render("posts_index.html", &ctx).unwrap(); } }