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}