1use std::collections::HashMap;
2
3use futures::Stream;
4
5use crate::{prelude::*, users::User};
6
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8#[cfg_attr(feature = "ts_rs", derive(TS))]
9pub struct UserDetail {
10 pub user_id: Uuid,
11 pub created_at: DateTime<Utc>,
12 pub updated_at: DateTime<Utc>,
13 pub email: String,
14 pub first_name: Option<String>,
15 pub last_name: Option<String>,
16 pub search_helper: Option<String>,
17 pub country: Option<String>,
18 pub email_communication_consent: Option<bool>,
19}
20
21pub async fn get_user_details_by_user_id(
22 conn: &mut PgConnection,
23 user_id: Uuid,
24) -> ModelResult<UserDetail> {
25 let res = sqlx::query_as!(
26 UserDetail,
27 "
28SELECT *
29FROM user_details
30WHERE user_id = $1 ",
31 user_id
32 )
33 .fetch_one(conn)
34 .await?;
35 Ok(res)
36}
37
38pub async fn get_users_details_by_user_id_map(
39 conn: &mut PgConnection,
40 users: &[User],
41) -> ModelResult<HashMap<Uuid, UserDetail>> {
42 let ids = users.iter().map(|u| u.id).collect::<Vec<_>>();
43 let details = sqlx::query_as!(
44 UserDetail,
45 "
46SELECT *
47FROM user_details
48WHERE user_id IN (
49 SELECT UNNEST($1::uuid [])
50 )
51",
52 &ids
53 )
54 .fetch_all(conn)
55 .await?;
56 let mut res = HashMap::new();
57 details.into_iter().for_each(|d| {
58 res.insert(d.user_id, d);
59 });
60 Ok(res)
61}
62
63pub fn stream_users_details_having_user_exercise_states_on_course(
65 conn: &mut PgConnection,
66 course_id: Uuid,
67) -> impl Stream<Item = sqlx::Result<UserDetail>> + '_ {
68 sqlx::query_as!(
69 UserDetail,
70 "
71SELECT distinct (ud.user_id),
72 ud.created_at,
73 ud.updated_at,
74 ud.first_name,
75 ud.last_name,
76 ud.email,
77 ud.search_helper,
78 ud.country,
79 ud.email_communication_consent
80FROM user_details ud
81JOIN users u
82 ON u.id = ud.user_id
83JOIN user_exercise_states ues
84 ON ud.user_id = ues.user_id
85WHERE ues.course_id = $1
86 AND u.deleted_at IS NULL
87 AND ues.deleted_at IS NULL
88 ",
89 course_id
90 )
91 .fetch(conn)
92}
93
94pub async fn search_for_user_details_by_email(
95 conn: &mut PgConnection,
96 email: &str,
97) -> ModelResult<Vec<UserDetail>> {
98 let res = sqlx::query_as!(
99 UserDetail,
100 "
101SELECT *
102FROM user_details
103WHERE lower(email::text) LIKE '%' || lower($1) || '%'
104LIMIT 1000;
105",
106 email.trim(),
107 )
108 .fetch_all(conn)
109 .await?;
110 Ok(res)
111}
112
113pub async fn search_for_user_details_by_other_details(
114 conn: &mut PgConnection,
115 search: &str,
116) -> ModelResult<Vec<UserDetail>> {
117 let res = sqlx::query_as!(
118 UserDetail,
119 "
120SELECT *
121FROM user_details
122WHERE lower(search_helper::text) LIKE '%' || lower($1) || '%'
123LIMIT 1000;
124",
125 search.trim(),
126 )
127 .fetch_all(conn)
128 .await?;
129 Ok(res)
130}
131
132pub async fn search_for_user_details_fuzzy_match(
133 conn: &mut PgConnection,
134 search: &str,
135) -> ModelResult<Vec<UserDetail>> {
136 let search = search.split('@').next().unwrap_or(search);
139
140 let res = sqlx::query_as!(
141 UserDetail,
142 "
143SELECT user_id,
144 created_at,
145 updated_at,
146 email,
147 first_name,
148 last_name,
149 search_helper,
150 country,
151 email_communication_consent
152FROM (
153 SELECT *,
154 LOWER($1) <<->search_helper AS dist
155 FROM user_details
156 ORDER BY dist, LENGTH(search_helper)
157 LIMIT 100
158 ) search
159WHERE dist < 0.7;
160",
161 search.trim(),
162 )
163 .fetch_all(conn)
164 .await?;
165 Ok(res)
166}
167
168pub async fn get_users_by_course_id(
170 conn: &mut PgConnection,
171 course_id: Uuid,
172) -> ModelResult<Vec<UserDetail>> {
173 let res = sqlx::query_as!(
174 UserDetail,
175 r#"
176SELECT d.user_id,
177 d.created_at,
178 d.updated_at,
179 d.email,
180 d.first_name,
181 d.last_name,
182 d.search_helper,
183 d.country,
184 d.email_communication_consent
185FROM course_instance_enrollments e
186 JOIN user_details d ON e.user_id = d.user_id
187WHERE e.course_id = $1
188 AND e.deleted_at IS NULL
189 "#,
190 course_id
191 )
192 .fetch_all(conn)
193 .await?;
194
195 Ok(res)
196}
197
198pub async fn get_user_details_by_user_ids(
200 conn: &mut PgConnection,
201 user_ids: &[Uuid],
202) -> ModelResult<Vec<UserDetail>> {
203 let res = sqlx::query_as!(
204 UserDetail,
205 r#"
206SELECT *
207FROM user_details
208WHERE user_id = ANY($1::uuid[])
209 "#,
210 user_ids
211 )
212 .fetch_all(conn)
213 .await?;
214
215 Ok(res)
216}
217
218pub async fn get_user_details_by_user_ids_for_course(
220 conn: &mut PgConnection,
221 user_ids: &[Uuid],
222 course_id: Uuid,
223) -> ModelResult<Vec<UserDetail>> {
224 let res = sqlx::query_as!(
225 UserDetail,
226 r#"
227SELECT ud.*
228FROM user_details ud
229JOIN user_course_settings ucs ON ud.user_id = ucs.user_id
230WHERE ud.user_id = ANY($1::uuid[])
231 AND ucs.current_course_id = $2
232 AND ucs.deleted_at IS NULL
233 "#,
234 user_ids,
235 course_id
236 )
237 .fetch_all(conn)
238 .await?;
239
240 Ok(res)
241}
242
243pub async fn get_user_details_by_user_id_for_course(
245 conn: &mut PgConnection,
246 user_id: Uuid,
247 course_id: Uuid,
248) -> ModelResult<UserDetail> {
249 let res = sqlx::query_as!(
250 UserDetail,
251 r#"
252SELECT ud.*
253FROM user_details ud
254JOIN user_course_settings ucs ON ud.user_id = ucs.user_id
255WHERE ud.user_id = $1
256 AND ucs.current_course_id = $2
257 AND ucs.deleted_at IS NULL
258 "#,
259 user_id,
260 course_id
261 )
262 .fetch_one(conn)
263 .await?;
264
265 Ok(res)
266}
267
268pub async fn update_user_country(
269 conn: &mut PgConnection,
270 user_id: Uuid,
271 country: &str,
272) -> Result<(), sqlx::Error> {
273 sqlx::query!(
274 r#"
275UPDATE user_details
276SET country = $1
277WHERE user_id = $2
278"#,
279 country,
280 user_id,
281 )
282 .execute(conn)
283 .await?;
284 Ok(())
285}
286
287pub async fn update_user_email_communication_consent(
288 conn: &mut PgConnection,
289 user_id: Uuid,
290 email_communication_consent: bool,
291) -> Result<(), sqlx::Error> {
292 sqlx::query!(
293 r#"
294UPDATE user_details
295SET email_communication_consent = $1
296WHERE user_id = $2
297"#,
298 email_communication_consent,
299 user_id,
300 )
301 .execute(conn)
302 .await?;
303 Ok(())
304}
305
306pub async fn update_user_info(
307 conn: &mut PgConnection,
308 user_id: Uuid,
309 email: &str,
310 first_name: &str,
311 last_name: &str,
312 country: &str,
313 email_communication_consent: bool,
314) -> Result<UserDetail, sqlx::Error> {
315 let updated_user = sqlx::query_as!(
316 UserDetail,
317 r#"
318UPDATE user_details
319SET email = $1,
320 first_name = $2,
321 last_name = $3,
322 country = $4,
323 email_communication_consent = $5
324WHERE user_id = $6
325RETURNING *
326"#,
327 email,
328 first_name,
329 last_name,
330 country,
331 email_communication_consent,
332 user_id,
333 )
334 .fetch_one(conn)
335 .await?;
336
337 Ok(updated_user)
338}