1use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use systemprompt_identifiers::{SessionId, SessionSource, UserId};
9
10#[derive(Debug)]
11pub struct CreateSessionParams<'a> {
12 pub session_id: &'a SessionId,
13 pub user_id: Option<&'a UserId>,
14 pub session_source: SessionSource,
15 pub fingerprint_hash: Option<&'a str>,
16 pub ip_address: Option<&'a str>,
17 pub user_agent: Option<&'a str>,
18 pub device_type: Option<&'a str>,
19 pub browser: Option<&'a str>,
20 pub os: Option<&'a str>,
21 pub country: Option<&'a str>,
22 pub region: Option<&'a str>,
23 pub city: Option<&'a str>,
24 pub preferred_locale: Option<&'a str>,
25 pub referrer_source: Option<&'a str>,
26 pub referrer_url: Option<&'a str>,
27 pub landing_page: Option<&'a str>,
28 pub entry_url: Option<&'a str>,
29 pub utm_source: Option<&'a str>,
30 pub utm_medium: Option<&'a str>,
31 pub utm_campaign: Option<&'a str>,
32 pub utm_content: Option<&'a str>,
33 pub utm_term: Option<&'a str>,
34 pub is_bot: bool,
35 pub is_ai_crawler: bool,
36 pub expires_at: DateTime<Utc>,
37}
38
39#[derive(Debug, Clone)]
40pub struct SessionRecord {
41 pub session_id: SessionId,
42 pub user_id: Option<UserId>,
43 pub expires_at: Option<DateTime<Utc>>,
44}
45
46#[derive(Debug, Clone)]
47pub struct ActiveSessionLookup {
48 pub user_id: Option<UserId>,
49}
50
51#[derive(Debug, Clone)]
52pub struct SessionBehavioralData {
53 pub session_id: SessionId,
54 pub fingerprint_hash: Option<String>,
55 pub user_agent: Option<String>,
56 pub request_count: Option<i32>,
57 pub started_at: DateTime<Utc>,
58 pub last_activity_at: DateTime<Utc>,
59 pub landing_page: Option<String>,
60 pub entry_url: Option<String>,
61}
62
63#[derive(Debug, Clone, Copy)]
64pub struct SessionMigrationResult {
65 pub sessions_migrated: u64,
66}
67
68impl SessionMigrationResult {
69 pub const fn total_records_migrated(&self) -> u64 {
70 self.sessions_migrated
71 }
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct SessionSnapshot {
76 pub session_id: SessionId,
77 pub user_id: Option<UserId>,
78 pub fingerprint_hash: Option<String>,
79 pub ip_address: Option<String>,
80 pub user_agent: Option<String>,
81 pub device_type: Option<String>,
82 pub browser: Option<String>,
83 pub os: Option<String>,
84 pub country: Option<String>,
85 pub city: Option<String>,
86 pub referrer_url: Option<String>,
87 pub utm_source: Option<String>,
88 pub utm_medium: Option<String>,
89 pub utm_campaign: Option<String>,
90 pub utm_content: Option<String>,
91 pub utm_term: Option<String>,
92 pub is_bot: bool,
93 pub is_scanner: Option<bool>,
94 pub is_behavioral_bot: Option<bool>,
95 pub behavioral_bot_reason: Option<String>,
96 pub started_at: Option<DateTime<Utc>>,
97 pub last_activity_at: Option<DateTime<Utc>>,
98 pub ended_at: Option<DateTime<Utc>>,
99 pub request_count: Option<i32>,
100 pub task_count: Option<i32>,
101 pub ai_request_count: Option<i32>,
102 pub message_count: Option<i32>,
103}
104
105use crate::{AnalyticsResult, SessionProvider, SessionUsageCounters};
106use async_trait::async_trait;
107
108#[async_trait]
111pub trait SessionStore: SessionProvider + SessionUsageCounters {
112 async fn fingerprint_session_ids(
113 &self,
114 fingerprint: &str,
115 window_days: i64,
116 ) -> AnalyticsResult<Vec<SessionId>>;
117 async fn find_by_id(&self, session_id: &SessionId) -> AnalyticsResult<Option<SessionSnapshot>>;
118 async fn find_active_by_id(
119 &self,
120 session_id: &SessionId,
121 ) -> AnalyticsResult<Option<ActiveSessionLookup>>;
122 async fn revoke_all_for_user(&self, user_id: &UserId) -> AnalyticsResult<u64>;
123 async fn find_by_fingerprint(
124 &self,
125 fingerprint_hash: &str,
126 user_id: &UserId,
127 ) -> AnalyticsResult<Option<SessionSnapshot>>;
128 async fn list_active_by_user(&self, user_id: &UserId) -> AnalyticsResult<Vec<SessionSnapshot>>;
129 async fn update_activity(&self, session_id: &SessionId) -> AnalyticsResult<()>;
130 async fn increment_request_count(&self, session_id: &SessionId) -> AnalyticsResult<()>;
131 async fn end_session(&self, session_id: &SessionId) -> AnalyticsResult<()>;
132 async fn mark_as_scanner(&self, session_id: &SessionId) -> AnalyticsResult<()>;
133 async fn mark_converted(&self, session_id: &SessionId) -> AnalyticsResult<()>;
134 async fn mark_as_behavioral_bot(
135 &self,
136 session_id: &SessionId,
137 reason: &str,
138 ) -> AnalyticsResult<()>;
139 async fn check_and_mark_behavioral_bot(
140 &self,
141 session_id: &SessionId,
142 request_count_threshold: i32,
143 ) -> AnalyticsResult<bool>;
144 async fn cleanup_inactive(&self, inactive_hours: i32) -> AnalyticsResult<u64>;
145 async fn count_inactive(&self, inactive_hours: i32) -> AnalyticsResult<i64>;
146 async fn count_sessions_missing_geo(&self) -> AnalyticsResult<i64>;
147 async fn insert_session(&self, params: &CreateSessionParams<'_>) -> AnalyticsResult<()>;
148 async fn find_recent_by_fingerprint(
149 &self,
150 fingerprint_hash: &str,
151 max_age_seconds: i64,
152 ) -> AnalyticsResult<Option<SessionRecord>>;
153 async fn increment_ai_usage(
154 &self,
155 session_id: &SessionId,
156 tokens: i32,
157 cost_microdollars: i64,
158 ) -> AnalyticsResult<()>;
159 async fn update_behavioral_detection(
160 &self,
161 session_id: &SessionId,
162 score: i32,
163 is_behavioral_bot: bool,
164 reason: Option<&str>,
165 ) -> AnalyticsResult<()>;
166 async fn count_sessions_by_fingerprint(
167 &self,
168 fingerprint_hash: &str,
169 window_hours: i64,
170 ) -> AnalyticsResult<i64>;
171 async fn get_session_for_behavioral_analysis(
172 &self,
173 session_id: &SessionId,
174 ) -> AnalyticsResult<Option<SessionBehavioralData>>;
175 async fn count_unique_ips_by_fingerprint(
176 &self,
177 fingerprint_hash: &str,
178 window_days: i64,
179 ) -> AnalyticsResult<i64>;
180 async fn get_session_starts_by_fingerprint(
181 &self,
182 fingerprint_hash: &str,
183 window_days: i64,
184 ) -> AnalyticsResult<Vec<DateTime<Utc>>>;
185 async fn get_session_velocity(
186 &self,
187 session_id: &SessionId,
188 ) -> AnalyticsResult<(Option<i64>, Option<i64>)>;
189 async fn count_active_fingerprint_sessions(&self, fingerprint: &str) -> AnalyticsResult<i32>;
190 async fn find_reusable_fingerprint_session(
191 &self,
192 fingerprint: &str,
193 ) -> AnalyticsResult<Option<SessionId>>;
194 async fn sessions_missing_geo(
195 &self,
196 after: Option<&SessionId>,
197 limit: i64,
198 ) -> AnalyticsResult<Vec<(SessionId, String)>>;
199 async fn set_session_geo(
200 &self,
201 session_id: &SessionId,
202 country: Option<&str>,
203 region: Option<&str>,
204 city: Option<&str>,
205 ) -> AnalyticsResult<u64>;
206}
207pub type DynSessionStore = std::sync::Arc<dyn SessionStore>;