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