Skip to main content

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