Skip to main content

headless_lms_server/controllers/cms/
pages.rs

1//! Controllers for requests starting with `/api/v0/cms/pages`.
2
3use models::{
4    CourseOrExamId,
5    page_history::HistoryChangeReason,
6    pages::{
7        CmsPageUpdate, ContentManagementPage, PageInfo, PageNavigationInformation, PageUpdateArgs,
8        PageVisibility,
9    },
10};
11
12use crate::{
13    domain::{
14        models_requests::{self, JwtKey},
15        request_id::RequestId,
16    },
17    prelude::*,
18};
19use utoipa::OpenApi;
20
21#[derive(OpenApi)]
22#[openapi(paths(get_page, get_page_info, update_page, get_page_navigation))]
23pub(crate) struct CmsPagesApiDoc;
24
25/**
26GET `/api/v0/cms/pages/:page_id` - Get a page with exercises and exercise tasks by id.
27
28Request: `GET /api/v0/cms/pages/40ca9bcf-8eaa-41ba-940e-0fd5dd0c3c02`
29*/
30#[instrument(skip(pool))]
31#[utoipa::path(
32    get,
33    path = "/{page_id}",
34    operation_id = "getCmsPage",
35    tag = "cms_pages",
36    params(
37        ("page_id" = Uuid, Path, description = "Page id")
38    ),
39    responses(
40        (status = 200, description = "CMS page with exercises and peer review data", body = ContentManagementPage)
41    )
42)]
43async fn get_page(
44    page_id: web::Path<Uuid>,
45    pool: web::Data<PgPool>,
46    user: AuthUser,
47) -> ControllerResult<web::Json<ContentManagementPage>> {
48    let mut conn = pool.acquire().await?;
49    let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Page(*page_id)).await?;
50
51    let cms_page = models::pages::get_page_with_exercises(&mut conn, *page_id).await?;
52    token.authorized_ok(web::Json(cms_page))
53}
54
55/**
56GET `/api/v0/cms/pages/:page_id/info` - Get a pages's course id, course name, organization slug
57
58Request: `GET /api/v0/cms/pages/40ca9bcf-8eaa-41ba-940e-0fd5dd0c3c02/info`
59*/
60#[utoipa::path(
61    get,
62    path = "/{page_id}/info",
63    operation_id = "getCmsPageInfo",
64    tag = "cms_pages",
65    params(
66        ("page_id" = Uuid, Path, description = "Page id")
67    ),
68    responses(
69        (status = 200, description = "Page info", body = PageInfo)
70    )
71)]
72async fn get_page_info(
73    page_id: web::Path<Uuid>,
74    pool: web::Data<PgPool>,
75    user: AuthUser,
76) -> ControllerResult<web::Json<PageInfo>> {
77    let mut conn = pool.acquire().await?;
78    let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Page(*page_id)).await?;
79
80    let cms_page_info = models::pages::get_page_info(&mut conn, *page_id).await?;
81    token.authorized_ok(web::Json(cms_page_info))
82}
83
84/**
85PUT `/api/v0/cms/pages/:page_id` - Update a page by id.
86
87Please note that this endpoint will change all the exercise and exercise task ids you've created. Make sure the use the updated ids from the response object.
88
89If optional property front_page_of_chapter_id is set, this page will become the front page of the specified course part.
90
91# Example: OUTDATED
92
93Request:
94
95```http
96PUT /api/v0/cms/pages/40ca9bcf-8eaa-41ba-940e-0fd5dd0c3c02 HTTP/1.1
97Content-Type: application/json
98
99{
100  "content": [{"type": "x"}],
101  "url_path": "/part-1/hello-world",
102  "title": "Hello world!",
103  "chapter_id": "2495ffa3-7ea9-4615-baa5-828023688c79"
104}
105```
106*/
107
108#[instrument(skip(pool, app_conf))]
109#[utoipa::path(
110    put,
111    path = "/{page_id}",
112    operation_id = "updateCmsPage",
113    tag = "cms_pages",
114    params(
115        ("page_id" = Uuid, Path, description = "Page id")
116    ),
117    request_body = CmsPageUpdate,
118    responses(
119        (status = 200, description = "Updated CMS page", body = ContentManagementPage)
120    )
121)]
122async fn update_page(
123    request_id: RequestId,
124    payload: web::Json<CmsPageUpdate>,
125    page_id: web::Path<Uuid>,
126    pool: web::Data<PgPool>,
127    jwt_key: web::Data<JwtKey>,
128    app_conf: web::Data<ApplicationConfiguration>,
129    user: AuthUser,
130) -> ControllerResult<web::Json<ContentManagementPage>> {
131    let mut conn = pool.acquire().await?;
132    let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Page(*page_id)).await?;
133
134    let cms_page_update = payload.0;
135    let course_or_exam_id = models::pages::get_course_and_exam_id(&mut conn, *page_id).await?;
136    let is_exam_page = matches!(course_or_exam_id, CourseOrExamId::Exam(_));
137    let (expected_course_id, expected_exam_id) = match course_or_exam_id {
138        CourseOrExamId::Course(course_id) => (Some(course_id), None),
139        CourseOrExamId::Exam(exam_id) => (None, Some(exam_id)),
140    };
141    let saved = models::pages::update_by_id_in_parent_context(
142        &mut conn,
143        PageUpdateArgs {
144            page_id: *page_id,
145            author: user.id,
146            cms_page_update,
147            retain_ids: false,
148            history_change_reason: HistoryChangeReason::PageSaved,
149            is_exam_page,
150        },
151        expected_course_id,
152        expected_exam_id,
153        models_requests::make_spec_fetcher(
154            app_conf.base_url.clone(),
155            request_id.0,
156            jwt_key.into_inner(),
157        ),
158        models_requests::fetch_service_info,
159    )
160    .await?;
161    token.authorized_ok(web::Json(saved))
162}
163
164/**
165GET /api/v0/cms/pages/:page_id/page-navigation - tells what's the next page, previous page, and the chapter front page given a page id.
166*/
167#[instrument(skip(pool))]
168#[utoipa::path(
169    get,
170    path = "/{page_id}/page-navigation",
171    operation_id = "getCmsPageNavigation",
172    tag = "cms_pages",
173    params(
174        ("page_id" = Uuid, Path, description = "Page id")
175    ),
176    responses(
177        (status = 200, description = "Page navigation", body = PageNavigationInformation)
178    )
179)]
180async fn get_page_navigation(
181    page_id: web::Path<Uuid>,
182    pool: web::Data<PgPool>,
183) -> ControllerResult<web::Json<PageNavigationInformation>> {
184    let mut conn = pool.acquire().await?;
185    let token = skip_authorize();
186    let res =
187        models::pages::get_page_navigation_data(&mut conn, *page_id, PageVisibility::Any).await?;
188
189    token.authorized_ok(web::Json(res))
190}
191/**
192Add a route for each controller in this module.
193
194The name starts with an underline in order to appear before other functions in the module documentation.
195
196We add the routes by calling the route method instead of using the route annotations because this method preserves the function signatures for documentation.
197*/
198pub fn _add_routes(cfg: &mut ServiceConfig) {
199    cfg.route("/{page_id}", web::get().to(get_page))
200        .route("/{page_id}/info", web::get().to(get_page_info))
201        .route(
202            "/{page_id}/page-navigation",
203            web::get().to(get_page_navigation),
204        )
205        .route("/{page_id}", web::put().to(update_page));
206}