//! Admin queue for monthly mail allowance increases. //! //! The operator half of `db::mail_caps`. A creator asks with a number and a //! reason; this is where the ask lands and where granting it writes the //! per-account override. Granting and writing the override are one transaction //! in the db layer, because a granted request whose override never landed is a //! creator still being refused. use axum::{ Form, extract::{Path, State}, response::{IntoResponse, Response}, }; use serde::Deserialize; use sqlx::PgPool; use tower_sessions::Session; use crate::{ auth::AdminUser, db, error::Result, helpers::get_csrf_token, templates::{AdminMailCapEntriesTemplate, AdminMailCapsTemplate}, types::AdminMailCapRow, }; #[derive(Debug, Deserialize)] pub(super) struct GrantForm { /// What the operator is granting, which need not be what was asked for. pub granted_cap: i32, } /// The pending queue, oldest first. #[tracing::instrument(skip_all, name = "admin::admin_mail_caps")] pub(super) async fn admin_mail_caps( State(db): State, session: Session, AdminUser(admin): AdminUser, ) -> Result { Ok(AdminMailCapsTemplate { csrf_token: get_csrf_token(&session).await, session_user: Some(admin), entries: rows(&db).await?, admin_active_page: "mail-caps", }) } /// Grant a request, at the operator's number rather than the creator's. #[tracing::instrument(skip_all, name = "admin::admin_mail_cap_grant")] pub(super) async fn admin_mail_cap_grant( State(db): State, session: Session, AdminUser(admin): AdminUser, Path(id): Path, Form(form): Form, ) -> Result { if form.granted_cap > 0 { match db::mail_caps::grant_request(&db, id, form.granted_cap, admin.id).await? { Some(user_id) => tracing::info!( request_id = %id, user_id = %user_id, granted_cap = form.granted_cap, "granted a monthly mail allowance increase" ), // Already decided by another operator, or gone. The queue below // re-renders without it, which is the answer. None => tracing::info!(request_id = %id, "mail cap request was already decided"), } } Ok(AdminMailCapEntriesTemplate { csrf_token: get_csrf_token(&session).await, entries: rows(&db).await?, } .into_response()) } /// Deny a request. The tier default keeps applying. #[tracing::instrument(skip_all, name = "admin::admin_mail_cap_deny")] pub(super) async fn admin_mail_cap_deny( State(db): State, session: Session, AdminUser(admin): AdminUser, Path(id): Path, ) -> Result { if let Some(user_id) = db::mail_caps::deny_request(&db, id, admin.id).await? { tracing::info!(request_id = %id, user_id = %user_id, "denied a monthly mail allowance increase"); } Ok(AdminMailCapEntriesTemplate { csrf_token: get_csrf_token(&session).await, entries: rows(&db).await?, } .into_response()) } /// The queue as the table renders it: the request, plus who asked and what they /// have today, since a number means nothing without the one it would replace. async fn rows(db: &PgPool) -> Result> { let requests = db::mail_caps::pending_requests(db).await?; let mut entries = Vec::with_capacity(requests.len()); for request in requests { let (username, email) = match db::users::get_user_by_id(db, request.user_id).await? { Some(user) => (user.username.to_string(), user.email.to_string()), // A request whose account went away between the query and this // read. Show the row rather than dropping it silently; an operator // deciding it is harmless and the alternative is a queue that // quietly shrinks. None => ("(deleted account)".to_string(), String::new()), }; let current_cap = db::mail_caps::effective_cap(db, request.user_id).await?; entries.push(AdminMailCapRow { id: request.id, username, email, current_cap, requested_cap: request.requested_cap, reason: request.reason, created_at: request.created_at.format("%b %d, %Y %H:%M").to_string(), }); } Ok(entries) }