headless_lms_server/controllers/main_frontend/
proposed_edits.rs

1use std::sync::Arc;
2
3use models::proposed_page_edits::{self, EditProposalInfo, PageProposal, ProposalCount};
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, get_edit_proposal_count, 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/**
72GET `/api/v0/main-frontend/proposed-edits/course/:id/count` - Returns the amount of feedback for the given course.
73*/
74#[instrument(skip(pool))]
75#[utoipa::path(
76    get,
77    path = "/course/{course_id}/count",
78    operation_id = "getEditProposalCount",
79    tag = "proposed_edits",
80    params(
81        ("course_id" = Uuid, Path, description = "Course id")
82    ),
83    responses(
84        (status = 200, description = "Edit proposal counts", body = ProposalCount)
85    )
86)]
87pub async fn get_edit_proposal_count(
88    course_id: web::Path<Uuid>,
89    pool: web::Data<PgPool>,
90    user: AuthUser,
91) -> ControllerResult<web::Json<ProposalCount>> {
92    let mut conn = pool.acquire().await?;
93
94    let edit_proposal_count =
95        proposed_page_edits::get_proposal_count_for_course(&mut conn, *course_id).await?;
96
97    let token = authorize(
98        &mut conn,
99        Act::Teach,
100        Some(user.id),
101        Res::Course(*course_id),
102    )
103    .await?;
104    token.authorized_ok(web::Json(edit_proposal_count))
105}
106
107/**
108POST `/api/v0/main-frontend/proposed-edits/process-edit-proposal` - Processes the given edit proposal.
109*/
110#[instrument(skip(pool, app_conf))]
111#[utoipa::path(
112    post,
113    path = "/process-edit-proposal",
114    operation_id = "processEditProposal",
115    tag = "proposed_edits",
116    request_body = EditProposalInfo,
117    responses(
118        (status = 200, description = "Processed edit proposal")
119    )
120)]
121pub async fn process_edit_proposal(
122    request_id: RequestId,
123    proposal: web::Json<EditProposalInfo>,
124    app_conf: web::Data<ApplicationConfiguration>,
125    user: AuthUser,
126    pool: web::Data<PgPool>,
127    jwt_key: web::Data<JwtKey>,
128) -> ControllerResult<HttpResponse> {
129    let mut conn = pool.acquire().await?;
130    let proposal = proposal.into_inner();
131
132    proposed_page_edits::process_proposal(
133        &mut conn,
134        proposal.page_id,
135        proposal.page_proposal_id,
136        proposal.block_proposals,
137        user.id,
138        models_requests::make_spec_fetcher(
139            app_conf.base_url.clone(),
140            request_id.0,
141            Arc::clone(&jwt_key),
142        ),
143        models_requests::fetch_service_info,
144    )
145    .await?;
146
147    let token = authorize(
148        &mut conn,
149        Act::Edit,
150        Some(user.id),
151        Res::Page(proposal.page_id),
152    )
153    .await?;
154    token.authorized_ok(HttpResponse::Ok().finish())
155}
156
157/**
158Add a route for each controller in this module.
159
160The name starts with an underline in order to appear before other functions in the module documentation.
161
162We add the routes by calling the route method instead of using the route annotations because this method preserves the function signatures for documentation.
163*/
164pub fn _add_routes(cfg: &mut ServiceConfig) {
165    cfg.route("/course/{course_id}", web::get().to(get_edit_proposals))
166        .route(
167            "/course/{course_id}/count",
168            web::get().to(get_edit_proposal_count),
169        )
170        .route(
171            "/process-edit-proposal",
172            web::post().to(process_edit_proposal),
173        );
174}