systemprompt_analytics/services/
service.rs1use std::sync::Arc;
7
8use crate::Result;
9use http::HeaderMap;
10
11use systemprompt_database::DbPool;
12use systemprompt_models::ContentRouting;
13use systemprompt_traits::{CreateSessionInput, ExtractSignals};
14
15use crate::GeoIpReader;
16use crate::repository::{CreateSessionParams, SessionRecord, SessionRepository};
17use crate::services::{SessionAnalytics, SessionAnalyticsBuilder};
18
19#[derive(Clone)]
20pub struct AnalyticsService {
21 geoip_reader: Option<GeoIpReader>,
22 content_routing: Option<Arc<dyn ContentRouting>>,
23 session_repo: SessionRepository,
24}
25
26impl std::fmt::Debug for AnalyticsService {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 f.debug_struct("AnalyticsService")
29 .field("geoip_reader", &self.geoip_reader.is_some())
30 .field("content_routing", &self.content_routing.is_some())
31 .field("session_repo", &"SessionRepository")
32 .finish()
33 }
34}
35
36impl AnalyticsService {
37 pub fn new(
38 db_pool: &DbPool,
39 geoip_reader: Option<GeoIpReader>,
40 content_routing: Option<Arc<dyn ContentRouting>>,
41 ) -> Result<Self> {
42 Ok(Self {
43 geoip_reader,
44 content_routing,
45 session_repo: SessionRepository::new(db_pool)?,
46 })
47 }
48
49 pub fn extract_analytics(
50 &self,
51 headers: &HeaderMap,
52 signals: ExtractSignals<'_>,
53 ) -> SessionAnalytics {
54 let mut builder = SessionAnalyticsBuilder::new(headers);
55 if let Some(uri) = signals.uri {
56 builder = builder.with_uri(uri);
57 }
58 if let Some(reader) = self.geoip_reader.as_ref() {
59 builder = builder.with_geoip(reader);
60 }
61 if let Some(content_routing) = self.content_routing.as_deref() {
62 builder = builder.with_content_routing(content_routing);
63 }
64 if let Some(caller_ip) = signals.caller_ip {
65 builder = builder.with_caller_ip(caller_ip);
66 }
67 builder.build()
68 }
69
70 pub async fn create_analytics_session(&self, input: CreateSessionInput<'_>) -> Result<()> {
71 let fingerprint = input.analytics.compute_fingerprint();
72
73 let params = CreateSessionParams {
74 session_id: input.session_id,
75 user_id: input.user_id,
76 session_source: input.session_source,
77 fingerprint_hash: Some(&fingerprint),
78 ip_address: input.analytics.ip_address.as_deref(),
79 user_agent: input.analytics.user_agent.as_deref(),
80 device_type: input.analytics.device_type.as_deref(),
81 browser: input.analytics.browser.as_deref(),
82 os: input.analytics.os.as_deref(),
83 country: input.analytics.country.as_deref(),
84 region: input.analytics.region.as_deref(),
85 city: input.analytics.city.as_deref(),
86 preferred_locale: input.analytics.preferred_locale.as_deref(),
87 referrer_source: input.analytics.referrer_source.as_deref(),
88 referrer_url: input.analytics.referrer_url.as_deref(),
89 landing_page: input.analytics.landing_page.as_deref(),
90 entry_url: input.analytics.entry_url.as_deref(),
91 utm_source: input.analytics.utm_source.as_deref(),
92 utm_medium: input.analytics.utm_medium.as_deref(),
93 utm_content: input.analytics.utm_content.as_deref(),
94 utm_term: input.analytics.utm_term.as_deref(),
95 utm_campaign: input.analytics.utm_campaign.as_deref(),
96 is_bot: input.is_bot,
97 is_ai_crawler: input.is_ai_crawler,
98 expires_at: input.expires_at,
99 };
100
101 self.session_repo.create_session(¶ms).await?;
102
103 Ok(())
104 }
105
106 pub async fn find_recent_session_by_fingerprint(
107 &self,
108 fingerprint: &str,
109 max_age_seconds: i64,
110 ) -> Result<Option<SessionRecord>> {
111 self.session_repo
112 .find_recent_by_fingerprint(fingerprint, max_age_seconds)
113 .await
114 }
115
116 pub const fn session_repo(&self) -> &SessionRepository {
117 &self.session_repo
118 }
119}