headless_lms_server/controllers/main_frontend/
exercises.rs1use futures::future;
4
5use headless_lms_models::exercises::Exercise;
6use models::{
7 exercise_slide_submissions::ExerciseSlideSubmission,
8 library::grading::AnswersRequiringAttention,
9};
10
11use crate::{domain::models_requests, prelude::*};
12
13#[derive(Debug, Serialize)]
14#[cfg_attr(feature = "ts_rs", derive(TS))]
15pub struct ExerciseSubmissions {
16 pub data: Vec<ExerciseSlideSubmission>,
17 pub total_pages: u32,
18}
19
20#[instrument(skip(pool))]
24async fn get_exercise(
25 pool: web::Data<PgPool>,
26 exercise_id: web::Path<Uuid>,
27 user: AuthUser,
28) -> ControllerResult<web::Json<Exercise>> {
29 let mut conn = pool.acquire().await?;
30
31 let exercise = models::exercises::get_by_id(&mut conn, *exercise_id).await?;
32
33 let token = if let Some(course_id) = exercise.course_id {
34 authorize(&mut conn, Act::View, Some(user.id), Res::Course(course_id)).await?
35 } else if let Some(exam_id) = exercise.exam_id {
36 authorize(&mut conn, Act::View, Some(user.id), Res::Exam(exam_id)).await?
37 } else {
38 return Err(ControllerError::new(
39 ControllerErrorType::BadRequest,
40 "Exercise is not associated with a course or exam".to_string(),
41 None,
42 ));
43 };
44
45 token.authorized_ok(web::Json(exercise))
46}
47
48#[instrument(skip(pool))]
52async fn get_exercise_submissions(
53 pool: web::Data<PgPool>,
54 exercise_id: web::Path<Uuid>,
55 pagination: web::Query<Pagination>,
56 user: AuthUser,
57) -> ControllerResult<web::Json<ExerciseSubmissions>> {
58 let mut conn = pool.acquire().await?;
59
60 let token = match models::exercises::get_course_or_exam_id(&mut conn, *exercise_id).await? {
61 CourseOrExamId::Course(id) => {
62 authorize(&mut conn, Act::Teach, Some(user.id), Res::Course(id)).await?
63 }
64 CourseOrExamId::Exam(id) => {
65 authorize(&mut conn, Act::Teach, Some(user.id), Res::Exam(id)).await?
66 }
67 };
68
69 let submission_count = models::exercise_slide_submissions::exercise_slide_submission_count(
70 &mut conn,
71 *exercise_id,
72 );
73 let mut conn = pool.acquire().await?;
74 let submissions = models::exercise_slide_submissions::exercise_slide_submissions(
75 &mut conn,
76 *exercise_id,
77 *pagination,
78 );
79 let (submission_count, submissions) = future::try_join(submission_count, submissions).await?;
80
81 let total_pages = pagination.total_pages(submission_count);
82
83 token.authorized_ok(web::Json(ExerciseSubmissions {
84 data: submissions,
85 total_pages,
86 }))
87}
88
89#[instrument(skip(pool, user))]
93async fn get_exercise_submissions_for_user(
94 pool: web::Data<PgPool>,
95 ids: web::Path<(Uuid, Uuid)>,
96 user: AuthUser,
97) -> ControllerResult<web::Json<Vec<ExerciseSlideSubmission>>> {
98 let (exercise_id, user_id) = ids.into_inner();
99 let mut conn = pool.acquire().await?;
100
101 let target_user = models::users::get_by_id(&mut conn, user_id).await?;
102
103 let course_or_exam_id =
104 models::exercises::get_course_or_exam_id(&mut conn, exercise_id).await?;
105
106 let token = match course_or_exam_id {
107 CourseOrExamId::Course(id) => {
108 authorize(&mut conn, Act::Teach, Some(user.id), Res::Course(id)).await?
109 }
110 CourseOrExamId::Exam(id) => {
111 authorize(&mut conn, Act::Teach, Some(user.id), Res::Exam(id)).await?
112 }
113 };
114
115 let submissions = models::exercise_slide_submissions::get_users_submissions_for_exercise(
116 &mut conn,
117 target_user.id,
118 exercise_id,
119 )
120 .await?;
121
122 token.authorized_ok(web::Json(submissions))
123}
124
125#[instrument(skip(pool))]
129async fn get_exercise_answers_requiring_attention(
130 pool: web::Data<PgPool>,
131 exercise_id: web::Path<Uuid>,
132 pagination: web::Query<Pagination>,
133 user: AuthUser,
134) -> ControllerResult<web::Json<AnswersRequiringAttention>> {
135 let mut conn = pool.acquire().await?;
136 let token = match models::exercises::get_course_or_exam_id(&mut conn, *exercise_id).await? {
137 CourseOrExamId::Course(id) => {
138 authorize(&mut conn, Act::Teach, Some(user.id), Res::Course(id)).await?
139 }
140 CourseOrExamId::Exam(id) => {
141 authorize(&mut conn, Act::Teach, Some(user.id), Res::Exam(id)).await?
142 }
143 };
144 let res = models::library::grading::get_paginated_answers_requiring_attention_for_exercise(
145 &mut conn,
146 *exercise_id,
147 *pagination,
148 user.id,
149 models_requests::fetch_service_info,
150 )
151 .await?;
152 token.authorized_ok(web::Json(res))
153}
154
155pub async fn get_exercises_by_course_id(
159 course_id: web::Path<Uuid>,
160 pool: web::Data<PgPool>,
161 user: AuthUser,
162) -> ControllerResult<web::Json<Vec<Exercise>>> {
163 let mut conn = pool.acquire().await?;
164
165 let token = authorize(
166 &mut conn,
167 Act::ViewUserProgressOrDetails,
168 Some(user.id),
169 Res::Course(*course_id),
170 )
171 .await?;
172
173 let mut exercises =
174 models::exercises::get_exercises_by_course_id(&mut conn, *course_id).await?;
175
176 exercises.sort_by_key(|e| (e.chapter_id, e.page_id, e.order_number));
177
178 token.authorized_ok(web::Json(exercises))
179}
180
181#[derive(Deserialize)]
182pub struct ResetExercisesPayload {
183 pub user_ids: Vec<Uuid>,
184 pub exercise_ids: Vec<Uuid>,
185 pub threshold: Option<f64>,
186 pub reset_all_below_max_points: bool,
187 pub reset_only_locked_peer_reviews: bool,
188}
189
190pub async fn reset_exercises_for_selected_users(
194 course_id: web::Path<Uuid>,
195 pool: web::Data<PgPool>,
196 user: AuthUser,
197 payload: web::Json<ResetExercisesPayload>,
198) -> ControllerResult<web::Json<i32>> {
199 let mut conn = pool.acquire().await?;
200
201 let token = authorize(
202 &mut conn,
203 Act::Teach,
204 Some(user.id),
205 Res::Course(*course_id),
206 )
207 .await?;
208
209 let users_and_exercises = models::exercises::collect_user_ids_and_exercise_ids_for_reset(
211 &mut conn,
212 &payload.user_ids,
213 &payload.exercise_ids,
214 payload.threshold,
215 payload.reset_all_below_max_points,
216 payload.reset_only_locked_peer_reviews,
217 )
218 .await?;
219
220 let reset_results = models::exercises::reset_exercises_for_selected_users(
222 &mut conn,
223 &users_and_exercises,
224 user.id,
225 *course_id,
226 )
227 .await?;
228
229 let successful_resets_count = reset_results.len();
230
231 token.authorized_ok(web::Json(successful_resets_count as i32))
232}
233
234pub fn _add_routes(cfg: &mut ServiceConfig) {
242 cfg.route(
243 "/{exercise_id}/submissions",
244 web::get().to(get_exercise_submissions),
245 )
246 .route(
247 "/{exercise_id}/answers-requiring-attention",
248 web::get().to(get_exercise_answers_requiring_attention),
249 )
250 .route(
251 "/{course_id}/exercises-by-course-id",
252 web::get().to(get_exercises_by_course_id),
253 )
254 .route(
255 "/{course_id}/reset-exercises-for-selected-users",
256 web::post().to(reset_exercises_for_selected_users),
257 )
258 .route("/{exercise_id}", web::get().to(get_exercise))
259 .route(
260 "/{exercise_id}/submissions/user/{user_id}",
261 web::get().to(get_exercise_submissions_for_user),
262 );
263}