Skip to main content

headless_lms_server/controllers/course_material/
course_modules.rs

1use headless_lms_models::{course_instances, user_course_exercise_service_variables};
2use models::{course_modules, library::custom_view_exercises::CustomViewExerciseSubmissions};
3use utoipa::OpenApi;
4
5use crate::{domain::authorization::authorize_access_to_course_material, prelude::*};
6
7#[derive(OpenApi)]
8#[openapi(paths(
9    get_course_module_id_by_chapter_id,
10    get_default_course_module_id_by_course_id,
11    get_user_course_module_exercises_by_exercise_type
12))]
13pub(crate) struct CourseMaterialCourseModulesApiDoc;
14
15/**
16GET `/api/v0/course-material/course-modules/chapter/:chapter_id`
17
18Returns course module id based on chapter.
19*/
20#[utoipa::path(
21    get,
22    path = "/chapter/{chapter_id}",
23    operation_id = "getCourseMaterialModuleIdByChapterId",
24    tag = "course-material-course-modules",
25    params(
26        ("chapter_id" = Uuid, Path, description = "Chapter id")
27    ),
28    responses(
29        (status = 200, description = "Course module id", body = Uuid)
30    )
31)]
32#[instrument(skip(pool))]
33async fn get_course_module_id_by_chapter_id(
34    chapter_id: web::Path<Uuid>,
35    pool: web::Data<PgPool>,
36    user: AuthUser,
37) -> ControllerResult<web::Json<Uuid>> {
38    let mut conn = pool.acquire().await?;
39    let chapter = models::chapters::get_chapter(&mut conn, *chapter_id).await?;
40    let token =
41        authorize_access_to_course_material(&mut conn, Some(user.id), chapter.course_id).await?;
42    let module_id = chapter.course_module_id;
43    token.authorized_ok(web::Json(module_id))
44}
45
46/**
47GET `/api/v0/course-material/course-modules/course/:course_instance_id`
48
49Returns course module id based on chapter.
50*/
51#[utoipa::path(
52    get,
53    path = "/course/{course_id}",
54    operation_id = "getCourseMaterialDefaultModuleIdByCourseId",
55    tag = "course-material-course-modules",
56    params(
57        ("course_id" = Uuid, Path, description = "Course id")
58    ),
59    responses(
60        (status = 200, description = "Default course module id", body = Uuid)
61    )
62)]
63#[instrument(skip(pool))]
64async fn get_default_course_module_id_by_course_id(
65    course_id: web::Path<Uuid>,
66    pool: web::Data<PgPool>,
67    user: AuthUser,
68) -> ControllerResult<web::Json<Uuid>> {
69    let mut conn = pool.acquire().await?;
70    let token = authorize_access_to_course_material(&mut conn, Some(user.id), *course_id).await?;
71    let module = course_modules::get_default_by_course_id(&mut conn, *course_id).await?;
72    let module_id = module.id;
73    token.authorized_ok(web::Json(module_id))
74}
75
76/**
77GET `/api/v0/course-material/course-modules/:course_module_id/exercise-tasks/:exercise_type/:course_instance_id`
78
79Returns exercise submissions for user to be used in en exercise service Custom view.
80*/
81#[utoipa::path(
82    get,
83    path = "/{course_module_id}/exercise-tasks/{exercise_type}/{course_instance_id}",
84    operation_id = "getCourseMaterialExerciseTasksByModuleAndType",
85    tag = "course-material-course-modules",
86    params(
87        ("course_module_id" = Uuid, Path, description = "Course module id"),
88        ("exercise_type" = String, Path, description = "Exercise type"),
89        ("course_instance_id" = Uuid, Path, description = "Course instance id")
90    ),
91    responses(
92        (
93            status = 200,
94            description = "Exercise tasks, exercises, and user variables",
95            body = CustomViewExerciseSubmissions
96        )
97    )
98)]
99#[instrument(skip(pool, file_store, app_conf))]
100async fn get_user_course_module_exercises_by_exercise_type(
101    path: web::Path<(Uuid, String, Uuid)>,
102    pool: web::Data<PgPool>,
103    user: AuthUser,
104    file_store: web::Data<dyn FileStore>,
105    app_conf: web::Data<ApplicationConfiguration>,
106) -> ControllerResult<web::Json<CustomViewExerciseSubmissions>> {
107    let mut conn = pool.acquire().await?;
108    let (course_module_id, exercise_type, course_instance_id) = path.into_inner();
109    let course_instance =
110        course_instances::get_course_instance(&mut conn, course_instance_id).await?;
111    let module = course_modules::get_by_id(&mut conn, course_module_id).await?;
112    if module.course_id != course_instance.course_id {
113        return Err(controller_err!(
114            Forbidden,
115            "Course module does not belong to the requested course instance".to_string()
116        ));
117    }
118    let token =
119        authorize_access_to_course_material(&mut conn, Some(user.id), module.course_id).await?;
120    let course_id = course_instance.course_id;
121    let exercise_tasks = models::exercise_task_submissions::get_user_custom_view_exercise_tasks_by_module_and_exercise_type(
122        &mut conn,
123        &exercise_type,
124        course_module_id,
125        user.id,
126    course_id, file_store.as_ref(), app_conf.as_ref())
127        .await?;
128
129    let exercises = models::exercises::get_exercises_by_module_containing_exercise_type(
130        &mut conn,
131        &exercise_type,
132        course_module_id,
133    )
134    .await?;
135    let user_variables =
136    user_course_exercise_service_variables::get_all_user_variables_for_user_and_course_and_exercise_type(&mut conn, user.id, course_id, &exercise_type).await?;
137    let res = CustomViewExerciseSubmissions {
138        exercise_tasks,
139        exercises,
140        user_variables,
141    };
142
143    token.authorized_ok(web::Json(res))
144}
145
146/**
147Add a route for each controller in this module.
148
149The name starts with an underline in order to appear before other functions in the module documentation.
150
151We add the routes by calling the route method instead of using the route annotations because this method preserves the function signatures for documentation.
152*/
153pub fn _add_routes(cfg: &mut ServiceConfig) {
154    cfg.route(
155        "/chapter/{chapter_id}",
156        web::get().to(get_course_module_id_by_chapter_id),
157    )
158    .route(
159        "/course/{course_id}",
160        web::get().to(get_default_course_module_id_by_course_id),
161    )
162    .route(
163        "/{course_module_id}/exercise-tasks/{exercise_type}/{course_instance_id}",
164        web::get().to(get_user_course_module_exercises_by_exercise_type),
165    );
166}