Skip to main content

shared/domain/session/
repository.rs

1use std::sync::Arc;
2
3use chrono::{Duration, Utc};
4
5use crate::error::{CoreError, InternalError, Result, TokenErrorType};
6
7use super::model::Session;
8use super::ports::database::DatabaseAdapter;
9use super::ports::query::QueryBuilder;
10
11#[derive(Clone)]
12pub struct SessionRepository {
13    adapter: Arc<dyn DatabaseAdapter<Session>>,
14}
15
16impl SessionRepository {
17    pub fn new(adapter: Arc<dyn DatabaseAdapter<Session>>) -> Self {
18        Self { adapter }
19    }
20}
21
22impl SessionRepository {
23    #[tracing::instrument(name = "db.account.insert", skip(self, session))]
24    pub async fn insert(&self, session: Session) -> Result<()> {
25        match self.adapter.insert(session).await {
26            Ok(_id) => Ok(()),
27            Err(err) => {
28                tracing::error!("Failed to insert account to database - {err}");
29                Err(CoreError::Internal(InternalError::Database(
30                    err.to_string(),
31                )))
32            }
33        }
34    }
35
36    #[tracing::instrument(name = "db.account.find", skip(self, token))]
37    pub async fn find(&self, token: &str) -> Result<Session> {
38        let filter = QueryBuilder::default().eq("token", token);
39
40        match self.adapter.find_one(filter).await {
41            Ok(Some(session)) => Ok(session),
42            Ok(None) => Err(CoreError::Unauthenticated(
43                crate::error::AuthError::TokenInvalid {
44                    token_type: TokenErrorType::SessionToken,
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.account.extend_timeout", skip(self, id))]
55    pub async fn extend_timeout(&self, id: &str) -> Result<Session> {
56        let filter = QueryBuilder::default().eq("id", id);
57        let update = QueryBuilder::default()
58            .set("usedAt", Utc::now())
59            .set("expiresAt", Utc::now() + Duration::hours(24));
60
61        match self.adapter.find_one_and_update(filter, update).await {
62            Ok(Some(session)) => Ok(session),
63            Ok(None) => Err(CoreError::NotFound(crate::error::ResourceKind::Token {
64                token_type: TokenErrorType::SessionToken,
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.account.invalidate", skip(self, token))]
74    pub async fn invalidate(&self, token: &str) -> Result<()> {
75        let filter = QueryBuilder::default().eq("token", token);
76
77        self.adapter.delete_one(filter).await
78    }
79
80    #[tracing::instrument(name = "db.account.revoke", skip(self), fields(user.id = user_id))]
81    pub async fn revoke(&self, user_id: &str) -> Result<()> {
82        let filter = QueryBuilder::default().eq("userId", user_id);
83
84        self.adapter.delete_many(filter).await.map_err(|err| {
85            tracing::error!(error_code = "InternalError::Database", error = %err, "Database query failed");
86            CoreError::Internal(InternalError::Hashing)
87        })
88    }
89}