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 increment_request_count(&self, session_id: &SessionId) -> Result<()> {
101        systemprompt_traits::SessionStore::increment_request_count(&*self.owner, session_id)
102            .await
103            .map_err(crate::AnalyticsError::from)
104    }
105
106    pub async fn increment_task_count(&self, session_id: &SessionId) -> Result<()> {
107        systemprompt_traits::SessionUsageCounters::increment_task_count(&*self.owner, session_id)
108            .await
109            .map_err(crate::AnalyticsError::from)
110    }
111
112    pub async fn increment_message_count(&self, session_id: &SessionId) -> Result<()> {
113        systemprompt_traits::SessionUsageCounters::increment_message_count(&*self.owner, session_id)
114            .await
115            .map_err(crate::AnalyticsError::from)
116    }
117
118    pub async fn end_session(&self, session_id: &SessionId) -> Result<()> {
119        systemprompt_traits::SessionStore::end_session(&*self.owner, session_id)
120            .await
121            .map_err(crate::AnalyticsError::from)
122    }
123
124    pub async fn mark_as_scanner(&self, session_id: &SessionId) -> Result<()> {
125        systemprompt_traits::SessionStore::mark_as_scanner(&*self.owner, session_id)
126            .await
127            .map_err(crate::AnalyticsError::from)
128    }
129
130    pub async fn mark_converted(&self, session_id: &SessionId) -> Result<()> {
131        systemprompt_traits::SessionStore::mark_converted(&*self.owner, session_id)
132            .await
133            .map_err(crate::AnalyticsError::from)
134    }
135
136    pub async fn mark_as_behavioral_bot(&self, session_id: &SessionId, reason: &str) -> Result<()> {
137        systemprompt_traits::SessionStore::mark_as_behavioral_bot(&*self.owner, session_id, reason)
138            .await
139            .map_err(crate::AnalyticsError::from)
140    }
141
142    pub async fn check_and_mark_behavioral_bot(
143        &self,
144        session_id: &SessionId,
145        request_count_threshold: i32,
146    ) -> Result<bool> {
147        systemprompt_traits::SessionStore::check_and_mark_behavioral_bot(
148            &*self.owner,
149            session_id,
150            request_count_threshold,
151        )
152        .await
153        .map_err(crate::AnalyticsError::from)
154    }
155
156    pub async fn cleanup_inactive(&self, inactive_hours: i32) -> Result<u64> {
157        systemprompt_traits::SessionStore::cleanup_inactive(&*self.owner, inactive_hours)
158            .await
159            .map_err(crate::AnalyticsError::from)
160    }
161
162    pub async fn count_inactive(&self, inactive_hours: i32) -> Result<i64> {
163        systemprompt_traits::SessionStore::count_inactive(&*self.owner, inactive_hours)
164            .await
165            .map_err(crate::AnalyticsError::from)
166    }
167
168    pub async fn backfill_session_geo(
169        &self,
170        geoip_reader: Option<&crate::GeoIpReader>,
171        batch_size: i64,
172    ) -> Result<u64> {
173        self.backfill_geo(geoip_reader, batch_size).await
174    }
175
176    pub async fn count_sessions_missing_geo(&self) -> Result<i64> {
177        systemprompt_traits::SessionStore::count_sessions_missing_geo(&*self.owner)
178            .await
179            .map_err(crate::AnalyticsError::from)
180    }
181
182    pub async fn migrate_user_sessions(
183        &self,
184        old_user_id: &UserId,
185        new_user_id: &UserId,
186    ) -> Result<u64> {
187        systemprompt_traits::SessionProvider::migrate_user_sessions(
188            &*self.owner,
189            old_user_id,
190            new_user_id,
191        )
192        .await
193        .map_err(crate::AnalyticsError::from)
194    }
195
196    pub async fn create_session(&self, params: &CreateSessionParams<'_>) -> Result<()> {
197        systemprompt_traits::SessionStore::insert_session(&*self.owner, params)
198            .await
199            .map_err(crate::AnalyticsError::from)
200    }
201
202    pub async fn find_recent_by_fingerprint(
203        &self,
204        fingerprint_hash: &str,
205        max_age_seconds: i64,
206    ) -> Result<Option<SessionRecord>> {
207        systemprompt_traits::SessionStore::find_recent_by_fingerprint(
208            &*self.owner,
209            fingerprint_hash,
210            max_age_seconds,
211        )
212        .await
213        .map_err(crate::AnalyticsError::from)
214    }
215
216    pub async fn increment_ai_usage(
217        &self,
218        session_id: &SessionId,
219        tokens: i32,
220        cost_microdollars: i64,
221    ) -> Result<()> {
222        systemprompt_traits::SessionStore::increment_ai_usage(
223            &*self.owner,
224            session_id,
225            tokens,
226            cost_microdollars,
227        )
228        .await
229        .map_err(crate::AnalyticsError::from)
230    }
231
232    pub async fn update_behavioral_detection(
233        &self,
234        session_id: &SessionId,
235        score: i32,
236        is_behavioral_bot: bool,
237        reason: Option<&str>,
238    ) -> Result<()> {
239        systemprompt_traits::SessionStore::update_behavioral_detection(
240            &*self.owner,
241            session_id,
242            score,
243            is_behavioral_bot,
244            reason,
245        )
246        .await
247        .map_err(crate::AnalyticsError::from)
248    }
249}
250
251impl std::fmt::Debug for SessionRepository {
252    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
253        f.debug_struct("SessionRepository").finish_non_exhaustive()
254    }
255}