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