headless_lms_models/
peer_or_self_review_submissions.rs

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