Skip to main content

headless_lms_models/
user_email_codes.rs

1use crate::prelude::*;
2
3#[derive(sqlx::FromRow, Debug, Clone)]
4pub struct UserEmailCode {
5    pub id: Uuid,
6    pub user_id: Uuid,
7    pub code: String,
8    pub expires_at: DateTime<Utc>,
9    pub used_at: Option<DateTime<Utc>>,
10    pub created_at: DateTime<Utc>,
11    pub updated_at: DateTime<Utc>,
12    pub deleted_at: Option<DateTime<Utc>>,
13}
14
15pub async fn insert_user_email_code(
16    conn: &mut PgConnection,
17    user_id: Uuid,
18    code: String,
19) -> ModelResult<String> {
20    let mut tx = conn.begin().await?;
21
22    // Soft delete possible previous codes so that only one code is at use at a time
23    let _ = sqlx::query!(
24        r#"
25   UPDATE user_email_codes
26SET deleted_at = NOW()
27WHERE user_id = $1
28  AND deleted_at IS NULL
29    "#,
30        user_id
31    )
32    .execute(&mut *tx)
33    .await?;
34
35    // Attempt to insert new code; the unique index ensures no more than one active code per user
36    let record = sqlx::query!(
37        r#"
38      INSERT INTO user_email_codes (code, user_id)
39VALUES ($1, $2)
40RETURNING *
41        "#,
42        code,
43        user_id
44    )
45    .fetch_one(&mut *tx)
46    .await?;
47
48    tx.commit().await?;
49
50    Ok(record.code)
51}
52
53pub async fn get_unused_user_email_code_with_user_id(
54    conn: &mut PgConnection,
55    user_id: Uuid,
56) -> ModelResult<Option<UserEmailCode>> {
57    let now = Utc::now();
58    let record = sqlx::query_as!(
59        UserEmailCode,
60        r#"
61SELECT *
62FROM user_email_codes
63WHERE user_id = $1
64  AND deleted_at IS NULL
65  AND used_at IS NULL
66  AND expires_at > $2
67        "#,
68        user_id,
69        now
70    )
71    .fetch_optional(conn)
72    .await?;
73
74    Ok(record)
75}
76
77pub async fn is_reset_user_email_code_valid(
78    conn: &mut PgConnection,
79    user_id: Uuid,
80    code: &String,
81) -> ModelResult<bool> {
82    let now = Utc::now();
83    let record = sqlx::query!(
84        r#"
85SELECT *
86FROM user_email_codes
87WHERE user_id = $1
88  AND code = $2
89  AND deleted_at IS NULL
90  AND used_at IS NULL
91  AND expires_at > $3
92       "#,
93        user_id,
94        code,
95        now
96    )
97    .fetch_optional(conn)
98    .await?;
99
100    Ok(record.is_some())
101}
102
103pub async fn mark_user_email_code_used(
104    conn: &mut PgConnection,
105    user_id: Uuid,
106    code: &str,
107) -> ModelResult<bool> {
108    let result = sqlx::query!(
109        r#"
110UPDATE user_email_codes
111SET used_at = NOW(),
112  deleted_at = NOW()
113WHERE user_id = $1
114  AND code = $2
115  AND deleted_at IS NULL
116        "#,
117        user_id,
118        code
119    )
120    .execute(conn)
121    .await?;
122
123    Ok(result.rows_affected() > 0)
124}