headless_lms_server/controllers/main_frontend/
exercise_slide_submissions.rs1use crate::{domain::models_requests, prelude::*};
2use headless_lms_models::exercise_slide_submissions::ExerciseSlideSubmissionInfo;
3use models::{
4 exercises::get_exercise_by_id,
5 teacher_grading_decisions::{
6 NewTeacherGradingDecision, TeacherDecisionType, TeacherGradingDecision,
7 },
8 user_exercise_states::UserExerciseState,
9};
10use utoipa::{OpenApi, ToSchema};
11
12#[derive(OpenApi)]
13#[openapi(paths(get_submission_info, get_user_exercise_state_info, add_teacher_grading))]
14pub(crate) struct MainFrontendExerciseSlideSubmissionsApiDoc;
15
16#[utoipa::path(
20 get,
21 path = "/{submission_id}/info",
22 operation_id = "getExerciseSlideSubmissionInfo",
23 tag = "exercise_slide_submissions",
24 params(
25 ("submission_id" = Uuid, Path, description = "Exercise slide submission id")
26 ),
27 responses(
28 (status = 200, description = "Exercise slide submission info", body = ExerciseSlideSubmissionInfo)
29 )
30)]
31#[instrument(skip(pool))]
32async fn get_submission_info(
33 submission_id: web::Path<Uuid>,
34 pool: web::Data<PgPool>,
35 user: AuthUser,
36) -> ControllerResult<web::Json<ExerciseSlideSubmissionInfo>> {
37 let mut conn = pool.acquire().await?;
38 let token = authorize(
39 &mut conn,
40 Act::Teach,
41 Some(user.id),
42 Res::ExerciseSlideSubmission(*submission_id),
43 )
44 .await?;
45
46 let submission_id_uuid = submission_id.into_inner();
47
48 let submission =
50 models::exercise_slide_submissions::get_by_id(&mut conn, submission_id_uuid).await?;
51
52 let res = models::exercise_slide_submissions::get_exercise_slide_submission_info(
53 &mut conn,
54 submission_id_uuid,
55 submission.user_id,
56 models_requests::fetch_service_info,
57 true,
58 )
59 .await?;
60
61 token.authorized_ok(web::Json(res))
62}
63
64#[derive(Debug, Deserialize, ToSchema)]
65
66pub struct ExerciseStateIds {
67 exercise_id: Uuid,
68 user_id: Uuid,
69}
70#[utoipa::path(
74 get,
75 path = "/{exam_id}/user-exercise-state-info",
76 operation_id = "getExamUserExerciseStateInfo",
77 tag = "exercise_slide_submissions",
78 params(
79 ("exam_id" = Uuid, Path, description = "Exam id"),
80 ("exercise_id" = Uuid, Query, description = "Exercise id"),
81 ("user_id" = Uuid, Query, description = "User id")
82 ),
83 responses(
84 (status = 200, description = "User exercise state for the exam submission", body = UserExerciseState)
85 )
86)]
87#[instrument(skip(pool))]
88async fn get_user_exercise_state_info(
89 exam_id: web::Path<Uuid>,
90 pool: web::Data<PgPool>,
91 query_ids: web::Query<ExerciseStateIds>,
92 user: AuthUser,
93) -> ControllerResult<web::Json<UserExerciseState>> {
94 let mut conn = pool.acquire().await?;
95 let token = authorize(&mut conn, Act::Teach, Some(user.id), Res::Exam(*exam_id)).await?;
96
97 let res = models::user_exercise_states::get_or_create_user_exercise_state(
98 &mut conn,
99 query_ids.user_id,
100 query_ids.exercise_id,
101 None,
102 Some(*exam_id),
103 )
104 .await?;
105 token.authorized_ok(web::Json(res))
106}
107
108#[utoipa::path(
112 put,
113 path = "/add-teacher-grading-for-exam-submission",
114 operation_id = "addTeacherGradingForExamSubmission",
115 tag = "exercise_slide_submissions",
116 request_body = NewTeacherGradingDecision,
117 responses(
118 (status = 200, description = "Created teacher grading decision", body = TeacherGradingDecision)
119 )
120)]
121#[instrument(skip(pool))]
122async fn add_teacher_grading(
123 payload: web::Json<NewTeacherGradingDecision>,
124 pool: web::Data<PgPool>,
125 user: AuthUser,
126) -> ControllerResult<web::Json<TeacherGradingDecision>> {
127 let action = &payload.action;
128 let exercise_id = payload.exercise_id;
129 let user_exercise_state_id = payload.user_exercise_state_id;
130 let manual_points = payload.manual_points;
131 let justification = &payload.justification;
132 let mut conn = pool.acquire().await?;
133 let token = authorize(
134 &mut conn,
135 Act::Edit,
136 Some(user.id),
137 Res::Exercise(exercise_id),
138 )
139 .await?;
140
141 let points_given;
142 if *action == TeacherDecisionType::CustomPoints {
143 let exercise = get_exercise_by_id(&mut conn, exercise_id).await?;
144 let max_points = exercise.score_maximum as f32;
145
146 points_given = manual_points.unwrap_or(0.0);
147
148 if max_points < points_given {
149 return Err(ControllerError::new(
150 ControllerErrorType::BadRequest,
151 "Cannot give more points than maximum score".to_string(),
152 None,
153 ));
154 }
155 } else {
156 return Err(ControllerError::new(
157 ControllerErrorType::BadRequest,
158 "Invalid query".to_string(),
159 None,
160 ));
161 }
162
163 info!(
164 "Teacher took the following action: {:?}. Points given: {:?}.",
165 &action, points_given
166 );
167
168 let mut tx = conn.begin().await?;
169
170 let _res = models::teacher_grading_decisions::add_teacher_grading_decision(
171 &mut tx,
172 user_exercise_state_id,
173 *action,
174 points_given,
175 Some(user.id),
176 justification.clone(),
177 true,
178 )
179 .await?;
180
181 tx.commit().await?;
182
183 token.authorized_ok(web::Json(_res))
184}
185
186pub fn _add_routes(cfg: &mut ServiceConfig) {
187 cfg.route("/{submission_id}/info", web::get().to(get_submission_info))
188 .route(
189 "/{exam_id}/user-exercise-state-info",
190 web::get().to(get_user_exercise_state_info),
191 )
192 .route(
193 "/add-teacher-grading-for-exam-submission",
194 web::put().to(add_teacher_grading),
195 );
196}