Skip to main content

systemprompt_traits/
session_store.rs

1//! Session repository row and migration-result types.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use 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/// Users-owned session persistence injected into analytics as
109/// `dyn SessionStore`, hence `#[async_trait]`.
110#[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>;