shared/models/
user_recovery_code.rs1use crate::prelude::*;
2use rand::distr::SampleString;
3use serde::{Deserialize, Serialize};
4use sqlx::{Row, postgres::PgRow};
5use std::collections::BTreeMap;
6
7#[derive(Serialize, Deserialize)]
8pub struct UserRecoveryCode {
9 pub code: compact_str::CompactString,
10
11 pub created: chrono::NaiveDateTime,
12}
13
14impl BaseModel for UserRecoveryCode {
15 const NAME: &'static str = "user_recovery_code";
16
17 #[inline]
18 fn columns(prefix: Option<&str>) -> BTreeMap<&'static str, compact_str::CompactString> {
19 let prefix = prefix.unwrap_or_default();
20
21 BTreeMap::from([
22 (
23 "user_recovery_codes.code",
24 compact_str::format_compact!("{prefix}code"),
25 ),
26 (
27 "user_recovery_codes.created",
28 compact_str::format_compact!("{prefix}created"),
29 ),
30 ])
31 }
32
33 #[inline]
34 fn map(prefix: Option<&str>, row: &PgRow) -> Result<Self, crate::database::DatabaseError> {
35 let prefix = prefix.unwrap_or_default();
36
37 Ok(Self {
38 code: row.try_get(compact_str::format_compact!("{prefix}code").as_str())?,
39 created: row.try_get(compact_str::format_compact!("{prefix}created").as_str())?,
40 })
41 }
42}
43
44impl UserRecoveryCode {
45 pub async fn create_all(
46 database: &crate::database::Database,
47 user_uuid: uuid::Uuid,
48 ) -> Result<Vec<String>, crate::database::DatabaseError> {
49 let mut codes = Vec::new();
50 codes.reserve_exact(10);
51
52 let mut transaction = database.write().begin().await?;
53
54 for _ in 0..10 {
55 let code = rand::distr::Alphanumeric.sample_string(&mut rand::rng(), 10);
56
57 sqlx::query(
58 r#"
59 INSERT INTO user_recovery_codes (user_uuid, code, created)
60 VALUES ($1, $2, NOW())
61 "#,
62 )
63 .bind(user_uuid)
64 .bind(&code)
65 .execute(&mut *transaction)
66 .await?;
67
68 codes.push(code);
69 }
70
71 transaction.commit().await?;
72
73 Ok(codes)
74 }
75
76 pub async fn delete_by_user_uuid_code(
77 database: &crate::database::Database,
78 user_uuid: uuid::Uuid,
79 code: &str,
80 ) -> Result<Option<Self>, crate::database::DatabaseError> {
81 let row = sqlx::query(&format!(
82 r#"
83 DELETE FROM user_recovery_codes
84 WHERE user_recovery_codes.user_uuid = $1 AND user_recovery_codes.code = $2
85 RETURNING {}
86 "#,
87 Self::columns_sql(None)
88 ))
89 .bind(user_uuid)
90 .bind(code)
91 .fetch_optional(database.write())
92 .await?;
93
94 row.try_map(|row| Self::map(None, &row))
95 }
96
97 pub async fn delete_by_user_uuid(
98 database: &crate::database::Database,
99 user_uuid: uuid::Uuid,
100 ) -> Result<(), crate::database::DatabaseError> {
101 sqlx::query(
102 r#"
103 DELETE FROM user_recovery_codes
104 WHERE user_recovery_codes.user_uuid = $1
105 "#,
106 )
107 .bind(user_uuid)
108 .execute(database.write())
109 .await?;
110
111 Ok(())
112 }
113}