use crate::{routes::AppError, startup::AppState}; use anyhow::Context; use askama::Template; use axum::{ extract::{Path, State}, response::{Html, IntoResponse, Response}, }; use chrono::{DateTime, Utc}; use sqlx::PgPool; use uuid::Uuid; struct PostEntry { post_id: Uuid, author: Option, title: String, content: String, published_at: DateTime, } impl PostEntry { #[allow(dead_code)] fn formatted_date(&self) -> String { self.published_at.format("%B %d, %Y").to_string() } } #[derive(Template)] #[template(path = "../templates/posts.html")] struct PostsTemplate { posts: Vec, } #[derive(Template)] #[template(path = "../templates/post.html")] struct PostTemplate { post: PostEntry, } pub async fn list_posts( State(AppState { connection_pool, .. }): State, ) -> Result { let posts = get_latest_posts(&connection_pool, 5) .await .context("Could not fetch latest posts") .map_err(AppError::unexpected_page)?; let template = PostsTemplate { posts }; Ok(Html(template.render().unwrap()).into_response()) } async fn get_latest_posts(connection_pool: &PgPool, n: i64) -> Result, sqlx::Error> { sqlx::query_as!( PostEntry, r#" SELECT p.post_id, u.username AS author, p.title, p.content, p.published_at FROM posts p LEFT JOIN users u ON p.author_id = u.user_id ORDER BY p.published_at DESC LIMIT $1 "#, n ) .fetch_all(connection_pool) .await } pub async fn see_post( State(AppState { connection_pool, .. }): State, Path(post_id): Path, ) -> Result { let post = get_post(&connection_pool, post_id) .await .context(format!("Failed to fetch post #{}", post_id)) .map_err(AppError::unexpected_page)?; let template = PostTemplate { post }; Ok(Html(template.render().unwrap()).into_response()) } async fn get_post(connection_pool: &PgPool, post_id: Uuid) -> Result { sqlx::query_as!( PostEntry, r#" SELECT p.post_id, u.username AS author, p.title, p.content, p.published_at FROM posts p LEFT JOIN users u ON p.author_id = u.user_id WHERE p.post_id = $1 "#, post_id ) .fetch_one(connection_pool) .await }