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::{UserCourseInstanceChapterExerciseProgress, UserCourseInstanceProgress},
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<UserCourseInstanceProgress>>> {
25 let mut conn = pool.acquire().await?;
26 let user_course_instance_progress =
27 models::user_exercise_states::get_user_course_instance_progress(
28 &mut conn,
29 *course_instance_id,
30 user.id,
31 )
32 .await?;
33 let token = skip_authorize();
34 token.authorized_ok(web::Json(user_course_instance_progress))
35}
36
37#[instrument(skip(pool))]
41async fn get_user_progress_for_course_instance_chapter(
42 user: AuthUser,
43 params: web::Path<(Uuid, Uuid)>,
44 pool: web::Data<PgPool>,
45) -> ControllerResult<web::Json<UserCourseInstanceChapterProgress>> {
46 let mut conn = pool.acquire().await?;
47 let (course_instance_id, chapter_id) = params.into_inner();
48 let user_course_instance_chapter_progress =
49 models::chapters::get_user_course_instance_chapter_progress(
50 &mut conn,
51 course_instance_id,
52 chapter_id,
53 user.id,
54 )
55 .await?;
56 let token = skip_authorize();
57 token.authorized_ok(web::Json(user_course_instance_chapter_progress))
58}
59
60#[instrument(skip(pool))]
64async fn get_user_progress_for_course_instance_chapter_exercises(
65 user: AuthUser,
66 params: web::Path<(Uuid, Uuid)>,
67 pool: web::Data<PgPool>,
68) -> ControllerResult<web::Json<Vec<UserCourseInstanceChapterExerciseProgress>>> {
69 let mut conn = pool.acquire().await?;
70 let (course_instance_id, chapter_id) = params.into_inner();
71 let chapter_exercises =
72 models::exercises::get_exercises_by_chapter_id(&mut conn, chapter_id).await?;
73 let exercise_ids: Vec<Uuid> = chapter_exercises.into_iter().map(|e| e.id).collect();
74
75 let user_course_instance_exercise_progress =
76 models::user_exercise_states::get_user_course_instance_chapter_exercises_progress(
77 &mut conn,
78 course_instance_id,
79 &exercise_ids,
80 user.id,
81 )
82 .await?;
83 let rounded_score_given_instances: Vec<UserCourseInstanceChapterExerciseProgress> =
84 user_course_instance_exercise_progress
85 .into_iter()
86 .map(|i| UserCourseInstanceChapterExerciseProgress {
87 score_given: option_f32_to_f32_two_decimals_with_none_as_zero(i.score_given),
88 exercise_id: i.exercise_id,
89 })
90 .collect();
91 let token = skip_authorize();
92 token.authorized_ok(web::Json(rounded_score_given_instances))
93}
94
95#[instrument(skip(pool))]
99async fn get_module_completions_for_course_instance(
100 user: AuthUser,
101 course_instance_id: web::Path<Uuid>,
102 pool: web::Data<PgPool>,
103) -> ControllerResult<web::Json<Vec<UserModuleCompletionStatus>>> {
104 let mut conn = pool.acquire().await?;
105 let token = skip_authorize();
106 let mut module_completion_statuses =
107 models::library::progressing::get_user_module_completion_statuses_for_course_instance(
108 &mut conn,
109 user.id,
110 *course_instance_id,
111 )
112 .await?;
113 module_completion_statuses.iter_mut().for_each(|module| {
115 if !module.prerequisite_modules_completed {
116 module.completed = false;
117 module.certificate_configuration_id = None;
118 }
119 });
120 token.authorized_ok(web::Json(module_completion_statuses))
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
124#[cfg_attr(feature = "ts_rs", derive(TS))]
125pub struct SaveCourseSettingsPayload {
126 pub background_question_answers: Vec<NewCourseBackgroundQuestionAnswer>,
127}
128
129#[instrument(skip(pool))]
133async fn save_course_settings(
134 pool: web::Data<PgPool>,
135 course_instance_id: web::Path<Uuid>,
136 payload: web::Json<SaveCourseSettingsPayload>,
137 user: AuthUser,
138) -> ControllerResult<web::Json<CourseInstanceEnrollment>> {
139 let mut conn = pool.acquire().await?;
140
141 let enrollment = models::library::course_instances::enroll(
142 &mut conn,
143 user.id,
144 *course_instance_id,
145 payload.background_question_answers.as_slice(),
146 )
147 .await?;
148 let token = skip_authorize();
149 token.authorized_ok(web::Json(enrollment))
150}
151
152#[instrument(skip(pool))]
156
157async fn get_all_get_all_course_module_completions_for_user_by_course_instance_id(
158 params: web::Path<(Uuid, Uuid)>,
159 pool: web::Data<PgPool>,
160 user: AuthUser,
161) -> ControllerResult<web::Json<Vec<CourseModuleCompletion>>> {
162 let (course_instance_id, user_id) = params.into_inner();
163 let mut conn = pool.acquire().await?;
164 let token = authorize(
165 &mut conn,
166 Act::ViewUserProgressOrDetails,
167 Some(user.id),
168 Res::CourseInstance(course_instance_id),
169 )
170 .await?;
171
172 let res = models::course_module_completions::get_all_by_course_instance_and_user_id(
173 &mut conn,
174 course_instance_id,
175 user_id,
176 )
177 .await?;
178
179 token.authorized_ok(web::Json(res))
180}
181
182#[instrument(skip(pool))]
186async fn get_background_questions_and_answers(
187 pool: web::Data<PgPool>,
188 course_instance_id: web::Path<Uuid>,
189 user: AuthUser,
190) -> ControllerResult<web::Json<CourseBackgroundQuestionsAndAnswers>> {
191 let mut conn = pool.acquire().await?;
192
193 let instance =
194 models::course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
195 let res = models::course_background_questions::get_background_questions_and_answers(
196 &mut conn, &instance, user.id,
197 )
198 .await?;
199 let token = skip_authorize();
200 token.authorized_ok(web::Json(res))
201}
202
203pub fn _add_routes(cfg: &mut ServiceConfig) {
204 cfg.route(
205 "/{course_instance_id}/save-course-settings",
206 web::post().to(save_course_settings),
207 )
208 .route(
209 "/{course_instance_id}/progress",
210 web::get().to(get_user_progress_for_course_instance),
211 )
212 .route(
213 "/{course_instance_id}/chapters/{chapter_id}/exercises/progress",
214 web::get().to(get_user_progress_for_course_instance_chapter_exercises),
215 )
216 .route(
217 "/{course_instance_id}/chapters/{chapter_id}/progress",
218 web::get().to(get_user_progress_for_course_instance_chapter),
219 )
220 .route(
221 "/{course_instance_id}/module-completions",
222 web::get().to(get_module_completions_for_course_instance),
223 )
224 .route(
225 "/{course_instance_id}/course-module-completions/{user_id}",
226 web::get().to(get_all_get_all_course_module_completions_for_user_by_course_instance_id),
227 )
228 .route(
229 "/{course_instance_id}/background-questions-and-answers",
230 web::get().to(get_background_questions_and_answers),
231 );
232}