headless_lms_server/controllers/main_frontend/
course_instances.rs

1//! Controllers for requests starting with `/api/v0/main-frontend/course-instances`.
2
3use chrono::Utc;
4use models::{
5    certificate_configurations::CertificateConfigurationAndRequirements,
6    course_instances::{self, CourseInstance, CourseInstanceForm, Points},
7    course_module_completions::CourseModuleCompletion,
8    courses,
9    email_templates::{EmailTemplate, EmailTemplateNew},
10    exercises::ExerciseStatusSummaryForUser,
11    library::{
12        self,
13        progressing::{
14            CourseInstanceCompletionSummary, ManualCompletionPreview,
15            TeacherManualCompletionRequest,
16        },
17    },
18    user_exercise_states::UserCourseProgress,
19};
20
21use crate::{
22    domain::csv_export::{
23        course_instance_export::CompletionsExportOperation, general_export,
24        points::PointExportOperation,
25    },
26    prelude::*,
27};
28
29/**
30GET /course-instances/:id
31*/
32#[instrument(skip(pool))]
33async fn get_course_instance(
34    course_instance_id: web::Path<Uuid>,
35    user: AuthUser,
36    pool: web::Data<PgPool>,
37) -> ControllerResult<web::Json<CourseInstance>> {
38    let mut conn = pool.acquire().await?;
39    let token = authorize(
40        &mut conn,
41        Act::Edit,
42        Some(user.id),
43        Res::CourseInstance(*course_instance_id),
44    )
45    .await?;
46    let course_instance =
47        models::course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
48    token.authorized_ok(web::Json(course_instance))
49}
50
51#[instrument(skip(payload, pool))]
52async fn post_new_email_template(
53    course_instance_id: web::Path<Uuid>,
54    payload: web::Json<EmailTemplateNew>,
55    pool: web::Data<PgPool>,
56    user: AuthUser,
57) -> ControllerResult<web::Json<EmailTemplate>> {
58    let mut conn = pool.acquire().await?;
59    let token = authorize(
60        &mut conn,
61        Act::Edit,
62        Some(user.id),
63        Res::CourseInstance(*course_instance_id),
64    )
65    .await?;
66    let new_email_template = payload.0;
67    let email_template = models::email_templates::insert_email_template(
68        &mut conn,
69        *course_instance_id,
70        new_email_template,
71        None,
72    )
73    .await?;
74    token.authorized_ok(web::Json(email_template))
75}
76
77#[instrument(skip(pool))]
78async fn get_email_templates_by_course_instance_id(
79    course_instance_id: web::Path<Uuid>,
80    pool: web::Data<PgPool>,
81    user: AuthUser,
82) -> ControllerResult<web::Json<Vec<EmailTemplate>>> {
83    let mut conn = pool.acquire().await?;
84    let token = authorize(
85        &mut conn,
86        Act::Edit,
87        Some(user.id),
88        Res::CourseInstance(*course_instance_id),
89    )
90    .await?;
91
92    let email_templates =
93        models::email_templates::get_email_templates(&mut conn, *course_instance_id).await?;
94    token.authorized_ok(web::Json(email_templates))
95}
96
97/**
98GET `/api/v0/main-frontend/course-instances/${courseInstanceId}/export-points` - gets CSV of course instance points based on course_instance ID.
99*/
100#[instrument(skip(pool))]
101pub async fn point_export(
102    course_instance_id: web::Path<Uuid>,
103    pool: web::Data<PgPool>,
104    user: AuthUser,
105) -> ControllerResult<HttpResponse> {
106    let mut conn = pool.acquire().await?;
107    let token = authorize(
108        &mut conn,
109        Act::Edit,
110        Some(user.id),
111        Res::CourseInstance(*course_instance_id),
112    )
113    .await?;
114
115    let course_instance =
116        course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
117    let course = courses::get_course(&mut conn, course_instance.course_id).await?;
118
119    general_export(
120        pool,
121        &format!(
122            "attachment; filename=\"{} - {} - Point export {}.csv\"",
123            course.name,
124            course_instance.name.as_deref().unwrap_or("unnamed"),
125            Utc::now().format("%Y-%m-%d")
126        ),
127        PointExportOperation {
128            course_instance_id: *course_instance_id,
129        },
130        token,
131    )
132    .await
133}
134
135#[instrument(skip(pool))]
136async fn points(
137    course_instance_id: web::Path<Uuid>,
138    pagination: web::Query<Pagination>,
139    pool: web::Data<PgPool>,
140    user: AuthUser,
141) -> ControllerResult<web::Json<Points>> {
142    let mut conn = pool.acquire().await?;
143    let token = authorize(
144        &mut conn,
145        Act::ViewUserProgressOrDetails,
146        Some(user.id),
147        Res::CourseInstance(*course_instance_id),
148    )
149    .await?;
150    let points = course_instances::get_points(&mut conn, *course_instance_id, *pagination).await?;
151    token.authorized_ok(web::Json(points))
152}
153
154/**
155GET `/api/v0/main-frontend/course-instances/{course_instance_id}/completions`
156*/
157#[instrument(skip(pool))]
158async fn completions(
159    course_instance_id: web::Path<Uuid>,
160    pool: web::Data<PgPool>,
161    user: AuthUser,
162) -> ControllerResult<web::Json<CourseInstanceCompletionSummary>> {
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    let course_instance =
172        course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
173    let completions =
174        library::progressing::get_course_instance_completion_summary(&mut conn, &course_instance)
175            .await?;
176    token.authorized_ok(web::Json(completions))
177}
178
179/**
180POST `/api/v0/main-frontend/course-instances/{course_instance_id}/completions`
181*/
182#[instrument(skip(pool, payload))]
183async fn post_completions(
184    course_instance_id: web::Path<Uuid>,
185    pool: web::Data<PgPool>,
186    user: AuthUser,
187    payload: web::Json<TeacherManualCompletionRequest>,
188) -> ControllerResult<web::Json<()>> {
189    let mut conn = pool.acquire().await?;
190    let token = authorize(
191        &mut conn,
192        Act::Edit,
193        Some(user.id),
194        Res::CourseInstance(*course_instance_id),
195    )
196    .await?;
197    let data = payload.0;
198    let course_instance =
199        course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
200    library::progressing::add_manual_completions(&mut conn, user.id, &course_instance, &data)
201        .await?;
202    token.authorized_ok(web::Json(()))
203}
204
205#[instrument(skip(pool, payload))]
206async fn preview_post_completions(
207    course_instance_id: web::Path<Uuid>,
208    pool: web::Data<PgPool>,
209    user: AuthUser,
210    payload: web::Json<TeacherManualCompletionRequest>,
211) -> ControllerResult<web::Json<ManualCompletionPreview>> {
212    let mut conn = pool.acquire().await?;
213    let token = authorize(
214        &mut conn,
215        Act::Edit,
216        Some(user.id),
217        Res::CourseInstance(*course_instance_id),
218    )
219    .await?;
220    let data = payload.0;
221    let instance = course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
222    let preview =
223        library::progressing::get_manual_completion_result_preview(&mut conn, &instance, &data)
224            .await?;
225    token.authorized_ok(web::Json(preview))
226}
227
228/**
229POST /course-instances/:id/edit
230*/
231#[instrument(skip(pool))]
232pub async fn edit(
233    update: web::Json<CourseInstanceForm>,
234    course_instance_id: web::Path<Uuid>,
235    pool: web::Data<PgPool>,
236    user: AuthUser,
237) -> ControllerResult<HttpResponse> {
238    let mut conn = pool.acquire().await?;
239    let token = authorize(
240        &mut conn,
241        Act::Edit,
242        Some(user.id),
243        Res::CourseInstance(*course_instance_id),
244    )
245    .await?;
246    course_instances::edit(&mut conn, *course_instance_id, update.into_inner()).await?;
247    token.authorized_ok(HttpResponse::Ok().finish())
248}
249
250/**
251POST /course-instances/:id/delete
252*/
253#[instrument(skip(pool))]
254async fn delete(
255    id: web::Path<Uuid>,
256    pool: web::Data<PgPool>,
257    user: AuthUser,
258) -> ControllerResult<HttpResponse> {
259    let mut conn = pool.acquire().await?;
260    let token = authorize(
261        &mut conn,
262        Act::Edit,
263        Some(user.id),
264        Res::CourseInstance(*id),
265    )
266    .await?;
267    models::course_instances::delete(&mut conn, *id).await?;
268    token.authorized_ok(HttpResponse::Ok().finish())
269}
270
271/**
272GET /course-instances/:id/export-completions - gets CSV of course completion based on course_instance ID.
273*/
274#[instrument(skip(pool))]
275pub async fn completions_export(
276    course_instance_id: web::Path<Uuid>,
277    pool: web::Data<PgPool>,
278    user: AuthUser,
279) -> ControllerResult<HttpResponse> {
280    let mut conn = pool.acquire().await?;
281    let token = authorize(
282        &mut conn,
283        Act::Edit,
284        Some(user.id),
285        Res::CourseInstance(*course_instance_id),
286    )
287    .await?;
288
289    let course_instance =
290        course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
291    let course = courses::get_course(&mut conn, course_instance.course_id).await?;
292
293    general_export(
294        pool,
295        &format!(
296            "attachment; filename=\"{} - {} - Completions export {}.csv\"",
297            course.name,
298            course_instance.name.as_deref().unwrap_or("unnamed"),
299            Utc::now().format("%Y-%m-%d")
300        ),
301        CompletionsExportOperation {
302            course_instance_id: *course_instance_id,
303        },
304        token,
305    )
306    .await
307}
308/**
309GET /course-instances/:id/default-certificate-configurations - gets default certificate configurations of the given course instance. A default certificate configuration requires only one course module to be completed.
310*/
311#[instrument(skip(pool))]
312pub async fn certificate_configurations(
313    course_instance_id: web::Path<Uuid>,
314    pool: web::Data<PgPool>,
315    user: AuthUser,
316) -> ControllerResult<web::Json<Vec<CertificateConfigurationAndRequirements>>> {
317    let mut conn = pool.acquire().await?;
318    let token = authorize(
319        &mut conn,
320        Act::Teach,
321        Some(user.id),
322        Res::CourseInstance(*course_instance_id),
323    )
324    .await?;
325
326    let course_instance =
327        models::course_instances::get_course_instance(&mut conn, *course_instance_id).await?;
328
329    let certificate_configurations =
330        models::certificate_configurations::get_default_certificate_configurations_and_requirements_by_course(
331            &mut conn,
332            course_instance.course_id,
333        )
334        .await?;
335    token.authorized_ok(web::Json(certificate_configurations))
336}
337
338/**
339GET /course-instances/:id/status-for-all-exercises/:user_id - Returns a status for all exercises in a course instance for a given user.
340*/
341#[instrument(skip(pool))]
342
343async fn get_all_exercise_statuses_by_course_instance_id(
344    params: web::Path<(Uuid, Uuid)>,
345    pool: web::Data<PgPool>,
346    user: AuthUser,
347) -> ControllerResult<web::Json<Vec<ExerciseStatusSummaryForUser>>> {
348    let (course_instance_id, user_id) = params.into_inner();
349    let mut conn = pool.acquire().await?;
350    let token = authorize(
351        &mut conn,
352        Act::ViewUserProgressOrDetails,
353        Some(user.id),
354        Res::CourseInstance(course_instance_id),
355    )
356    .await?;
357
358    let course_instance =
359        models::course_instances::get_course_instance(&mut conn, course_instance_id).await?;
360
361    let res = models::exercises::get_all_exercise_statuses_by_user_id_and_course_id(
362        &mut conn,
363        course_instance.course_id,
364        user_id,
365    )
366    .await?;
367
368    token.authorized_ok(web::Json(res))
369}
370
371/**
372GET /course-instances/:id/course-module-completions/:user_id - Returns a list of all course module completions for a given user for this course instance.
373*/
374#[instrument(skip(pool))]
375
376async fn get_all_get_all_course_module_completions_for_user_by_course_instance_id(
377    params: web::Path<(Uuid, Uuid)>,
378    pool: web::Data<PgPool>,
379    user: AuthUser,
380) -> ControllerResult<web::Json<Vec<CourseModuleCompletion>>> {
381    let (course_instance_id, user_id) = params.into_inner();
382    let mut conn = pool.acquire().await?;
383    let token = authorize(
384        &mut conn,
385        Act::ViewUserProgressOrDetails,
386        Some(user.id),
387        Res::CourseInstance(course_instance_id),
388    )
389    .await?;
390
391    let course_instance =
392        models::course_instances::get_course_instance(&mut conn, course_instance_id).await?;
393
394    let res = models::course_module_completions::get_all_by_course_id_and_user_id(
395        &mut conn,
396        course_instance.course_id,
397        user_id,
398    )
399    .await?;
400
401    token.authorized_ok(web::Json(res))
402}
403
404/**
405 GET /api/v0/main-frontend/course-instance/:course_instance_id/progress/:user_id - returns user progress information.
406*/
407#[instrument(skip(pool))]
408async fn get_user_progress_for_course_instance(
409    user: AuthUser,
410    params: web::Path<(Uuid, Uuid)>,
411    pool: web::Data<PgPool>,
412) -> ControllerResult<web::Json<Vec<UserCourseProgress>>> {
413    let (course_instance_id, user_id) = params.into_inner();
414    let mut conn = pool.acquire().await?;
415    let token = authorize(
416        &mut conn,
417        Act::ViewUserProgressOrDetails,
418        Some(user.id),
419        Res::CourseInstance(course_instance_id),
420    )
421    .await?;
422
423    let course_instance =
424        models::course_instances::get_course_instance(&mut conn, course_instance_id).await?;
425
426    let user_course_progress = models::user_exercise_states::get_user_course_progress(
427        &mut conn,
428        course_instance.course_id,
429        user_id,
430    )
431    .await?;
432    token.authorized_ok(web::Json(user_course_progress))
433}
434
435/**
436Add a route for each controller in this module.
437
438The name starts with an underline in order to appear before other functions in the module documentation.
439
440We add the routes by calling the route method instead of using the route annotations because this method preserves the function signatures for documentation.
441*/
442pub fn _add_routes(cfg: &mut ServiceConfig) {
443    cfg.route("/{course_instance_id}", web::get().to(get_course_instance))
444        .route(
445            "/{course_instance_id}/email-templates",
446            web::post().to(post_new_email_template),
447        )
448        .route(
449            "/{course_instance_id}/email-templates",
450            web::get().to(get_email_templates_by_course_instance_id),
451        )
452        .route(
453            "/{course_instance_id}/export-points",
454            web::get().to(point_export),
455        )
456        .route("/{course_instance_id}/edit", web::post().to(edit))
457        .route("/{course_instance_id}/delete", web::post().to(delete))
458        .route(
459            "/{course_instance_id}/completions",
460            web::get().to(completions),
461        )
462        .route(
463            "/{course_instance_id}/export-completions",
464            web::get().to(completions_export),
465        )
466        .route(
467            "/{course_instance_id}/completions",
468            web::post().to(post_completions),
469        )
470        .route(
471            "/{course_instance_id}/completions/preview",
472            web::post().to(preview_post_completions),
473        )
474        .route("/{course_instance_id}/points", web::get().to(points))
475        .route(
476            "/{course_instance_id}/status-for-all-exercises/{user_id}",
477            web::get().to(get_all_exercise_statuses_by_course_instance_id),
478        )
479        .route(
480            "/{course_instance_id}/course-module-completions/{user_id}",
481            web::get().to(get_all_get_all_course_module_completions_for_user_by_course_instance_id),
482        )
483        .route(
484            "/{course_instance_id}/progress/{user_id}",
485            web::get().to(get_user_progress_for_course_instance),
486        )
487        .route(
488            "/{course_instance_id}/default-certificate-configurations",
489            web::get().to(certificate_configurations),
490        );
491}