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
85JOIN course_instances ci
86 ON ci.id = ues.course_instance_id
87WHERE ci.course_id = $1
88 AND u.deleted_at IS NULL
89 AND ues.deleted_at IS NULL
90 AND ci.deleted_at IS NULL;
91 ",
92 course_id
93 )
94 .fetch(conn)
95}
96
97pub async fn search_for_user_details_by_email(
98 conn: &mut PgConnection,
99 email: &str,
100) -> ModelResult<Vec<UserDetail>> {
101 let res = sqlx::query_as!(
102 UserDetail,
103 "
104SELECT *
105FROM user_details
106WHERE lower(email::text) LIKE '%' || lower($1) || '%'
107LIMIT 1000;
108",
109 email.trim(),
110 )
111 .fetch_all(conn)
112 .await?;
113 Ok(res)
114}
115
116pub async fn search_for_user_details_by_other_details(
117 conn: &mut PgConnection,
118 search: &str,
119) -> ModelResult<Vec<UserDetail>> {
120 let res = sqlx::query_as!(
121 UserDetail,
122 "
123SELECT *
124FROM user_details
125WHERE lower(search_helper::text) LIKE '%' || lower($1) || '%'
126LIMIT 1000;
127",
128 search.trim(),
129 )
130 .fetch_all(conn)
131 .await?;
132 Ok(res)
133}
134
135pub async fn search_for_user_details_fuzzy_match(
136 conn: &mut PgConnection,
137 search: &str,
138) -> ModelResult<Vec<UserDetail>> {
139 let search = search.split('@').next().unwrap_or(search);
142
143 let res = sqlx::query_as!(
144 UserDetail,
145 "
146SELECT user_id,
147 created_at,
148 updated_at,
149 email,
150 first_name,
151 last_name,
152 search_helper,
153 country,
154 email_communication_consent
155FROM (
156 SELECT *,
157 LOWER($1) <<->search_helper AS dist
158 FROM user_details
159 ORDER BY dist, LENGTH(search_helper)
160 LIMIT 100
161 ) search
162WHERE dist < 0.7;
163",
164 search.trim(),
165 )
166 .fetch_all(conn)
167 .await?;
168 Ok(res)
169}
170
171pub async fn get_users_by_course_id(
173 conn: &mut PgConnection,
174 course_id: Uuid,
175) -> ModelResult<Vec<UserDetail>> {
176 let res = sqlx::query_as!(
177 UserDetail,
178 r#"
179SELECT d.user_id,
180 d.created_at,
181 d.updated_at,
182 d.email,
183 d.first_name,
184 d.last_name,
185 d.search_helper,
186 d.country,
187 d.email_communication_consent
188FROM course_instance_enrollments e
189 JOIN user_details d ON e.user_id = d.user_id
190WHERE e.course_id = $1
191 AND e.deleted_at IS NULL
192 "#,
193 course_id
194 )
195 .fetch_all(conn)
196 .await?;
197
198 Ok(res)
199}
200
201pub async fn update_user_country(
202 conn: &mut PgConnection,
203 user_id: Uuid,
204 country: &str,
205) -> Result<(), sqlx::Error> {
206 sqlx::query!(
207 r#"
208UPDATE user_details
209SET country = $1
210WHERE user_id = $2
211"#,
212 country,
213 user_id,
214 )
215 .execute(conn)
216 .await?;
217 Ok(())
218}
219
220pub async fn update_user_email_communication_consent(
221 conn: &mut PgConnection,
222 user_id: Uuid,
223 email_communication_consent: bool,
224) -> Result<(), sqlx::Error> {
225 sqlx::query!(
226 r#"
227UPDATE user_details
228SET email_communication_consent = $1
229WHERE user_id = $2
230"#,
231 email_communication_consent,
232 user_id,
233 )
234 .execute(conn)
235 .await?;
236 Ok(())
237}
238
239pub async fn update_user_info(
240 conn: &mut PgConnection,
241 user_id: Uuid,
242 email: &str,
243 first_name: &str,
244 last_name: &str,
245 country: &str,
246 email_communication_consent: bool,
247) -> Result<UserDetail, sqlx::Error> {
248 let updated_user = sqlx::query_as!(
249 UserDetail,
250 r#"
251UPDATE user_details
252SET email = $1,
253 first_name = $2,
254 last_name = $3,
255 country = $4,
256 email_communication_consent = $5
257WHERE user_id = $6
258RETURNING *
259"#,
260 email,
261 first_name,
262 last_name,
263 country,
264 email_communication_consent,
265 user_id,
266 )
267 .fetch_one(conn)
268 .await?;
269
270 Ok(updated_user)
271}