1use std::collections::HashMap;
2
3use crate::prelude::*;
4use utoipa::ToSchema;
5
6#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq, ToSchema)]
7
8pub struct PeerOrSelfReviewSubmission {
9 pub id: Uuid,
10 pub created_at: DateTime<Utc>,
11 pub updated_at: DateTime<Utc>,
12 pub deleted_at: Option<DateTime<Utc>>,
13 pub user_id: Uuid,
14 pub exercise_id: Uuid,
15 pub course_id: Uuid,
16 pub peer_or_self_review_config_id: Uuid,
17 pub exercise_slide_submission_id: Uuid,
18}
19
20#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq, ToSchema)]
22
23pub struct PeerOrSelfReviewSubmissionWithSubmissionOwner {
24 #[serde(flatten)]
25 pub submission: PeerOrSelfReviewSubmission,
26 pub submission_owner_user_id: Option<Uuid>,
27}
28
29pub async fn insert(
30 conn: &mut PgConnection,
31 pkey_policy: PKeyPolicy<Uuid>,
32 user_id: Uuid,
33 exercise_id: Uuid,
34 course_id: Uuid,
35 peer_or_self_review_config_id: Uuid,
36 exercise_slide_submission_id: Uuid,
37) -> ModelResult<Uuid> {
38 let res = sqlx::query!(
39 "
40INSERT INTO peer_or_self_review_submissions (
41 id,
42 user_id,
43 exercise_id,
44 course_id,
45 peer_or_self_review_config_id,
46 exercise_slide_submission_id
47 )
48VALUES ($1, $2, $3, $4, $5, $6)
49RETURNING id
50 ",
51 pkey_policy.into_uuid(),
52 user_id,
53 exercise_id,
54 course_id,
55 peer_or_self_review_config_id,
56 exercise_slide_submission_id,
57 )
58 .fetch_one(conn)
59 .await?;
60 Ok(res.id)
61}
62
63#[allow(clippy::too_many_arguments)]
64pub async fn insert_for_submission_context(
65 conn: &mut PgConnection,
66 pkey_policy: PKeyPolicy<Uuid>,
67 reviewer_user_id: Uuid,
68 receiver_user_id: Uuid,
69 exercise_id: Uuid,
70 course_id: Uuid,
71 peer_or_self_review_config_id: Uuid,
72 exercise_slide_submission_id: Uuid,
73 reviewer_user_exercise_state_id: Uuid,
74 receiver_user_exercise_state_id: Uuid,
75) -> ModelResult<PeerOrSelfReviewSubmission> {
76 let res = sqlx::query_as!(
77 PeerOrSelfReviewSubmission,
78 r#"
79INSERT INTO peer_or_self_review_submissions (
80 id,
81 user_id,
82 exercise_id,
83 course_id,
84 peer_or_self_review_config_id,
85 exercise_slide_submission_id
86)
87SELECT
88 $1,
89 reviewer_state.user_id,
90 exercise.id,
91 exercise.course_id,
92 config.id,
93 receiver_submission.id
94FROM exercises exercise
95 JOIN peer_or_self_review_configs config
96 ON config.id = $5
97 AND config.course_id = $4
98 AND (config.exercise_id = exercise.id OR config.exercise_id IS NULL)
99 AND config.deleted_at IS NULL
100 JOIN exercise_slide_submissions receiver_submission
101 ON receiver_submission.id = $6
102 AND receiver_submission.exercise_id = exercise.id
103 AND receiver_submission.course_id = $4
104 AND receiver_submission.user_id = $3
105 AND receiver_submission.deleted_at IS NULL
106 JOIN user_exercise_states reviewer_state
107 ON reviewer_state.id = $7
108 AND reviewer_state.user_id = $2
109 AND reviewer_state.exercise_id = exercise.id
110 AND reviewer_state.course_id = $4
111 AND reviewer_state.deleted_at IS NULL
112 JOIN user_exercise_states receiver_state
113 ON receiver_state.id = $8
114 AND receiver_state.user_id = $3
115 AND receiver_state.exercise_id = exercise.id
116 AND receiver_state.course_id = $4
117 AND receiver_state.deleted_at IS NULL
118WHERE exercise.id = $9
119 AND exercise.course_id = $4
120 AND exercise.deleted_at IS NULL
121RETURNING *
122 "#,
123 pkey_policy.into_uuid(),
124 reviewer_user_id,
125 receiver_user_id,
126 course_id,
127 peer_or_self_review_config_id,
128 exercise_slide_submission_id,
129 reviewer_user_exercise_state_id,
130 receiver_user_exercise_state_id,
131 exercise_id
132 )
133 .fetch_one(conn)
134 .await?;
135 Ok(res)
136}
137
138pub async fn get_by_id(
139 conn: &mut PgConnection,
140 id: Uuid,
141) -> ModelResult<PeerOrSelfReviewSubmission> {
142 let res = sqlx::query_as!(
143 PeerOrSelfReviewSubmission,
144 "
145SELECT *
146FROM peer_or_self_review_submissions
147WHERE id = $1
148 AND deleted_at IS NULL
149 ",
150 id
151 )
152 .fetch_one(conn)
153 .await?;
154 Ok(res)
155}
156
157pub async fn get_by_ids(
158 conn: &mut PgConnection,
159 ids: &[Uuid],
160) -> ModelResult<Vec<PeerOrSelfReviewSubmission>> {
161 let res = sqlx::query_as!(
162 PeerOrSelfReviewSubmission,
163 "
164SELECT *
165FROM peer_or_self_review_submissions
166WHERE id = ANY($1)
167 AND deleted_at IS NULL
168 ",
169 ids
170 )
171 .fetch_all(conn)
172 .await?;
173 Ok(res)
174}
175
176pub async fn get_users_submission_ids_for_exercise_and_course_instance(
177 conn: &mut PgConnection,
178 user_id: Uuid,
179 exercise_id: Uuid,
180 course_id: Uuid,
181) -> ModelResult<Vec<Uuid>> {
182 let res = sqlx::query!(
183 "
184SELECT exercise_slide_submission_id
185FROM peer_or_self_review_submissions
186WHERE user_id = $1
187 AND exercise_id = $2
188 AND course_id = $3
189 AND deleted_at IS NULL
190 ",
191 user_id,
192 exercise_id,
193 course_id
194 )
195 .fetch_all(conn)
196 .await?
197 .into_iter()
198 .map(|record| record.exercise_slide_submission_id)
199 .collect();
200 Ok(res)
201}
202
203pub async fn get_all_received_peer_or_self_review_submissions_for_user_and_course(
204 conn: &mut PgConnection,
205 user_id: Uuid,
206 course_id: Uuid,
207) -> ModelResult<Vec<PeerOrSelfReviewSubmission>> {
208 let res = sqlx::query_as!(
209 PeerOrSelfReviewSubmission,
210 "
211SELECT prs.*
212FROM exercise_slide_submissions ess
213INNER JOIN peer_or_self_review_submissions prs ON (ess.id = prs.exercise_slide_submission_id)
214WHERE ess.user_id = $1
215 AND ess.course_id = $2
216 AND ess.deleted_at IS NULL
217 AND prs.deleted_at IS NULL
218 ",
219 user_id,
220 course_id
221 )
222 .fetch_all(conn)
223 .await?;
224 Ok(res)
225}
226
227pub async fn get_all_given_peer_or_self_review_submissions_for_user_and_course(
228 conn: &mut PgConnection,
229 user_id: Uuid,
230 course_id: Uuid,
231) -> ModelResult<Vec<PeerOrSelfReviewSubmission>> {
232 let res = sqlx::query_as!(
233 PeerOrSelfReviewSubmission,
234 "
235SELECT *
236FROM peer_or_self_review_submissions
237WHERE user_id = $1
238 AND course_id = $2
239 AND deleted_at IS NULL
240 ",
241 user_id,
242 course_id
243 )
244 .fetch_all(conn)
245 .await?;
246 Ok(res)
247}
248
249pub async fn get_num_peer_reviews_given_by_user_and_course_instance_and_exercise(
250 conn: &mut PgConnection,
251 user_id: Uuid,
252 course_id: Uuid,
253 exercise_id: Uuid,
254) -> ModelResult<i64> {
255 let res = sqlx::query!(
256 "
257SELECT COUNT(*)
258FROM peer_or_self_review_submissions
259WHERE user_id = $1
260 AND exercise_id = $3
261 AND course_id = $2
262 AND deleted_at IS NULL
263 ",
264 user_id,
265 course_id,
266 exercise_id
267 )
268 .fetch_one(conn)
269 .await?;
270 Ok(res.count.unwrap_or(0))
271}
272
273pub async fn get_peer_reviews_given_by_user_and_course_instance_and_exercise(
274 conn: &mut PgConnection,
275 user_id: Uuid,
276 course_id: Uuid,
277 exercise_id: Uuid,
278) -> ModelResult<Vec<PeerOrSelfReviewSubmission>> {
279 let res = sqlx::query_as!(
280 PeerOrSelfReviewSubmission,
281 "
282SELECT *
283FROM peer_or_self_review_submissions
284WHERE user_id = $1
285 AND exercise_id = $3
286 AND course_id = $2
287 AND deleted_at IS NULL
288 ",
289 user_id,
290 course_id,
291 exercise_id
292 )
293 .fetch_all(conn)
294 .await?;
295 Ok(res)
296}
297
298pub async fn get_users_submission_count_for_exercise_and_course_instance(
299 conn: &mut PgConnection,
300 user_id: Uuid,
301 exercise_id: Uuid,
302 course_id: Uuid,
303) -> ModelResult<u32> {
304 let res = sqlx::query!(
305 "
306SELECT COUNT(*) AS count
307FROM peer_or_self_review_submissions
308WHERE user_id = $1
309 AND exercise_id = $2
310 AND course_id = $3
311 AND deleted_at IS NULL
312 ",
313 user_id,
314 exercise_id,
315 course_id
316 )
317 .fetch_one(conn)
318 .await?;
319 Ok(res.count.unwrap_or(0).try_into()?)
320}
321
322pub async fn get_last_time_user_submitted_peer_review(
323 conn: &mut PgConnection,
324 user_id: Uuid,
325 exercise_id: Uuid,
326 course_id: Uuid,
327) -> ModelResult<Option<DateTime<Utc>>> {
328 let res = sqlx::query!(
329 "
330SELECT MAX(created_at) as latest_submission_time
331FROM peer_or_self_review_submissions
332WHERE user_id = $1
333 AND exercise_id = $2
334 AND course_id = $3
335 AND deleted_at IS NULL
336 ",
337 user_id,
338 exercise_id,
339 course_id
340 )
341 .fetch_optional(conn)
342 .await?;
343 Ok(res.and_then(|o| o.latest_submission_time))
344}
345
346pub async fn count_peer_or_self_review_submissions_for_exercise_slide_submission(
347 conn: &mut PgConnection,
348 exercise_slide_submission_id: Uuid,
349 exclude_user_ids: &[Uuid],
350) -> ModelResult<u32> {
351 let res = sqlx::query!(
352 "
353SELECT COUNT(*) AS count
354FROM peer_or_self_review_submissions
355WHERE exercise_slide_submission_id = $1
356 AND user_id != ALL($2)
357 AND deleted_at IS NULL
358 ",
359 exercise_slide_submission_id,
360 exclude_user_ids
361 )
362 .fetch_one(conn)
363 .await?;
364 Ok(res.count.unwrap_or(0).try_into()?)
365}
366
367pub async fn get_self_review_submission_by_user_and_exercise(
368 conn: &mut PgConnection,
369 user_id: Uuid,
370 exercise_id: Uuid,
371 course_id: Uuid,
372) -> ModelResult<Option<PeerOrSelfReviewSubmission>> {
373 let res = sqlx::query_as!(
374 PeerOrSelfReviewSubmission,
375 "
376SELECT prs.*
377FROM peer_or_self_review_submissions prs
378JOIN exercise_slide_submissions ess ON (ess.id = prs.exercise_slide_submission_id)
379WHERE ess.user_id = $1
380 AND prs.exercise_id = $2
381 AND prs.course_id = $3
382 AND prs.deleted_at IS NULL
383 AND ess.deleted_at IS NULL
384 ",
385 user_id,
386 exercise_id,
387 course_id
388 )
389 .fetch_optional(conn)
390 .await?;
391 Ok(res)
392}
393
394pub async fn get_received_peer_or_self_review_submissions_for_user_by_peer_or_self_review_config_id_and_exercise_slide_submission(
395 conn: &mut PgConnection,
396 user_id: Uuid,
397 exercise_slide_submission_id: Uuid,
398 peer_or_self_review_config_id: Uuid,
399) -> ModelResult<Vec<PeerOrSelfReviewSubmission>> {
400 let res = sqlx::query_as!(
401 PeerOrSelfReviewSubmission,
402 "
403SELECT prs.*
404FROM peer_or_self_review_submissions prs
405 JOIN exercise_slide_submissions ess ON (ess.id = prs.exercise_slide_submission_id)
406WHERE ess.user_id = $1
407 AND ess.id = $2
408 AND prs.peer_or_self_review_config_id = $3
409 AND prs.deleted_at IS NULL
410 AND ess.deleted_at IS NULL
411 ",
412 user_id,
413 exercise_slide_submission_id,
414 peer_or_self_review_config_id
415 )
416 .fetch_all(conn)
417 .await?;
418 Ok(res)
419}
420
421pub async fn get_mapping_from_peer_or_self_review_submission_ids_to_peer_review_giver_user_ids(
422 conn: &mut PgConnection,
423 peer_or_self_review_submission_ids: &[Uuid],
424) -> ModelResult<HashMap<Uuid, Uuid>> {
425 let full = get_by_ids(conn, peer_or_self_review_submission_ids).await?;
426 Ok(full
427 .into_iter()
428 .map(|submission: PeerOrSelfReviewSubmission| (submission.id, submission.user_id))
429 .collect())
430}