Skip to main content

shared/domain/email/
model.rs

1use chrono::{DateTime, Utc};
2use mongodb::bson;
3use serde::{Deserialize, Serialize};
4use sqlx::prelude::FromRow;
5
6use crate::domain::query::IntoBsonDocument;
7
8use super::super::serde::{
9    deserialize_datetime, deserialize_object_id, deserialize_object_id_as_string,
10    deserialize_option_datetime,
11};
12
13#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, FromRow)]
14pub struct EmailVerificationToken {
15    #[serde(
16        rename = "_id",
17        default,
18        skip_serializing_if = "Option::is_none",
19        deserialize_with = "deserialize_object_id_as_string"
20    )]
21    pub id: Option<String>,
22
23    #[sqlx(rename = "userId")]
24    #[serde(
25        rename = "userId",
26        default,
27        // serialize_with = "serialize_object_id_as_string",
28        deserialize_with = "deserialize_object_id"
29    )]
30    pub user_id: String,
31
32    #[sqlx(rename = "issuedAt")]
33    #[serde(rename = "issuedAt", deserialize_with = "deserialize_datetime")]
34    pub issued_at: DateTime<Utc>,
35    #[sqlx(rename = "expiresAt")]
36    #[serde(rename = "expiresAt", deserialize_with = "deserialize_datetime")]
37    pub expires_at: DateTime<Utc>,
38    #[sqlx(rename = "usedAt")]
39    #[serde(rename = "usedAt", deserialize_with = "deserialize_option_datetime")]
40    pub used_at: Option<DateTime<Utc>>,
41
42    pub token: String,
43}
44
45impl Default for EmailVerificationToken {
46    fn default() -> Self {
47        Self {
48            id: None,
49            user_id: String::default(),
50            token: String::default(),
51            issued_at: Utc::now(),
52            expires_at: Utc::now() + chrono::Duration::seconds(86400),
53            used_at: None,
54        }
55    }
56}
57impl EmailVerificationToken {
58    pub fn with_user_id(mut self, user_id: &str) -> Self {
59        self.user_id = user_id.into();
60        self
61    }
62    pub fn with_token_hash(mut self, hash: &str) -> Self {
63        self.token = hash.into();
64        self
65    }
66    pub fn with_expiray(mut self, expires_at: chrono::Duration) -> Self {
67        self.expires_at = Utc::now() + expires_at;
68        self
69    }
70}
71
72impl EmailVerificationToken {
73    pub fn id(&self) -> Result<&str, crate::error::CoreError> {
74        self.id.as_deref().ok_or_else(|| {
75            tracing::error!(
76                error_code = "ValidationError::Malformed",
77                "Unexpected null/missing data"
78            );
79            crate::error::CoreError::Validation(crate::error::ValidationError::Malformed {
80                field: crate::error::CredentialField::ObjectId,
81            })
82        })
83    }
84}
85impl IntoBsonDocument for EmailVerificationToken {
86    fn into_bson_document(self) -> Result<bson::Document, bson::ser::Error> {
87        let mut doc = bson::to_document(&self)?;
88
89        for key in &["issuedAt", "expiresAt", "usedAt"] {
90            if let Some(bson::Bson::String(s)) = doc.get(*key).cloned()
91                && let Ok(dt) = DateTime::parse_from_rfc3339(&s)
92            {
93                doc.insert(
94                    *key,
95                    bson::Bson::DateTime(bson::DateTime::from_millis(dt.timestamp_millis())),
96                );
97            }
98        }
99
100        Ok(doc)
101    }
102}