Compute dashboard stats
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

Track open rate for new post notifications (user clicked the button in
the link or not). No data about the user is collected during the
process, it only uses an ID inserted by the issue delivery worker.
This commit is contained in:
Alphonse Paix
2025-09-24 04:30:27 +02:00
parent 33281132c6
commit 4cb1d2b6fd
19 changed files with 303 additions and 60 deletions

View File

@@ -1,20 +1,84 @@
use crate::{authentication::AuthenticatedUser, templates::DashboardTemplate};
use crate::{
authentication::AuthenticatedUser, routes::AppError, startup::AppState,
templates::DashboardTemplate,
};
use anyhow::Context;
use askama::Template;
use axum::{
Extension,
extract::State,
response::{Html, IntoResponse, Response},
};
use uuid::Uuid;
pub struct DashboardStats {
pub subscribers: i64,
pub posts: i64,
pub notifications_sent: i64,
pub open_rate: f64,
}
impl DashboardStats {
pub fn formatted_rate(&self) -> String {
format!("{:.1}%", self.open_rate)
}
}
pub async fn admin_dashboard(
State(AppState {
connection_pool, ..
}): State<AppState>,
Extension(AuthenticatedUser { username, .. }): Extension<AuthenticatedUser>,
) -> Response {
) -> Result<Response, AppError> {
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 template = DashboardTemplate {
username,
idempotency_key_1,
idempotency_key_2,
stats,
};
Html(template.render().unwrap()).into_response()
Ok(Html(template.render().unwrap()).into_response())
}
async fn get_stats(connection_pool: &sqlx::PgPool) -> Result<DashboardStats, anyhow::Error> {
let subscribers =
sqlx::query_scalar!("SELECT count(*) FROM subscriptions WHERE status = 'confirmed'")
.fetch_one(connection_pool)
.await
.context("Failed to fetch subscribers count.")?
.unwrap_or(0);
let posts = sqlx::query_scalar!("SELECT count(*) FROM posts")
.fetch_one(connection_pool)
.await
.context("Failed to fetch posts count.")?
.unwrap_or(0);
let notifications_sent = sqlx::query_scalar!("SELECT count(*) FROM notifications_delivered")
.fetch_one(connection_pool)
.await
.context("Failed to fetch notifications sent count.")?
.unwrap_or(0);
let opened =
sqlx::query_scalar!("SELECT count(*) FROM notifications_delivered WHERE opened = TRUE")
.fetch_one(connection_pool)
.await
.context("Failed to fetch notifications sent count.")?
.unwrap_or(0);
let open_rate = if notifications_sent == 0 {
0.0
} else {
(opened as f64) / (notifications_sent as f64) * 100.0
};
Ok(DashboardStats {
subscribers,
posts,
notifications_sent,
open_rate,
})
}