1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
use std::collections::HashMap;

use crate::peer_or_self_review_questions::PeerOrSelfReviewQuestionType;
use crate::prelude::*;

#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[cfg_attr(feature = "ts_rs", derive(TS))]
pub struct PeerOrSelfReviewQuestionSubmission {
    pub id: Uuid,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
    pub deleted_at: Option<DateTime<Utc>>,
    pub peer_or_self_review_question_id: Uuid,
    pub peer_or_self_review_submission_id: Uuid,
    pub text_data: Option<String>,
    pub number_data: Option<f32>,
}

pub async fn insert(
    conn: &mut PgConnection,
    pkey_policy: PKeyPolicy<Uuid>,
    peer_or_self_review_question_id: Uuid,
    peer_or_self_review_submission_id: Uuid,
    text_data: Option<String>,
    number_data: Option<f32>,
) -> ModelResult<Uuid> {
    let res = sqlx::query!(
        "
INSERT INTO peer_or_self_review_question_submissions (
    id,
    peer_or_self_review_question_id,
    peer_or_self_review_submission_id,
    text_data,
    number_data
  )
VALUES ($1, $2, $3, $4, $5)
RETURNING id
        ",
        pkey_policy.into_uuid(),
        peer_or_self_review_question_id,
        peer_or_self_review_submission_id,
        text_data,
        number_data,
    )
    .fetch_one(conn)
    .await?;
    Ok(res.id)
}

pub async fn get_by_peer_reviews_question_ids(
    conn: &mut PgConnection,
    ids: &[Uuid],
    user_id: Uuid,
    exercise_slide_submission_id: Uuid,
) -> ModelResult<Vec<PeerOrSelfReviewQuestionSubmission>> {
    let res = sqlx::query_as!(
        PeerOrSelfReviewQuestionSubmission,
        "
    SELECT qs.id,
        qs.created_at,
        qs.updated_at,
        qs.deleted_at,
        qs.peer_or_self_review_question_id,
        qs.peer_or_self_review_submission_id,
        qs.text_data,
        qs.number_data
    FROM peer_or_self_review_question_submissions qs
        JOIN peer_or_self_review_submissions s ON (qs.peer_or_self_review_submission_id = s.id)
        JOIN exercise_slide_submissions es ON (s.exercise_slide_submission_id = es.id)
    WHERE peer_or_self_review_question_id IN (
        SELECT UNNEST($1::uuid [])
    )
        AND s.exercise_slide_submission_id = $3
        AND es.user_id = $2
        AND qs.deleted_at IS NULL;
        ",
        ids,
        user_id,
        exercise_slide_submission_id
    )
    .fetch_all(conn)
    .await?;
    Ok(res)
}

pub async fn get_received_question_submissions_for_exercise_slide_submission(
    conn: &mut PgConnection,
    exercise_slide_submission_id: Uuid,
) -> ModelResult<Vec<PeerOrSelfReviewQuestionSubmission>> {
    let res = sqlx::query_as!(
        PeerOrSelfReviewQuestionSubmission,
        "
SELECT prqs.*
FROM peer_or_self_review_submissions prs
  JOIN peer_or_self_review_question_submissions prqs on prs.id = prqs.peer_or_self_review_submission_id
WHERE prs.exercise_slide_submission_id = $1
  AND prs.deleted_at IS NULL
  AND prqs.deleted_at IS NULL
    ",
        exercise_slide_submission_id
    )
    .fetch_all(conn)
    .await?;
    Ok(res)
}

pub async fn get_question_submissions_from_from_peer_or_self_review_submission_ids(
    conn: &mut PgConnection,
    peer_or_self_review_submission_ids: &[Uuid],
) -> ModelResult<Vec<PeerOrSelfReviewQuestionSubmission>> {
    let res = sqlx::query_as!(
        PeerOrSelfReviewQuestionSubmission,
        "
SELECT *
FROM peer_or_self_review_question_submissions
WHERE peer_or_self_review_submission_id IN (
    SELECT UNNEST($1::uuid [])
  )
  AND deleted_at IS NULL
            ",
        peer_or_self_review_submission_ids
    )
    .fetch_all(&mut *conn)
    .await?;

    Ok(res)
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[cfg_attr(feature = "ts_rs", derive(TS))]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum PeerOrSelfReviewAnswer {
    NoAnswer,
    Essay { value: String },
    Scale { value: f32 },
}

impl PeerOrSelfReviewAnswer {
    fn new(
        question_type: PeerOrSelfReviewQuestionType,
        text_data: Option<String>,
        number_data: Option<f32>,
    ) -> Self {
        match (question_type, text_data, number_data) {
            (PeerOrSelfReviewQuestionType::Essay, Some(value), _) => Self::Essay { value },
            (PeerOrSelfReviewQuestionType::Scale, _, Some(value)) => Self::Scale { value },
            _ => Self::NoAnswer,
        }
    }
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[cfg_attr(feature = "ts_rs", derive(TS))]
pub struct PeerOrSelfReviewQuestionAndAnswer {
    pub peer_or_self_review_config_id: Uuid,
    pub peer_or_self_review_question_id: Uuid,
    pub peer_or_self_review_submission_id: Uuid,
    pub peer_review_question_submission_id: Uuid,
    pub order_number: i32,
    pub question: String,
    pub answer: PeerOrSelfReviewAnswer,
    pub answer_required: bool,
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[cfg_attr(feature = "ts_rs", derive(TS))]
pub struct PeerReviewWithQuestionsAndAnswers {
    pub peer_or_self_review_submission_id: Uuid,
    pub peer_review_giver_user_id: Uuid,
    pub questions_and_answers: Vec<PeerOrSelfReviewQuestionAndAnswer>,
}

pub async fn get_questions_and_answers_by_submission_id(
    conn: &mut PgConnection,
    exercise_slide_submission_id: Uuid,
) -> ModelResult<Vec<PeerReviewWithQuestionsAndAnswers>> {
    let res = sqlx::query!(
        r#"
SELECT answers.id AS peer_review_question_submission_id,
  answers.text_data,
  answers.number_data,
  questions.peer_or_self_review_config_id,
  questions.id AS peer_or_self_review_question_id,
  questions.order_number,
  questions.question,
  questions.question_type AS "question_type: PeerOrSelfReviewQuestionType",
  questions.answer_required,
  submissions.id AS peer_or_self_review_submission_id
FROM peer_or_self_review_question_submissions answers
  JOIN peer_or_self_review_questions questions ON (
    answers.peer_or_self_review_question_id = questions.id
  )
  JOIN peer_or_self_review_submissions submissions ON (
    answers.peer_or_self_review_submission_id = submissions.id
  )
WHERE submissions.exercise_slide_submission_id = $1
  AND questions.deleted_at IS NULL
  AND answers.deleted_at IS NULL
  AND submissions.deleted_at IS NULL
        "#,
        exercise_slide_submission_id,
    )
    .map(|x| PeerOrSelfReviewQuestionAndAnswer {
        peer_or_self_review_config_id: x.peer_or_self_review_config_id,
        peer_or_self_review_question_id: x.peer_or_self_review_question_id,
        peer_review_question_submission_id: x.peer_review_question_submission_id,
        peer_or_self_review_submission_id: x.peer_or_self_review_submission_id,
        order_number: x.order_number,
        question: x.question,
        answer: PeerOrSelfReviewAnswer::new(x.question_type, x.text_data, x.number_data),
        answer_required: x.answer_required,
    })
    .fetch_all(&mut *conn)
    .await?;
    let peer_or_self_review_submission_ids = res
        .iter()
        .map(|x| x.peer_or_self_review_submission_id)
        .collect::<Vec<Uuid>>();
    let peer_review_submission_id_to_giver_user_id =
    crate::peer_or_self_review_submissions::get_mapping_from_peer_or_self_review_submission_ids_to_peer_review_giver_user_ids(
        &mut *conn,
        &peer_or_self_review_submission_ids,
    )
    .await?;
    Ok(bundle_peer_or_self_review_questions_and_answers(
        res,
        peer_review_submission_id_to_giver_user_id,
    ))
}

/** Returns the peer reviews given by this user. Does not return the self reviews given by the user. */
pub async fn get_given_peer_reviews(
    conn: &mut PgConnection,
    user_id: Uuid,
    exercise_id: Uuid,
    course_instance_id: Uuid,
) -> ModelResult<Vec<PeerReviewWithQuestionsAndAnswers>> {
    let res = sqlx::query!(
        r#"
SELECT answers.id AS peer_review_question_submission_id,
  answers.text_data,
  answers.number_data,
  questions.peer_or_self_review_config_id,
  questions.id AS peer_or_self_review_question_id,
  questions.order_number,
  questions.question,
  questions.question_type AS "question_type: PeerOrSelfReviewQuestionType",
  questions.answer_required,
  submissions.id AS peer_or_self_review_submission_id
FROM peer_or_self_review_question_submissions answers
  JOIN peer_or_self_review_questions questions ON (
    answers.peer_or_self_review_question_id = questions.id
  )
  JOIN peer_or_self_review_submissions submissions ON (
    answers.peer_or_self_review_submission_id = submissions.id
  )
  JOIN exercise_slide_submissions ess ON (
    submissions.exercise_slide_submission_id = ess.id
  )
WHERE submissions.user_id = $1
  AND submissions.exercise_id = $2
  AND submissions.course_instance_id = $3
  AND questions.deleted_at IS NULL
  AND answers.deleted_at IS NULL
  AND submissions.deleted_at IS NULL
  AND ess.user_id != $1
        "#,
        user_id,
        exercise_id,
        course_instance_id,
    )
    .map(|x| PeerOrSelfReviewQuestionAndAnswer {
        peer_or_self_review_config_id: x.peer_or_self_review_config_id,
        peer_or_self_review_question_id: x.peer_or_self_review_question_id,
        peer_review_question_submission_id: x.peer_review_question_submission_id,
        peer_or_self_review_submission_id: x.peer_or_self_review_submission_id,
        order_number: x.order_number,
        question: x.question,
        answer: PeerOrSelfReviewAnswer::new(x.question_type, x.text_data, x.number_data),
        answer_required: x.answer_required,
    })
    .fetch_all(&mut *conn)
    .await?;
    let peer_or_self_review_submission_ids = res
        .iter()
        .map(|x| x.peer_or_self_review_submission_id)
        .collect::<Vec<Uuid>>();
    let peer_review_submission_id_to_giver_user_id =
    crate::peer_or_self_review_submissions::get_mapping_from_peer_or_self_review_submission_ids_to_peer_review_giver_user_ids(
        &mut *conn,
        &peer_or_self_review_submission_ids,
    )
    .await?;
    Ok(bundle_peer_or_self_review_questions_and_answers(
        res,
        peer_review_submission_id_to_giver_user_id,
    ))
}

/// Groups answers to peer reviews by peer review ids.
fn bundle_peer_or_self_review_questions_and_answers(
    questions_and_answers: Vec<PeerOrSelfReviewQuestionAndAnswer>,
    peer_review_submission_id_to_giver_user_id: HashMap<Uuid, Uuid>,
) -> Vec<PeerReviewWithQuestionsAndAnswers> {
    let mut mapped: HashMap<Uuid, Vec<PeerOrSelfReviewQuestionAndAnswer>> = HashMap::new();
    questions_and_answers.into_iter().for_each(|x| {
        mapped
            .entry(x.peer_or_self_review_submission_id)
            .or_default()
            .push(x)
    });

    mapped
        .into_iter()
        .map(|(id, qa)| PeerReviewWithQuestionsAndAnswers {
            peer_or_self_review_submission_id: id,
            peer_review_giver_user_id: *(peer_review_submission_id_to_giver_user_id
                .get(&id)
                .unwrap_or(&Uuid::nil())),
            questions_and_answers: qa,
        })
        .collect()
}