Skip to main content

systemprompt_analytics/services/
providers.rs

1//! Bridges this crate's analytics services to the `systemprompt_traits`
2//! provider contracts.
3//!
4//! Implements [`AnalyticsProvider`] for `AnalyticsService` and
5//! [`FingerprintProvider`] for `FingerprintRepository`, translating between
6//! the crate-local types and the trait-level types and mapping every error
7//! into the providers' error enums. `#[async_trait]` is required because
8//! these provider traits are consumed as `dyn`.
9//!
10//! Copyright (c) systemprompt.io — Business Source License 1.1.
11//! See <https://systemprompt.io> for licensing details.
12
13use async_trait::async_trait;
14use chrono::Utc;
15use http::HeaderMap;
16use systemprompt_identifiers::{SessionId, UserId};
17use systemprompt_traits::{
18    ActiveSession, AnalyticsProvider, AnalyticsProviderError, AnalyticsResult, AnalyticsSession,
19    CreateSessionInput, ExtractSignals, FingerprintProvider, SessionAnalytics,
20};
21
22use super::service::AnalyticsService;
23use crate::repository::FingerprintRepository;
24
25#[async_trait]
26impl AnalyticsProvider for AnalyticsService {
27    fn extract_analytics(
28        &self,
29        headers: &HeaderMap,
30        signals: ExtractSignals<'_>,
31    ) -> SessionAnalytics {
32        Self::extract_analytics(self, headers, signals)
33    }
34
35    async fn create_session(&self, input: CreateSessionInput<'_>) -> AnalyticsResult<()> {
36        self.create_analytics_session(input)
37            .await
38            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
39    }
40
41    async fn find_recent_session_by_fingerprint(
42        &self,
43        fingerprint: &str,
44        max_age_seconds: i64,
45    ) -> AnalyticsResult<Option<AnalyticsSession>> {
46        let result = Self::find_recent_session_by_fingerprint(self, fingerprint, max_age_seconds)
47            .await
48            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))?;
49
50        Ok(result.map(|r| AnalyticsSession {
51            session_id: r.session_id,
52            user_id: r.user_id,
53            fingerprint: Some(fingerprint.to_owned()),
54            created_at: Utc::now(),
55        }))
56    }
57
58    async fn find_session_by_id(
59        &self,
60        session_id: &SessionId,
61    ) -> AnalyticsResult<Option<AnalyticsSession>> {
62        let result = self
63            .session_repo()
64            .find_by_id(session_id)
65            .await
66            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))?;
67
68        Ok(result.map(|r| AnalyticsSession {
69            session_id: r.session_id,
70            user_id: r.user_id,
71            fingerprint: r.fingerprint_hash,
72            created_at: r.started_at.unwrap_or_else(Utc::now),
73        }))
74    }
75
76    async fn find_active_session_by_id(
77        &self,
78        session_id: &SessionId,
79    ) -> AnalyticsResult<Option<ActiveSession>> {
80        let result = self
81            .session_repo()
82            .find_active_by_id(session_id)
83            .await
84            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))?;
85
86        Ok(result.map(|r| ActiveSession { user_id: r.user_id }))
87    }
88
89    async fn revoke_session(&self, session_id: &SessionId) -> AnalyticsResult<()> {
90        self.session_repo()
91            .revoke_session(session_id)
92            .await
93            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
94    }
95
96    async fn revoke_all_sessions_for_user(&self, user_id: &UserId) -> AnalyticsResult<u64> {
97        self.session_repo()
98            .revoke_all_for_user(user_id)
99            .await
100            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
101    }
102
103    async fn migrate_user_sessions(
104        &self,
105        from_user_id: &UserId,
106        to_user_id: &UserId,
107    ) -> AnalyticsResult<u64> {
108        self.session_repo()
109            .migrate_user_sessions(from_user_id, to_user_id)
110            .await
111            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
112    }
113
114    async fn mark_session_converted(&self, session_id: &SessionId) -> AnalyticsResult<()> {
115        self.session_repo()
116            .mark_converted(session_id)
117            .await
118            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
119    }
120}
121
122#[async_trait]
123impl FingerprintProvider for FingerprintRepository {
124    async fn count_active_sessions(&self, fingerprint: &str) -> AnalyticsResult<i64> {
125        self.count_active_sessions(fingerprint)
126            .await
127            .map(i64::from)
128            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
129    }
130
131    async fn find_reusable_session(&self, fingerprint: &str) -> AnalyticsResult<Option<String>> {
132        self.find_reusable_session(fingerprint)
133            .await
134            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
135    }
136
137    async fn upsert_fingerprint(
138        &self,
139        fingerprint: &str,
140        ip_address: Option<&str>,
141        user_agent: Option<&str>,
142        _screen_info: Option<&str>,
143    ) -> AnalyticsResult<()> {
144        self.upsert_fingerprint(fingerprint, ip_address, user_agent, None)
145            .await
146            .map(|_| ())
147            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
148    }
149}