headless_lms_models/
teacher_grading_decisions.rs

1use std::collections::HashMap;
2
3use crate::prelude::*;
4
5#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
6#[cfg_attr(feature = "ts_rs", derive(TS))]
7pub struct TeacherGradingDecision {
8    pub id: Uuid,
9    pub user_exercise_state_id: Uuid,
10    pub created_at: DateTime<Utc>,
11    pub updated_at: DateTime<Utc>,
12    pub deleted_at: Option<DateTime<Utc>>,
13    pub score_given: f32,
14    pub teacher_decision: TeacherDecisionType,
15    pub justification: Option<String>,
16    pub hidden: Option<bool>,
17}
18
19#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy, sqlx::Type)]
20#[cfg_attr(feature = "ts_rs", derive(TS))]
21#[sqlx(type_name = "teacher_decision_type", rename_all = "kebab-case")]
22pub enum TeacherDecisionType {
23    FullPoints,
24    ZeroPoints,
25    CustomPoints,
26    SuspectedPlagiarism,
27    RejectAndReset,
28}
29
30#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
31#[cfg_attr(feature = "ts_rs", derive(TS))]
32pub struct NewTeacherGradingDecision {
33    pub user_exercise_state_id: Uuid,
34    pub exercise_id: Uuid,
35    pub action: TeacherDecisionType,
36    pub manual_points: Option<f32>,
37    pub justification: Option<String>,
38    pub hidden: bool,
39}
40
41pub async fn add_teacher_grading_decision(
42    conn: &mut PgConnection,
43    user_exercise_state_id: Uuid,
44    action: TeacherDecisionType,
45    score_given: f32,
46    decision_maker_user_id: Option<Uuid>,
47    justification: Option<String>,
48    hidden: bool,
49) -> ModelResult<TeacherGradingDecision> {
50    let res = sqlx::query_as!(
51        TeacherGradingDecision,
52        r#"
53INSERT INTO teacher_grading_decisions (
54    user_exercise_state_id,
55    teacher_decision,
56    score_given,
57    user_id,
58    justification,
59    hidden
60  )
61VALUES ($1, $2, $3, $4, $5, $6)
62RETURNING id,
63  user_exercise_state_id,
64  created_at,
65  updated_at,
66  deleted_at,
67  score_given,
68  teacher_decision AS "teacher_decision: _",
69  justification,
70  hidden;
71      "#,
72        user_exercise_state_id,
73        action as TeacherDecisionType,
74        score_given,
75        decision_maker_user_id,
76        justification,
77        hidden
78    )
79    .fetch_one(conn)
80    .await?;
81    Ok(res)
82}
83
84pub async fn try_to_get_latest_grading_decision_by_user_exercise_state_id(
85    conn: &mut PgConnection,
86    user_exercise_state_id: Uuid,
87) -> ModelResult<Option<TeacherGradingDecision>> {
88    let res = sqlx::query_as!(
89        TeacherGradingDecision,
90        r#"
91SELECT id,
92  user_exercise_state_id,
93  created_at,
94  updated_at,
95  deleted_at,
96  score_given,
97  teacher_decision AS "teacher_decision: _",
98  justification,
99  hidden
100FROM teacher_grading_decisions
101WHERE user_exercise_state_id = $1
102  AND deleted_at IS NULL
103ORDER BY created_at DESC
104LIMIT 1
105      "#,
106        user_exercise_state_id,
107    )
108    .fetch_optional(conn)
109    .await?;
110    Ok(res)
111}
112
113pub async fn try_to_get_latest_grading_decision_by_user_exercise_state_id_for_users(
114    conn: &mut PgConnection,
115    user_exercise_state_ids: &[Uuid],
116) -> ModelResult<HashMap<Uuid, TeacherGradingDecision>> {
117    let decisions = sqlx::query_as!(
118        TeacherGradingDecision,
119        r#"
120SELECT DISTINCT ON (user_exercise_state_id) id,
121  user_exercise_state_id,
122  created_at,
123  updated_at,
124  deleted_at,
125  score_given,
126  teacher_decision AS "teacher_decision: _",
127  justification,
128  hidden
129FROM teacher_grading_decisions
130WHERE user_exercise_state_id = ANY($1)
131  AND deleted_at IS NULL
132ORDER BY user_exercise_state_id,
133  created_at DESC
134      "#,
135        user_exercise_state_ids,
136    )
137    .fetch_all(conn)
138    .await?;
139
140    let mut res: HashMap<Uuid, TeacherGradingDecision> = HashMap::new();
141    for item in decisions.into_iter() {
142        res.insert(item.user_exercise_state_id, item);
143    }
144
145    Ok(res)
146}
147
148pub async fn get_all_latest_grading_decisions_by_user_id_and_course_id(
149    conn: &mut PgConnection,
150    user_id: Uuid,
151    course_id: Uuid,
152) -> ModelResult<Vec<TeacherGradingDecision>> {
153    let res = sqlx::query_as!(
154        TeacherGradingDecision,
155        r#"
156SELECT DISTINCT ON (user_exercise_state_id)
157  id,
158  user_exercise_state_id,
159  created_at,
160  updated_at,
161  deleted_at,
162  score_given,
163  teacher_decision AS "teacher_decision: _",
164  justification,
165  hidden
166FROM teacher_grading_decisions
167WHERE user_exercise_state_id IN (
168    SELECT user_exercise_states.id
169    FROM user_exercise_states
170    WHERE user_exercise_states.user_id = $1
171      AND user_exercise_states.course_id = $2
172      AND user_exercise_states.deleted_at IS NULL
173  )
174  AND deleted_at IS NULL
175  ORDER BY user_exercise_state_id, created_at DESC
176      "#,
177        user_id,
178        course_id,
179    )
180    .fetch_all(conn)
181    .await?;
182    Ok(res)
183}
184
185pub async fn get_all_latest_grading_decisions_by_user_id_and_exam_id(
186    conn: &mut PgConnection,
187    user_id: Uuid,
188    exam_id: Uuid,
189) -> ModelResult<Vec<TeacherGradingDecision>> {
190    let res = sqlx::query_as!(
191        TeacherGradingDecision,
192        r#"
193SELECT DISTINCT ON (user_exercise_state_id)
194  id,
195  user_exercise_state_id,
196  created_at,
197  updated_at,
198  deleted_at,
199  score_given,
200  teacher_decision AS "teacher_decision: _",
201  justification,
202  hidden
203FROM teacher_grading_decisions
204WHERE user_exercise_state_id IN (
205    SELECT user_exercise_states.id
206    FROM user_exercise_states
207    WHERE user_exercise_states.user_id = $1
208      AND user_exercise_states.exam_id = $2
209      AND user_exercise_states.deleted_at IS NULL
210  )
211  AND deleted_at IS NULL
212  ORDER BY user_exercise_state_id, created_at DESC
213      "#,
214        user_id,
215        exam_id,
216    )
217    .fetch_all(conn)
218    .await?;
219    Ok(res)
220}
221
222pub async fn update_teacher_grading_decision_hidden_field(
223    conn: &mut PgConnection,
224    teacher_grading_decision_id: Uuid,
225    hidden: bool,
226) -> ModelResult<TeacherGradingDecision> {
227    let res = sqlx::query_as!(
228        TeacherGradingDecision,
229        r#"
230        UPDATE teacher_grading_decisions
231        SET hidden = $1
232        WHERE id = $2
233        RETURNING id,
234          user_exercise_state_id,
235          created_at,
236          updated_at,
237          deleted_at,
238          score_given,
239          teacher_decision AS "teacher_decision: _",
240          justification,
241          hidden;
242              "#,
243        hidden,
244        teacher_grading_decision_id
245    )
246    .fetch_one(conn)
247    .await?;
248    Ok(res)
249}
250
251pub async fn get_by_ids(
252    conn: &mut PgConnection,
253    ids: &[Uuid],
254) -> ModelResult<Vec<TeacherGradingDecision>> {
255    let res = sqlx::query_as!(
256        TeacherGradingDecision,
257        r#"
258SELECT id,
259  user_exercise_state_id,
260  created_at,
261  updated_at,
262  deleted_at,
263  score_given,
264  teacher_decision AS "teacher_decision: _",
265  justification,
266  hidden
267FROM teacher_grading_decisions
268WHERE id = ANY($1)
269  AND deleted_at IS NULL
270        "#,
271        ids
272    )
273    .fetch_all(conn)
274    .await?;
275    Ok(res)
276}