Skip to main content

max / makenotwork

4.3 KB · 122 lines History Blame Raw
1 //! Admin queue for monthly mail allowance increases.
2 //!
3 //! The operator half of `db::mail_caps`. A creator asks with a number and a
4 //! reason; this is where the ask lands and where granting it writes the
5 //! per-account override. Granting and writing the override are one transaction
6 //! in the db layer, because a granted request whose override never landed is a
7 //! creator still being refused.
8
9 use axum::{
10 Form,
11 extract::{Path, State},
12 response::{IntoResponse, Response},
13 };
14 use serde::Deserialize;
15 use sqlx::PgPool;
16 use tower_sessions::Session;
17
18 use crate::{
19 auth::AdminUser,
20 db,
21 error::Result,
22 helpers::get_csrf_token,
23 templates::{AdminMailCapEntriesTemplate, AdminMailCapsTemplate},
24 types::AdminMailCapRow,
25 };
26
27 #[derive(Debug, Deserialize)]
28 pub(super) struct GrantForm {
29 /// What the operator is granting, which need not be what was asked for.
30 pub granted_cap: i32,
31 }
32
33 /// The pending queue, oldest first.
34 #[tracing::instrument(skip_all, name = "admin::admin_mail_caps")]
35 pub(super) async fn admin_mail_caps(
36 State(db): State<PgPool>,
37 session: Session,
38 AdminUser(admin): AdminUser,
39 ) -> Result<impl IntoResponse> {
40 Ok(AdminMailCapsTemplate {
41 csrf_token: get_csrf_token(&session).await,
42 session_user: Some(admin),
43 entries: rows(&db).await?,
44 admin_active_page: "mail-caps",
45 })
46 }
47
48 /// Grant a request, at the operator's number rather than the creator's.
49 #[tracing::instrument(skip_all, name = "admin::admin_mail_cap_grant")]
50 pub(super) async fn admin_mail_cap_grant(
51 State(db): State<PgPool>,
52 session: Session,
53 AdminUser(admin): AdminUser,
54 Path(id): Path<uuid::Uuid>,
55 Form(form): Form<GrantForm>,
56 ) -> Result<Response> {
57 if form.granted_cap > 0 {
58 match db::mail_caps::grant_request(&db, id, form.granted_cap, admin.id).await? {
59 Some(user_id) => tracing::info!(
60 request_id = %id, user_id = %user_id, granted_cap = form.granted_cap,
61 "granted a monthly mail allowance increase"
62 ),
63 // Already decided by another operator, or gone. The queue below
64 // re-renders without it, which is the answer.
65 None => tracing::info!(request_id = %id, "mail cap request was already decided"),
66 }
67 }
68
69 Ok(AdminMailCapEntriesTemplate {
70 csrf_token: get_csrf_token(&session).await,
71 entries: rows(&db).await?,
72 }
73 .into_response())
74 }
75
76 /// Deny a request. The tier default keeps applying.
77 #[tracing::instrument(skip_all, name = "admin::admin_mail_cap_deny")]
78 pub(super) async fn admin_mail_cap_deny(
79 State(db): State<PgPool>,
80 session: Session,
81 AdminUser(admin): AdminUser,
82 Path(id): Path<uuid::Uuid>,
83 ) -> Result<Response> {
84 if let Some(user_id) = db::mail_caps::deny_request(&db, id, admin.id).await? {
85 tracing::info!(request_id = %id, user_id = %user_id, "denied a monthly mail allowance increase");
86 }
87
88 Ok(AdminMailCapEntriesTemplate {
89 csrf_token: get_csrf_token(&session).await,
90 entries: rows(&db).await?,
91 }
92 .into_response())
93 }
94
95 /// The queue as the table renders it: the request, plus who asked and what they
96 /// have today, since a number means nothing without the one it would replace.
97 async fn rows(db: &PgPool) -> Result<Vec<AdminMailCapRow>> {
98 let requests = db::mail_caps::pending_requests(db).await?;
99 let mut entries = Vec::with_capacity(requests.len());
100 for request in requests {
101 let (username, email) = match db::users::get_user_by_id(db, request.user_id).await? {
102 Some(user) => (user.username.to_string(), user.email.to_string()),
103 // A request whose account went away between the query and this
104 // read. Show the row rather than dropping it silently; an operator
105 // deciding it is harmless and the alternative is a queue that
106 // quietly shrinks.
107 None => ("(deleted account)".to_string(), String::new()),
108 };
109 let current_cap = db::mail_caps::effective_cap(db, request.user_id).await?;
110 entries.push(AdminMailCapRow {
111 id: request.id,
112 username,
113 email,
114 current_cap,
115 requested_cap: request.requested_cap,
116 reason: request.reason,
117 created_at: request.created_at.format("%b %d, %Y %H:%M").to_string(),
118 });
119 }
120 Ok(entries)
121 }
122