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    SessionUsageCounters,
21};
22
23use super::service::AnalyticsService;
24use crate::repository::{FingerprintRepository, SessionRepository};
25
26#[async_trait]
27impl AnalyticsProvider for AnalyticsService {
28    fn extract_analytics(
29        &self,
30        headers: &HeaderMap,
31        signals: ExtractSignals<'_>,
32    ) -> SessionAnalytics {
33        Self::extract_analytics(self, headers, signals)
34    }
35
36    async fn create_session(&self, input: CreateSessionInput<'_>) -> AnalyticsResult<()> {
37        self.create_analytics_session(input)
38            .await
39            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
40    }
41
42    async fn find_recent_session_by_fingerprint(
43        &self,
44        fingerprint: &str,
45        max_age_seconds: i64,
46    ) -> AnalyticsResult<Option<AnalyticsSession>> {
47        let result = Self::find_recent_session_by_fingerprint(self, fingerprint, max_age_seconds)
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: Some(fingerprint.to_owned()),
55            created_at: Utc::now(),
56        }))
57    }
58
59    async fn find_session_by_id(
60        &self,
61        session_id: &SessionId,
62    ) -> AnalyticsResult<Option<AnalyticsSession>> {
63        let result = self
64            .session_repo()
65            .find_by_id(session_id)
66            .await
67            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))?;
68
69        Ok(result.map(|r| AnalyticsSession {
70            session_id: r.session_id,
71            user_id: r.user_id,
72            fingerprint: r.fingerprint_hash,
73            created_at: r.started_at.unwrap_or_else(Utc::now),
74        }))
75    }
76
77    async fn find_active_session_by_id(
78        &self,
79        session_id: &SessionId,
80    ) -> AnalyticsResult<Option<ActiveSession>> {
81        let result = self
82            .session_repo()
83            .find_active_by_id(session_id)
84            .await
85            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))?;
86
87        Ok(result.map(|r| ActiveSession { user_id: r.user_id }))
88    }
89
90    async fn revoke_session(&self, session_id: &SessionId) -> AnalyticsResult<()> {
91        self.session_repo()
92            .revoke_session(session_id)
93            .await
94            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
95    }
96
97    async fn revoke_all_sessions_for_user(&self, user_id: &UserId) -> AnalyticsResult<u64> {
98        self.session_repo()
99            .revoke_all_for_user(user_id)
100            .await
101            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
102    }
103
104    async fn migrate_user_sessions(
105        &self,
106        from_user_id: &UserId,
107        to_user_id: &UserId,
108    ) -> AnalyticsResult<u64> {
109        self.session_repo()
110            .migrate_user_sessions(from_user_id, to_user_id)
111            .await
112            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
113    }
114
115    async fn mark_session_converted(&self, session_id: &SessionId) -> AnalyticsResult<()> {
116        self.session_repo()
117            .mark_converted(session_id)
118            .await
119            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
120    }
121}
122
123#[async_trait]
124impl FingerprintProvider for FingerprintRepository {
125    async fn count_active_sessions(&self, fingerprint: &str) -> AnalyticsResult<i64> {
126        self.count_active_sessions(fingerprint)
127            .await
128            .map(i64::from)
129            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
130    }
131
132    async fn find_reusable_session(&self, fingerprint: &str) -> AnalyticsResult<Option<String>> {
133        self.find_reusable_session(fingerprint)
134            .await
135            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
136    }
137
138    async fn upsert_fingerprint(
139        &self,
140        fingerprint: &str,
141        ip_address: Option<&str>,
142        user_agent: Option<&str>,
143        _screen_info: Option<&str>,
144    ) -> AnalyticsResult<()> {
145        self.upsert_fingerprint(fingerprint, ip_address, user_agent, None)
146            .await
147            .map(|_| ())
148            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
149    }
150}
151
152#[async_trait]
153impl SessionUsageCounters for SessionRepository {
154    async fn increment_task_count(&self, session_id: &SessionId) -> AnalyticsResult<()> {
155        Self::increment_task_count(self, session_id)
156            .await
157            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
158    }
159
160    async fn increment_message_count(&self, session_id: &SessionId) -> AnalyticsResult<()> {
161        Self::increment_message_count(self, session_id)
162            .await
163            .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
164    }
165}