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}