Skip to main content

headless_lms_server/controllers/main_frontend/credit_registration_admin/
materialize.rs

1//! Manually running the two database-only steps that turn eligible completions into ledger rows.
2
3use headless_lms_models::credit_registration_admin_actions::{
4    CreditRegistrationAdminAction, CreditRegistrationAdminActionTarget, GLOBAL_ADMIN_ROLE,
5    NewCreditRegistrationAdminAction,
6};
7use headless_lms_models::credit_registrations;
8use headless_lms_models::library::credit_registration::preconditions::{
9    PRECONDITIONS_LIMIT, recompute_preconditions,
10};
11use utoipa::ToSchema;
12
13use crate::prelude::*;
14use headless_lms_credit_registration::{CreditRegistrationPhase, Materialized, materialize_now};
15
16use super::authorize_credit_registration_admin;
17
18#[derive(Debug, Deserialize, ToSchema)]
19pub struct AdminMaterializePayload {
20    pub reason: Option<String>,
21}
22
23#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema)]
24pub struct AdminMaterializeResult {
25    /// Rows created, both for newly eligible completions and for grades that improved on a
26    /// registered one.
27    pub created_registration_count: i64,
28    pub moved_registration_count: i64,
29}
30
31/**
32POST `/api/v0/main-frontend/credit-registration-admin/materialize` - Creates ledger rows for eligible
33completions and recomputes preconditions, now.
34
35Runs the `materialize` phase's body and the precondition recompute directly rather than through the
36phase dispatcher, because the phase-state row describes the worker loops: an admin pressing a button
37must not make a dead worker look alive.
38*/
39#[instrument(skip(pool, payload))]
40#[utoipa::path(
41    post,
42    path = "/materialize",
43    operation_id = "adminMaterializeCreditRegistrations",
44    tag = "credit-registration-admin",
45    request_body = AdminMaterializePayload,
46    responses(
47        (status = 200, description = "How many rows were created and moved", body = AdminMaterializeResult)
48    )
49)]
50pub async fn admin_materialize_credit_registrations(
51    user: AuthUser,
52    pool: web::Data<PgPool>,
53    payload: web::Json<AdminMaterializePayload>,
54) -> ControllerResult<web::Json<AdminMaterializeResult>> {
55    let mut conn = pool.acquire().await?;
56    let token = authorize_credit_registration_admin(&mut conn, user.id).await?;
57
58    info!(actor = %user.id, "Admin ran materialize and preconditions now");
59    let mut tx = conn.begin().await?;
60    let scope = credit_registrations::RegistrationScope::default();
61    let Materialized {
62        created,
63        re_attempted,
64    } = materialize_now(&mut tx, &scope).await?;
65    let created_registration_count = created + re_attempted;
66    let moved_registration_count =
67        recompute_preconditions(&mut tx, &scope, PRECONDITIONS_LIMIT).await?;
68    info!(
69        created_registration_count,
70        moved_registration_count, "Admin materialize finished"
71    );
72    models::credit_registration_admin_actions::record(
73        &mut tx,
74        &NewCreditRegistrationAdminAction {
75            target_phase: Some(CreditRegistrationPhase::Materialize.as_str().to_string()),
76            reason: payload.reason.clone(),
77            details: Some(serde_json::json!({
78                "created_registration_count": created_registration_count,
79                "moved_registration_count": moved_registration_count,
80            })),
81            affected_row_count: Some(
82                i32::try_from(created_registration_count + moved_registration_count)
83                    .unwrap_or(i32::MAX),
84            ),
85            ..NewCreditRegistrationAdminAction::new(
86                CreditRegistrationAdminAction::RequeueBatch,
87                CreditRegistrationAdminActionTarget::Phase,
88                user.id,
89                GLOBAL_ADMIN_ROLE,
90            )
91        },
92    )
93    .await?;
94    tx.commit().await?;
95
96    token.authorized_ok(web::Json(AdminMaterializeResult {
97        created_registration_count,
98        moved_registration_count,
99    }))
100}
101
102pub fn _add_routes(cfg: &mut ServiceConfig) {
103    cfg.route(
104        "/materialize",
105        web::post().to(admin_materialize_credit_registrations),
106    );
107}