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