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