headless_lms_server/controllers/main_frontend/
exercise_slide_submissions.rs

1use 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/**
17GET `/api/v0/main-frontend/exercise-slide-submissions/{submission_id}/info"`- Returns data necessary for rendering a submission.
18*/
19#[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    // First get the submission to find the correct user_id
49    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/**
71GET `/api/v0/main-frontend/exercise-slide-submissions/{exam_id}/{exercise_id}/{user_id}/user-exercise-state-info`-
72*/
73#[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/**
109PUT `/api/v0/main-frontend/exercise-slide-submissions/add_teacher_grading"` - Adds a new teacher grading decision, without updating user exercise state
110*/
111#[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}