headless_lms_server/controllers/main_frontend/
proposed_edits.rs1use 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#[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#[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
131pub 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}