Skip to main content

systemprompt_users/sessions/
providers.rs

1//! Authoritative session persistence contracts.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use super::SessionRepository;
7use crate::Result;
8use async_trait::async_trait;
9use chrono::Utc;
10use systemprompt_identifiers::{SessionId, UserId};
11use systemprompt_traits::session_store::CreateSessionParams;
12use systemprompt_traits::{
13    ActiveSession, AnalyticsProviderError, AnalyticsResult, AnalyticsSession, CreateSessionInput,
14    SessionProvider, SessionUsageCounters,
15};
16#[async_trait]
17impl SessionProvider for SessionRepository {
18    async fn create_session(&self, input: CreateSessionInput<'_>) -> AnalyticsResult<()> {
19        self.create_analytics_session(input)
20            .await
21            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
22    }
23
24    async fn find_recent_session_by_fingerprint(
25        &self,
26        fingerprint: &str,
27        max_age_seconds: i64,
28    ) -> AnalyticsResult<Option<AnalyticsSession>> {
29        let result = self
30            .find_recent_by_fingerprint(fingerprint, max_age_seconds)
31            .await
32            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))?;
33
34        Ok(result.map(|r| AnalyticsSession {
35            session_id: r.session_id,
36            user_id: r.user_id,
37            fingerprint: Some(fingerprint.to_owned()),
38            created_at: Utc::now(),
39        }))
40    }
41
42    async fn find_session_by_id(
43        &self,
44        session_id: &SessionId,
45    ) -> AnalyticsResult<Option<AnalyticsSession>> {
46        let result = self
47            .find_by_id(session_id)
48            .await
49            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))?;
50
51        Ok(result.map(|r| AnalyticsSession {
52            session_id: r.session_id,
53            user_id: r.user_id,
54            fingerprint: r.fingerprint_hash,
55            created_at: r.started_at.unwrap_or_else(Utc::now),
56        }))
57    }
58
59    async fn find_active_session_by_id(
60        &self,
61        session_id: &SessionId,
62    ) -> AnalyticsResult<Option<ActiveSession>> {
63        let result = self
64            .find_active_by_id(session_id)
65            .await
66            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))?;
67
68        Ok(result.map(|r| ActiveSession { user_id: r.user_id }))
69    }
70
71    async fn revoke_session(&self, session_id: &SessionId) -> AnalyticsResult<()> {
72        self.revoke_session(session_id)
73            .await
74            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
75    }
76
77    async fn revoke_all_sessions_for_user(&self, user_id: &UserId) -> AnalyticsResult<u64> {
78        self.revoke_all_for_user(user_id)
79            .await
80            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
81    }
82
83    async fn migrate_user_sessions(
84        &self,
85        from_user_id: &UserId,
86        to_user_id: &UserId,
87    ) -> AnalyticsResult<u64> {
88        self.migrate_user_sessions(from_user_id, to_user_id)
89            .await
90            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
91    }
92
93    async fn mark_session_converted(&self, session_id: &SessionId) -> AnalyticsResult<()> {
94        self.mark_converted(session_id)
95            .await
96            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
97    }
98}
99
100#[async_trait]
101impl SessionUsageCounters for SessionRepository {
102    async fn increment_task_count(&self, session_id: &SessionId) -> AnalyticsResult<()> {
103        Self::increment_task_count(self, session_id)
104            .await
105            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
106    }
107
108    async fn increment_message_count(&self, session_id: &SessionId) -> AnalyticsResult<()> {
109        Self::increment_message_count(self, session_id)
110            .await
111            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
112    }
113}
114
115impl SessionRepository {
116    pub async fn create_analytics_session(&self, input: CreateSessionInput<'_>) -> Result<()> {
117        let fingerprint = input.analytics.compute_fingerprint();
118
119        let params = CreateSessionParams {
120            session_id: input.session_id,
121            user_id: input.user_id,
122            session_source: input.session_source,
123            fingerprint_hash: Some(&fingerprint),
124            ip_address: input.analytics.ip_address.as_deref(),
125            user_agent: input.analytics.user_agent.as_deref(),
126            device_type: input.analytics.device_type.as_deref(),
127            browser: input.analytics.browser.as_deref(),
128            os: input.analytics.os.as_deref(),
129            country: input.analytics.country.as_deref(),
130            region: input.analytics.region.as_deref(),
131            city: input.analytics.city.as_deref(),
132            preferred_locale: input.analytics.preferred_locale.as_deref(),
133            referrer_source: input.analytics.referrer_source.as_deref(),
134            referrer_url: input.analytics.referrer_url.as_deref(),
135            landing_page: input.analytics.landing_page.as_deref(),
136            entry_url: input.analytics.entry_url.as_deref(),
137            utm_source: input.analytics.utm_source.as_deref(),
138            utm_medium: input.analytics.utm_medium.as_deref(),
139            utm_content: input.analytics.utm_content.as_deref(),
140            utm_term: input.analytics.utm_term.as_deref(),
141            utm_campaign: input.analytics.utm_campaign.as_deref(),
142            is_bot: input.is_bot,
143            is_ai_crawler: input.is_ai_crawler,
144            expires_at: input.expires_at,
145        };
146
147        self.create_session(&params).await?;
148
149        Ok(())
150    }
151}