headless_lms_server/controllers/main_frontend/
course_modules.rs

1use headless_lms_models::course_module_completions::CourseModuleCompletion;
2use headless_lms_models::suspected_cheaters::ThresholdData;
3use models::{
4    course_modules::{self, CourseModule},
5    library::progressing::{CompletionRegistrationLink, UserCompletionInformation},
6    suspected_cheaters,
7};
8
9use crate::prelude::*;
10
11/**
12GET `/api/v0/main-frontend/course-modules/{course_module_id}`
13
14Returns information about the course module.
15*/
16#[instrument(skip(pool))]
17async fn get_course_module(
18    course_module_id: web::Path<Uuid>,
19    pool: web::Data<PgPool>,
20    user: AuthUser,
21) -> ControllerResult<web::Json<CourseModule>> {
22    let mut conn = pool.acquire().await?;
23    let course_module = course_modules::get_by_id(&mut conn, *course_module_id).await?;
24    let token = authorize(
25        &mut conn,
26        Act::View,
27        Some(user.id),
28        Res::Course(course_module.course_id),
29    )
30    .await?;
31    token.authorized_ok(web::Json(course_module))
32}
33
34/**
35GET `/api/v0/main-frontend/course-modules/{course_module_id}/user-completion`
36
37Gets active users's completion for the course, if it exists.
38*/
39#[instrument(skip(pool))]
40async fn get_course_module_completion_information_for_user(
41    course_module_id: web::Path<Uuid>,
42    pool: web::Data<PgPool>,
43    user: AuthUser,
44) -> ControllerResult<web::Json<UserCompletionInformation>> {
45    let mut conn = pool.acquire().await?;
46    let course_module = course_modules::get_by_id(&mut conn, *course_module_id).await?;
47    // Proper request validation is based on whether a completion exists for the user or not.
48    let token = authorize(
49        &mut conn,
50        Act::View,
51        Some(user.id),
52        Res::Course(course_module.course_id),
53    )
54    .await?;
55    let information = models::library::progressing::get_user_completion_information(
56        &mut conn,
57        user.id,
58        &course_module,
59    )
60    .await?;
61    token.authorized_ok(web::Json(information))
62}
63
64/**
65GET `/api/v0/main-frontend/course-modules/{course_slug}/completion-registration-link`
66*/
67#[instrument(skip(pool))]
68async fn get_course_module_completion_registration_link(
69    course_module_id: web::Path<Uuid>,
70    pool: web::Data<PgPool>,
71    user: AuthUser,
72) -> ControllerResult<web::Json<CompletionRegistrationLink>> {
73    let mut conn = pool.acquire().await?;
74    let course_module = course_modules::get_by_id(&mut conn, *course_module_id).await?;
75    // Proper request validation is based on whether a completion exists for the user or not.
76    let token = authorize(
77        &mut conn,
78        Act::View,
79        Some(user.id),
80        Res::Course(course_module.course_id),
81    )
82    .await?;
83    let completion_registration_link =
84        models::library::progressing::get_completion_registration_link_and_save_attempt(
85            &mut conn,
86            user.id,
87            &course_module,
88        )
89        .await?;
90    token.authorized_ok(web::Json(completion_registration_link))
91}
92
93async fn enable_or_disable_certificate_generation(
94    params: web::Path<(Uuid, bool)>,
95    pool: web::Data<PgPool>,
96    user: AuthUser,
97) -> ControllerResult<web::Json<bool>> {
98    let mut conn = pool.acquire().await?;
99    let (course_module_id, enabled) = params.into_inner();
100
101    let course_module = course_modules::get_by_id(&mut conn, course_module_id).await?;
102    // Proper request validation is based on whether a completion exists for the user or not.
103    let token = authorize(
104        &mut conn,
105        Act::Edit,
106        Some(user.id),
107        Res::Course(course_module.course_id),
108    )
109    .await?;
110    models::course_modules::update_certification_enabled(&mut conn, course_module_id, enabled)
111        .await?;
112
113    token.authorized_ok(web::Json(true))
114}
115
116/**
117GET `/api/v0/main-frontend/course-modules/{course_module_id}/course-module-completion`
118
119Gets users's best completion for the course.
120*/
121#[instrument(skip(pool))]
122async fn get_best_course_module_completion_for_user(
123    course_module_id: web::Path<Uuid>,
124    pool: web::Data<PgPool>,
125    user: AuthUser,
126) -> ControllerResult<web::Json<Option<CourseModuleCompletion>>> {
127    let mut conn = pool.acquire().await?;
128    let course_module = course_modules::get_by_id(&mut conn, *course_module_id).await?;
129
130    let token = authorize(
131        &mut conn,
132        Act::View,
133        Some(user.id),
134        Res::Course(course_module.course_id),
135    )
136    .await?;
137
138    let information =
139        models::course_module_completions::get_best_completion_by_user_and_course_module_id(
140            &mut conn,
141            user.id,
142            *course_module_id,
143        )
144        .await?;
145    token.authorized_ok(web::Json(information))
146}
147
148/**
149 POST /api/v0/main-frontend/course-modules/${course_module_id}/threshold - post threshold for a specific course module.
150*/
151#[instrument(skip(pool))]
152async fn insert_threshold_for_module(
153    pool: web::Data<PgPool>,
154    params: web::Path<Uuid>,
155    payload: web::Json<ThresholdData>,
156    user: AuthUser,
157) -> ControllerResult<web::Json<()>> {
158    let mut conn = pool.acquire().await?;
159
160    let course_module_id = params.into_inner();
161    let new_threshold = payload.0;
162
163    let course_module = course_modules::get_by_id(&mut conn, course_module_id).await?;
164    let token = authorize(
165        &mut conn,
166        Act::Edit,
167        Some(user.id),
168        Res::Course(course_module.course_id),
169    )
170    .await?;
171
172    suspected_cheaters::insert_thresholds_by_module_id(
173        &mut conn,
174        course_module_id,
175        new_threshold.duration_seconds,
176    )
177    .await?;
178
179    token.authorized_ok(web::Json(()))
180}
181
182/**
183 DELETE /api/v0/main-frontend/course-modules/${course_module_id}/threshold - delete threshold for a specific course module.
184*/
185#[instrument(skip(pool))]
186async fn delete_threshold_for_module(
187    pool: web::Data<PgPool>,
188    params: web::Path<Uuid>,
189    user: AuthUser,
190) -> ControllerResult<web::Json<()>> {
191    let mut conn = pool.acquire().await?;
192
193    let course_module_id = params.into_inner();
194
195    let course_module = course_modules::get_by_id(&mut conn, course_module_id).await?;
196    let token = authorize(
197        &mut conn,
198        Act::Edit,
199        Some(user.id),
200        Res::Course(course_module.course_id),
201    )
202    .await?;
203
204    suspected_cheaters::delete_threshold_for_module(&mut conn, course_module_id).await?;
205
206    token.authorized_ok(web::Json(()))
207}
208
209/**
210Add a route for each controller in this module.
211
212The name starts with an underline in order to appear before other functions in the module documentation.
213
214We add the routes by calling the route method instead of using the route annotations because this method preserves the function signatures for documentation.
215*/
216pub fn _add_routes(cfg: &mut ServiceConfig) {
217    cfg.route("/{course_module_id}", web::get().to(get_course_module))
218        .route(
219            "/{course_module_id}/user-completion",
220            web::get().to(get_course_module_completion_information_for_user),
221        )
222        .route(
223            "/{course_module_id}/completion-registration-link",
224            web::get().to(get_course_module_completion_registration_link),
225        )
226        .route(
227            "/{course_module_id}/set-certificate-generation/{enabled}",
228            web::post().to(enable_or_disable_certificate_generation),
229        )
230        .route(
231            "/{course_module_id}/course-module-completion",
232            web::get().to(get_best_course_module_completion_for_user),
233        )
234        .route(
235            "/{course_module_id}/threshold",
236            web::post().to(insert_threshold_for_module),
237        )
238        .route(
239            "/{course_module_id}/threshold",
240            web::delete().to(delete_threshold_for_module),
241        );
242}