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