systemprompt_analytics/services/
providers.rs1use async_trait::async_trait;
2use chrono::Utc;
3use http::{HeaderMap, Uri};
4use systemprompt_identifiers::{SessionId, UserId};
5use systemprompt_traits::{
6 AnalyticsProvider, AnalyticsProviderError, AnalyticsResult, AnalyticsSession,
7 CreateSessionInput, FingerprintProvider, SessionAnalytics as TraitSessionAnalytics,
8 SessionAnalyticsProvider, SessionAnalyticsProviderError, SessionAnalyticsResult,
9};
10
11use super::service::AnalyticsService;
12use super::SessionAnalytics;
13use crate::repository::{FingerprintRepository, SessionRepository};
14
15#[async_trait]
16impl AnalyticsProvider for AnalyticsService {
17 fn extract_analytics(&self, headers: &HeaderMap, uri: Option<&Uri>) -> TraitSessionAnalytics {
18 let local = Self::extract_analytics(self, headers, uri);
19 TraitSessionAnalytics {
20 ip_address: local.ip_address.clone(),
21 user_agent: local.user_agent.clone(),
22 device_type: local.device_type.clone(),
23 browser: local.browser.clone(),
24 os: local.os.clone(),
25 fingerprint_hash: local.fingerprint_hash.clone(),
26 referer: local.referrer_url.clone(),
27 referrer_url: local.referrer_url.clone(),
28 referrer_source: local.referrer_source.clone(),
29 accept_language: local.preferred_locale.clone(),
30 preferred_locale: local.preferred_locale.clone(),
31 screen_width: None,
32 screen_height: None,
33 timezone: None,
34 page_url: local.entry_url.clone(),
35 landing_page: local.landing_page.clone(),
36 entry_url: local.entry_url,
37 country: local.country.clone(),
38 region: local.region.clone(),
39 city: local.city,
40 utm_source: local.utm_source,
41 utm_medium: local.utm_medium,
42 utm_campaign: local.utm_campaign,
43 }
44 }
45
46 async fn create_session(&self, input: CreateSessionInput<'_>) -> AnalyticsResult<()> {
47 let local_analytics = SessionAnalytics {
48 ip_address: input.analytics.ip_address.clone(),
49 user_agent: input.analytics.user_agent.clone(),
50 device_type: input.analytics.device_type.clone(),
51 browser: input.analytics.browser.clone(),
52 os: input.analytics.os.clone(),
53 fingerprint_hash: input.analytics.fingerprint_hash.clone(),
54 referrer_url: input
55 .analytics
56 .referrer_url
57 .clone()
58 .or_else(|| input.analytics.referer.clone()),
59 referrer_source: input.analytics.referrer_source.clone(),
60 preferred_locale: input
61 .analytics
62 .preferred_locale
63 .clone()
64 .or_else(|| input.analytics.accept_language.clone()),
65 landing_page: input.analytics.landing_page.clone(),
66 entry_url: input
67 .analytics
68 .entry_url
69 .clone()
70 .or_else(|| input.analytics.page_url.clone()),
71 country: input.analytics.country.clone(),
72 region: input.analytics.region.clone(),
73 city: input.analytics.city.clone(),
74 utm_source: input.analytics.utm_source.clone(),
75 utm_medium: input.analytics.utm_medium.clone(),
76 utm_campaign: input.analytics.utm_campaign.clone(),
77 };
78
79 let local_input = super::service::CreateAnalyticsSessionInput {
80 session_id: input.session_id,
81 user_id: input.user_id,
82 analytics: &local_analytics,
83 session_source: input.session_source,
84 is_bot: input.is_bot,
85 expires_at: input.expires_at,
86 };
87
88 self.create_analytics_session(local_input)
89 .await
90 .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
91 }
92
93 async fn find_recent_session_by_fingerprint(
94 &self,
95 fingerprint: &str,
96 max_age_seconds: i64,
97 ) -> AnalyticsResult<Option<AnalyticsSession>> {
98 let result = Self::find_recent_session_by_fingerprint(self, fingerprint, max_age_seconds)
99 .await
100 .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))?;
101
102 Ok(result.map(|r| AnalyticsSession {
103 session_id: r.session_id.to_string(),
104 user_id: r.user_id.map(|u| u.to_string()),
105 fingerprint: Some(fingerprint.to_string()),
106 created_at: Utc::now(),
107 }))
108 }
109
110 async fn find_session_by_id(
111 &self,
112 session_id: &SessionId,
113 ) -> AnalyticsResult<Option<AnalyticsSession>> {
114 let result = self
115 .session_repo()
116 .find_by_id(session_id)
117 .await
118 .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))?;
119
120 Ok(result.map(|r| AnalyticsSession {
121 session_id: r.session_id.to_string(),
122 user_id: r.user_id.map(|u| u.to_string()),
123 fingerprint: r.fingerprint_hash,
124 created_at: r.started_at.unwrap_or_else(Utc::now),
125 }))
126 }
127
128 async fn migrate_user_sessions(
129 &self,
130 from_user_id: &UserId,
131 to_user_id: &UserId,
132 ) -> AnalyticsResult<u64> {
133 self.session_repo()
134 .migrate_user_sessions(from_user_id, to_user_id)
135 .await
136 .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
137 }
138}
139
140#[async_trait]
141impl FingerprintProvider for FingerprintRepository {
142 async fn count_active_sessions(&self, fingerprint: &str) -> AnalyticsResult<i64> {
143 self.count_active_sessions(fingerprint)
144 .await
145 .map(i64::from)
146 .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
147 }
148
149 async fn find_reusable_session(&self, fingerprint: &str) -> AnalyticsResult<Option<String>> {
150 self.find_reusable_session(fingerprint)
151 .await
152 .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
153 }
154
155 async fn upsert_fingerprint(
156 &self,
157 fingerprint: &str,
158 ip_address: Option<&str>,
159 user_agent: Option<&str>,
160 _screen_info: Option<&str>,
161 ) -> AnalyticsResult<()> {
162 self.upsert_fingerprint(fingerprint, ip_address, user_agent, None)
163 .await
164 .map(|_| ())
165 .map_err(|e| AnalyticsProviderError::Internal(e.to_string()))
166 }
167}
168
169#[async_trait]
170impl SessionAnalyticsProvider for SessionRepository {
171 async fn increment_task_count(&self, session_id: &SessionId) -> SessionAnalyticsResult<()> {
172 Self::increment_task_count(self, session_id)
173 .await
174 .map_err(|e| SessionAnalyticsProviderError::Internal(e.to_string()))
175 }
176
177 async fn increment_message_count(&self, session_id: &SessionId) -> SessionAnalyticsResult<()> {
178 Self::increment_message_count(self, session_id)
179 .await
180 .map_err(|e| SessionAnalyticsProviderError::Internal(e.to_string()))
181 }
182}