Skip to main content

headless_lms_server/controllers/main_frontend/
agent_preview.rs

1//! Controllers for requests starting with `/api/v0/main-frontend/agent-preview`: the login-free,
2//! read-only preview of a page as an AI agent staged it, or as published.
3
4use models::library::agent_changesets::{
5    PagePreview, resolve_changeset_preview, resolve_preview, resolve_published_course_preview,
6    resolve_published_preview, validate_preview_token,
7};
8use utoipa::{IntoParams, OpenApi};
9
10use super::agent_changesets::authorize_own_changeset;
11use crate::prelude::*;
12
13#[derive(OpenApi)]
14#[openapi(paths(get_preview))]
15pub(crate) struct MainFrontendAgentPreviewApiDoc;
16
17#[derive(Debug, Deserialize, IntoParams)]
18#[into_params(parameter_in = Query)]
19pub struct AgentPreviewQuery {
20    /// The page to show; for a token of several pages only. Defaults to the first.
21    page: Option<Uuid>,
22}
23
24/**
25GET `/api/v0/main-frontend/agent-preview/{token}` - The page a preview token points at, staged or
26published, or for a token of several pages the page `page` with the list of pages. No login: the
27short-lived random token stands in for it, while its user may still edit the course.
28*/
29#[instrument(skip(pool, token))]
30#[utoipa::path(
31    get,
32    path = "/{token}",
33    operation_id = "getAgentPagePreview",
34    tag = "agent_preview",
35    params(
36        ("token" = String, Path, description = "Preview token"),
37        AgentPreviewQuery
38    ),
39    responses(
40        (status = 200, description = "The staged page", body = PagePreview)
41    )
42)]
43async fn get_preview(
44    pool: web::Data<PgPool>,
45    token: web::Path<String>,
46    query: web::Query<AgentPreviewQuery>,
47) -> ControllerResult<web::Json<PagePreview>> {
48    let mut conn = pool.acquire().await?;
49    let claim = validate_preview_token(&mut conn, &token).await?;
50    let (preview, auth) = match claim.changeset_id {
51        Some(changeset_id) => {
52            let (changeset, auth) =
53                authorize_own_changeset(&mut conn, claim.user_id, changeset_id).await?;
54            let preview = match claim.page_id {
55                Some(page_id) => resolve_preview(&mut conn, &changeset, page_id).await?,
56                None => resolve_changeset_preview(&mut conn, &changeset, query.page).await?,
57            };
58            (preview, auth)
59        }
60        None => {
61            let auth = authorize(
62                &mut conn,
63                Act::Edit,
64                Some(claim.user_id),
65                Res::Course(claim.course_id),
66            )
67            .await?;
68            let preview = match claim.page_id {
69                Some(page_id) => {
70                    resolve_published_preview(&mut conn, claim.course_id, page_id).await?
71                }
72                None => {
73                    resolve_published_course_preview(&mut conn, claim.course_id, query.page).await?
74                }
75            };
76            (preview, auth)
77        }
78    };
79    auth.authorized_ok(web::Json(preview))
80}
81
82pub fn _add_routes(cfg: &mut ServiceConfig) {
83    cfg.route("/{token}", web::get().to(get_preview));
84}