headless_lms_models/
exercise_reset_logs.rs1use 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
23pub 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
94pub 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}