Skip to main content

headless_lms_server/controllers/main_frontend/
proposed_edits.rs

1use std::sync::Arc;
2
3use models::proposed_page_edits::{self, EditProposalInfo, PageProposal};
4use utoipa::OpenApi;
5
6use crate::{
7    domain::{
8        models_requests::{self, JwtKey},
9        request_id::RequestId,
10    },
11    prelude::*,
12};
13
14#[derive(OpenApi)]
15#[openapi(paths(get_edit_proposals, process_edit_proposal))]
16pub(crate) struct MainFrontendProposedEditsApiDoc;
17
18#[derive(Debug, Deserialize)]
19
20pub struct GetEditProposalsQuery {
21    pending: bool,
22    #[serde(flatten)]
23    pagination: Pagination,
24}
25
26/**
27GET `/api/v0/main-frontend/proposed-edits/course/:id?pending=true` - Returns feedback for the given course.
28*/
29#[instrument(skip(pool))]
30#[utoipa::path(
31    get,
32    path = "/course/{course_id}",
33    operation_id = "getEditProposals",
34    tag = "proposed_edits",
35    params(
36        ("course_id" = Uuid, Path, description = "Course id"),
37        ("pending" = bool, Query, description = "Whether to fetch pending proposals"),
38        ("page" = Option<i64>, Query, description = "Page number"),
39        ("limit" = Option<i64>, Query, description = "Page size")
40    ),
41    responses(
42        (status = 200, description = "Edit proposals", body = Vec<PageProposal>)
43    )
44)]
45pub async fn get_edit_proposals(
46    course_id: web::Path<Uuid>,
47    pool: web::Data<PgPool>,
48    query: web::Query<GetEditProposalsQuery>,
49    user: AuthUser,
50) -> ControllerResult<web::Json<Vec<PageProposal>>> {
51    let mut conn = pool.acquire().await?;
52
53    let feedback = proposed_page_edits::get_proposals_for_course(
54        &mut conn,
55        *course_id,
56        query.pending,
57        query.pagination,
58    )
59    .await?;
60
61    let token = authorize(
62        &mut conn,
63        Act::Teach,
64        Some(user.id),
65        Res::Course(*course_id),
66    )
67    .await?;
68    token.authorized_ok(web::Json(feedback))
69}
70
71/**
72POST `/api/v0/main-frontend/proposed-edits/process-edit-proposal` - Processes the given edit proposal.
73*/
74#[instrument(skip(pool, app_conf))]
75#[utoipa::path(
76    post,
77    path = "/process-edit-proposal",
78    operation_id = "processEditProposal",
79    tag = "proposed_edits",
80    request_body = EditProposalInfo,
81    responses(
82        (status = 200, description = "Processed edit proposal")
83    )
84)]
85pub async fn process_edit_proposal(
86    request_id: RequestId,
87    proposal: web::Json<EditProposalInfo>,
88    app_conf: web::Data<ApplicationConfiguration>,
89    user: AuthUser,
90    pool: web::Data<PgPool>,
91    jwt_key: web::Data<JwtKey>,
92) -> ControllerResult<HttpResponse> {
93    let mut conn = pool.acquire().await?;
94    let proposal = proposal.into_inner();
95
96    let page_proposal =
97        proposed_page_edits::get_by_id(&mut conn, proposal.page_proposal_id).await?;
98    if page_proposal.page_id != proposal.page_id {
99        return Err(controller_err!(
100            Forbidden,
101            "Proposal does not belong to the requested page".to_string()
102        ));
103    }
104
105    let token = authorize(
106        &mut conn,
107        Act::Edit,
108        Some(user.id),
109        Res::Page(page_proposal.page_id),
110    )
111    .await?;
112
113    proposed_page_edits::process_by_id_and_page_id(
114        &mut conn,
115        page_proposal.page_id,
116        proposal.page_proposal_id,
117        proposal.block_proposals,
118        user.id,
119        models_requests::make_spec_fetcher(
120            app_conf.base_url.clone(),
121            request_id.0,
122            Arc::clone(&jwt_key),
123        ),
124        models_requests::fetch_service_info,
125    )
126    .await?;
127
128    token.authorized_ok(HttpResponse::Ok().finish())
129}
130
131/**
132Add a route for each controller in this module.
133
134The name starts with an underline in order to appear before other functions in the module documentation.
135
136We add the routes by calling the route method instead of using the route annotations because this method preserves the function signatures for documentation.
137*/
138pub fn _add_routes(cfg: &mut ServiceConfig) {
139    cfg.route("/course/{course_id}", web::get().to(get_edit_proposals))
140        .route(
141            "/process-edit-proposal",
142            web::post().to(process_edit_proposal),
143        );
144}