headless_lms_models/
users.rs

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
154/// Includes all users who have returned an exercise on a course course instance
155pub 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}