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::{chapters, 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 existing_statuses = user_chapter_locking_statuses::get_by_user_and_course(
55 &mut *conn,
56 course_instance_enrollment.user_id,
57 course_instance_enrollment.course_id,
58 )
59 .await?;
60
61 let has_unlocked_or_completed = existing_statuses.iter().any(|s| {
62 matches!(
63 s.status,
64 user_chapter_locking_statuses::ChapterLockingStatus::Unlocked
65 | user_chapter_locking_statuses::ChapterLockingStatus::CompletedAndLocked
66 )
67 });
68
69 if !has_unlocked_or_completed {
70 chapters::unlock_first_chapters_for_user(
71 &mut *conn,
72 course_instance_enrollment.user_id,
73 course_instance_enrollment.course_id,
74 )
75 .await?;
76 }
77 }
78
79 Ok(user_course_settings)
80}
81
82pub async fn get_user_course_settings(
83 conn: &mut PgConnection,
84 user_id: Uuid,
85 course_language_group_id: Uuid,
86) -> ModelResult<UserCourseSettings> {
87 let user_course_settings = sqlx::query_as!(
88 UserCourseSettings,
89 "
90SELECT *
91FROM user_course_settings
92WHERE user_id = $1
93 AND course_language_group_id = $2
94 AND deleted_at IS NULL;
95 ",
96 user_id,
97 course_language_group_id
98 )
99 .fetch_one(conn)
100 .await?;
101 Ok(user_course_settings)
102}
103
104pub async fn get_user_course_settings_by_course_id(
105 conn: &mut PgConnection,
106 user_id: Uuid,
107 course_id: Uuid,
108) -> ModelResult<Option<UserCourseSettings>> {
109 let user_course_settings = sqlx::query_as!(
110 UserCourseSettings,
111 "
112SELECT ucs.*
113FROM courses c
114 JOIN user_course_settings ucs ON (
115 ucs.course_language_group_id = c.course_language_group_id
116 )
117WHERE c.id = $1
118 AND ucs.user_id = $2
119 AND c.deleted_at IS NULL
120 AND ucs.deleted_at IS NULL;
121 ",
122 course_id,
123 user_id,
124 )
125 .fetch_optional(conn)
126 .await?;
127 Ok(user_course_settings)
128}
129
130pub async fn get_all_by_user_and_multiple_current_courses(
141 conn: &mut PgConnection,
142 course_ids: &[Uuid],
143 user_id: Uuid,
144) -> ModelResult<Vec<UserCourseSettings>> {
145 let res = sqlx::query_as!(
146 UserCourseSettings,
147 "
148SELECT *
149FROM user_course_settings
150WHERE current_course_id = ANY($1)
151 AND user_id = $2
152 AND deleted_at IS NULL
153 ",
154 course_ids,
155 user_id,
156 )
157 .fetch_all(conn)
158 .await?;
159 Ok(res)
160}
161
162pub async fn get_all_by_user_id(
164 conn: &mut PgConnection,
165 user_id: Uuid,
166) -> ModelResult<Vec<UserCourseSettings>> {
167 let res = sqlx::query_as!(
168 UserCourseSettings,
169 "
170SELECT *
171FROM user_course_settings
172WHERE user_id = $1
173 AND deleted_at IS NULL
174 ",
175 user_id,
176 )
177 .fetch_all(conn)
178 .await?;
179 Ok(res)
180}
181
182pub async fn get_all_by_course_id(
183 conn: &mut PgConnection,
184 course_id: Uuid,
185) -> ModelResult<Vec<UserCourseSettings>> {
186 let res = sqlx::query_as!(
187 UserCourseSettings,
188 "
189SELECT ucs.*
190FROM courses c
191 JOIN user_course_settings ucs ON (
192 ucs.course_language_group_id = c.course_language_group_id
193 )
194WHERE c.id = $1
195 AND c.deleted_at IS NULL
196 AND ucs.deleted_at IS NULL
197 ",
198 course_id
199 )
200 .fetch_all(conn)
201 .await?;
202 Ok(res)
203}
204
205#[cfg(test)]
206mod test {
207 use super::*;
208 use crate::{
209 course_instance_enrollments::{self, NewCourseInstanceEnrollment},
210 course_instances::{self, NewCourseInstance},
211 test_helper::*,
212 };
213
214 #[tokio::test]
215 async fn upserts_user_course_settings() {
216 insert_data!(:tx, :user, :org, :course, :instance);
217
218 let enrollment = course_instance_enrollments::insert_enrollment_if_it_doesnt_exist(
219 tx.as_mut(),
220 NewCourseInstanceEnrollment {
221 course_id: course,
222 course_instance_id: instance.id,
223 user_id: user,
224 },
225 )
226 .await
227 .unwrap();
228 let settings = upsert_user_course_settings_for_enrollment(tx.as_mut(), &enrollment)
229 .await
230 .unwrap();
231 assert_eq!(settings.current_course_id, enrollment.course_id);
232 assert_eq!(
233 settings.current_course_instance_id,
234 enrollment.course_instance_id
235 );
236
237 let instance_2 = course_instances::insert(
238 tx.as_mut(),
239 PKeyPolicy::Generate,
240 NewCourseInstance {
241 course_id: course,
242 name: Some("instance-2"),
243 description: None,
244 teacher_in_charge_name: "teacher",
245 teacher_in_charge_email: "teacher@example.com",
246 support_email: None,
247 opening_time: None,
248 closing_time: None,
249 },
250 )
251 .await
252 .unwrap()
253 .id;
254 let enrollment_2 = course_instance_enrollments::insert_enrollment_if_it_doesnt_exist(
255 tx.as_mut(),
256 NewCourseInstanceEnrollment {
257 course_id: course,
258 course_instance_id: instance_2,
259 user_id: user,
260 },
261 )
262 .await
263 .unwrap();
264 let settings_2 = upsert_user_course_settings_for_enrollment(tx.as_mut(), &enrollment_2)
265 .await
266 .unwrap();
267 assert_eq!(
268 settings_2.current_course_instance_id,
269 enrollment_2.course_instance_id
270 );
271 }
272}