headless_lms_models/
user_course_settings.rs

1use crate::{course_instance_enrollments::CourseInstanceEnrollment, prelude::*};
2
3#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
4#[cfg_attr(feature = "ts_rs", derive(TS))]
5pub struct UserCourseSettings {
6    pub user_id: Uuid,
7    pub course_language_group_id: Uuid,
8    pub created_at: DateTime<Utc>,
9    pub updated_at: DateTime<Utc>,
10    pub deleted_at: Option<DateTime<Utc>>,
11    pub current_course_id: Uuid,
12    pub current_course_instance_id: Uuid,
13}
14
15/// Creates new user course settings based on the enrollment or updates an existing one.
16pub async fn upsert_user_course_settings_for_enrollment(
17    conn: &mut PgConnection,
18    course_instance_enrollment: &CourseInstanceEnrollment,
19) -> ModelResult<UserCourseSettings> {
20    let user_course_settings = sqlx::query_as!(
21        UserCourseSettings,
22        "
23INSERT INTO user_course_settings (
24    user_id,
25    course_language_group_id,
26    current_course_id,
27    current_course_instance_id
28  )
29SELECT $1,
30  course_language_group_id,
31  $2,
32  $3
33FROM courses
34WHERE id = $2
35  AND deleted_at IS NULL ON CONFLICT (user_id, course_language_group_id) DO
36UPDATE
37SET current_course_id = $2,
38  current_course_instance_id = $3,
39  deleted_at = NULL
40RETURNING *;
41        ",
42        course_instance_enrollment.user_id,
43        course_instance_enrollment.course_id,
44        course_instance_enrollment.course_instance_id
45    )
46    .fetch_one(conn)
47    .await?;
48    Ok(user_course_settings)
49}
50
51pub async fn get_user_course_settings(
52    conn: &mut PgConnection,
53    user_id: Uuid,
54    course_language_group_id: Uuid,
55) -> ModelResult<UserCourseSettings> {
56    let user_course_settings = sqlx::query_as!(
57        UserCourseSettings,
58        "
59SELECT *
60FROM user_course_settings
61WHERE user_id = $1
62  AND course_language_group_id = $2
63  AND deleted_at IS NULL;
64        ",
65        user_id,
66        course_language_group_id
67    )
68    .fetch_one(conn)
69    .await?;
70    Ok(user_course_settings)
71}
72
73pub async fn get_user_course_settings_by_course_id(
74    conn: &mut PgConnection,
75    user_id: Uuid,
76    course_id: Uuid,
77) -> ModelResult<Option<UserCourseSettings>> {
78    let user_course_settings = sqlx::query_as!(
79        UserCourseSettings,
80        "
81SELECT ucs.*
82FROM courses c
83  JOIN user_course_settings ucs ON (
84    ucs.course_language_group_id = c.course_language_group_id
85  )
86WHERE c.id = $1
87  AND ucs.user_id = $2
88  AND c.deleted_at IS NULL
89  AND ucs.deleted_at IS NULL;
90        ",
91        course_id,
92        user_id,
93    )
94    .fetch_optional(conn)
95    .await?;
96    Ok(user_course_settings)
97}
98
99/// Gets all of the user's course settings that have their current course id included in the provided
100/// list.
101///
102/// The distinction for current courses is stated, because multiple courses can share the same
103/// course settings if they are different language versions of each other. Course settings that may
104/// exist for inactive courses will be omited. This behavior can be desireable in some cases, and
105/// should not be changed.
106///
107/// Note that this function doesn't create any settings that are missing for the user, so the amount
108/// of results may be less than the amount of courses provided.
109pub async fn get_all_by_user_and_multiple_current_courses(
110    conn: &mut PgConnection,
111    course_ids: &[Uuid],
112    user_id: Uuid,
113) -> ModelResult<Vec<UserCourseSettings>> {
114    let res = sqlx::query_as!(
115        UserCourseSettings,
116        "
117SELECT *
118FROM user_course_settings
119WHERE current_course_id = ANY($1)
120  AND user_id = $2
121  AND deleted_at IS NULL
122        ",
123        course_ids,
124        user_id,
125    )
126    .fetch_all(conn)
127    .await?;
128    Ok(res)
129}
130
131#[cfg(test)]
132mod test {
133    use super::*;
134    use crate::{
135        course_instance_enrollments::{self, NewCourseInstanceEnrollment},
136        course_instances::{self, NewCourseInstance},
137        test_helper::*,
138    };
139
140    #[tokio::test]
141    async fn upserts_user_course_settings() {
142        insert_data!(:tx, :user, :org, :course, :instance);
143
144        let enrollment = course_instance_enrollments::insert_enrollment_if_it_doesnt_exist(
145            tx.as_mut(),
146            NewCourseInstanceEnrollment {
147                course_id: course,
148                course_instance_id: instance.id,
149                user_id: user,
150            },
151        )
152        .await
153        .unwrap();
154        let settings = upsert_user_course_settings_for_enrollment(tx.as_mut(), &enrollment)
155            .await
156            .unwrap();
157        assert_eq!(settings.current_course_id, enrollment.course_id);
158        assert_eq!(
159            settings.current_course_instance_id,
160            enrollment.course_instance_id
161        );
162
163        let instance_2 = course_instances::insert(
164            tx.as_mut(),
165            PKeyPolicy::Generate,
166            NewCourseInstance {
167                course_id: course,
168                name: Some("instance-2"),
169                description: None,
170                teacher_in_charge_name: "teacher",
171                teacher_in_charge_email: "teacher@example.com",
172                support_email: None,
173                opening_time: None,
174                closing_time: None,
175            },
176        )
177        .await
178        .unwrap()
179        .id;
180        let enrollment_2 = course_instance_enrollments::insert_enrollment_if_it_doesnt_exist(
181            tx.as_mut(),
182            NewCourseInstanceEnrollment {
183                course_id: course,
184                course_instance_id: instance_2,
185                user_id: user,
186            },
187        )
188        .await
189        .unwrap();
190        let settings_2 = upsert_user_course_settings_for_enrollment(tx.as_mut(), &enrollment_2)
191            .await
192            .unwrap();
193        assert_eq!(
194            settings_2.current_course_instance_id,
195            enrollment_2.course_instance_id
196        );
197    }
198}