headless_lms_server/controllers/course_material/
course_instances.rs1use headless_lms_utils::numbers::option_f32_to_f32_two_decimals_with_none_as_zero;
4use models::{
5 chapters::UserCourseInstanceChapterProgress,
6 course_background_question_answers::NewCourseBackgroundQuestionAnswer,
7 course_background_questions::CourseBackgroundQuestionsAndAnswers,
8 course_instance_enrollments::CourseInstanceEnrollment,
9 course_module_completions::CourseModuleCompletion,
10 library::progressing::UserModuleCompletionStatus,
11 user_exercise_states::{UserCourseChapterExerciseProgress, UserCourseProgress},
12};
13
14use crate::{domain::authorization::skip_authorize, prelude::*};
15
16#[instrument(skip(pool))]
20async fn get_user_progress_for_course_instance(
21 user: AuthUser,
22 course_instance_id: web::Path<Uuid>,
23 pool: web::Data<PgPool>,
24) -> ControllerResult<web::Json<Vec<UserCourseProgress>>> {
25 let mut conn = pool.acquire().await?;
26 let course_instance =
27 models::course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
28 let user_course_progress = models::user_exercise_states::get_user_course_progress(
29 &mut conn,
30 course_instance.course_id,
31 user.id,
32 )
33 .await?;
34 let token = skip_authorize();
35 token.authorized_ok(web::Json(user_course_progress))
36}
37
38#[instrument(skip(pool))]
42async fn get_user_progress_for_course_instance_chapter(
43 user: AuthUser,
44 params: web::Path<(Uuid, Uuid)>,
45 pool: web::Data<PgPool>,
46) -> ControllerResult<web::Json<UserCourseInstanceChapterProgress>> {
47 let mut conn = pool.acquire().await?;
48 let (course_instance_id, chapter_id) = params.into_inner();
49 let user_course_instance_chapter_progress =
50 models::chapters::get_user_course_instance_chapter_progress(
51 &mut conn,
52 course_instance_id,
53 chapter_id,
54 user.id,
55 )
56 .await?;
57 let token = skip_authorize();
58 token.authorized_ok(web::Json(user_course_instance_chapter_progress))
59}
60
61#[instrument(skip(pool))]
65async fn get_user_progress_for_course_instance_chapter_exercises(
66 user: AuthUser,
67 params: web::Path<(Uuid, Uuid)>,
68 pool: web::Data<PgPool>,
69) -> ControllerResult<web::Json<Vec<UserCourseChapterExerciseProgress>>> {
70 let mut conn = pool.acquire().await?;
71 let (course_instance_id, chapter_id) = params.into_inner();
72 let chapter_exercises =
73 models::exercises::get_exercises_by_chapter_id(&mut conn, chapter_id).await?;
74 let exercise_ids: Vec<Uuid> = chapter_exercises.into_iter().map(|e| e.id).collect();
75 let course_instance =
76 models::course_instances::get_course_instance(&mut conn, course_instance_id).await?;
77 let user_course_instance_exercise_progress =
78 models::user_exercise_states::get_user_course_chapter_exercises_progress(
79 &mut conn,
80 course_instance.course_id,
81 &exercise_ids,
82 user.id,
83 )
84 .await?;
85 let rounded_score_given_instances: Vec<UserCourseChapterExerciseProgress> =
86 user_course_instance_exercise_progress
87 .into_iter()
88 .map(|i| UserCourseChapterExerciseProgress {
89 score_given: option_f32_to_f32_two_decimals_with_none_as_zero(i.score_given),
90 exercise_id: i.exercise_id,
91 })
92 .collect();
93 let token = skip_authorize();
94 token.authorized_ok(web::Json(rounded_score_given_instances))
95}
96
97#[instrument(skip(pool))]
101async fn get_module_completions_for_course_instance(
102 user: AuthUser,
103 course_instance_id: web::Path<Uuid>,
104 pool: web::Data<PgPool>,
105) -> ControllerResult<web::Json<Vec<UserModuleCompletionStatus>>> {
106 let mut conn = pool.acquire().await?;
107 let token = skip_authorize();
108
109 let course_instance =
110 models::course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
111 let mut module_completion_statuses =
112 models::library::progressing::get_user_module_completion_statuses_for_course(
113 &mut conn,
114 user.id,
115 course_instance.course_id,
116 )
117 .await?;
118 module_completion_statuses.iter_mut().for_each(|module| {
120 if !module.prerequisite_modules_completed {
121 module.completed = false;
122 module.certificate_configuration_id = None;
123 }
124 });
125 token.authorized_ok(web::Json(module_completion_statuses))
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
129#[cfg_attr(feature = "ts_rs", derive(TS))]
130pub struct SaveCourseSettingsPayload {
131 pub background_question_answers: Vec<NewCourseBackgroundQuestionAnswer>,
132}
133
134#[instrument(skip(pool))]
138async fn save_course_settings(
139 pool: web::Data<PgPool>,
140 course_instance_id: web::Path<Uuid>,
141 payload: web::Json<SaveCourseSettingsPayload>,
142 user: AuthUser,
143) -> ControllerResult<web::Json<CourseInstanceEnrollment>> {
144 let mut conn = pool.acquire().await?;
145
146 let enrollment = models::library::course_instances::enroll(
147 &mut conn,
148 user.id,
149 *course_instance_id,
150 payload.background_question_answers.as_slice(),
151 )
152 .await?;
153 let token = skip_authorize();
154 token.authorized_ok(web::Json(enrollment))
155}
156
157#[instrument(skip(pool))]
161
162async fn get_all_get_all_course_module_completions_for_user_by_course_instance_id(
163 params: web::Path<(Uuid, Uuid)>,
164 pool: web::Data<PgPool>,
165 user: AuthUser,
166) -> ControllerResult<web::Json<Vec<CourseModuleCompletion>>> {
167 let (course_instance_id, user_id) = params.into_inner();
168 let mut conn = pool.acquire().await?;
169 let token = authorize(
170 &mut conn,
171 Act::ViewUserProgressOrDetails,
172 Some(user.id),
173 Res::CourseInstance(course_instance_id),
174 )
175 .await?;
176
177 let course_instance =
178 models::course_instances::get_course_instance(&mut conn, course_instance_id).await?;
179
180 let res = models::course_module_completions::get_all_by_course_id_and_user_id(
181 &mut conn,
182 course_instance.course_id,
183 user_id,
184 )
185 .await?;
186
187 token.authorized_ok(web::Json(res))
188}
189
190#[instrument(skip(pool))]
194async fn get_background_questions_and_answers(
195 pool: web::Data<PgPool>,
196 course_instance_id: web::Path<Uuid>,
197 user: AuthUser,
198) -> ControllerResult<web::Json<CourseBackgroundQuestionsAndAnswers>> {
199 let mut conn = pool.acquire().await?;
200
201 let instance =
202 models::course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
203 let res = models::course_background_questions::get_background_questions_and_answers(
204 &mut conn, &instance, user.id,
205 )
206 .await?;
207 let token = skip_authorize();
208 token.authorized_ok(web::Json(res))
209}
210
211pub fn _add_routes(cfg: &mut ServiceConfig) {
212 cfg.route(
213 "/{course_instance_id}/save-course-settings",
214 web::post().to(save_course_settings),
215 )
216 .route(
217 "/{course_instance_id}/progress",
218 web::get().to(get_user_progress_for_course_instance),
219 )
220 .route(
221 "/{course_instance_id}/chapters/{chapter_id}/exercises/progress",
222 web::get().to(get_user_progress_for_course_instance_chapter_exercises),
223 )
224 .route(
225 "/{course_instance_id}/chapters/{chapter_id}/progress",
226 web::get().to(get_user_progress_for_course_instance_chapter),
227 )
228 .route(
229 "/{course_instance_id}/module-completions",
230 web::get().to(get_module_completions_for_course_instance),
231 )
232 .route(
233 "/{course_instance_id}/course-module-completions/{user_id}",
234 web::get().to(get_all_get_all_course_module_completions_for_user_by_course_instance_id),
235 )
236 .route(
237 "/{course_instance_id}/background-questions-and-answers",
238 web::get().to(get_background_questions_and_answers),
239 );
240}