1use std::collections::HashMap;
2
3use crate::peer_or_self_review_questions::PeerOrSelfReviewQuestionType;
4use crate::prelude::*;
5
6#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
7#[cfg_attr(feature = "ts_rs", derive(TS))]
8pub struct PeerOrSelfReviewQuestionSubmission {
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 peer_or_self_review_question_id: Uuid,
14 pub peer_or_self_review_submission_id: Uuid,
15 pub text_data: Option<String>,
16 pub number_data: Option<f32>,
17}
18
19pub async fn insert(
20 conn: &mut PgConnection,
21 pkey_policy: PKeyPolicy<Uuid>,
22 peer_or_self_review_question_id: Uuid,
23 peer_or_self_review_submission_id: Uuid,
24 text_data: Option<String>,
25 number_data: Option<f32>,
26) -> ModelResult<Uuid> {
27 let res = sqlx::query!(
28 "
29INSERT INTO peer_or_self_review_question_submissions (
30 id,
31 peer_or_self_review_question_id,
32 peer_or_self_review_submission_id,
33 text_data,
34 number_data
35 )
36VALUES ($1, $2, $3, $4, $5)
37RETURNING id
38 ",
39 pkey_policy.into_uuid(),
40 peer_or_self_review_question_id,
41 peer_or_self_review_submission_id,
42 text_data,
43 number_data,
44 )
45 .fetch_one(conn)
46 .await?;
47 Ok(res.id)
48}
49
50pub async fn get_by_peer_reviews_question_ids(
51 conn: &mut PgConnection,
52 ids: &[Uuid],
53 user_id: Uuid,
54 exercise_slide_submission_id: Uuid,
55) -> ModelResult<Vec<PeerOrSelfReviewQuestionSubmission>> {
56 let res = sqlx::query_as!(
57 PeerOrSelfReviewQuestionSubmission,
58 "
59 SELECT qs.id,
60 qs.created_at,
61 qs.updated_at,
62 qs.deleted_at,
63 qs.peer_or_self_review_question_id,
64 qs.peer_or_self_review_submission_id,
65 qs.text_data,
66 qs.number_data
67 FROM peer_or_self_review_question_submissions qs
68 JOIN peer_or_self_review_submissions s ON (qs.peer_or_self_review_submission_id = s.id)
69 JOIN exercise_slide_submissions es ON (s.exercise_slide_submission_id = es.id)
70 WHERE peer_or_self_review_question_id IN (
71 SELECT UNNEST($1::uuid [])
72 )
73 AND s.exercise_slide_submission_id = $3
74 AND es.user_id = $2
75 AND qs.deleted_at IS NULL;
76 ",
77 ids,
78 user_id,
79 exercise_slide_submission_id
80 )
81 .fetch_all(conn)
82 .await?;
83 Ok(res)
84}
85
86pub async fn get_received_question_submissions_for_exercise_slide_submission(
87 conn: &mut PgConnection,
88 exercise_slide_submission_id: Uuid,
89) -> ModelResult<Vec<PeerOrSelfReviewQuestionSubmission>> {
90 let res = sqlx::query_as!(
91 PeerOrSelfReviewQuestionSubmission,
92 "
93SELECT prqs.*
94FROM peer_or_self_review_submissions prs
95 JOIN peer_or_self_review_question_submissions prqs on prs.id = prqs.peer_or_self_review_submission_id
96WHERE prs.exercise_slide_submission_id = $1
97 AND prs.deleted_at IS NULL
98 AND prqs.deleted_at IS NULL
99 ",
100 exercise_slide_submission_id
101 )
102 .fetch_all(conn)
103 .await?;
104 Ok(res)
105}
106
107pub async fn get_question_submissions_from_from_peer_or_self_review_submission_ids(
108 conn: &mut PgConnection,
109 peer_or_self_review_submission_ids: &[Uuid],
110) -> ModelResult<Vec<PeerOrSelfReviewQuestionSubmission>> {
111 let res = sqlx::query_as!(
112 PeerOrSelfReviewQuestionSubmission,
113 "
114SELECT *
115FROM peer_or_self_review_question_submissions
116WHERE peer_or_self_review_submission_id IN (
117 SELECT UNNEST($1::uuid [])
118 )
119 AND deleted_at IS NULL
120 ",
121 peer_or_self_review_submission_ids
122 )
123 .fetch_all(&mut *conn)
124 .await?;
125
126 Ok(res)
127}
128
129#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
130#[cfg_attr(feature = "ts_rs", derive(TS))]
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)]
153#[cfg_attr(feature = "ts_rs", derive(TS))]
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)]
166#[cfg_attr(feature = "ts_rs", derive(TS))]
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
231pub async fn get_given_peer_reviews(
233 conn: &mut PgConnection,
234 user_id: Uuid,
235 exercise_id: Uuid,
236 course_instance_id: Uuid,
237) -> ModelResult<Vec<PeerReviewWithQuestionsAndAnswers>> {
238 let res = sqlx::query!(
239 r#"
240SELECT answers.id AS peer_review_question_submission_id,
241 answers.text_data,
242 answers.number_data,
243 questions.peer_or_self_review_config_id,
244 questions.id AS peer_or_self_review_question_id,
245 questions.order_number,
246 questions.question,
247 questions.question_type AS "question_type: PeerOrSelfReviewQuestionType",
248 questions.answer_required,
249 submissions.id AS peer_or_self_review_submission_id
250FROM peer_or_self_review_question_submissions answers
251 JOIN peer_or_self_review_questions questions ON (
252 answers.peer_or_self_review_question_id = questions.id
253 )
254 JOIN peer_or_self_review_submissions submissions ON (
255 answers.peer_or_self_review_submission_id = submissions.id
256 )
257 JOIN exercise_slide_submissions ess ON (
258 submissions.exercise_slide_submission_id = ess.id
259 )
260WHERE submissions.user_id = $1
261 AND submissions.exercise_id = $2
262 AND submissions.course_instance_id = $3
263 AND questions.deleted_at IS NULL
264 AND answers.deleted_at IS NULL
265 AND submissions.deleted_at IS NULL
266 AND ess.user_id != $1
267 "#,
268 user_id,
269 exercise_id,
270 course_instance_id,
271 )
272 .map(|x| PeerOrSelfReviewQuestionAndAnswer {
273 peer_or_self_review_config_id: x.peer_or_self_review_config_id,
274 peer_or_self_review_question_id: x.peer_or_self_review_question_id,
275 peer_review_question_submission_id: x.peer_review_question_submission_id,
276 peer_or_self_review_submission_id: x.peer_or_self_review_submission_id,
277 order_number: x.order_number,
278 question: x.question,
279 answer: PeerOrSelfReviewAnswer::new(x.question_type, x.text_data, x.number_data),
280 answer_required: x.answer_required,
281 })
282 .fetch_all(&mut *conn)
283 .await?;
284 let peer_or_self_review_submission_ids = res
285 .iter()
286 .map(|x| x.peer_or_self_review_submission_id)
287 .collect::<Vec<Uuid>>();
288 let peer_review_submission_id_to_giver_user_id =
289 crate::peer_or_self_review_submissions::get_mapping_from_peer_or_self_review_submission_ids_to_peer_review_giver_user_ids(
290 &mut *conn,
291 &peer_or_self_review_submission_ids,
292 )
293 .await?;
294 Ok(bundle_peer_or_self_review_questions_and_answers(
295 res,
296 peer_review_submission_id_to_giver_user_id,
297 ))
298}
299
300fn bundle_peer_or_self_review_questions_and_answers(
302 questions_and_answers: Vec<PeerOrSelfReviewQuestionAndAnswer>,
303 peer_review_submission_id_to_giver_user_id: HashMap<Uuid, Uuid>,
304) -> Vec<PeerReviewWithQuestionsAndAnswers> {
305 let mut mapped: HashMap<Uuid, Vec<PeerOrSelfReviewQuestionAndAnswer>> = HashMap::new();
306 questions_and_answers.into_iter().for_each(|x| {
307 mapped
308 .entry(x.peer_or_self_review_submission_id)
309 .or_default()
310 .push(x)
311 });
312
313 mapped
314 .into_iter()
315 .map(|(id, qa)| PeerReviewWithQuestionsAndAnswers {
316 peer_or_self_review_submission_id: id,
317 peer_review_giver_user_id: *(peer_review_submission_id_to_giver_user_id
318 .get(&id)
319 .unwrap_or(&Uuid::nil())),
320 questions_and_answers: qa,
321 })
322 .collect()
323}