headless_lms_server/controllers/cms/
exams.rs

1//! Controllers for requests starting with `/api/v0/cms/organizations`.
2
3use models::exams::{ExamInstructions, ExamInstructionsUpdate};
4use utoipa::OpenApi;
5
6use crate::prelude::*;
7
8#[derive(OpenApi)]
9#[openapi(paths(add_media, get_exam_instructions, update_exam_instructions))]
10pub(crate) struct CmsExamsApiDoc;
11
12/**
13POST `/api/v0/cms/exams/:exam_id/upload` - Uploads a media (image, audio, file) for the course from Gutenberg page edit.
14
15Put the the contents of the media in a form and add a content type header multipart/form-data.
16# Example
17
18Request:
19```http
20POST /api/v0/cms/pages/d86cf910-4d26-40e9-8c9c-1cc35294fdbb/upload HTTP/1.1
21Content-Type: multipart/form-data
22
23BINARY_DATA
24```
25*/
26
27#[utoipa::path(
28    post,
29    path = "/{exam_id}/upload",
30    operation_id = "uploadCmsExamMedia",
31    tag = "cms_exams",
32    params(
33        ("exam_id" = Uuid, Path, description = "Exam id")
34    ),
35    request_body(
36        content = String,
37        content_type = "multipart/form-data"
38    ),
39    responses(
40        (status = 200, description = "Uploaded media result", body = UploadResult)
41    )
42)]
43#[instrument(skip(payload, request, file_store, app_conf))]
44async fn add_media(
45    pool: web::Data<PgPool>,
46    exam_id: web::Path<Uuid>,
47    payload: Multipart,
48    request: HttpRequest,
49    user: AuthUser,
50    file_store: web::Data<dyn FileStore>,
51    app_conf: web::Data<ApplicationConfiguration>,
52) -> ControllerResult<web::Json<UploadResult>> {
53    let mut conn = pool.acquire().await?;
54    let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Exam(*exam_id)).await?;
55    let media_path = upload_file_from_cms(
56        request.headers(),
57        payload,
58        StoreKind::Exam(*exam_id),
59        file_store.as_ref(),
60        &mut conn,
61        user,
62    )
63    .await?;
64    let download_url = file_store.get_download_url(media_path.as_path(), app_conf.as_ref());
65
66    token.authorized_ok(web::Json(UploadResult { url: download_url }))
67}
68
69/**
70GET `/api/v0/cms/exams/:exam_id/edit` - Get the exam instructions for Gutenberg Editor.
71*/
72#[instrument(skip(pool))]
73#[utoipa::path(
74    get,
75    path = "/{exam_id}/edit",
76    operation_id = "getCmsExamInstructions",
77    tag = "cms_exams",
78    params(
79        ("exam_id" = Uuid, Path, description = "Exam id")
80    ),
81    responses(
82        (status = 200, description = "Exam instructions", body = ExamInstructions)
83    )
84)]
85async fn get_exam_instructions(
86    pool: web::Data<PgPool>,
87    exam_id: web::Path<Uuid>,
88    user: AuthUser,
89) -> ControllerResult<web::Json<ExamInstructions>> {
90    let mut conn = pool.acquire().await?;
91    let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Exam(*exam_id)).await?;
92    let exam_instructions_data =
93        models::exams::get_exam_instructions_data(&mut conn, *exam_id).await?;
94
95    token.authorized_ok(web::Json(exam_instructions_data))
96}
97
98/**
99PUT `/api/v0/cms/exams/:exam_id/edit` - Insert new instructions from Gutenberg editor.
100
101# Example
102
103Request:
104```http
105PUT /api/v0/cms/exams/d86cf910-4d26-40e9-8c9c-1cc35294fdbb/edit HTTP/1.1
106```
107*/
108
109#[instrument(skip(pool, payload))]
110#[utoipa::path(
111    put,
112    path = "/{exam_id}/edit",
113    operation_id = "updateCmsExamInstructions",
114    tag = "cms_exams",
115    params(
116        ("exam_id" = Uuid, Path, description = "Exam id")
117    ),
118    request_body = ExamInstructionsUpdate,
119    responses(
120        (status = 200, description = "Updated exam instructions", body = ExamInstructions)
121    )
122)]
123async fn update_exam_instructions(
124    payload: web::Json<ExamInstructionsUpdate>,
125    pool: web::Data<PgPool>,
126    exam_id: web::Path<Uuid>,
127    user: AuthUser,
128) -> ControllerResult<web::Json<ExamInstructions>> {
129    let mut conn = pool.acquire().await?;
130
131    let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Exam(*exam_id)).await?;
132    let instructions_update = payload.0;
133    let saved_instructions =
134        models::exams::update_exam_instructions(&mut conn, *exam_id, instructions_update).await?;
135
136    token.authorized_ok(web::Json(saved_instructions))
137}
138
139/**
140Add a route for each controller in this module.
141
142The name starts with an underline in order to appear before other functions in the module documentation.
143
144We add the routes by calling the route method instead of using the route annotations because this method preserves the function signatures for documentation.
145*/
146pub fn _add_routes(cfg: &mut ServiceConfig) {
147    cfg.route("/{exam_id}/upload", web::post().to(add_media))
148        .route("/{exam_id}/edit", web::get().to(get_exam_instructions))
149        .route("/{exam_id}/edit", web::put().to(update_exam_instructions));
150}