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