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