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