headless_lms_models/
peer_or_self_review_question_submissions.rs

1use std::collections::HashMap;
2
3use crate::peer_or_self_review_questions::PeerOrSelfReviewQuestionType;
4use crate::prelude::*;
5use utoipa::ToSchema;
6
7#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema)]
8
9pub struct PeerOrSelfReviewQuestionSubmission {
10    pub id: Uuid,
11    pub created_at: DateTime<Utc>,
12    pub updated_at: DateTime<Utc>,
13    pub deleted_at: Option<DateTime<Utc>>,
14    pub peer_or_self_review_question_id: Uuid,
15    pub peer_or_self_review_submission_id: Uuid,
16    pub text_data: Option<String>,
17    pub number_data: Option<f32>,
18}
19
20pub async fn insert(
21    conn: &mut PgConnection,
22    pkey_policy: PKeyPolicy<Uuid>,
23    peer_or_self_review_question_id: Uuid,
24    peer_or_self_review_submission_id: Uuid,
25    text_data: Option<String>,
26    number_data: Option<f32>,
27) -> ModelResult<Uuid> {
28    let res = sqlx::query!(
29        "
30INSERT INTO peer_or_self_review_question_submissions (
31    id,
32    peer_or_self_review_question_id,
33    peer_or_self_review_submission_id,
34    text_data,
35    number_data
36  )
37VALUES ($1, $2, $3, $4, $5)
38RETURNING id
39        ",
40        pkey_policy.into_uuid(),
41        peer_or_self_review_question_id,
42        peer_or_self_review_submission_id,
43        text_data,
44        number_data,
45    )
46    .fetch_one(conn)
47    .await?;
48    Ok(res.id)
49}
50
51pub async fn get_by_peer_reviews_question_ids(
52    conn: &mut PgConnection,
53    ids: &[Uuid],
54    user_id: Uuid,
55    exercise_slide_submission_id: Uuid,
56) -> ModelResult<Vec<PeerOrSelfReviewQuestionSubmission>> {
57    let res = sqlx::query_as!(
58        PeerOrSelfReviewQuestionSubmission,
59        "
60    SELECT qs.id,
61        qs.created_at,
62        qs.updated_at,
63        qs.deleted_at,
64        qs.peer_or_self_review_question_id,
65        qs.peer_or_self_review_submission_id,
66        qs.text_data,
67        qs.number_data
68    FROM peer_or_self_review_question_submissions qs
69        JOIN peer_or_self_review_submissions s ON (qs.peer_or_self_review_submission_id = s.id)
70        JOIN exercise_slide_submissions es ON (s.exercise_slide_submission_id = es.id)
71    WHERE peer_or_self_review_question_id IN (
72        SELECT UNNEST($1::uuid [])
73    )
74        AND s.exercise_slide_submission_id = $3
75        AND es.user_id = $2
76        AND qs.deleted_at IS NULL;
77        ",
78        ids,
79        user_id,
80        exercise_slide_submission_id
81    )
82    .fetch_all(conn)
83    .await?;
84    Ok(res)
85}
86
87pub async fn get_received_question_submissions_for_exercise_slide_submission(
88    conn: &mut PgConnection,
89    exercise_slide_submission_id: Uuid,
90) -> ModelResult<Vec<PeerOrSelfReviewQuestionSubmission>> {
91    let res = sqlx::query_as!(
92        PeerOrSelfReviewQuestionSubmission,
93        "
94SELECT prqs.*
95FROM peer_or_self_review_submissions prs
96  JOIN peer_or_self_review_question_submissions prqs on prs.id = prqs.peer_or_self_review_submission_id
97WHERE prs.exercise_slide_submission_id = $1
98  AND prs.deleted_at IS NULL
99  AND prqs.deleted_at IS NULL
100    ",
101        exercise_slide_submission_id
102    )
103    .fetch_all(conn)
104    .await?;
105    Ok(res)
106}
107
108pub async fn get_question_submissions_from_from_peer_or_self_review_submission_ids(
109    conn: &mut PgConnection,
110    peer_or_self_review_submission_ids: &[Uuid],
111) -> ModelResult<Vec<PeerOrSelfReviewQuestionSubmission>> {
112    let res = sqlx::query_as!(
113        PeerOrSelfReviewQuestionSubmission,
114        "
115SELECT *
116FROM peer_or_self_review_question_submissions
117WHERE peer_or_self_review_submission_id IN (
118    SELECT UNNEST($1::uuid [])
119  )
120  AND deleted_at IS NULL
121            ",
122        peer_or_self_review_submission_ids
123    )
124    .fetch_all(&mut *conn)
125    .await?;
126
127    Ok(res)
128}
129
130#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema)]
131#[serde(tag = "type", rename_all = "kebab-case")]
132pub enum PeerOrSelfReviewAnswer {
133    NoAnswer,
134    Essay { value: String },
135    Scale { value: f32 },
136}
137
138impl PeerOrSelfReviewAnswer {
139    fn new(
140        question_type: PeerOrSelfReviewQuestionType,
141        text_data: Option<String>,
142        number_data: Option<f32>,
143    ) -> Self {
144        match (question_type, text_data, number_data) {
145            (PeerOrSelfReviewQuestionType::Essay, Some(value), _) => Self::Essay { value },
146            (PeerOrSelfReviewQuestionType::Scale, _, Some(value)) => Self::Scale { value },
147            _ => Self::NoAnswer,
148        }
149    }
150}
151
152#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema)]
153
154pub struct PeerOrSelfReviewQuestionAndAnswer {
155    pub peer_or_self_review_config_id: Uuid,
156    pub peer_or_self_review_question_id: Uuid,
157    pub peer_or_self_review_submission_id: Uuid,
158    pub peer_review_question_submission_id: Uuid,
159    pub order_number: i32,
160    pub question: String,
161    pub answer: PeerOrSelfReviewAnswer,
162    pub answer_required: bool,
163}
164
165#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema)]
166
167pub struct PeerReviewWithQuestionsAndAnswers {
168    pub peer_or_self_review_submission_id: Uuid,
169    pub peer_review_giver_user_id: Uuid,
170    pub questions_and_answers: Vec<PeerOrSelfReviewQuestionAndAnswer>,
171}
172
173pub async fn get_questions_and_answers_by_submission_id(
174    conn: &mut PgConnection,
175    exercise_slide_submission_id: Uuid,
176) -> ModelResult<Vec<PeerReviewWithQuestionsAndAnswers>> {
177    let res = sqlx::query!(
178        r#"
179SELECT answers.id AS peer_review_question_submission_id,
180  answers.text_data,
181  answers.number_data,
182  questions.peer_or_self_review_config_id,
183  questions.id AS peer_or_self_review_question_id,
184  questions.order_number,
185  questions.question,
186  questions.question_type AS "question_type: PeerOrSelfReviewQuestionType",
187  questions.answer_required,
188  submissions.id AS peer_or_self_review_submission_id
189FROM peer_or_self_review_question_submissions answers
190  JOIN peer_or_self_review_questions questions ON (
191    answers.peer_or_self_review_question_id = questions.id
192  )
193  JOIN peer_or_self_review_submissions submissions ON (
194    answers.peer_or_self_review_submission_id = submissions.id
195  )
196WHERE submissions.exercise_slide_submission_id = $1
197  AND questions.deleted_at IS NULL
198  AND answers.deleted_at IS NULL
199  AND submissions.deleted_at IS NULL
200        "#,
201        exercise_slide_submission_id,
202    )
203    .map(|x| PeerOrSelfReviewQuestionAndAnswer {
204        peer_or_self_review_config_id: x.peer_or_self_review_config_id,
205        peer_or_self_review_question_id: x.peer_or_self_review_question_id,
206        peer_review_question_submission_id: x.peer_review_question_submission_id,
207        peer_or_self_review_submission_id: x.peer_or_self_review_submission_id,
208        order_number: x.order_number,
209        question: x.question,
210        answer: PeerOrSelfReviewAnswer::new(x.question_type, x.text_data, x.number_data),
211        answer_required: x.answer_required,
212    })
213    .fetch_all(&mut *conn)
214    .await?;
215    let peer_or_self_review_submission_ids = res
216        .iter()
217        .map(|x| x.peer_or_self_review_submission_id)
218        .collect::<Vec<Uuid>>();
219    let peer_review_submission_id_to_giver_user_id =
220    crate::peer_or_self_review_submissions::get_mapping_from_peer_or_self_review_submission_ids_to_peer_review_giver_user_ids(
221        &mut *conn,
222        &peer_or_self_review_submission_ids,
223    )
224    .await?;
225    Ok(bundle_peer_or_self_review_questions_and_answers(
226        res,
227        peer_review_submission_id_to_giver_user_id,
228    ))
229}
230
231/** Returns the peer reviews given by this user. Does not return the self reviews given by the user. */
232pub async fn get_given_peer_reviews(
233    conn: &mut PgConnection,
234    user_id: Uuid,
235    exercise_id: Uuid,
236) -> ModelResult<Vec<PeerReviewWithQuestionsAndAnswers>> {
237    let res = sqlx::query!(
238        r#"
239SELECT answers.id AS peer_review_question_submission_id,
240  answers.text_data,
241  answers.number_data,
242  questions.peer_or_self_review_config_id,
243  questions.id AS peer_or_self_review_question_id,
244  questions.order_number,
245  questions.question,
246  questions.question_type AS "question_type: PeerOrSelfReviewQuestionType",
247  questions.answer_required,
248  submissions.id AS peer_or_self_review_submission_id
249FROM peer_or_self_review_question_submissions answers
250  JOIN peer_or_self_review_questions questions ON (
251    answers.peer_or_self_review_question_id = questions.id
252  )
253  JOIN peer_or_self_review_submissions submissions ON (
254    answers.peer_or_self_review_submission_id = submissions.id
255  )
256  JOIN exercise_slide_submissions ess ON (
257    submissions.exercise_slide_submission_id = ess.id
258  )
259WHERE submissions.user_id = $1
260  AND submissions.exercise_id = $2
261  AND questions.deleted_at IS NULL
262  AND answers.deleted_at IS NULL
263  AND submissions.deleted_at IS NULL
264  AND ess.user_id != $1
265        "#,
266        user_id,
267        exercise_id,
268    )
269    .map(|x| PeerOrSelfReviewQuestionAndAnswer {
270        peer_or_self_review_config_id: x.peer_or_self_review_config_id,
271        peer_or_self_review_question_id: x.peer_or_self_review_question_id,
272        peer_review_question_submission_id: x.peer_review_question_submission_id,
273        peer_or_self_review_submission_id: x.peer_or_self_review_submission_id,
274        order_number: x.order_number,
275        question: x.question,
276        answer: PeerOrSelfReviewAnswer::new(x.question_type, x.text_data, x.number_data),
277        answer_required: x.answer_required,
278    })
279    .fetch_all(&mut *conn)
280    .await?;
281    let peer_or_self_review_submission_ids = res
282        .iter()
283        .map(|x| x.peer_or_self_review_submission_id)
284        .collect::<Vec<Uuid>>();
285    let peer_review_submission_id_to_giver_user_id =
286    crate::peer_or_self_review_submissions::get_mapping_from_peer_or_self_review_submission_ids_to_peer_review_giver_user_ids(
287        &mut *conn,
288        &peer_or_self_review_submission_ids,
289    )
290    .await?;
291    Ok(bundle_peer_or_self_review_questions_and_answers(
292        res,
293        peer_review_submission_id_to_giver_user_id,
294    ))
295}
296
297/// Groups answers to peer reviews by peer review ids.
298fn bundle_peer_or_self_review_questions_and_answers(
299    questions_and_answers: Vec<PeerOrSelfReviewQuestionAndAnswer>,
300    peer_review_submission_id_to_giver_user_id: HashMap<Uuid, Uuid>,
301) -> Vec<PeerReviewWithQuestionsAndAnswers> {
302    let mut mapped: HashMap<Uuid, Vec<PeerOrSelfReviewQuestionAndAnswer>> = HashMap::new();
303    questions_and_answers.into_iter().for_each(|x| {
304        mapped
305            .entry(x.peer_or_self_review_submission_id)
306            .or_default()
307            .push(x)
308    });
309
310    mapped
311        .into_iter()
312        .map(|(id, qa)| PeerReviewWithQuestionsAndAnswers {
313            peer_or_self_review_submission_id: id,
314            peer_review_giver_user_id: *(peer_review_submission_id_to_giver_user_id
315                .get(&id)
316                .unwrap_or(&Uuid::nil())),
317            questions_and_answers: qa,
318        })
319        .collect()
320}