headless_lms_server/controllers/cms/
courses.rs

1//! Controllers for requests starting with `/api/v0/cms/courses`.
2
3use crate::prelude::*;
4
5use models::{
6    course_instances::CourseInstance,
7    pages::{Page, PageVisibility},
8    partner_block::PartnersBlock,
9    peer_or_self_review_configs::{self, CmsPeerOrSelfReviewConfiguration},
10    peer_or_self_review_questions::normalize_cms_peer_or_self_review_questions,
11};
12
13use crate::prelude::models::course_modules::CourseModule;
14use models::research_forms::{
15    NewResearchForm, NewResearchFormQuestion, ResearchForm, ResearchFormQuestion,
16};
17
18/**
19POST `/api/v0/cms/courses/:course_id/upload` - Uploads a media (image, audio, file) for the course from Gutenberg page edit.
20
21Put the the contents of the media in a form and add a content type header multipart/form-data.
22# Example
23
24Request:
25```http
26POST /api/v0/cms/pages/d86cf910-4d26-40e9-8c9c-1cc35294fdbb/upload HTTP/1.1
27Content-Type: multipart/form-data
28
29BINARY_DATA
30```
31*/
32
33#[instrument(skip(payload, request, pool, file_store, app_conf))]
34async fn add_media(
35    course_id: web::Path<Uuid>,
36    payload: Multipart,
37    request: HttpRequest,
38    pool: web::Data<PgPool>,
39    user: AuthUser,
40    file_store: web::Data<dyn FileStore>,
41    app_conf: web::Data<ApplicationConfiguration>,
42) -> ControllerResult<web::Json<UploadResult>> {
43    let mut conn = pool.acquire().await?;
44    let course = models::courses::get_course(&mut conn, *course_id).await?;
45    let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(course.id)).await?;
46
47    let media_path = upload_file_from_cms(
48        request.headers(),
49        payload,
50        StoreKind::Course(course.id),
51        file_store.as_ref(),
52        &mut conn,
53        user,
54    )
55    .await?;
56    let download_url = file_store.get_download_url(media_path.as_path(), app_conf.as_ref());
57
58    token.authorized_ok(web::Json(UploadResult { url: download_url }))
59}
60
61#[instrument(skip(pool))]
62async fn get_course_default_peer_or_self_review_configuration(
63    course_id: web::Path<Uuid>,
64    user: AuthUser,
65    pool: web::Data<PgPool>,
66) -> ControllerResult<web::Json<CmsPeerOrSelfReviewConfiguration>> {
67    let mut conn = pool.acquire().await?;
68    let token = authorize(
69        &mut conn,
70        Act::Teach,
71        Some(user.id),
72        Res::Course(*course_id),
73    )
74    .await?;
75
76    let peer_or_self_review_config =
77        models::peer_or_self_review_configs::get_course_default_cms_peer_review(
78            &mut conn, *course_id,
79        )
80        .await?;
81
82    let peer_or_self_review_questions =
83        models::peer_or_self_review_questions::get_course_default_cms_peer_or_self_review_questions(
84            &mut conn,
85            peer_or_self_review_config.id,
86        )
87        .await?;
88
89    token.authorized_ok(web::Json(CmsPeerOrSelfReviewConfiguration {
90        peer_or_self_review_config,
91        peer_or_self_review_questions,
92    }))
93}
94
95#[instrument(skip(pool))]
96async fn put_course_default_peer_or_self_review_configuration(
97    course_id: web::Path<Uuid>,
98    user: AuthUser,
99    pool: web::Data<PgPool>,
100    payload: web::Json<CmsPeerOrSelfReviewConfiguration>,
101) -> ControllerResult<web::Json<CmsPeerOrSelfReviewConfiguration>> {
102    let mut conn = pool.acquire().await?;
103    let token = authorize(
104        &mut conn,
105        Act::Teach,
106        Some(user.id),
107        Res::Course(*course_id),
108    )
109    .await?;
110    let mut config = payload.0;
111    normalize_cms_peer_or_self_review_questions(&mut config.peer_or_self_review_questions);
112    let cms_peer_or_self_review_configuration =
113        peer_or_self_review_configs::upsert_course_default_cms_peer_review_and_questions(
114            &mut conn, &config,
115        )
116        .await?;
117    token.authorized_ok(web::Json(cms_peer_or_self_review_configuration))
118}
119
120/**
121GET `/api/v0/cms/courses/:course_id/pages` - Gets all pages for a course.
122*/
123#[instrument(skip(pool))]
124async fn get_all_pages(
125    course_id: web::Path<Uuid>,
126    pool: web::Data<PgPool>,
127    user: AuthUser,
128) -> ControllerResult<web::Json<Vec<Page>>> {
129    let mut conn = pool.acquire().await?;
130    let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(*course_id)).await?;
131
132    let res = models::pages::get_all_by_course_id_and_visibility(
133        &mut conn,
134        *course_id,
135        PageVisibility::Any,
136    )
137    .await?;
138
139    token.authorized_ok(web::Json(res))
140}
141
142/**
143PUT `/api/v0/cms/courses/:course_id/research-consent-form` - Upserts courses research form from Gutenberg research form edit.
144*/
145
146#[instrument(skip(pool, payload))]
147async fn upsert_course_research_form(
148    payload: web::Json<NewResearchForm>,
149    pool: web::Data<PgPool>,
150    course_id: web::Path<Uuid>,
151    user: AuthUser,
152) -> ControllerResult<web::Json<ResearchForm>> {
153    let mut conn = pool.acquire().await?;
154
155    let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::GlobalPermissions).await?;
156    let new_research_form = payload;
157    let res = models::research_forms::upsert_research_form(
158        &mut conn,
159        PKeyPolicy::Generate,
160        &new_research_form,
161    )
162    .await?;
163
164    token.authorized_ok(web::Json(res))
165}
166
167/**
168GET `/api/v0/cms/courses/:course_id/research-consent-form` - Fetches courses research form with course id.
169*/
170#[instrument(skip(pool))]
171async fn get_research_form_with_course_id(
172    course_id: web::Path<Uuid>,
173    user: AuthUser,
174    pool: web::Data<PgPool>,
175) -> ControllerResult<web::Json<Option<ResearchForm>>> {
176    let mut conn = pool.acquire().await?;
177
178    let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::GlobalPermissions).await?;
179    let res = models::research_forms::get_research_form_with_course_id(&mut conn, *course_id)
180        .await
181        .optional()?;
182
183    token.authorized_ok(web::Json(res))
184}
185
186/**
187PUT `/api/v0/cms/courses/:course_id/research-consent-form-questions` - Upserts questions for the courses research form from Gutenberg research form edit.
188*/
189
190#[instrument(skip(pool, payload))]
191async fn upsert_course_research_form_questions(
192    payload: web::Json<Vec<NewResearchFormQuestion>>,
193    pool: web::Data<PgPool>,
194    course_id: web::Path<Uuid>,
195    user: AuthUser,
196) -> ControllerResult<web::Json<Vec<ResearchFormQuestion>>> {
197    let mut conn = pool.acquire().await?;
198
199    let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::GlobalPermissions).await?;
200
201    let res = models::research_forms::upsert_research_form_questions(&mut conn, &payload).await?;
202
203    token.authorized_ok(web::Json(res))
204}
205
206/**
207GET `/api/v0/cms/courses/:course_id/modules`
208Returns modules in the course.
209*/
210#[instrument(skip(pool))]
211async fn get_course_modules(
212    course_id: web::Path<Uuid>,
213    user: AuthUser,
214    pool: web::Data<PgPool>,
215) -> ControllerResult<web::Json<Vec<CourseModule>>> {
216    let mut conn = pool.acquire().await?;
217    let course_modules = models::course_modules::get_by_course_id(&mut conn, *course_id).await?;
218    let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(*course_id)).await?;
219    token.authorized_ok(web::Json(course_modules))
220}
221
222/**
223GET `/api/v0/cms/courses/:course_id/course-instances` - Returns all course instances for given course id.
224*/
225#[instrument(skip(pool))]
226async fn get_course_instances(
227    course_id: web::Path<Uuid>,
228    user: AuthUser,
229    pool: web::Data<PgPool>,
230) -> ControllerResult<web::Json<Vec<CourseInstance>>> {
231    let mut conn = pool.acquire().await?;
232    let instances =
233        models::course_instances::get_course_instances_for_course(&mut conn, *course_id).await?;
234    let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(*course_id)).await?;
235    token.authorized_ok(web::Json(instances))
236}
237
238/**
239 POST /api/v0/main-frontend/courses/:course_id/partners_block - Create or updates a partners block for a course
240*/
241#[instrument(skip(payload, pool))]
242async fn post_partners_block(
243    path: web::Path<Uuid>,
244    payload: web::Json<Option<serde_json::Value>>,
245    pool: web::Data<PgPool>,
246    user: AuthUser,
247) -> ControllerResult<web::Json<()>> {
248    let course_id = path.into_inner();
249
250    let content = payload.into_inner();
251    let mut conn = pool.acquire().await?;
252    let token = authorize(&mut conn, Act::Teach, Some(user.id), Res::Course(course_id)).await?;
253
254    models::partner_block::upsert_partner_block(&mut conn, course_id, content).await?;
255
256    token.authorized_ok(web::Json(()))
257}
258
259/**
260GET /courses/:course_id/partners_blocks - Gets a partners block related to a course
261*/
262#[instrument(skip(pool))]
263async fn get_partners_block(
264    path: web::Path<Uuid>,
265    user: AuthUser,
266    pool: web::Data<PgPool>,
267) -> ControllerResult<web::Json<PartnersBlock>> {
268    let course_id = path.into_inner();
269    let mut conn = pool.acquire().await?;
270    let token = skip_authorize();
271
272    // Check if the course exists in the partners_blocks table
273    let course_exists = models::partner_block::check_if_course_exists(&mut conn, course_id).await?;
274
275    let partner_block = if course_exists {
276        // If the course exists, fetch the partner block
277        models::partner_block::get_partner_block(&mut conn, course_id).await?
278    } else {
279        // If the course does not exist, create a new partner block with an empty content array
280        let empty_content: Option<serde_json::Value> = Some(serde_json::Value::Array(vec![]));
281
282        // Upsert the partner block with the empty content
283        models::partner_block::upsert_partner_block(&mut conn, course_id, empty_content).await?
284    };
285
286    token.authorized_ok(web::Json(partner_block))
287}
288
289/**
290DELETE `/api/v0/main-frontend/courses/:course_id` - Delete a partners block in a course.
291*/
292#[instrument(skip(pool))]
293async fn delete_partners_block(
294    path: web::Path<Uuid>,
295    pool: web::Data<PgPool>,
296    user: AuthUser,
297) -> ControllerResult<web::Json<PartnersBlock>> {
298    let course_id = path.into_inner();
299    let mut conn = pool.acquire().await?;
300    let token = authorize(
301        &mut conn,
302        Act::UsuallyUnacceptableDeletion,
303        Some(user.id),
304        Res::Course(course_id),
305    )
306    .await?;
307    let deleted_partners_block =
308        models::partner_block::delete_partner_block(&mut conn, course_id).await?;
309
310    token.authorized_ok(web::Json(deleted_partners_block))
311}
312
313/**
314Add a route for each controller in this module.
315
316The name starts with an underline in order to appear before other functions in the module documentation.
317
318We add the routes by calling the route method instead of using the route annotations because this method preserves the function signatures for documentation.
319*/
320pub fn _add_routes(cfg: &mut ServiceConfig) {
321    cfg.route("/{course_id}/upload", web::post().to(add_media))
322        .route(
323            "/{course_id}/default-peer-review",
324            web::get().to(get_course_default_peer_or_self_review_configuration),
325        )
326        .route(
327            "/{course_id}/default-peer-review",
328            web::put().to(put_course_default_peer_or_self_review_configuration),
329        )
330        .route("/{course_id}/pages", web::get().to(get_all_pages))
331        .route(
332            "/{courseId}/research-consent-form-questions",
333            web::put().to(upsert_course_research_form_questions),
334        )
335        .route(
336            "/{course_id}/research-consent-form",
337            web::get().to(get_research_form_with_course_id),
338        )
339        .route(
340            "/{course_id}/research-consent-form",
341            web::put().to(upsert_course_research_form),
342        )
343        .route(
344            "/{course_id}/partners-block",
345            web::post().to(post_partners_block),
346        )
347        .route(
348            "/{course_id}/partners-block",
349            web::get().to(get_partners_block),
350        )
351        .route(
352            "/{course_id}/partners-block",
353            web::delete().to(delete_partners_block),
354        )
355        .route("/{course_id}/modules", web::get().to(get_course_modules))
356        .route(
357            "/{course_id}/course-instances",
358            web::get().to(get_course_instances),
359        );
360}