headless_lms_server/controllers/main_frontend/
teacher_grading_decisions.rs1use crate::prelude::*;
2use headless_lms_models::{
3 teacher_grading_decisions::{NewTeacherGradingDecision, TeacherDecisionType},
4 user_exercise_states::UserExerciseState,
5};
6use utoipa::OpenApi;
7
8#[derive(OpenApi)]
9#[openapi(paths(create_teacher_grading_decision))]
10pub(crate) struct MainFrontendTeacherGradingDecisionsApiDoc;
11
12#[utoipa::path(
16 post,
17 path = "",
18 operation_id = "createTeacherGradingDecision",
19 tag = "teacher_grading_decisions",
20 request_body = NewTeacherGradingDecision,
21 responses(
22 (status = 200, description = "Teacher grading decision created", body = Option<UserExerciseState>)
23 )
24)]
25#[instrument(skip(pool))]
26async fn create_teacher_grading_decision(
27 payload: web::Json<NewTeacherGradingDecision>,
28 pool: web::Data<PgPool>,
29 user: AuthUser,
30) -> ControllerResult<web::Json<Option<UserExerciseState>>> {
31 let action = &payload.action;
32 let exercise_id = payload.exercise_id;
33 let user_exercise_state_id = payload.user_exercise_state_id;
34 let manual_points = payload.manual_points;
35 let justification = &payload.justification;
36 let hidden = payload.hidden;
37 let mut conn = pool.acquire().await?;
38
39 let student_state =
40 models::user_exercise_states::get_by_id(&mut conn, user_exercise_state_id).await?;
41 if student_state.exercise_id != exercise_id {
42 return Err(controller_err!(
43 Forbidden,
44 "User exercise state does not belong to the requested exercise".to_string()
45 ));
46 }
47 let exercise =
48 models::exercises::get_non_deleted_by_id(&mut conn, student_state.exercise_id).await?;
49 if exercise.course_id != student_state.course_id || exercise.exam_id != student_state.exam_id {
50 return Err(controller_err!(
51 Forbidden,
52 "User exercise state does not match the requested exercise context".to_string()
53 ));
54 }
55
56 let token = authorize(
57 &mut conn,
58 Act::Edit,
59 Some(user.id),
60 Res::Exercise(student_state.exercise_id),
61 )
62 .await?;
63 let points_given;
64 if *action == TeacherDecisionType::FullPoints {
65 points_given = exercise.score_maximum as f32;
66 } else if *action == TeacherDecisionType::ZeroPoints {
67 points_given = 0.0;
68 } else if *action == TeacherDecisionType::CustomPoints {
69 points_given = manual_points.unwrap_or(0.0);
70 } else if *action == TeacherDecisionType::SuspectedPlagiarism {
71 points_given = 0.0;
72 } else if *action == TeacherDecisionType::RejectAndReset {
73 points_given = 0.0;
74
75 models::teacher_grading_decisions::upsert_by_state_id_and_exercise_id(
76 &mut conn,
77 user_exercise_state_id,
78 student_state.exercise_id,
79 *action,
80 points_given,
81 Some(user.id),
82 justification.clone(),
83 hidden,
84 )
85 .await?;
86
87 let course_id = student_state.course_id.ok_or_else(|| {
88 ControllerError::new(
89 ControllerErrorType::BadRequest,
90 "RejectAndReset requires course_id".to_string(),
91 None,
92 )
93 })?;
94
95 let _reset = models::exercises::reset_progress_by_course_id_user_ids_and_exercise_ids(
96 &mut conn,
97 course_id,
98 &[student_state.user_id],
99 &[student_state.exercise_id],
100 Some(user.id),
101 Some("reset-by-staff".to_string()),
102 )
103 .await?;
104
105 info!("Teacher took the following action: RejectAndReset.",);
106
107 return token.authorized_ok(web::Json(None));
108 } else {
109 return Err(ControllerError::new(
110 ControllerErrorType::BadRequest,
111 "Invalid query".to_string(),
112 None,
113 ));
114 }
115
116 info!(
117 "Teacher took the following action: {:?}. Points given: {:?}.",
118 &action, points_given
119 );
120
121 let _res = models::teacher_grading_decisions::upsert_by_state_id_and_exercise_id(
122 &mut conn,
123 user_exercise_state_id,
124 student_state.exercise_id,
125 *action,
126 points_given,
127 Some(user.id),
128 justification.clone(),
129 hidden,
130 )
131 .await?;
132
133 let new_user_exercise_state = models::user_exercise_states::recalculate_by_id_and_exercise_id(
134 &mut conn,
135 user_exercise_state_id,
136 student_state.exercise_id,
137 )
138 .await?;
139
140 if let Some(course_id) = new_user_exercise_state.course_id {
141 models::peer_review_queue_entries::remove_queue_entries_for_unusual_reason(
143 &mut conn,
144 new_user_exercise_state.user_id,
145 new_user_exercise_state.exercise_id,
146 course_id,
147 )
148 .await?;
149 }
150
151 token.authorized_ok(web::Json(Some(new_user_exercise_state)))
152}
153
154pub fn _add_routes(cfg: &mut ServiceConfig) {
155 cfg.route("", web::post().to(create_teacher_grading_decision));
156}