Posts dedicated page with cards linking to specific post

This commit is contained in:
Alphonse Paix
2025-09-19 01:04:10 +02:00
parent 13cb477598
commit 7578097754
10 changed files with 260 additions and 16 deletions

View File

@@ -2,6 +2,7 @@ mod admin;
mod health_check;
mod home;
mod login;
mod posts;
mod subscriptions;
mod subscriptions_confirm;
@@ -9,5 +10,6 @@ pub use admin::*;
pub use health_check::*;
pub use home::*;
pub use login::*;
pub use posts::*;
pub use subscriptions::*;
pub use subscriptions_confirm::*;

View File

@@ -9,6 +9,7 @@ use askama::Template;
use axum::{
Form, Json,
extract::State,
http::HeaderMap,
response::{Html, IntoResponse, Response},
};
use axum::{http::StatusCode, response::Redirect};
@@ -114,11 +115,9 @@ pub async fn post_login(
.await
.map_err(|e| LoginError::UnexpectedError(e.into()))?;
let mut response = Redirect::to("/admin/dashboard").into_response();
response
.headers_mut()
.insert("HX-Redirect", "/admin/dashboard".parse().unwrap());
Ok(response)
let mut headers = HeaderMap::new();
headers.insert("HX-Redirect", "/admin/dashboard".parse().unwrap());
Ok((StatusCode::OK, headers).into_response())
}
}
}

103
src/routes/posts.rs Normal file
View File

@@ -0,0 +1,103 @@
use crate::startup::AppState;
use askama::Template;
use axum::{
extract::{Path, State},
response::{Html, IntoResponse, Response},
};
use chrono::{DateTime, Utc};
use reqwest::StatusCode;
use sqlx::PgPool;
use uuid::Uuid;
struct PostEntry {
post_id: Uuid,
author: Option<String>,
title: String,
content: String,
published_at: DateTime<Utc>,
}
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<PostEntry>,
}
#[derive(Template)]
#[template(path = "../templates/post.html")]
struct PostTemplate {
post: PostEntry,
}
pub async fn list_posts(
State(AppState {
connection_pool, ..
}): State<AppState>,
) -> Response {
match get_latest_posts(&connection_pool, 5).await {
Err(e) => {
tracing::error!("Could not fetch latest posts: {}", e);
StatusCode::INTERNAL_SERVER_ERROR.into_response()
}
Ok(posts) => {
let template = PostsTemplate { posts };
Html(template.render().unwrap()).into_response()
}
}
}
async fn get_latest_posts(connection_pool: &PgPool, n: i64) -> Result<Vec<PostEntry>, 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<AppState>,
Path(post_id): Path<Uuid>,
) -> Response {
match get_post(&connection_pool, post_id).await {
Err(e) => {
tracing::error!("Could not fetch post #{}: {}", post_id, e);
StatusCode::INTERNAL_SERVER_ERROR.into_response()
}
Ok(post) => {
let template = PostTemplate { post };
Html(template.render().unwrap()).into_response()
}
}
}
async fn get_post(connection_pool: &PgPool, post_id: Uuid) -> Result<PostEntry, 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
WHERE p.post_id = $1
"#,
post_id
)
.fetch_one(connection_pool)
.await
}

View File

@@ -129,6 +129,8 @@ pub fn app(
.route("/health_check", get(health_check))
.route("/subscriptions", post(subscribe))
.route("/subscriptions/confirm", get(confirm))
.route("/posts", get(list_posts))
.route("/posts/{post_id}", get(see_post))
.nest("/admin", admin_routes)
.layer(
TraceLayer::new_for_http().make_span_with(|request: &Request<_>| {