systemprompt_traits/
analytics.rs1use async_trait::async_trait;
10use chrono::{DateTime, Utc};
11use http::{HeaderMap, Uri};
12use std::net::IpAddr;
13use std::sync::Arc;
14use systemprompt_identifiers::{SessionId, SessionSource, UserId};
15
16pub type AnalyticsResult<T> = Result<T, AnalyticsProviderError>;
17
18#[derive(Debug, thiserror::Error)]
19#[non_exhaustive]
20pub enum AnalyticsProviderError {
21 #[error("Session not found")]
22 SessionNotFound,
23
24 #[error("Fingerprint not found")]
25 FingerprintNotFound,
26
27 #[error("Internal error: {0}")]
28 Internal(String),
29}
30
31#[derive(Debug, Clone, Default)]
38pub struct SessionAnalytics {
39 pub ip_address: Option<String>,
40 pub user_agent: Option<String>,
41 pub device_type: Option<String>,
42 pub browser: Option<String>,
43 pub os: Option<String>,
44 pub fingerprint_hash: Option<String>,
45 pub preferred_locale: Option<String>,
46 pub country: Option<String>,
47 pub region: Option<String>,
48 pub city: Option<String>,
49 pub referrer_source: Option<String>,
50 pub referrer_url: Option<String>,
51 pub landing_page: Option<String>,
52 pub entry_url: Option<String>,
53 pub utm_source: Option<String>,
54 pub utm_medium: Option<String>,
55 pub utm_campaign: Option<String>,
56 pub utm_content: Option<String>,
57 pub utm_term: Option<String>,
58 pub is_bot: bool,
59 pub is_ai_crawler: bool,
60 pub skip_tracking: bool,
64}
65
66impl SessionAnalytics {
67 pub fn compute_fingerprint(&self) -> String {
70 use xxhash_rust::xxh64::xxh64;
71
72 if let Some(hash) = &self.fingerprint_hash {
73 return hash.clone();
74 }
75
76 let data = format!(
77 "{}|{}",
78 self.user_agent.as_deref().unwrap_or(""),
79 self.preferred_locale.as_deref().unwrap_or("")
80 );
81
82 format!("fp_{:016x}", xxh64(data.as_bytes(), 0))
83 }
84}
85
86#[derive(Debug, Clone)]
87pub struct AnalyticsSession {
88 pub session_id: SessionId,
89 pub user_id: Option<UserId>,
90 pub fingerprint: Option<String>,
91 pub created_at: DateTime<Utc>,
92}
93
94#[derive(Debug, Clone)]
95pub struct ActiveSession {
96 pub user_id: Option<UserId>,
97}
98
99#[derive(Debug)]
100pub struct CreateSessionInput<'a> {
101 pub session_id: &'a SessionId,
102 pub user_id: Option<&'a UserId>,
103 pub analytics: &'a SessionAnalytics,
104 pub session_source: SessionSource,
105 pub is_bot: bool,
106 pub is_ai_crawler: bool,
107 pub expires_at: DateTime<Utc>,
108}
109
110#[derive(Debug, Default, Clone, Copy)]
114pub struct ExtractSignals<'a> {
115 pub uri: Option<&'a Uri>,
116 pub caller_ip: Option<IpAddr>,
117}
118
119#[async_trait]
120pub trait AnalyticsProvider: Send + Sync {
121 fn extract_analytics(
122 &self,
123 headers: &HeaderMap,
124 signals: ExtractSignals<'_>,
125 ) -> SessionAnalytics;
126
127 async fn create_session(&self, input: CreateSessionInput<'_>) -> AnalyticsResult<()>;
128
129 async fn find_recent_session_by_fingerprint(
130 &self,
131 fingerprint: &str,
132 max_age_seconds: i64,
133 ) -> AnalyticsResult<Option<AnalyticsSession>>;
134
135 async fn find_session_by_id(
136 &self,
137 session_id: &SessionId,
138 ) -> AnalyticsResult<Option<AnalyticsSession>>;
139
140 async fn find_active_session_by_id(
141 &self,
142 session_id: &SessionId,
143 ) -> AnalyticsResult<Option<ActiveSession>>;
144
145 async fn revoke_session(&self, session_id: &SessionId) -> AnalyticsResult<()>;
146
147 async fn revoke_all_sessions_for_user(&self, user_id: &UserId) -> AnalyticsResult<u64>;
148
149 async fn migrate_user_sessions(
150 &self,
151 from_user_id: &UserId,
152 to_user_id: &UserId,
153 ) -> AnalyticsResult<u64>;
154
155 async fn mark_session_converted(&self, session_id: &SessionId) -> AnalyticsResult<()>;
156}
157
158#[async_trait]
159pub trait FingerprintProvider: Send + Sync {
160 async fn count_active_sessions(&self, fingerprint: &str) -> AnalyticsResult<i64>;
161
162 async fn find_reusable_session(&self, fingerprint: &str) -> AnalyticsResult<Option<String>>;
163
164 async fn upsert_fingerprint(
165 &self,
166 fingerprint: &str,
167 ip_address: Option<&str>,
168 user_agent: Option<&str>,
169 screen_info: Option<&str>,
170 ) -> AnalyticsResult<()>;
171}
172
173pub type DynAnalyticsProvider = Arc<dyn AnalyticsProvider>;
174
175pub type DynFingerprintProvider = Arc<dyn FingerprintProvider>;