headless_lms_server/controllers/main_frontend/
teacher_grading_decisions.rs

1use crate::prelude::*;
2use headless_lms_models::{
3    exercises::get_exercise_by_id,
4    library::user_exercise_state_updater,
5    teacher_grading_decisions::{NewTeacherGradingDecision, TeacherDecisionType},
6    user_exercise_states::UserExerciseState,
7};
8use utoipa::OpenApi;
9
10#[derive(OpenApi)]
11#[openapi(paths(create_teacher_grading_decision))]
12pub(crate) struct MainFrontendTeacherGradingDecisionsApiDoc;
13
14/**
15POST `/api/v0/main-frontend/teacher-grading-decisions` - Creates a new teacher grading decision, overriding the points a user has received from an exercise.
16*/
17#[utoipa::path(
18    post,
19    path = "",
20    operation_id = "createTeacherGradingDecision",
21    tag = "teacher_grading_decisions",
22    request_body = NewTeacherGradingDecision,
23    responses(
24        (status = 200, description = "Teacher grading decision created", body = Option<UserExerciseState>)
25    )
26)]
27#[instrument(skip(pool))]
28async fn create_teacher_grading_decision(
29    payload: web::Json<NewTeacherGradingDecision>,
30    pool: web::Data<PgPool>,
31    user: AuthUser,
32) -> ControllerResult<web::Json<Option<UserExerciseState>>> {
33    let action = &payload.action;
34    let exercise_id = payload.exercise_id;
35    let user_exercise_state_id = payload.user_exercise_state_id;
36    let manual_points = payload.manual_points;
37    let justification = &payload.justification;
38    let hidden = payload.hidden;
39    let mut conn = pool.acquire().await?;
40    let token = authorize(
41        &mut conn,
42        Act::Edit,
43        Some(user.id),
44        Res::Exercise(exercise_id),
45    )
46    .await?;
47    let points_given;
48    if *action == TeacherDecisionType::FullPoints {
49        let exercise = get_exercise_by_id(&mut conn, exercise_id).await?;
50        points_given = exercise.score_maximum as f32;
51    } else if *action == TeacherDecisionType::ZeroPoints {
52        points_given = 0.0;
53    } else if *action == TeacherDecisionType::CustomPoints {
54        points_given = manual_points.unwrap_or(0.0);
55    } else if *action == TeacherDecisionType::SuspectedPlagiarism {
56        points_given = 0.0;
57    } else if *action == TeacherDecisionType::RejectAndReset {
58        points_given = 0.0;
59
60        let mut tx = conn.begin().await?;
61
62        let _res = models::teacher_grading_decisions::add_teacher_grading_decision(
63            &mut tx,
64            user_exercise_state_id,
65            *action,
66            points_given,
67            Some(user.id),
68            justification.clone(),
69            hidden,
70        )
71        .await?;
72
73        let student_state =
74            models::user_exercise_states::get_by_id(&mut tx, user_exercise_state_id).await?;
75        let users_and_exercises = vec![(student_state.user_id, vec![exercise_id])];
76
77        let course_id = student_state.course_id.ok_or_else(|| {
78            ControllerError::new(
79                ControllerErrorType::BadRequest,
80                "RejectAndReset requires course_id".to_string(),
81                None,
82            )
83        })?;
84
85        let _reset = models::exercises::reset_exercises_for_selected_users(
86            &mut tx,
87            &users_and_exercises,
88            Some(user.id),
89            course_id,
90            Some("reset-by-staff".to_string()),
91        )
92        .await?;
93
94        tx.commit().await?;
95        info!("Teacher took the following action: RejectAndReset.",);
96
97        return token.authorized_ok(web::Json(None));
98    } else {
99        return Err(ControllerError::new(
100            ControllerErrorType::BadRequest,
101            "Invalid query".to_string(),
102            None,
103        ));
104    }
105
106    info!(
107        "Teacher took the following action: {:?}. Points given: {:?}.",
108        &action, points_given
109    );
110
111    let mut tx = conn.begin().await?;
112
113    let _res = models::teacher_grading_decisions::add_teacher_grading_decision(
114        &mut tx,
115        user_exercise_state_id,
116        *action,
117        points_given,
118        Some(user.id),
119        justification.clone(),
120        hidden,
121    )
122    .await?;
123
124    let new_user_exercise_state =
125        user_exercise_state_updater::update_user_exercise_state(&mut tx, user_exercise_state_id)
126            .await?;
127
128    if let Some(course_id) = new_user_exercise_state.course_id {
129        // Since the teacher just reviewed the submission we should mark possible peer review queue entries so that they won't be given to others to review. Receiving peer reviews for this answer now would not make much sense.
130        models::peer_review_queue_entries::remove_queue_entries_for_unusual_reason(
131            &mut tx,
132            new_user_exercise_state.user_id,
133            new_user_exercise_state.exercise_id,
134            course_id,
135        )
136        .await?;
137    }
138
139    tx.commit().await?;
140
141    token.authorized_ok(web::Json(Some(new_user_exercise_state)))
142}
143
144pub fn _add_routes(cfg: &mut ServiceConfig) {
145    cfg.route("", web::post().to(create_teacher_grading_decision));
146}