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}
19
20pub async fn get_user_details_by_user_id(
21 conn: &mut PgConnection,
22 user_id: Uuid,
23) -> ModelResult<UserDetail> {
24 let res = sqlx::query_as!(
25 UserDetail,
26 "
27SELECT *
28FROM user_details
29WHERE user_id = $1 ",
30 user_id
31 )
32 .fetch_one(conn)
33 .await?;
34 Ok(res)
35}
36
37pub async fn get_users_details_by_user_id_map(
38 conn: &mut PgConnection,
39 users: &[User],
40) -> ModelResult<HashMap<Uuid, UserDetail>> {
41 let ids = users.iter().map(|u| u.id).collect::<Vec<_>>();
42 let details = sqlx::query_as!(
43 UserDetail,
44 "
45SELECT *
46FROM user_details
47WHERE user_id IN (
48 SELECT UNNEST($1::uuid [])
49 )
50",
51 &ids
52 )
53 .fetch_all(conn)
54 .await?;
55 let mut res = HashMap::new();
56 details.into_iter().for_each(|d| {
57 res.insert(d.user_id, d);
58 });
59 Ok(res)
60}
61
62pub fn stream_users_details_having_user_exercise_states_on_course(
64 conn: &mut PgConnection,
65 course_id: Uuid,
66) -> impl Stream<Item = sqlx::Result<UserDetail>> + '_ {
67 sqlx::query_as!(
68 UserDetail,
69 "
70SELECT distinct (ud.user_id),
71 ud.created_at,
72 ud.updated_at,
73 ud.first_name,
74 ud.last_name,
75 ud.email,
76 ud.search_helper,
77 ud.country
78FROM user_details ud
79JOIN users u
80 ON u.id = ud.user_id
81JOIN user_exercise_states ues
82 ON ud.user_id = ues.user_id
83JOIN course_instances ci
84 ON ci.id = ues.course_instance_id
85WHERE ci.course_id = $1
86 AND u.deleted_at IS NULL
87 AND ues.deleted_at IS NULL
88 AND ci.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) || '%'
105LIMIT 1000;
106",
107 email.trim(),
108 )
109 .fetch_all(conn)
110 .await?;
111 Ok(res)
112}
113
114pub async fn search_for_user_details_by_other_details(
115 conn: &mut PgConnection,
116 search: &str,
117) -> ModelResult<Vec<UserDetail>> {
118 let res = sqlx::query_as!(
119 UserDetail,
120 "
121SELECT *
122FROM user_details
123WHERE lower(search_helper::text) LIKE '%' || lower($1) || '%'
124LIMIT 1000;
125",
126 search.trim(),
127 )
128 .fetch_all(conn)
129 .await?;
130 Ok(res)
131}
132
133pub async fn search_for_user_details_fuzzy_match(
134 conn: &mut PgConnection,
135 search: &str,
136) -> ModelResult<Vec<UserDetail>> {
137 let search = search.split('@').next().unwrap_or(search);
140
141 let res = sqlx::query_as!(
142 UserDetail,
143 "
144SELECT user_id,
145 created_at,
146 updated_at,
147 email,
148 first_name,
149 last_name,
150 search_helper,
151 country
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
184FROM course_instance_enrollments e
185 JOIN user_details d ON e.user_id = d.user_id
186WHERE e.course_id = $1
187 AND e.deleted_at IS NULL
188 "#,
189 course_id
190 )
191 .fetch_all(conn)
192 .await?;
193
194 Ok(res)
195}
196
197pub async fn update_user_country(
198 conn: &mut PgConnection,
199 user_id: Uuid,
200 country: &str,
201) -> Result<(), sqlx::Error> {
202 sqlx::query!(
203 r#"
204UPDATE user_details
205SET country = $1
206WHERE user_id = $2
207"#,
208 country,
209 user_id,
210 )
211 .execute(conn)
212 .await?;
213 Ok(())
214}
215
216pub async fn update_user_info(
217 conn: &mut PgConnection,
218 user_id: Uuid,
219 first_name: &str,
220 last_name: &str,
221 country: &str,
222) -> Result<(), sqlx::Error> {
223 sqlx::query!(
224 r#"
225UPDATE user_details
226SET first_name = $1,
227 last_name = $2,
228 country = $3
229WHERE user_id = $4
230"#,
231 first_name,
232 last_name,
233 country,
234 user_id,
235 )
236 .execute(conn)
237 .await?;
238 Ok(())
239}