headless_lms_models/library/user_exercise_state_updater/
mod.rs1mod data_loader;
6mod state_deriver;
7mod validation;
8
9use crate::{
10 chapters::DatabaseChapter,
11 course_modules,
12 courses::Course,
13 exercise_slide_submissions::ExerciseSlideSubmission,
14 exercises::Exercise,
15 peer_or_self_review_configs::PeerOrSelfReviewConfig,
16 peer_or_self_review_question_submissions::PeerOrSelfReviewQuestionSubmission,
17 peer_or_self_review_questions::PeerOrSelfReviewQuestion,
18 peer_or_self_review_submissions::PeerOrSelfReviewSubmission,
19 peer_review_queue_entries::PeerReviewQueueEntry,
20 prelude::*,
21 teacher_grading_decisions::TeacherGradingDecision,
22 user_exercise_slide_states::UserExerciseSlideStateGradingSummary,
23 user_exercise_states::{self, UserExerciseState, UserExerciseStateUpdate},
24};
25
26use std::default::Default;
27
28#[derive(Debug)]
30pub struct UserExerciseStateUpdateRequiredData {
31 pub exercise: Exercise,
32 pub current_user_exercise_state: UserExerciseState,
33 pub peer_or_self_review_information:
35 Option<UserExerciseStateUpdateRequiredDataPeerReviewInformation>,
36 pub latest_teacher_grading_decision: Option<TeacherGradingDecision>,
38 pub user_exercise_slide_state_grading_summary: UserExerciseSlideStateGradingSummary,
40 pub chapter: Option<DatabaseChapter>,
42 pub course: Option<Course>,
44}
45
46#[derive(Debug)]
48pub struct UserExerciseStateUpdateRequiredDataPeerReviewInformation {
49 pub given_peer_or_self_review_submissions: Vec<PeerOrSelfReviewSubmission>,
50 pub given_self_review_submission: Option<PeerOrSelfReviewSubmission>,
51 pub latest_exercise_slide_submission_received_peer_or_self_review_question_submissions:
52 Vec<PeerOrSelfReviewQuestionSubmission>,
53 pub peer_review_queue_entry: Option<PeerReviewQueueEntry>,
54 pub peer_or_self_review_config: PeerOrSelfReviewConfig,
55 pub peer_or_self_review_questions: Vec<PeerOrSelfReviewQuestion>,
56}
57
58#[derive(Default)]
62pub struct UserExerciseStateUpdateAlreadyLoadedRequiredData {
63 pub exercise: Option<Exercise>,
64 pub current_user_exercise_state: Option<UserExerciseState>,
65 pub peer_or_self_review_information:
66 Option<UserExerciseStateUpdateAlreadyLoadedRequiredDataPeerReviewInformation>,
67 pub latest_teacher_grading_decision: Option<Option<TeacherGradingDecision>>,
69 pub user_exercise_slide_state_grading_summary: Option<UserExerciseSlideStateGradingSummary>,
70 pub chapter: Option<Option<DatabaseChapter>>,
71 pub course: Option<Course>,
72}
73
74#[derive(Default)]
78pub struct UserExerciseStateUpdateAlreadyLoadedRequiredDataPeerReviewInformation {
79 pub given_peer_or_self_review_submissions: Option<Vec<PeerOrSelfReviewSubmission>>,
80 pub given_self_review_submission: Option<Option<PeerOrSelfReviewSubmission>>,
81 pub latest_exercise_slide_submission: Option<ExerciseSlideSubmission>,
82 pub latest_exercise_slide_submission_received_peer_or_self_review_question_submissions:
83 Option<Vec<PeerOrSelfReviewQuestionSubmission>>,
84 pub peer_review_queue_entry: Option<Option<PeerReviewQueueEntry>>,
86 pub peer_or_self_review_config: Option<PeerOrSelfReviewConfig>,
87 pub peer_or_self_review_questions: Option<Vec<PeerOrSelfReviewQuestion>>,
88}
89
90pub async fn update_user_exercise_state(
92 conn: &mut PgConnection,
93 user_exercise_state_id: Uuid,
94) -> ModelResult<UserExerciseState> {
95 update_user_exercise_state_with_some_already_loaded_data(
96 conn,
97 user_exercise_state_id,
98 Default::default(),
100 )
101 .await
102}
103
104#[instrument(skip(conn, already_loaded_internal_dependencies))]
132pub async fn update_user_exercise_state_with_some_already_loaded_data(
133 conn: &mut PgConnection,
134 user_exercise_state_id: Uuid,
135 already_loaded_internal_dependencies: UserExerciseStateUpdateAlreadyLoadedRequiredData,
136) -> ModelResult<UserExerciseState> {
137 let required_data = data_loader::load_required_data(
138 conn,
139 user_exercise_state_id,
140 already_loaded_internal_dependencies,
141 )
142 .await?;
143 let exercise_id = required_data.exercise.id;
144 let exercise_is_part_of_exam = required_data.exercise.exam_id.is_some();
145
146 let prev_user_exercise_state = required_data.current_user_exercise_state.clone();
147
148 let derived_user_exercise_state = state_deriver::derive_new_user_exercise_state(required_data)?;
149
150 if derived_user_exercise_state
152 == (UserExerciseStateUpdate {
153 id: prev_user_exercise_state.id,
154 score_given: prev_user_exercise_state.score_given,
155 activity_progress: prev_user_exercise_state.activity_progress,
156 reviewing_stage: prev_user_exercise_state.reviewing_stage,
157 grading_progress: prev_user_exercise_state.grading_progress,
158 })
159 {
160 info!("Update resulting in no changes, not updating the database.");
161 return Ok(prev_user_exercise_state);
162 }
163
164 let new_saved_user_exercise_state =
165 user_exercise_states::update(conn, derived_user_exercise_state).await?;
166
167 if !exercise_is_part_of_exam {
170 let course_module = course_modules::get_by_exercise_id(conn, exercise_id).await?;
171 super::progressing::update_automatic_completion_status_and_grant_if_eligible(
172 conn,
173 &course_module,
174 new_saved_user_exercise_state.user_id,
175 )
176 .await?;
177 }
178
179 Ok(new_saved_user_exercise_state)
180}