Skip to main content

headless_lms_server/domain/credit_registration_phases/
retention_sweep.rs

1//! The `retention-sweep` phase: the call log's 90-day window and the expired linking tokens.
2//!
3//! Bounded per iteration and run hourly, so the first sweep after the window opens clears a backlog
4//! over several hours instead of in one statement that locks every
5//! `credit_registration_events` row referencing it.
6
7use chrono::{Duration, Utc};
8use headless_lms_models::credit_registration_phase_state::PhaseRunOutcome;
9use headless_lms_models::student_number_verification_tokens::soft_delete_expired;
10use headless_lms_models::suotar_api_calls::{RETENTION_DAYS, delete_older_than};
11
12use super::{PhaseContext, PhaseScope};
13
14/// How much one iteration removes from each table.
15const SWEEP_LIMIT: i64 = 500;
16
17pub async fn run(ctx: &PhaseContext<'_>, _scope: &PhaseScope) -> anyhow::Result<PhaseRunOutcome> {
18    let mut conn = ctx.pool.acquire().await?;
19    let cutoff = Utc::now() - Duration::days(RETENTION_DAYS);
20    let purged_calls = delete_older_than(&mut conn, cutoff, SWEEP_LIMIT).await?;
21    let retired_tokens = soft_delete_expired(&mut conn, SWEEP_LIMIT).await?;
22    if purged_calls > 0 || retired_tokens > 0 {
23        info!(
24            "Purged {purged_calls} study registry call rows past the {RETENTION_DAYS} day window and retired {retired_tokens} expired student number verification tokens."
25        );
26    }
27    Ok(PhaseRunOutcome::processed(
28        i64::try_from(purged_calls + retired_tokens).unwrap_or(i64::MAX),
29    ))
30}