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}, startup::AppState, templates::DashboardTemplate, }; use anyhow::Context; use askama::Template; use axum::{ Extension, extract::State, response::{Html, IntoResponse, Response}, }; use sqlx::PgPool; 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, Extension(user): Extension, ) -> Result { 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, 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, idempotency_key_2, stats, subscribers, current_page, max_page, users, posts, posts_current_page, posts_max_page, }; Ok(Html(template.render().unwrap()).into_response()) } #[tracing::instrument("Computing dashboard stats", skip_all)] async fn get_stats(connection_pool: &PgPool) -> Result { 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, }) }