Skip to main content

systemprompt_analytics/repository/session/
mod.rs

1//! Session analytics orchestration through authoritative owner contracts.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6mod behavioral;
7mod behavioral_queries;
8mod geo;
9mod types;
10
11use std::sync::Arc;
12
13use crate::Result;
14use sqlx::PgPool;
15use systemprompt_database::DbPool;
16use systemprompt_identifiers::{SessionId, UserId};
17
18use crate::models::AnalyticsSession;
19
20use systemprompt_traits::session_store::ActiveSessionLookup;
21pub use types::{
22    CreateSessionParams, SessionBehavioralData, SessionMigrationResult, SessionRecord,
23};
24
25#[derive(Clone)]
26pub struct SessionRepository {
27    write_pool: Arc<PgPool>,
28    owner: systemprompt_traits::DynSessionStore,
29    events: systemprompt_traits::DynAnalyticsEventStore,
30    content: systemprompt_traits::DynContentCatalogStats,
31}
32
33impl SessionRepository {
34    pub fn owner(&self) -> systemprompt_traits::DynSessionStore {
35        Arc::clone(&self.owner)
36    }
37
38    pub fn new(
39        db: &DbPool,
40        owner: systemprompt_traits::DynSessionStore,
41        events: systemprompt_traits::DynAnalyticsEventStore,
42        content: systemprompt_traits::DynContentCatalogStats,
43    ) -> Result<Self> {
44        let write_pool = db.write_pool_arc()?;
45        Ok(Self {
46            write_pool,
47            owner,
48            events,
49            content,
50        })
51    }
52
53    pub async fn find_by_id(&self, session_id: &SessionId) -> Result<Option<AnalyticsSession>> {
54        systemprompt_traits::SessionStore::find_by_id(&*self.owner, session_id)
55            .await
56            .map_err(crate::AnalyticsError::from)
57    }
58
59    pub async fn find_active_by_id(
60        &self,
61        session_id: &SessionId,
62    ) -> Result<Option<ActiveSessionLookup>> {
63        systemprompt_traits::SessionStore::find_active_by_id(&*self.owner, session_id)
64            .await
65            .map_err(crate::AnalyticsError::from)
66    }
67
68    pub async fn revoke_session(&self, session_id: &SessionId) -> Result<()> {
69        systemprompt_traits::SessionProvider::revoke_session(&*self.owner, session_id)
70            .await
71            .map_err(crate::AnalyticsError::from)
72    }
73
74    pub async fn revoke_all_for_user(&self, user_id: &UserId) -> Result<u64> {
75        systemprompt_traits::SessionStore::revoke_all_for_user(&*self.owner, user_id)
76            .await
77            .map_err(crate::AnalyticsError::from)
78    }
79
80    pub async fn find_by_fingerprint(
81        &self,
82        fingerprint_hash: &str,
83        user_id: &UserId,
84    ) -> Result<Option<AnalyticsSession>> {
85        systemprompt_traits::SessionStore::find_by_fingerprint(
86            &*self.owner,
87            fingerprint_hash,
88            user_id,
89        )
90        .await
91        .map_err(crate::AnalyticsError::from)
92    }
93
94    pub async fn list_active_by_user(&self, user_id: &UserId) -> Result<Vec<AnalyticsSession>> {
95        systemprompt_traits::SessionStore::list_active_by_user(&*self.owner, user_id)
96            .await
97            .map_err(crate::AnalyticsError::from)
98    }
99
100    pub async fn update_activity(&self, session_id: &SessionId) -> Result<()> {
101        systemprompt_traits::SessionStore::update_activity(&*self.owner, session_id)
102            .await
103            .map_err(crate::AnalyticsError::from)
104    }
105
106    pub async fn increment_request_count(&self, session_id: &SessionId) -> Result<()> {
107        systemprompt_traits::SessionStore::increment_request_count(&*self.owner, session_id)
108            .await
109            .map_err(crate::AnalyticsError::from)
110    }
111
112    pub async fn increment_task_count(&self, session_id: &SessionId) -> Result<()> {
113        systemprompt_traits::SessionUsageCounters::increment_task_count(&*self.owner, session_id)
114            .await
115            .map_err(crate::AnalyticsError::from)
116    }
117
118    pub async fn increment_message_count(&self, session_id: &SessionId) -> Result<()> {
119        systemprompt_traits::SessionUsageCounters::increment_message_count(&*self.owner, session_id)
120            .await
121            .map_err(crate::AnalyticsError::from)
122    }
123
124    pub async fn end_session(&self, session_id: &SessionId) -> Result<()> {
125        systemprompt_traits::SessionStore::end_session(&*self.owner, session_id)
126            .await
127            .map_err(crate::AnalyticsError::from)
128    }
129
130    pub async fn mark_as_scanner(&self, session_id: &SessionId) -> Result<()> {
131        systemprompt_traits::SessionStore::mark_as_scanner(&*self.owner, session_id)
132            .await
133            .map_err(crate::AnalyticsError::from)
134    }
135
136    pub async fn mark_converted(&self, session_id: &SessionId) -> Result<()> {
137        systemprompt_traits::SessionStore::mark_converted(&*self.owner, session_id)
138            .await
139            .map_err(crate::AnalyticsError::from)
140    }
141
142    pub async fn mark_as_behavioral_bot(&self, session_id: &SessionId, reason: &str) -> Result<()> {
143        systemprompt_traits::SessionStore::mark_as_behavioral_bot(&*self.owner, session_id, reason)
144            .await
145            .map_err(crate::AnalyticsError::from)
146    }
147
148    pub async fn check_and_mark_behavioral_bot(
149        &self,
150        session_id: &SessionId,
151        request_count_threshold: i32,
152    ) -> Result<bool> {
153        systemprompt_traits::SessionStore::check_and_mark_behavioral_bot(
154            &*self.owner,
155            session_id,
156            request_count_threshold,
157        )
158        .await
159        .map_err(crate::AnalyticsError::from)
160    }
161
162    pub async fn cleanup_inactive(&self, inactive_hours: i32) -> Result<u64> {
163        systemprompt_traits::SessionStore::cleanup_inactive(&*self.owner, inactive_hours)
164            .await
165            .map_err(crate::AnalyticsError::from)
166    }
167
168    pub async fn count_inactive(&self, inactive_hours: i32) -> Result<i64> {
169        systemprompt_traits::SessionStore::count_inactive(&*self.owner, inactive_hours)
170            .await
171            .map_err(crate::AnalyticsError::from)
172    }
173
174    pub async fn backfill_session_geo(
175        &self,
176        geoip_reader: Option<&crate::GeoIpReader>,
177        batch_size: i64,
178    ) -> Result<u64> {
179        self.backfill_geo(geoip_reader, batch_size).await
180    }
181
182    pub async fn count_sessions_missing_geo(&self) -> Result<i64> {
183        systemprompt_traits::SessionStore::count_sessions_missing_geo(&*self.owner)
184            .await
185            .map_err(crate::AnalyticsError::from)
186    }
187
188    pub async fn migrate_user_sessions(
189        &self,
190        old_user_id: &UserId,
191        new_user_id: &UserId,
192    ) -> Result<u64> {
193        systemprompt_traits::SessionProvider::migrate_user_sessions(
194            &*self.owner,
195            old_user_id,
196            new_user_id,
197        )
198        .await
199        .map_err(crate::AnalyticsError::from)
200    }
201
202    pub async fn create_session(&self, params: &CreateSessionParams<'_>) -> Result<()> {
203        systemprompt_traits::SessionStore::insert_session(&*self.owner, params)
204            .await
205            .map_err(crate::AnalyticsError::from)
206    }
207
208    pub async fn find_recent_by_fingerprint(
209        &self,
210        fingerprint_hash: &str,
211        max_age_seconds: i64,
212    ) -> Result<Option<SessionRecord>> {
213        systemprompt_traits::SessionStore::find_recent_by_fingerprint(
214            &*self.owner,
215            fingerprint_hash,
216            max_age_seconds,
217        )
218        .await
219        .map_err(crate::AnalyticsError::from)
220    }
221
222    pub async fn increment_ai_usage(
223        &self,
224        session_id: &SessionId,
225        tokens: i32,
226        cost_microdollars: i64,
227    ) -> Result<()> {
228        systemprompt_traits::SessionStore::increment_ai_usage(
229            &*self.owner,
230            session_id,
231            tokens,
232            cost_microdollars,
233        )
234        .await
235        .map_err(crate::AnalyticsError::from)
236    }
237
238    pub async fn update_behavioral_detection(
239        &self,
240        session_id: &SessionId,
241        score: i32,
242        is_behavioral_bot: bool,
243        reason: Option<&str>,
244    ) -> Result<()> {
245        systemprompt_traits::SessionStore::update_behavioral_detection(
246            &*self.owner,
247            session_id,
248            score,
249            is_behavioral_bot,
250            reason,
251        )
252        .await
253        .map_err(crate::AnalyticsError::from)
254    }
255}
256
257impl std::fmt::Debug for SessionRepository {
258    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
259        f.debug_struct("SessionRepository").finish_non_exhaustive()
260    }
261}