Skip to main content

headless_lms_models/
user_course_settings.rs

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