Skip to main content

headless_lms_models/
exercise_reset_logs.rs

1use crate::prelude::*;
2use sqlx::PgConnection;
3use utoipa::ToSchema;
4
5#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema)]
6
7pub struct ExerciseResetLog {
8    pub id: Uuid,
9    pub reset_by: Option<Uuid>,
10    pub reset_by_first_name: Option<String>,
11    pub reset_by_last_name: Option<String>,
12    pub reset_for: Uuid,
13    pub exercise_id: Uuid,
14    pub exercise_name: String,
15    pub reason: Option<String>,
16    pub course_id: Uuid,
17    pub reset_at: DateTime<Utc>,
18    pub created_at: DateTime<Utc>,
19    pub updated_at: DateTime<Utc>,
20    pub deleted_at: Option<DateTime<Utc>>,
21}
22
23/// Adds a log entry for reset exercises for a user
24pub async fn log_exercise_reset(
25    tx: &mut PgConnection,
26    reset_by: Option<Uuid>,
27    user_id: Uuid,
28    exercise_ids: &[Uuid],
29    course_id: Uuid,
30    reason: Option<String>,
31) -> Result<(), sqlx::Error> {
32    sqlx::query!(
33        r#"
34INSERT INTO exercise_reset_logs (
35    reset_by,
36    reset_for,
37    exercise_id,
38    course_id,
39    reason,
40    reset_at
41  )
42SELECT $1,
43  $2,
44  unnest($3::uuid []),
45  $4,
46  $5,
47  NOW()
48      "#,
49        reset_by,
50        user_id,
51        &exercise_ids,
52        course_id,
53        reason
54    )
55    .execute(&mut *tx)
56    .await?;
57
58    Ok(())
59}
60
61pub async fn get_exercise_reset_logs_for_user(
62    conn: &mut PgConnection,
63    user_id: Uuid,
64) -> ModelResult<Vec<ExerciseResetLog>> {
65    let result = sqlx::query_as!(
66        ExerciseResetLog,
67        r#"
68SELECT erl.id,
69  erl.reset_by,
70  ud.first_name AS reset_by_first_name,
71  ud.last_name AS reset_by_last_name,
72  erl.reset_for,
73  erl.exercise_id,
74  e.name AS exercise_name,
75  erl.reason,
76  erl.course_id,
77  erl.reset_at,
78  erl.created_at,
79  erl.updated_at,
80  erl.deleted_at
81FROM exercise_reset_logs erl
82  JOIN exercises e ON erl.exercise_id = e.id
83  LEFT JOIN user_details ud ON erl.reset_by = ud.user_id
84WHERE erl.reset_for = $1
85ORDER BY erl.reset_at DESC"#,
86        user_id
87    )
88    .fetch_all(&mut *conn)
89    .await?;
90
91    Ok(result)
92}
93
94/// Check if the user's exercise has been reset and no new submissions have been made since.
95pub async fn user_should_see_reset_message_for_exercise(
96    conn: &mut PgConnection,
97    user_id: Uuid,
98    exercise_id: Uuid,
99) -> ModelResult<Option<String>> {
100    let row = sqlx::query!(
101        r#"
102SELECT erl.reset_by, erl.reason
103FROM exercise_reset_logs erl
104LEFT JOIN exercise_slide_submissions es
105  ON es.user_id = erl.reset_for
106  AND es.exercise_id = erl.exercise_id
107  AND es.created_at > erl.reset_at
108WHERE erl.reset_for = $1
109  AND erl.exercise_id = $2
110  AND es.id IS NULL
111ORDER BY erl.reset_at DESC
112LIMIT 1
113"#,
114        user_id,
115        exercise_id
116    )
117    .fetch_optional(conn)
118    .await?;
119
120    let message = row.and_then(|r| {
121        if let Some(reason) = r.reason {
122            Some(reason)
123        } else if r.reset_by.is_some() {
124            Some("reset-by-staff".to_string())
125        } else {
126            None
127        }
128    });
129
130    Ok(message)
131}