1use crate::prelude::*;
2
3#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4#[cfg_attr(feature = "ts_rs", derive(TS))]
5pub struct User {
6 pub id: Uuid,
7 pub created_at: DateTime<Utc>,
8 pub updated_at: DateTime<Utc>,
9 pub deleted_at: Option<DateTime<Utc>>,
10 pub upstream_id: Option<i32>,
11 pub email_domain: Option<String>,
12}
13
14pub async fn insert(
15 conn: &mut PgConnection,
16 pkey_policy: PKeyPolicy<Uuid>,
17 email: &str,
18 first_name: Option<&str>,
19 last_name: Option<&str>,
20) -> ModelResult<Uuid> {
21 let mut tx = conn.begin().await?;
22 let email_domain = email.trim().split('@').next_back();
23 let res = sqlx::query!(
24 "
25INSERT INTO users (id, email_domain)
26VALUES ($1, $2)
27RETURNING id
28",
29 pkey_policy.into_uuid(),
30 email_domain
31 )
32 .fetch_one(&mut *tx)
33 .await?;
34
35 let _res2 = sqlx::query!(
36 "
37INSERT INTO user_details (user_id, email, first_name, last_name)
38VALUES ($1, $2, $3, $4)
39",
40 res.id,
41 email,
42 first_name,
43 last_name
44 )
45 .execute(&mut *tx)
46 .await?;
47 tx.commit().await?;
48 Ok(res.id)
49}
50
51pub async fn insert_with_upstream_id_and_moocfi_id(
52 conn: &mut PgConnection,
53 email: &str,
54 first_name: Option<&str>,
55 last_name: Option<&str>,
56 upstream_id: i32,
57 moocfi_id: Uuid,
58) -> ModelResult<User> {
59 info!("The user is not in the database yet, inserting");
60 let email_domain = email.trim().split('@').next_back();
61 let mut tx = conn.begin().await?;
62 let user = sqlx::query_as!(
63 User,
64 r#"
65INSERT INTO
66 users (id, upstream_id, email_domain)
67VALUES ($1, $2, $3)
68RETURNING *;
69 "#,
70 moocfi_id,
71 upstream_id,
72 email_domain
73 )
74 .fetch_one(&mut *tx)
75 .await?;
76
77 let _res2 = sqlx::query!(
78 "
79INSERT INTO user_details (user_id, email, first_name, last_name)
80VALUES ($1, $2, $3, $4)
81",
82 user.id,
83 email,
84 first_name,
85 last_name
86 )
87 .execute(&mut *tx)
88 .await?;
89 tx.commit().await?;
90 Ok(user)
91}
92
93pub async fn get_by_email(conn: &mut PgConnection, email: &str) -> ModelResult<User> {
94 let user = sqlx::query_as!(
95 User,
96 "
97SELECT users.*
98FROM user_details
99JOIN users ON (user_details.user_id = users.id)
100WHERE user_details.email = $1
101 ",
102 email
103 )
104 .fetch_one(conn)
105 .await?;
106 Ok(user)
107}
108
109pub async fn try_get_by_email(conn: &mut PgConnection, email: &str) -> ModelResult<Option<User>> {
110 let user = sqlx::query_as!(
111 User,
112 "
113SELECT users.*
114FROM user_details
115JOIN users ON (user_details.user_id = users.id)
116WHERE user_details.email = $1
117 ",
118 email
119 )
120 .fetch_optional(conn)
121 .await?;
122 Ok(user)
123}
124
125pub async fn get_by_id(conn: &mut PgConnection, id: Uuid) -> ModelResult<User> {
126 let user = sqlx::query_as!(
127 User,
128 "
129SELECT *
130FROM users
131WHERE id = $1
132 ",
133 id
134 )
135 .fetch_one(conn)
136 .await?;
137 Ok(user)
138}
139
140pub async fn find_by_upstream_id(
141 conn: &mut PgConnection,
142 upstream_id: i32,
143) -> ModelResult<Option<User>> {
144 let user = sqlx::query_as!(
145 User,
146 "SELECT * FROM users WHERE upstream_id = $1",
147 upstream_id
148 )
149 .fetch_optional(conn)
150 .await?;
151 Ok(user)
152}
153
154pub async fn get_all_user_ids_with_user_exercise_states_on_course_instance(
156 conn: &mut PgConnection,
157 course_instance_id: Uuid,
158) -> ModelResult<Vec<Uuid>> {
159 let res = sqlx::query!(
160 "
161SELECT DISTINCT user_id
162FROM user_exercise_states
163WHERE course_instance_id = $1
164 AND deleted_at IS NULL
165 ",
166 course_instance_id
167 )
168 .map(|x| x.user_id)
169 .fetch_all(conn)
170 .await?;
171 Ok(res)
172}
173
174pub async fn get_users_by_course_instance_enrollment(
175 conn: &mut PgConnection,
176 course_instance_id: Uuid,
177) -> ModelResult<Vec<User>> {
178 let res = sqlx::query_as!(
179 User,
180 "
181SELECT *
182FROM users
183WHERE id IN (
184 SELECT user_id
185 FROM course_instance_enrollments
186 WHERE course_instance_id = $1
187 AND deleted_at IS NULL
188 )
189",
190 course_instance_id,
191 )
192 .fetch_all(&mut *conn)
193 .await?;
194 Ok(res)
195}
196
197pub async fn get_users_ids_in_db_from_upstream_ids(
198 conn: &mut PgConnection,
199 upstream_ids: &[i32],
200) -> ModelResult<Vec<Uuid>> {
201 let res = sqlx::query!(
202 "
203SELECT id
204FROM users
205WHERE upstream_id IN (
206 SELECT UNNEST($1::integer [])
207 )
208AND deleted_at IS NULL
209",
210 upstream_ids,
211 )
212 .fetch_all(&mut *conn)
213 .await?;
214 Ok(res.iter().map(|x| x.id).collect::<Vec<_>>())
215}
216
217pub async fn update_email_for_user(
218 conn: &mut PgConnection,
219 upstream_id: &i32,
220 new_email: String,
221) -> ModelResult<()> {
222 info!("Updating user (Upstream id: {upstream_id})");
223 let mut tx = conn.begin().await?;
224
225 let user = sqlx::query_as!(
226 User,
227 "SELECT * FROM users WHERE upstream_id = $1",
228 upstream_id
229 )
230 .fetch_one(&mut *tx)
231 .await?;
232
233 sqlx::query!(
234 "UPDATE user_details SET email = $1 WHERE user_id = $2",
235 new_email,
236 user.id,
237 )
238 .execute(&mut *tx)
239 .await?;
240
241 let email_domain = new_email.trim().split('@').next_back();
242 sqlx::query!(
243 "UPDATE users SET email_domain = $1 WHERE id = $2",
244 email_domain,
245 user.id,
246 )
247 .execute(&mut *tx)
248 .await?;
249
250 tx.commit().await?;
251
252 info!("Email change succeeded");
253 Ok(())
254}
255
256pub async fn delete_user(conn: &mut PgConnection, id: Uuid) -> ModelResult<()> {
257 info!("Deleting user {id}");
258 let mut tx = conn.begin().await?;
259 sqlx::query!("DELETE FROM user_details WHERE user_id = $1", id,)
260 .execute(&mut *tx)
261 .await?;
262 sqlx::query!("UPDATE users set deleted_at = now() WHERE id = $1", id,)
263 .execute(&mut *tx)
264 .await?;
265 sqlx::query!("UPDATE roles set deleted_at = now() WHERE user_id = $1", id,)
266 .execute(&mut *tx)
267 .await?;
268 tx.commit().await?;
269 info!("Deletion succeeded");
270 Ok(())
271}