systemprompt_users/repository/user/
session.rs1use chrono::Utc;
7use systemprompt_identifiers::{SessionId, UserId};
8
9use crate::error::Result;
10use crate::models::{UserSession, UserSessionRow};
11use crate::repository::{MAX_PAGE_SIZE, UserRepository};
12
13impl UserRepository {
14 pub async fn list_sessions(&self, user_id: &UserId) -> Result<Vec<UserSession>> {
15 let rows = sqlx::query_as!(
16 UserSessionRow,
17 r#"
18 SELECT session_id, user_id as "user_id: UserId", ip_address, user_agent, device_type,
19 started_at, last_activity_at, ended_at
20 FROM user_sessions
21 WHERE user_id = $1
22 ORDER BY last_activity_at DESC
23 "#,
24 user_id.as_str()
25 )
26 .fetch_all(&*self.pool)
27 .await?;
28
29 Ok(rows.into_iter().map(UserSession::from).collect())
30 }
31
32 pub async fn list_active_sessions(&self, user_id: &UserId) -> Result<Vec<UserSession>> {
33 let rows = sqlx::query_as!(
34 UserSessionRow,
35 r#"
36 SELECT session_id, user_id as "user_id: UserId", ip_address, user_agent, device_type,
37 started_at, last_activity_at, ended_at
38 FROM user_sessions
39 WHERE user_id = $1 AND ended_at IS NULL
40 ORDER BY last_activity_at DESC
41 "#,
42 user_id.as_str()
43 )
44 .fetch_all(&*self.pool)
45 .await?;
46
47 Ok(rows.into_iter().map(UserSession::from).collect())
48 }
49
50 pub async fn list_recent_sessions(
51 &self,
52 user_id: &UserId,
53 limit: i64,
54 ) -> Result<Vec<UserSession>> {
55 let safe_limit = limit.min(MAX_PAGE_SIZE);
56 let rows = sqlx::query_as!(
57 UserSessionRow,
58 r#"
59 SELECT session_id, user_id as "user_id: UserId", ip_address, user_agent, device_type,
60 started_at, last_activity_at, ended_at
61 FROM user_sessions
62 WHERE user_id = $1
63 ORDER BY last_activity_at DESC
64 LIMIT $2
65 "#,
66 user_id.as_str(),
67 safe_limit
68 )
69 .fetch_all(&*self.pool)
70 .await?;
71
72 Ok(rows.into_iter().map(UserSession::from).collect())
73 }
74
75 pub async fn session_exists(&self, session_id: &SessionId) -> Result<bool> {
76 let exists = sqlx::query_scalar!(
77 r#"SELECT EXISTS(SELECT 1 FROM user_sessions WHERE session_id = $1 AND ended_at IS NULL) as "exists!""#,
78 session_id.as_str()
79 )
80 .fetch_one(&*self.pool)
81 .await?;
82
83 Ok(exists)
84 }
85
86 pub async fn end_session(&self, session_id: &SessionId) -> Result<bool> {
87 let result = sqlx::query!(
88 r#"
89 UPDATE user_sessions
90 SET ended_at = $1
91 WHERE session_id = $2 AND ended_at IS NULL
92 "#,
93 Utc::now(),
94 session_id.as_str()
95 )
96 .execute(&*self.write_pool)
97 .await?;
98
99 Ok(result.rows_affected() > 0)
100 }
101
102 pub async fn end_all_sessions(&self, user_id: &UserId) -> Result<u64> {
103 let result = sqlx::query!(
104 r#"
105 UPDATE user_sessions
106 SET ended_at = $1
107 WHERE user_id = $2 AND ended_at IS NULL
108 "#,
109 Utc::now(),
110 user_id.as_str()
111 )
112 .execute(&*self.write_pool)
113 .await?;
114
115 Ok(result.rows_affected())
116 }
117}