Skip to main content

headless_lms_models/
teacher_grading_decisions.rs

1use std::collections::HashMap;
2
3use crate::prelude::*;
4use utoipa::ToSchema;
5
6#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema)]
7
8pub struct TeacherGradingDecision {
9    pub id: Uuid,
10    pub user_exercise_state_id: Uuid,
11    pub created_at: DateTime<Utc>,
12    pub updated_at: DateTime<Utc>,
13    pub deleted_at: Option<DateTime<Utc>>,
14    pub score_given: f32,
15    pub teacher_decision: TeacherDecisionType,
16    pub justification: Option<String>,
17    pub hidden: Option<bool>,
18}
19
20#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy, sqlx::Type, ToSchema)]
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, ToSchema)]
31
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
84#[allow(clippy::too_many_arguments)]
85pub async fn upsert_by_state_id_and_exercise_id(
86    conn: &mut PgConnection,
87    user_exercise_state_id: Uuid,
88    exercise_id: Uuid,
89    action: TeacherDecisionType,
90    score_given: f32,
91    decision_maker_user_id: Option<Uuid>,
92    justification: Option<String>,
93    hidden: bool,
94) -> ModelResult<TeacherGradingDecision> {
95    let res = sqlx::query_as!(
96        TeacherGradingDecision,
97        r#"
98INSERT INTO teacher_grading_decisions (
99    user_exercise_state_id,
100    teacher_decision,
101    score_given,
102    user_id,
103    justification,
104    hidden
105)
106SELECT ues.id, $3, $4, $5, $6, $7
107FROM user_exercise_states ues
108WHERE ues.id = $1
109  AND ues.exercise_id = $2
110  AND ues.deleted_at IS NULL
111RETURNING id,
112  user_exercise_state_id,
113  created_at,
114  updated_at,
115  deleted_at,
116  score_given,
117  teacher_decision AS "teacher_decision: _",
118  justification,
119  hidden;
120      "#,
121        user_exercise_state_id,
122        exercise_id,
123        action as TeacherDecisionType,
124        score_given,
125        decision_maker_user_id,
126        justification,
127        hidden
128    )
129    .fetch_one(conn)
130    .await?;
131    Ok(res)
132}
133
134pub async fn try_to_get_latest_grading_decision_by_user_exercise_state_id(
135    conn: &mut PgConnection,
136    user_exercise_state_id: Uuid,
137) -> ModelResult<Option<TeacherGradingDecision>> {
138    let res = sqlx::query_as!(
139        TeacherGradingDecision,
140        r#"
141SELECT id,
142  user_exercise_state_id,
143  created_at,
144  updated_at,
145  deleted_at,
146  score_given,
147  teacher_decision AS "teacher_decision: _",
148  justification,
149  hidden
150FROM teacher_grading_decisions
151WHERE user_exercise_state_id = $1
152  AND deleted_at IS NULL
153ORDER BY created_at DESC
154LIMIT 1
155      "#,
156        user_exercise_state_id,
157    )
158    .fetch_optional(conn)
159    .await?;
160    Ok(res)
161}
162
163pub async fn try_to_get_latest_grading_decision_by_user_exercise_state_id_for_users(
164    conn: &mut PgConnection,
165    user_exercise_state_ids: &[Uuid],
166) -> ModelResult<HashMap<Uuid, TeacherGradingDecision>> {
167    let decisions = sqlx::query_as!(
168        TeacherGradingDecision,
169        r#"
170SELECT DISTINCT ON (user_exercise_state_id) id,
171  user_exercise_state_id,
172  created_at,
173  updated_at,
174  deleted_at,
175  score_given,
176  teacher_decision AS "teacher_decision: _",
177  justification,
178  hidden
179FROM teacher_grading_decisions
180WHERE user_exercise_state_id = ANY($1)
181  AND deleted_at IS NULL
182ORDER BY user_exercise_state_id,
183  created_at DESC
184      "#,
185        user_exercise_state_ids,
186    )
187    .fetch_all(conn)
188    .await?;
189
190    let mut res: HashMap<Uuid, TeacherGradingDecision> = HashMap::new();
191    for item in decisions.into_iter() {
192        res.insert(item.user_exercise_state_id, item);
193    }
194
195    Ok(res)
196}
197
198pub async fn get_all_latest_grading_decisions_by_user_id_and_course_id(
199    conn: &mut PgConnection,
200    user_id: Uuid,
201    course_id: Uuid,
202) -> ModelResult<Vec<TeacherGradingDecision>> {
203    let res = sqlx::query_as!(
204        TeacherGradingDecision,
205        r#"
206SELECT DISTINCT ON (user_exercise_state_id)
207  id,
208  user_exercise_state_id,
209  created_at,
210  updated_at,
211  deleted_at,
212  score_given,
213  teacher_decision AS "teacher_decision: _",
214  justification,
215  hidden
216FROM teacher_grading_decisions
217WHERE user_exercise_state_id IN (
218    SELECT user_exercise_states.id
219    FROM user_exercise_states
220    WHERE user_exercise_states.user_id = $1
221      AND user_exercise_states.course_id = $2
222      AND user_exercise_states.deleted_at IS NULL
223  )
224  AND deleted_at IS NULL
225  ORDER BY user_exercise_state_id, created_at DESC
226      "#,
227        user_id,
228        course_id,
229    )
230    .fetch_all(conn)
231    .await?;
232    Ok(res)
233}
234
235pub async fn get_all_latest_grading_decisions_by_user_id_and_exam_id(
236    conn: &mut PgConnection,
237    user_id: Uuid,
238    exam_id: Uuid,
239) -> ModelResult<Vec<TeacherGradingDecision>> {
240    let res = sqlx::query_as!(
241        TeacherGradingDecision,
242        r#"
243SELECT DISTINCT ON (user_exercise_state_id)
244  id,
245  user_exercise_state_id,
246  created_at,
247  updated_at,
248  deleted_at,
249  score_given,
250  teacher_decision AS "teacher_decision: _",
251  justification,
252  hidden
253FROM teacher_grading_decisions
254WHERE user_exercise_state_id IN (
255    SELECT user_exercise_states.id
256    FROM user_exercise_states
257    WHERE user_exercise_states.user_id = $1
258      AND user_exercise_states.exam_id = $2
259      AND user_exercise_states.deleted_at IS NULL
260  )
261  AND deleted_at IS NULL
262  ORDER BY user_exercise_state_id, created_at DESC
263      "#,
264        user_id,
265        exam_id,
266    )
267    .fetch_all(conn)
268    .await?;
269    Ok(res)
270}
271
272pub async fn update_teacher_grading_decision_hidden_field(
273    conn: &mut PgConnection,
274    teacher_grading_decision_id: Uuid,
275    hidden: bool,
276) -> ModelResult<TeacherGradingDecision> {
277    let res = sqlx::query_as!(
278        TeacherGradingDecision,
279        r#"
280        UPDATE teacher_grading_decisions
281        SET hidden = $1
282        WHERE id = $2
283        RETURNING id,
284          user_exercise_state_id,
285          created_at,
286          updated_at,
287          deleted_at,
288          score_given,
289          teacher_decision AS "teacher_decision: _",
290          justification,
291          hidden;
292              "#,
293        hidden,
294        teacher_grading_decision_id
295    )
296    .fetch_one(conn)
297    .await?;
298    Ok(res)
299}
300
301pub async fn get_by_ids(
302    conn: &mut PgConnection,
303    ids: &[Uuid],
304) -> ModelResult<Vec<TeacherGradingDecision>> {
305    let res = sqlx::query_as!(
306        TeacherGradingDecision,
307        r#"
308SELECT id,
309  user_exercise_state_id,
310  created_at,
311  updated_at,
312  deleted_at,
313  score_given,
314  teacher_decision AS "teacher_decision: _",
315  justification,
316  hidden
317FROM teacher_grading_decisions
318WHERE id = ANY($1)
319  AND deleted_at IS NULL
320        "#,
321        ids
322    )
323    .fetch_all(conn)
324    .await?;
325    Ok(res)
326}