headless_lms_server/controllers/course_material/
course_instances.rs

1//! Controllers for requests starting with `/api/v0/course-material/course-instances`.
2
3use 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/**
17 GET /api/v0/course-material/course-instance/:course_intance_id/progress - returns user progress information.
18*/
19#[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/**
38GET `/api/v0/course-material/course-instance/:course_instance_id/chapters/:chapter_id/progress - Returns user progress for chapter in course instance.
39*/
40#[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/**
61GET /api/v0/course-material/course-instance/:course_instance_id/chapters/:chapter_id/exercises/progress - Returns user progress for an exercise in given course instance.
62*/
63#[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/**
96GET `/api/v0/course-material/course-instance/{course_instance_id}/module-completions`
97 */
98#[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    // Override individual completions in modules with insufficient prerequisites
114    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/**
130POST /api/v0/course-material/course-instance/:course_instance_id/save-course-settings - enrolls user to the course instance and save background questions.
131*/
132#[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/**
153GET /course-instances/:id/course-module-completions/:user_id - Returns a list of all course module completions for a given user for this course instance.
154*/
155#[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/**
183GET /api/v0/course-material/course-instance/:course_instance_id/background-questions-and-answers - Gets background questions and answers for an course instance.
184*/
185#[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}