shared/domain/email/
repository.rs1use chrono::Utc;
2use std::sync::Arc;
3
4use crate::error::InternalError;
5use crate::error::{CoreError, Result, TokenErrorType};
6
7use super::model::EmailVerificationToken;
8use super::ports::database::DatabaseAdapter;
9use super::ports::query::QueryBuilder;
10
11#[derive(Clone)]
12pub struct EmailVerificationTokenRepository {
13 adapter: Arc<dyn DatabaseAdapter<EmailVerificationToken>>,
14}
15
16impl EmailVerificationTokenRepository {
17 pub fn new(adapter: Arc<dyn DatabaseAdapter<EmailVerificationToken>>) -> Self {
18 Self { adapter }
19 }
20
21 #[tracing::instrument(name = "db.email.insert", skip(self, otp))]
22 pub async fn insert(&self, otp: EmailVerificationToken) -> Result<()> {
23 match self.adapter.insert(otp).await {
24 Ok(_id) => Ok(()),
25 Err(e) => {
26 tracing::error!("Failed to insert user to database - {e}");
27 Err(CoreError::Internal(InternalError::Database(e.to_string())))
28 }
29 }
30 }
31
32 #[tracing::instrument(name = "db.email.consume", skip(self, hash))]
33 pub async fn consume(&self, hash: &str) -> Result<EmailVerificationToken> {
34 let filter = QueryBuilder::default()
35 .eq("hash", hash)
36 .gt("expiresAt", Utc::now())
37 .is_null("usedAt");
38 let update = QueryBuilder::default().set("usedAt", Utc::now());
39
40 match self.adapter.find_one_and_update(filter, update).await {
41 Ok(Some(token)) => Ok(token),
42 Ok(None) => Err(CoreError::Unauthenticated(
43 crate::error::AuthError::TokenInvalid {
44 token_type: TokenErrorType::EmailVerificationToken,
45 },
46 )),
47 Err(err) => {
48 tracing::error!(error_code = "InternalError::Database", error = %err, "Database query failed");
49 Err(err)
50 }
51 }
52 }
53
54 #[tracing::instrument(name = "db.email.invalidate", skip(self, id))]
55 pub async fn invalidate(&self, id: &str) -> Result<EmailVerificationToken> {
56 let filter = QueryBuilder::default().eq("id", id);
57 let update = QueryBuilder::default().set("usedAt", Utc::now());
58
59 match self.adapter.find_one_and_update(filter, update).await {
60 Ok(Some(token)) => Ok(token),
61 Ok(None) => Err(CoreError::Unauthenticated(
62 crate::error::AuthError::TokenInvalid {
63 token_type: TokenErrorType::EmailVerificationToken,
64 },
65 )),
66 Err(err) => {
67 tracing::error!(error_code = "InternalError::Database", error = %err, "Database query failed");
68 Err(err)
69 }
70 }
71 }
72
73 #[tracing::instrument(name = "db.email.revoke", skip(self), fields(user.id = user_id))]
74 pub async fn revoke(&self, user_id: &str) -> Result<()> {
75 let filter = QueryBuilder::default().eq("userId", user_id);
76 let update = QueryBuilder::default().set("usedAt", Utc::now());
78
79 match self.adapter.update_many(filter, update).await {
80 Ok(_) => Ok(()),
81 Err(err) => {
82 tracing::error!(error_code = "InternalError::Database", error = %err, "Database query failed");
83 Err(err)
84 }
85 }
86 }
87}