systemprompt_analytics/repository/session/
mod.rs1mod 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}