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}