From ce8c602ddbe45e99cca49f74b94d451f7f9f1cd0 Mon Sep 17 00:00:00 2001 From: Alphonse Paix Date: Fri, 3 Oct 2025 18:30:09 +0200 Subject: [PATCH] Posts management widget --- .gitignore | 3 ++ src/routes/admin/dashboard.rs | 15 +++++- src/routes/admin/subscribers.rs | 10 ++-- src/routes/posts.rs | 66 ++++++++++++++++++++++---- src/startup.rs | 1 + src/templates.rs | 11 +++++ templates/dashboard/comments/list.html | 0 templates/dashboard/dashboard.html | 2 + templates/dashboard/posts/card.html | 51 ++++++++++++++++++++ templates/dashboard/posts/list.html | 59 +++++++++++++++++++++++ 10 files changed, 203 insertions(+), 15 deletions(-) create mode 100644 templates/dashboard/comments/list.html create mode 100644 templates/dashboard/posts/card.html create mode 100644 templates/dashboard/posts/list.html diff --git a/.gitignore b/.gitignore index 337241a..7677353 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ /target /node_modules .env +/.idea +docker-compose.yml + diff --git a/src/routes/admin/dashboard.rs b/src/routes/admin/dashboard.rs index 1a7c09f..5a1df94 100644 --- a/src/routes/admin/dashboard.rs +++ b/src/routes/admin/dashboard.rs @@ -1,4 +1,4 @@ -use crate::routes::get_users; +use crate::routes::{POSTS_PER_PAGE, SUBS_PER_PAGE, get_posts_count, get_posts_page, get_users}; use crate::{ authentication::AuthenticatedUser, routes::{AppError, get_max_page, get_subs, get_total_subs}, @@ -45,10 +45,18 @@ pub async fn admin_dashboard( let count = get_total_subs(&connection_pool) .await .context("Could not fetch total subscribers count from the database.")?; - let max_page = get_max_page(count); + let max_page = get_max_page(count, SUBS_PER_PAGE); let users = get_users(&connection_pool) .await .context("Could not fetch users")?; + let posts = get_posts_page(&connection_pool, 1) + .await + .context("Could not fetch posts.")?; + let posts_current_page = 1; + let count = get_posts_count(&connection_pool) + .await + .context("Could not fetch posts count.")?; + let posts_max_page = get_max_page(count, POSTS_PER_PAGE); let template = DashboardTemplate { user, idempotency_key_1, @@ -58,6 +66,9 @@ pub async fn admin_dashboard( current_page, max_page, users, + posts, + posts_current_page, + posts_max_page, }; Ok(Html(template.render().unwrap()).into_response()) } diff --git a/src/routes/admin/subscribers.rs b/src/routes/admin/subscribers.rs index 3cb4a73..2c925f4 100644 --- a/src/routes/admin/subscribers.rs +++ b/src/routes/admin/subscribers.rs @@ -13,7 +13,7 @@ use axum::{ use sqlx::PgPool; use uuid::Uuid; -const SUBS_PER_PAGE: i64 = 5; +pub const SUBS_PER_PAGE: i64 = 5; #[tracing::instrument(name = "Retrieving subscribers from database", skip(connection_pool))] pub async fn get_subscribers_page( @@ -26,7 +26,7 @@ pub async fn get_subscribers_page( .await .context("Could not fetch total subscribers count from the database.") .map_err(AppError::unexpected_message)?; - let max_page = get_max_page(count); + let max_page = get_max_page(count, SUBS_PER_PAGE); let subscribers = get_subs(&connection_pool, page) .await .context("Could not fetch subscribers data.") @@ -102,9 +102,9 @@ pub async fn get_total_subs(connection_pool: &PgPool) -> Result i64 { - let mut max_page = count.div_euclid(SUBS_PER_PAGE); - if count % SUBS_PER_PAGE > 0 { +pub fn get_max_page(count: i64, num_per_page: i64) -> i64 { + let mut max_page = count.div_euclid(num_per_page); + if count % num_per_page > 0 { max_page += 1; } max_page diff --git a/src/routes/posts.rs b/src/routes/posts.rs index 834be82..256374b 100644 --- a/src/routes/posts.rs +++ b/src/routes/posts.rs @@ -1,3 +1,5 @@ +use crate::routes::get_max_page; +use crate::templates::PostsPageDashboardTemplate; use crate::{ domain::PostEntry, routes::{ @@ -16,7 +18,7 @@ use axum::{ use sqlx::PgPool; use uuid::Uuid; -const NUM_PER_PAGE: i64 = 3; +pub const POSTS_PER_PAGE: i64 = 3; #[tracing::instrument(name = "Fetching most recent posts from database", skip_all)] pub async fn list_posts( @@ -24,12 +26,16 @@ pub async fn list_posts( connection_pool, .. }): State, ) -> Result { - let count = get_posts_table_size(&connection_pool) + let count = get_posts_count(&connection_pool) .await .context("Could not fetch posts table size.") .map_err(AppError::unexpected_page)?; - let next_page = if count > NUM_PER_PAGE { Some(2) } else { None }; - let posts = get_posts(&connection_pool, NUM_PER_PAGE, None) + let next_page = if count > POSTS_PER_PAGE { + Some(2) + } else { + None + }; + let posts = get_posts(&connection_pool, POSTS_PER_PAGE, None) .await .context("Could not fetch latest posts") .map_err(AppError::unexpected_page)?; @@ -37,6 +43,29 @@ pub async fn list_posts( Ok(Html(template.render().unwrap()).into_response()) } +#[tracing::instrument(name = "Fetching next posts from database", skip_all)] +pub async fn get_posts_page_dashboard( + State(AppState { + connection_pool, .. + }): State, + Query(LoadMoreParams { page }): Query, +) -> Result { + let posts = get_posts_page(&connection_pool, page) + .await + .context("Could not fetch next posts page.")?; + let posts_current_page = page; + let count = get_posts_count(&connection_pool) + .await + .context("Could not fetch number of posts.")?; + let posts_max_page = get_max_page(count, POSTS_PER_PAGE); + let template = HtmlTemplate(PostsPageDashboardTemplate { + posts, + posts_current_page, + posts_max_page, + }); + Ok(template.into_response()) +} + async fn get_posts( connection_pool: &PgPool, n: i64, @@ -59,7 +88,29 @@ async fn get_posts( .await } -async fn get_posts_table_size(connection_pool: &PgPool) -> Result { +pub async fn get_posts_page( + connection_pool: &PgPool, + page: i64, +) -> Result, sqlx::Error> { + let offset = (page - 1) * POSTS_PER_PAGE; + 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 + OFFSET $2 + "#, + POSTS_PER_PAGE, + offset + ) + .fetch_all(connection_pool) + .await +} + +pub async fn get_posts_count(connection_pool: &PgPool) -> Result { sqlx::query!("SELECT count(*) FROM posts") .fetch_one(connection_pool) .await @@ -156,15 +207,14 @@ pub async fn load_more( }): State, Query(LoadMoreParams { page }): Query, ) -> Result { - let offset = (page - 1) * NUM_PER_PAGE; - let posts = get_posts(&connection_pool, NUM_PER_PAGE, Some(offset)) + let posts = get_posts_page(&connection_pool, page) .await .context("Could not fetch posts from database.")?; let count = posts.len(); Ok(Html( PostListTemplate { posts, - next_page: if count as i64 == NUM_PER_PAGE { + next_page: if count as i64 == POSTS_PER_PAGE { Some(page + 1) } else { None diff --git a/src/startup.rs b/src/startup.rs index 91999c3..66a6693 100644 --- a/src/startup.rs +++ b/src/startup.rs @@ -87,6 +87,7 @@ pub fn app( let admin_routes = Router::new() .route("/subscribers", get(get_subscribers_page)) .route("/subscribers/{subscriber_id}", delete(delete_subscriber)) + .route("/posts", get(get_posts_page_dashboard)) .route("/posts/{post_id}", delete(delete_post)) .route("/users", post(create_user)) .route("/users/{user_id}", delete(delete_user)) diff --git a/src/templates.rs b/src/templates.rs index 42abe83..bf5636c 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -65,6 +65,17 @@ pub struct DashboardTemplate { pub current_page: i64, pub max_page: i64, pub users: Vec, + pub posts: Vec, + pub posts_current_page: i64, + pub posts_max_page: i64, +} + +#[derive(Template)] +#[template(path = "dashboard/posts/list.html", block = "posts")] +pub struct PostsPageDashboardTemplate { + pub posts: Vec, + pub posts_current_page: i64, + pub posts_max_page: i64, } #[derive(Template)] diff --git a/templates/dashboard/comments/list.html b/templates/dashboard/comments/list.html new file mode 100644 index 0000000..e69de29 diff --git a/templates/dashboard/dashboard.html b/templates/dashboard/dashboard.html index 24007d2..2360dc5 100644 --- a/templates/dashboard/dashboard.html +++ b/templates/dashboard/dashboard.html @@ -33,6 +33,8 @@ {% include "users/list.html" %} {% include "users/form.html" %} + {% include "posts/list.html" %} + {% include "comments/list.html" %} {% endif %}
diff --git a/templates/dashboard/posts/card.html b/templates/dashboard/posts/card.html new file mode 100644 index 0000000..d684c67 --- /dev/null +++ b/templates/dashboard/posts/card.html @@ -0,0 +1,51 @@ +
+
+
+ +

+ {{ post.title }} +

+
+
+ +
+ + + + +
+
+
+ +
+
\ No newline at end of file diff --git a/templates/dashboard/posts/list.html b/templates/dashboard/posts/list.html new file mode 100644 index 0000000..ae3e06a --- /dev/null +++ b/templates/dashboard/posts/list.html @@ -0,0 +1,59 @@ +
+
+
+
+

+ + + + Posts management +

+

View and manage all published posts.

+
+
+
+
+ {% block posts %} + {% if posts.is_empty() %} +
+
+ + + +
+

No posts yet

+

Published posts will appear here.

+
+ {% else %} +
+ {% for post in posts %} + {% include "dashboard/posts/card.html" %} + {% endfor %} +
+ {% endif %} +
+ + Page: {{ posts_current_page }} + +
+ {% endblock %} +
+