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