Dashboard subscribers widget
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
99
src/routes/admin/subscribers.rs
Normal file
99
src/routes/admin/subscribers.rs
Normal 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,
|
||||
}
|
||||
Reference in New Issue
Block a user