Skip to main content

headless_lms_models/
cheating_confirmation_grade_snapshots.rs

1use crate::prelude::*;
2
3#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
4pub struct CheatingConfirmationGradeSnapshot {
5    pub id: Uuid,
6    pub course_module_completion_id: Uuid,
7    pub passed: bool,
8    pub grade: Option<i32>,
9    pub created_at: DateTime<Utc>,
10    pub updated_at: DateTime<Utc>,
11    pub deleted_at: Option<DateTime<Utc>>,
12}
13
14/// Snapshots the current passed/grade of all of a user's completions in a course and then fails
15/// them (passed = false, grade = 0) as the consequence of a confirmed cheating suspicion. The
16/// snapshots let the failure be undone exactly if the suspicion is later dismissed (see
17/// [`restore_and_clear_for_user_course`]). A completion that already has an active snapshot is left
18/// as-is, so confirming an already-confirmed student does not overwrite the original values.
19pub async fn snapshot_and_fail_completions(
20    conn: &mut PgConnection,
21    course_id: Uuid,
22    user_id: Uuid,
23) -> ModelResult<()> {
24    let mut tx = conn.begin().await?;
25    sqlx::query!(
26        "
27INSERT INTO cheating_confirmation_grade_snapshots (course_module_completion_id, passed, grade)
28SELECT cmc.id, cmc.passed, cmc.grade
29FROM course_module_completions cmc
30WHERE cmc.user_id = $1
31  AND cmc.course_id = $2
32  AND cmc.deleted_at IS NULL
33  AND NOT EXISTS (
34    SELECT 1
35    FROM cheating_confirmation_grade_snapshots s
36    WHERE s.course_module_completion_id = cmc.id
37      AND s.deleted_at IS NULL
38  )
39        ",
40        user_id,
41        course_id
42    )
43    .execute(&mut *tx)
44    .await?;
45    sqlx::query!(
46        "
47UPDATE course_module_completions
48SET passed = false, grade = 0
49WHERE user_id = $1
50  AND course_id = $2
51  AND deleted_at IS NULL
52        ",
53        user_id,
54        course_id
55    )
56    .execute(&mut *tx)
57    .await?;
58    tx.commit().await?;
59    Ok(())
60}
61
62/// Restores passed/grade onto a user's completions in a course from their active snapshots and then
63/// soft-deletes those snapshots, undoing a cheating confirmation. A no-op for users without active
64/// snapshots (e.g. a student dismissed straight from the flagged state, who was never failed).
65pub async fn restore_and_clear_for_user_course(
66    conn: &mut PgConnection,
67    course_id: Uuid,
68    user_id: Uuid,
69) -> ModelResult<()> {
70    let mut tx = conn.begin().await?;
71    sqlx::query!(
72        "
73UPDATE course_module_completions cmc
74SET passed = s.passed, grade = s.grade
75FROM cheating_confirmation_grade_snapshots s
76WHERE s.course_module_completion_id = cmc.id
77  AND s.deleted_at IS NULL
78  AND cmc.user_id = $1
79  AND cmc.course_id = $2
80  AND cmc.deleted_at IS NULL
81        ",
82        user_id,
83        course_id
84    )
85    .execute(&mut *tx)
86    .await?;
87    sqlx::query!(
88        "
89UPDATE cheating_confirmation_grade_snapshots s
90SET deleted_at = now()
91WHERE s.deleted_at IS NULL
92  AND s.course_module_completion_id IN (
93    SELECT cmc.id
94    FROM course_module_completions cmc
95    WHERE cmc.user_id = $1
96      AND cmc.course_id = $2
97      AND cmc.deleted_at IS NULL
98  )
99        ",
100        user_id,
101        course_id
102    )
103    .execute(&mut *tx)
104    .await?;
105    tx.commit().await?;
106    Ok(())
107}