Dashboard subscribers widget
Some checks failed
Rust / Test (push) Has been cancelled
Rust / Rustfmt (push) Has been cancelled
Rust / Clippy (push) Has been cancelled
Rust / Code coverage (push) Has been cancelled

This commit is contained in:
Alphonse Paix
2025-09-26 01:54:48 +02:00
parent 4cb1d2b6fd
commit 0f6b479af9
11 changed files with 246 additions and 9 deletions

View File

@@ -3,6 +3,7 @@ mod dashboard;
mod logout;
mod newsletters;
mod posts;
mod subscribers;
use crate::{
authentication::AuthenticatedUser,
@@ -15,6 +16,7 @@ pub use dashboard::*;
pub use logout::*;
pub use newsletters::*;
pub use posts::*;
pub use subscribers::*;
#[derive(thiserror::Error)]
pub enum AdminError {

View File

@@ -1,5 +1,7 @@
use crate::{
authentication::AuthenticatedUser, routes::AppError, startup::AppState,
authentication::AuthenticatedUser,
routes::{AppError, get_max_page, get_subs, get_total_subs},
startup::AppState,
templates::DashboardTemplate,
};
use anyhow::Context;
@@ -9,6 +11,7 @@ use axum::{
extract::State,
response::{Html, IntoResponse, Response},
};
use sqlx::PgPool;
use uuid::Uuid;
pub struct DashboardStats {
@@ -33,16 +36,28 @@ pub async fn admin_dashboard(
let stats = get_stats(&connection_pool).await?;
let idempotency_key_1 = Uuid::new_v4().to_string();
let idempotency_key_2 = Uuid::new_v4().to_string();
let current_page = 1;
let subscribers = get_subs(&connection_pool, current_page)
.await
.context("Could not fetch subscribers from database.")
.map_err(AppError::unexpected_message)?;
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 template = DashboardTemplate {
username,
idempotency_key_1,
idempotency_key_2,
stats,
subscribers,
current_page,
max_page,
};
Ok(Html(template.render().unwrap()).into_response())
}
async fn get_stats(connection_pool: &sqlx::PgPool) -> Result<DashboardStats, anyhow::Error> {
async fn get_stats(connection_pool: &PgPool) -> Result<DashboardStats, anyhow::Error> {
let subscribers =
sqlx::query_scalar!("SELECT count(*) FROM subscriptions WHERE status = 'confirmed'")
.fetch_one(connection_pool)

View File

@@ -0,0 +1,99 @@
use crate::{
domain::SubscriberEntry,
routes::AppError,
startup::AppState,
templates::{MessageTemplate, SubListTemplate},
};
use anyhow::Context;
use askama::Template;
use axum::{
extract::{Path, Query, State},
response::{Html, IntoResponse, Response},
};
use sqlx::PgPool;
use uuid::Uuid;
const SUBS_PER_PAGE: i64 = 5;
pub async fn get_subscribers_page(
State(AppState {
connection_pool, ..
}): State<AppState>,
Query(SubsQueryParams { page }): Query<SubsQueryParams>,
) -> Result<Response, AppError> {
let count = get_total_subs(&connection_pool)
.await
.context("Could not fetch total subscribers count from the database.")
.map_err(AppError::unexpected_message)?;
let max_page = get_max_page(count);
let subscribers = get_subs(&connection_pool, page)
.await
.context("Could not fetch subscribers data.")
.map_err(AppError::unexpected_message)?;
let template = SubListTemplate {
subscribers,
current_page: page,
max_page,
};
Ok(Html(template.render().unwrap()).into_response())
}
pub async fn delete_subscriber(
State(AppState {
connection_pool, ..
}): State<AppState>,
Path(subscriber_id): Path<Uuid>,
) -> Result<Response, AppError> {
let res = sqlx::query!("DELETE FROM subscriptions WHERE id = $1", subscriber_id)
.execute(&connection_pool)
.await
.context("Failed to delete subscriber from database.")
.map_err(AppError::unexpected_message)?;
if res.rows_affected() > 1 {
Err(AppError::unexpected_message(anyhow::anyhow!(
"We could not find the subscriber in the database."
)))
} else {
let template = MessageTemplate::Success {
message: "The subscriber has been deleted.".into(),
};
Ok(template.render().unwrap().into_response())
}
}
pub async fn get_subs(
connection_pool: &PgPool,
page: i64,
) -> Result<Vec<SubscriberEntry>, sqlx::Error> {
let offset = (page - 1) * SUBS_PER_PAGE;
let subscribers = sqlx::query_as!(
SubscriberEntry,
"SELECT * FROM subscriptions ORDER BY subscribed_at DESC LIMIT $1 OFFSET $2",
SUBS_PER_PAGE,
offset
)
.fetch_all(connection_pool)
.await?;
Ok(subscribers)
}
pub async fn get_total_subs(connection_pool: &PgPool) -> Result<i64, sqlx::Error> {
let count = sqlx::query_scalar!("SELECT count(*) FROM subscriptions")
.fetch_one(connection_pool)
.await?
.unwrap_or(0);
Ok(count)
}
pub fn get_max_page(count: i64) -> i64 {
let mut max_page = count.div_euclid(SUBS_PER_PAGE);
if count % SUBS_PER_PAGE > 0 {
max_page += 1;
}
max_page
}
#[derive(serde::Deserialize)]
pub struct SubsQueryParams {
page: i64,
}