headless_lms_server/controllers/main_frontend/
pages.rs

1//! Controllers for requests starting with `/api/v0/main-frontend/pages`.
2
3use std::sync::Arc;
4
5use models::{
6    page_history::PageHistory,
7    pages::{HistoryRestoreData, NewPage, Page, PageDetailsUpdate, PageInfo},
8};
9use utoipa::OpenApi;
10
11use crate::{
12    domain::{
13        models_requests::{self, JwtKey},
14        request_id::RequestId,
15    },
16    prelude::*,
17};
18
19#[derive(OpenApi)]
20#[openapi(paths(
21    post_new_page,
22    delete_page,
23    get_page_info,
24    update_page_details,
25    history,
26    history_count,
27    restore,
28    get_all_pages_by_course_id
29))]
30pub(crate) struct MainFrontendPagesApiDoc;
31
32/**
33POST `/api/v0/main-frontend/pages` - Create a new page.
34
35Please 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.
36
37If optional property front_page_of_chapter_id is set, this page will become the front page of the specified course part.
38
39# Example:
40
41Request:
42```http
43POST /api/v0/main-frontend/pages HTTP/1.1
44Content-Type: application/json
45
46{
47  "content": [
48    {
49      "type": "x",
50      "id": "2a4e517d-a7d2-4d82-89fb-a1333d8d01d1"
51    }
52  ],
53  "url_path": "/part-2/best-page",
54  "title": "Hello world!",
55  "course_id": "10363c5b-82b4-4121-8ef1-bae8fb42a5ce",
56  "chapter_id": "2495ffa3-7ea9-4615-baa5-828023688c79"
57}
58```
59*/
60
61#[instrument(skip(pool, app_conf))]
62#[utoipa::path(
63    post,
64    path = "",
65    operation_id = "createPage",
66    tag = "pages",
67    request_body = NewPage,
68    responses(
69        (status = 200, description = "Created page", body = Page)
70    )
71)]
72async fn post_new_page(
73    request_id: RequestId,
74    payload: web::Json<NewPage>,
75    pool: web::Data<PgPool>,
76    app_conf: web::Data<ApplicationConfiguration>,
77    user: AuthUser,
78    jwt_key: web::Data<JwtKey>,
79) -> ControllerResult<web::Json<Page>> {
80    let mut conn = pool.acquire().await?;
81    let new_page = payload.0;
82    let course_id = new_page.course_id.ok_or_else(|| {
83        ControllerError::new(
84            ControllerErrorType::BadRequest,
85            "Cannot create a new page without a course id".to_string(),
86            None,
87        )
88    })?;
89    let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(course_id)).await?;
90
91    let page = models::pages::insert_new_content_page(
92        &mut conn,
93        new_page,
94        user.id,
95        models_requests::make_spec_fetcher(
96            app_conf.base_url.clone(),
97            request_id.0,
98            Arc::clone(&jwt_key),
99        ),
100        models_requests::fetch_service_info,
101    )
102    .await?;
103    token.authorized_ok(web::Json(page))
104}
105
106/**
107DELETE `/api/v0/main-frontend/pages/:page_id` - Delete a page, related exercises, and related exercise tasks by id.
108
109
110# Example
111
112Request: `DELETE /api/v0/main-frontend/pages/40ca9bcf-8eaa-41ba-940e-0fd5dd0c3c02`
113*/
114#[instrument(skip(pool))]
115#[utoipa::path(
116    delete,
117    path = "/{page_id}",
118    operation_id = "deletePage",
119    tag = "pages",
120    params(
121        ("page_id" = Uuid, Path, description = "Page id")
122    ),
123    responses(
124        (status = 200, description = "Deleted page", body = Page)
125    )
126)]
127async fn delete_page(
128    page_id: web::Path<Uuid>,
129    pool: web::Data<PgPool>,
130    user: AuthUser,
131) -> ControllerResult<web::Json<Page>> {
132    let mut conn = pool.acquire().await?;
133    let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Page(*page_id)).await?;
134    let deleted_page =
135        models::pages::delete_page_and_exercises(&mut conn, *page_id, user.id).await?;
136
137    token.authorized_ok(web::Json(deleted_page))
138}
139
140/**
141GET /api/v0/main-frontend/pages/:page_id/history
142*/
143#[instrument(skip(pool))]
144#[utoipa::path(
145    get,
146    path = "/{page_id}/history",
147    operation_id = "getPageHistory",
148    tag = "pages",
149    params(
150        ("page_id" = Uuid, Path, description = "Page id"),
151        ("page" = Option<i64>, Query, description = "Page number"),
152        ("limit" = Option<i64>, Query, description = "Page size")
153    ),
154    responses(
155        (status = 200, description = "Page history entries", body = Vec<PageHistory>)
156    )
157)]
158async fn history(
159    pool: web::Data<PgPool>,
160    page_id: web::Path<Uuid>,
161    pagination: web::Query<Pagination>,
162    user: AuthUser,
163) -> ControllerResult<web::Json<Vec<PageHistory>>> {
164    let mut conn = pool.acquire().await?;
165    let token = authorize(&mut conn, Act::Teach, Some(user.id), Res::Page(*page_id)).await?;
166
167    let res = models::page_history::history(&mut conn, *page_id, *pagination).await?;
168
169    token.authorized_ok(web::Json(res))
170}
171
172/**
173GET /api/v0/main-frontend/pages/:page_id/history_count
174*/
175#[instrument(skip(pool))]
176#[utoipa::path(
177    get,
178    path = "/{page_id}/history_count",
179    operation_id = "getPageHistoryCount",
180    tag = "pages",
181    params(
182        ("page_id" = Uuid, Path, description = "Page id")
183    ),
184    responses(
185        (status = 200, description = "Page history count", body = i64)
186    )
187)]
188async fn history_count(
189    pool: web::Data<PgPool>,
190    page_id: web::Path<Uuid>,
191    user: AuthUser,
192) -> ControllerResult<web::Json<i64>> {
193    let mut conn = pool.acquire().await?;
194    let token = authorize(&mut conn, Act::Teach, Some(user.id), Res::Page(*page_id)).await?;
195    let res = models::page_history::history_count(&mut conn, *page_id).await?;
196
197    token.authorized_ok(web::Json(res))
198}
199
200/**
201POST /api/v0/main-frontend/pages/:page_id/restore
202*/
203
204#[instrument(skip(pool, app_conf))]
205#[utoipa::path(
206    post,
207    path = "/{page_id}/restore",
208    operation_id = "restorePageHistory",
209    tag = "pages",
210    params(
211        ("page_id" = Uuid, Path, description = "Page id")
212    ),
213    request_body = HistoryRestoreData,
214    responses(
215        (status = 200, description = "Restored history id", body = Uuid)
216    )
217)]
218async fn restore(
219    request_id: RequestId,
220    pool: web::Data<PgPool>,
221    page_id: web::Path<Uuid>,
222    restore_data: web::Json<HistoryRestoreData>,
223    app_conf: web::Data<ApplicationConfiguration>,
224    user: AuthUser,
225    jwt_key: web::Data<JwtKey>,
226) -> ControllerResult<web::Json<Uuid>> {
227    let mut conn = pool.acquire().await?;
228    let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Page(*page_id)).await?;
229    let res = models::pages::restore(
230        &mut conn,
231        *page_id,
232        restore_data.history_id,
233        user.id,
234        models_requests::make_spec_fetcher(
235            app_conf.base_url.clone(),
236            request_id.0,
237            Arc::clone(&jwt_key),
238        ),
239        models_requests::fetch_service_info,
240    )
241    .await?;
242
243    token.authorized_ok(web::Json(res))
244}
245
246/**
247GET `/api/v0/main-fronted/pages/:page_id/info` - Get a pages's course id, course name, organization slug
248
249Request: `GET /api/v0/cms/pages/40ca9bcf-8eaa-41ba-940e-0fd5dd0c3c02/info`
250*/
251#[utoipa::path(
252    get,
253    path = "/{page_id}/info",
254    operation_id = "getPageInfo",
255    tag = "pages",
256    params(
257        ("page_id" = Uuid, Path, description = "Page id")
258    ),
259    responses(
260        (status = 200, description = "Page info", body = PageInfo)
261    )
262)]
263async fn get_page_info(
264    page_id: web::Path<Uuid>,
265    pool: web::Data<PgPool>,
266    user: AuthUser,
267) -> ControllerResult<web::Json<PageInfo>> {
268    let mut conn = pool.acquire().await?;
269    let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Page(*page_id)).await?;
270
271    let page_info = models::pages::get_page_info(&mut conn, *page_id).await?;
272
273    token.authorized_ok(web::Json(page_info))
274}
275
276/**
277POST `/api/v0/main-frontend/pages/:page_id/page-details` - Update pages title and url_path.
278*/
279#[instrument(skip(pool))]
280#[utoipa::path(
281    put,
282    path = "/{page_id}/page-details",
283    operation_id = "updatePageDetails",
284    tag = "pages",
285    params(
286        ("page_id" = Uuid, Path, description = "Page id")
287    ),
288    request_body = PageDetailsUpdate,
289    responses(
290        (status = 200, description = "Updated page details", body = bool)
291    )
292)]
293async fn update_page_details(
294    page_id: web::Path<Uuid>,
295    payload: web::Json<PageDetailsUpdate>,
296    pool: web::Data<PgPool>,
297    user: AuthUser,
298) -> ControllerResult<web::Json<bool>> {
299    let mut conn = pool.acquire().await?;
300
301    let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Page(*page_id)).await?;
302
303    models::pages::update_page_details(&mut conn, *page_id, &payload).await?;
304    token.authorized_ok(web::Json(true))
305}
306
307/**
308GET `/api/v0/main-frontend/pages/:course_id/all-course-pages-for-course` - Get all pages of a course
309*/
310#[utoipa::path(
311    get,
312    path = "/{course_id}/all-course-pages-for-course",
313    operation_id = "getCoursePages",
314    tag = "pages",
315    params(
316        ("course_id" = Uuid, Path, description = "Course id")
317    ),
318    responses(
319        (status = 200, description = "Course pages", body = Vec<Page>)
320    )
321)]
322async fn get_all_pages_by_course_id(
323    course_id: web::Path<Uuid>,
324    pool: web::Data<PgPool>,
325    user: AuthUser,
326) -> ControllerResult<web::Json<Vec<Page>>> {
327    let mut conn = pool.acquire().await?;
328    let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(*course_id)).await?;
329
330    let mut pages = models::pages::get_pages_by_course_id(&mut conn, *course_id).await?;
331
332    pages.sort_by(|a, b| a.order_number.cmp(&b.order_number));
333
334    token.authorized_ok(web::Json(pages))
335}
336
337/**
338Add a route for each controller in this module.
339
340The name starts with an underline in order to appear before other functions in the module documentation.
341
342We add the routes by calling the route method instead of using the route annotations because this method preserves the function signatures for documentation.
343*/
344pub fn _add_routes(cfg: &mut ServiceConfig) {
345    cfg.route("", web::post().to(post_new_page))
346        .route("/{page_id}", web::delete().to(delete_page))
347        .route("/{page_id}/info", web::get().to(get_page_info))
348        .route(
349            "/{page_id}/page-details",
350            web::put().to(update_page_details),
351        )
352        .route("/{page_id}/history", web::get().to(history))
353        .route("/{page_id}/history_count", web::get().to(history_count))
354        .route("/{page_id}/restore", web::post().to(restore))
355        .route(
356            "/{course_id}/all-course-pages-for-course",
357            web::get().to(get_all_pages_by_course_id),
358        );
359}