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
15pub async fn upsert_user_course_settings_for_enrollment(
17 conn: &mut PgConnection,
18 course_instance_enrollment: &CourseInstanceEnrollment,
19) -> ModelResult<UserCourseSettings> {
20 use crate::{courses, user_chapter_locking_statuses};
21
22 let course = courses::get_course(conn, course_instance_enrollment.course_id).await?;
23
24 let user_course_settings = sqlx::query_as!(
25 UserCourseSettings,
26 "
27INSERT INTO user_course_settings (
28 user_id,
29 course_language_group_id,
30 current_course_id,
31 current_course_instance_id
32 )
33SELECT $1,
34 course_language_group_id,
35 $2,
36 $3
37FROM courses
38WHERE id = $2
39 AND deleted_at IS NULL ON CONFLICT (user_id, course_language_group_id) DO
40UPDATE
41SET current_course_id = $2,
42 current_course_instance_id = $3,
43 deleted_at = NULL
44RETURNING *;
45 ",
46 course_instance_enrollment.user_id,
47 course_instance_enrollment.course_id,
48 course_instance_enrollment.course_instance_id
49 )
50 .fetch_one(&mut *conn)
51 .await?;
52
53 if course.chapter_locking_enabled {
54 let _ = user_chapter_locking_statuses::get_or_init_all_for_course(
55 &mut *conn,
56 course_instance_enrollment.user_id,
57 course_instance_enrollment.course_id,
58 )
59 .await?;
60 }
61
62 Ok(user_course_settings)
63}
64
65pub async fn get_user_course_settings(
66 conn: &mut PgConnection,
67 user_id: Uuid,
68 course_language_group_id: Uuid,
69) -> ModelResult<UserCourseSettings> {
70 let user_course_settings = sqlx::query_as!(
71 UserCourseSettings,
72 "
73SELECT *
74FROM user_course_settings
75WHERE user_id = $1
76 AND course_language_group_id = $2
77 AND deleted_at IS NULL;
78 ",
79 user_id,
80 course_language_group_id
81 )
82 .fetch_one(conn)
83 .await?;
84 Ok(user_course_settings)
85}
86
87pub async fn get_user_course_settings_by_course_id(
88 conn: &mut PgConnection,
89 user_id: Uuid,
90 course_id: Uuid,
91) -> ModelResult<Option<UserCourseSettings>> {
92 let user_course_settings = sqlx::query_as!(
93 UserCourseSettings,
94 "
95SELECT ucs.*
96FROM courses c
97 JOIN user_course_settings ucs ON (
98 ucs.course_language_group_id = c.course_language_group_id
99 )
100WHERE c.id = $1
101 AND ucs.user_id = $2
102 AND c.deleted_at IS NULL
103 AND ucs.deleted_at IS NULL;
104 ",
105 course_id,
106 user_id,
107 )
108 .fetch_optional(conn)
109 .await?;
110 Ok(user_course_settings)
111}
112
113pub async fn get_all_by_user_and_multiple_current_courses(
124 conn: &mut PgConnection,
125 course_ids: &[Uuid],
126 user_id: Uuid,
127) -> ModelResult<Vec<UserCourseSettings>> {
128 let res = sqlx::query_as!(
129 UserCourseSettings,
130 "
131SELECT *
132FROM user_course_settings
133WHERE current_course_id = ANY($1)
134 AND user_id = $2
135 AND deleted_at IS NULL
136 ",
137 course_ids,
138 user_id,
139 )
140 .fetch_all(conn)
141 .await?;
142 Ok(res)
143}
144
145pub async fn get_all_by_user_id(
147 conn: &mut PgConnection,
148 user_id: Uuid,
149) -> ModelResult<Vec<UserCourseSettings>> {
150 let res = sqlx::query_as!(
151 UserCourseSettings,
152 "
153SELECT *
154FROM user_course_settings
155WHERE user_id = $1
156 AND deleted_at IS NULL
157 ",
158 user_id,
159 )
160 .fetch_all(conn)
161 .await?;
162 Ok(res)
163}
164
165pub async fn get_all_by_course_id(
166 conn: &mut PgConnection,
167 course_id: Uuid,
168) -> ModelResult<Vec<UserCourseSettings>> {
169 let res = sqlx::query_as!(
170 UserCourseSettings,
171 "
172SELECT ucs.*
173FROM courses c
174 JOIN user_course_settings ucs ON (
175 ucs.course_language_group_id = c.course_language_group_id
176 )
177WHERE c.id = $1
178 AND c.deleted_at IS NULL
179 AND ucs.deleted_at IS NULL
180 ",
181 course_id
182 )
183 .fetch_all(conn)
184 .await?;
185 Ok(res)
186}
187
188#[cfg(test)]
189mod test {
190 use super::*;
191 use crate::{
192 course_instance_enrollments::{self, NewCourseInstanceEnrollment},
193 course_instances::{self, NewCourseInstance},
194 test_helper::*,
195 };
196
197 #[tokio::test]
198 async fn upserts_user_course_settings() {
199 insert_data!(:tx, :user, :org, :course, :instance);
200
201 let enrollment = course_instance_enrollments::insert_enrollment_if_it_doesnt_exist(
202 tx.as_mut(),
203 NewCourseInstanceEnrollment {
204 course_id: course,
205 course_instance_id: instance.id,
206 user_id: user,
207 },
208 )
209 .await
210 .unwrap();
211 let settings = upsert_user_course_settings_for_enrollment(tx.as_mut(), &enrollment)
212 .await
213 .unwrap();
214 assert_eq!(settings.current_course_id, enrollment.course_id);
215 assert_eq!(
216 settings.current_course_instance_id,
217 enrollment.course_instance_id
218 );
219
220 let instance_2 = course_instances::insert(
221 tx.as_mut(),
222 PKeyPolicy::Generate,
223 NewCourseInstance {
224 course_id: course,
225 name: Some("instance-2"),
226 description: None,
227 teacher_in_charge_name: "teacher",
228 teacher_in_charge_email: "teacher@example.com",
229 support_email: None,
230 opening_time: None,
231 closing_time: None,
232 },
233 )
234 .await
235 .unwrap()
236 .id;
237 let enrollment_2 = course_instance_enrollments::insert_enrollment_if_it_doesnt_exist(
238 tx.as_mut(),
239 NewCourseInstanceEnrollment {
240 course_id: course,
241 course_instance_id: instance_2,
242 user_id: user,
243 },
244 )
245 .await
246 .unwrap();
247 let settings_2 = upsert_user_course_settings_for_enrollment(tx.as_mut(), &enrollment_2)
248 .await
249 .unwrap();
250 assert_eq!(
251 settings_2.current_course_instance_id,
252 enrollment_2.course_instance_id
253 );
254 }
255}