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 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}