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
16pub 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
114pub 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
146pub 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}