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