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))]
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) -> ControllerResult<web::Json<CustomViewExerciseSubmissions>> {
105    let mut conn = pool.acquire().await?;
106    let (course_module_id, exercise_type, course_instance_id) = path.into_inner();
107    let course_instance =
108        course_instances::get_course_instance(&mut conn, course_instance_id).await?;
109    let module = course_modules::get_by_id(&mut conn, course_module_id).await?;
110    if module.course_id != course_instance.course_id {
111        return Err(controller_err!(
112            Forbidden,
113            "Course module does not belong to the requested course instance".to_string()
114        ));
115    }
116    let token =
117        authorize_access_to_course_material(&mut conn, Some(user.id), module.course_id).await?;
118    let course_id = course_instance.course_id;
119    let exercise_tasks = models::exercise_task_submissions::get_user_custom_view_exercise_tasks_by_module_and_exercise_type(
120        &mut conn,
121        &exercise_type,
122        course_module_id,
123        user.id,
124    course_id)
125        .await?;
126
127    let exercises = models::exercises::get_exercises_by_module_containing_exercise_type(
128        &mut conn,
129        &exercise_type,
130        course_module_id,
131    )
132    .await?;
133    let user_variables =
134    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?;
135    let res = CustomViewExerciseSubmissions {
136        exercise_tasks,
137        exercises,
138        user_variables,
139    };
140
141    token.authorized_ok(web::Json(res))
142}
143
144/**
145Add a route for each controller in this module.
146
147The name starts with an underline in order to appear before other functions in the module documentation.
148
149We add the routes by calling the route method instead of using the route annotations because this method preserves the function signatures for documentation.
150*/
151pub fn _add_routes(cfg: &mut ServiceConfig) {
152    cfg.route(
153        "/chapter/{chapter_id}",
154        web::get().to(get_course_module_id_by_chapter_id),
155    )
156    .route(
157        "/course/{course_id}",
158        web::get().to(get_default_course_module_id_by_course_id),
159    )
160    .route(
161        "/{course_module_id}/exercise-tasks/{exercise_type}/{course_instance_id}",
162        web::get().to(get_user_course_module_exercises_by_exercise_type),
163    );
164}