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};
8use utoipa::OpenApi;
9
10use crate::prelude::*;
11
12#[derive(OpenApi)]
13#[openapi(paths(
14 get_course_module,
15 get_course_module_completion_information_for_user,
16 get_course_module_completion_registration_link,
17 enable_or_disable_certificate_generation,
18 get_best_course_module_completion_for_user,
19 insert_threshold_for_module,
20 delete_threshold_for_module
21))]
22pub(crate) struct MainFrontendCourseModulesApiDoc;
23
24#[utoipa::path(
30 get,
31 path = "/{course_module_id}",
32 operation_id = "getCourseModule",
33 tag = "course_modules",
34 params(
35 ("course_module_id" = Uuid, Path, description = "Course module id")
36 ),
37 responses(
38 (status = 200, description = "Course module", body = CourseModule)
39 )
40)]
41#[instrument(skip(pool))]
42async fn get_course_module(
43 course_module_id: web::Path<Uuid>,
44 pool: web::Data<PgPool>,
45 user: AuthUser,
46) -> ControllerResult<web::Json<CourseModule>> {
47 let mut conn = pool.acquire().await?;
48 let course_module = course_modules::get_by_id(&mut conn, *course_module_id).await?;
49 let token = authorize(
50 &mut conn,
51 Act::View,
52 Some(user.id),
53 Res::Course(course_module.course_id),
54 )
55 .await?;
56 token.authorized_ok(web::Json(course_module))
57}
58
59#[utoipa::path(
65 get,
66 path = "/{course_module_id}/user-completion",
67 operation_id = "getCourseModuleUserCompletion",
68 tag = "course_modules",
69 params(
70 ("course_module_id" = Uuid, Path, description = "Course module id")
71 ),
72 responses(
73 (status = 200, description = "User completion information", body = UserCompletionInformation)
74 )
75)]
76#[instrument(skip(pool))]
77async fn get_course_module_completion_information_for_user(
78 course_module_id: web::Path<Uuid>,
79 pool: web::Data<PgPool>,
80 user: AuthUser,
81) -> ControllerResult<web::Json<UserCompletionInformation>> {
82 let mut conn = pool.acquire().await?;
83 let course_module = course_modules::get_by_id(&mut conn, *course_module_id).await?;
84 let token = authorize(
86 &mut conn,
87 Act::View,
88 Some(user.id),
89 Res::Course(course_module.course_id),
90 )
91 .await?;
92 let information = models::library::progressing::get_user_completion_information(
93 &mut conn,
94 user.id,
95 &course_module,
96 )
97 .await?;
98 token.authorized_ok(web::Json(information))
99}
100
101#[utoipa::path(
105 get,
106 path = "/{course_module_id}/completion-registration-link",
107 operation_id = "getCourseModuleCompletionRegistrationLink",
108 tag = "course_modules",
109 params(
110 ("course_module_id" = Uuid, Path, description = "Course module id")
111 ),
112 responses(
113 (status = 200, description = "Completion registration link", body = CompletionRegistrationLink)
114 )
115)]
116#[instrument(skip(pool))]
117async fn get_course_module_completion_registration_link(
118 course_module_id: web::Path<Uuid>,
119 pool: web::Data<PgPool>,
120 user: AuthUser,
121) -> ControllerResult<web::Json<CompletionRegistrationLink>> {
122 let mut conn = pool.acquire().await?;
123 let course_module = course_modules::get_by_id(&mut conn, *course_module_id).await?;
124 let token = authorize(
126 &mut conn,
127 Act::View,
128 Some(user.id),
129 Res::Course(course_module.course_id),
130 )
131 .await?;
132 let completion_registration_link =
133 models::library::progressing::get_completion_registration_link_and_save_attempt(
134 &mut conn,
135 user.id,
136 &course_module,
137 )
138 .await?;
139 token.authorized_ok(web::Json(completion_registration_link))
140}
141
142#[utoipa::path(
143 post,
144 path = "/{course_module_id}/set-certificate-generation/{enabled}",
145 operation_id = "setCourseModuleCertificateGeneration",
146 tag = "course_modules",
147 params(
148 ("course_module_id" = Uuid, Path, description = "Course module id"),
149 ("enabled" = bool, Path, description = "Whether certificate generation should be enabled")
150 ),
151 responses(
152 (status = 200, description = "Certificate generation updated", body = bool)
153 )
154)]
155async fn enable_or_disable_certificate_generation(
156 params: web::Path<(Uuid, bool)>,
157 pool: web::Data<PgPool>,
158 user: AuthUser,
159) -> ControllerResult<web::Json<bool>> {
160 let mut conn = pool.acquire().await?;
161 let (course_module_id, enabled) = params.into_inner();
162
163 let course_module = course_modules::get_by_id(&mut conn, course_module_id).await?;
164 let token = authorize(
166 &mut conn,
167 Act::Edit,
168 Some(user.id),
169 Res::Course(course_module.course_id),
170 )
171 .await?;
172 models::course_modules::update_certification_enabled(&mut conn, course_module_id, enabled)
173 .await?;
174
175 token.authorized_ok(web::Json(true))
176}
177
178#[utoipa::path(
184 get,
185 path = "/{course_module_id}/course-module-completion",
186 operation_id = "getCourseModuleCompletion",
187 tag = "course_modules",
188 params(
189 ("course_module_id" = Uuid, Path, description = "Course module id")
190 ),
191 responses(
192 (status = 200, description = "Best course module completion for the current user", body = Option<CourseModuleCompletion>)
193 )
194)]
195#[instrument(skip(pool))]
196async fn get_best_course_module_completion_for_user(
197 course_module_id: web::Path<Uuid>,
198 pool: web::Data<PgPool>,
199 user: AuthUser,
200) -> ControllerResult<web::Json<Option<CourseModuleCompletion>>> {
201 let mut conn = pool.acquire().await?;
202 let course_module = course_modules::get_by_id(&mut conn, *course_module_id).await?;
203
204 let token = authorize(
205 &mut conn,
206 Act::View,
207 Some(user.id),
208 Res::Course(course_module.course_id),
209 )
210 .await?;
211
212 let information =
213 models::course_module_completions::get_best_completion_by_user_and_course_module_id(
214 &mut conn,
215 user.id,
216 *course_module_id,
217 )
218 .await?;
219 token.authorized_ok(web::Json(information))
220}
221
222#[utoipa::path(
226 post,
227 path = "/{course_module_id}/threshold",
228 operation_id = "createCourseModuleThreshold",
229 tag = "course_modules",
230 params(
231 ("course_module_id" = Uuid, Path, description = "Course module id")
232 ),
233 request_body = ThresholdData,
234 responses(
235 (status = 200, description = "Threshold created")
236 )
237)]
238#[instrument(skip(pool))]
239async fn insert_threshold_for_module(
240 pool: web::Data<PgPool>,
241 params: web::Path<Uuid>,
242 payload: web::Json<ThresholdData>,
243 user: AuthUser,
244) -> ControllerResult<web::Json<()>> {
245 let mut conn = pool.acquire().await?;
246
247 let course_module_id = params.into_inner();
248 let new_threshold = payload.0;
249
250 let course_module = course_modules::get_by_id(&mut conn, course_module_id).await?;
251 let token = authorize(
252 &mut conn,
253 Act::Edit,
254 Some(user.id),
255 Res::Course(course_module.course_id),
256 )
257 .await?;
258
259 suspected_cheaters::insert_thresholds_by_module_id(
260 &mut conn,
261 course_module_id,
262 new_threshold.duration_seconds,
263 )
264 .await?;
265
266 token.authorized_ok(web::Json(()))
267}
268
269#[utoipa::path(
273 delete,
274 path = "/{course_module_id}/threshold",
275 operation_id = "deleteCourseModuleThreshold",
276 tag = "course_modules",
277 params(
278 ("course_module_id" = Uuid, Path, description = "Course module id")
279 ),
280 responses(
281 (status = 200, description = "Threshold deleted")
282 )
283)]
284#[instrument(skip(pool))]
285async fn delete_threshold_for_module(
286 pool: web::Data<PgPool>,
287 params: web::Path<Uuid>,
288 user: AuthUser,
289) -> ControllerResult<web::Json<()>> {
290 let mut conn = pool.acquire().await?;
291
292 let course_module_id = params.into_inner();
293
294 let course_module = course_modules::get_by_id(&mut conn, course_module_id).await?;
295 let token = authorize(
296 &mut conn,
297 Act::Edit,
298 Some(user.id),
299 Res::Course(course_module.course_id),
300 )
301 .await?;
302
303 suspected_cheaters::delete_threshold_for_module(&mut conn, course_module_id).await?;
304
305 token.authorized_ok(web::Json(()))
306}
307
308pub fn _add_routes(cfg: &mut ServiceConfig) {
316 cfg.route("/{course_module_id}", web::get().to(get_course_module))
317 .route(
318 "/{course_module_id}/user-completion",
319 web::get().to(get_course_module_completion_information_for_user),
320 )
321 .route(
322 "/{course_module_id}/completion-registration-link",
323 web::get().to(get_course_module_completion_registration_link),
324 )
325 .route(
326 "/{course_module_id}/set-certificate-generation/{enabled}",
327 web::post().to(enable_or_disable_certificate_generation),
328 )
329 .route(
330 "/{course_module_id}/course-module-completion",
331 web::get().to(get_best_course_module_completion_for_user),
332 )
333 .route(
334 "/{course_module_id}/threshold",
335 web::post().to(insert_threshold_for_module),
336 )
337 .route(
338 "/{course_module_id}/threshold",
339 web::delete().to(delete_threshold_for_module),
340 );
341}