forge_core/realtime/
session.rs1use std::str::FromStr;
2
3use chrono::{DateTime, Utc};
4use uuid::Uuid;
5
6use crate::cluster::NodeId;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub struct SessionId(pub Uuid);
11
12impl SessionId {
13 pub fn new() -> Self {
15 Self(Uuid::new_v4())
16 }
17
18 pub fn from_uuid(id: Uuid) -> Self {
20 Self(id)
21 }
22
23 pub fn as_uuid(&self) -> Uuid {
25 self.0
26 }
27}
28
29impl Default for SessionId {
30 fn default() -> Self {
31 Self::new()
32 }
33}
34
35impl std::fmt::Display for SessionId {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 write!(f, "{}", self.0)
38 }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum SessionStatus {
44 Connecting,
46 Connected,
48 Reconnecting,
50 Disconnected,
52}
53
54impl SessionStatus {
55 pub fn as_str(&self) -> &'static str {
57 match self {
58 Self::Connecting => "connecting",
59 Self::Connected => "connected",
60 Self::Reconnecting => "reconnecting",
61 Self::Disconnected => "disconnected",
62 }
63 }
64}
65
66impl FromStr for SessionStatus {
67 type Err = std::convert::Infallible;
68
69 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
70 Ok(match s.to_lowercase().as_str() {
71 "connecting" => Self::Connecting,
72 "connected" => Self::Connected,
73 "reconnecting" => Self::Reconnecting,
74 "disconnected" => Self::Disconnected,
75 _ => Self::Disconnected,
76 })
77 }
78}
79
80#[derive(Debug, Clone)]
82pub struct SessionInfo {
83 pub id: SessionId,
85 pub node_id: NodeId,
87 pub user_id: Option<String>,
89 pub status: SessionStatus,
91 pub subscription_count: u32,
93 pub created_at: DateTime<Utc>,
95 pub last_active_at: DateTime<Utc>,
97 pub client_ip: Option<String>,
99 pub user_agent: Option<String>,
101}
102
103impl SessionInfo {
104 pub fn new(node_id: NodeId) -> Self {
106 let now = Utc::now();
107 Self {
108 id: SessionId::new(),
109 node_id,
110 user_id: None,
111 status: SessionStatus::Connecting,
112 subscription_count: 0,
113 created_at: now,
114 last_active_at: now,
115 client_ip: None,
116 user_agent: None,
117 }
118 }
119
120 pub fn with_user_id(mut self, user_id: impl Into<String>) -> Self {
122 self.user_id = Some(user_id.into());
123 self
124 }
125
126 pub fn with_client_info(
128 mut self,
129 client_ip: Option<String>,
130 user_agent: Option<String>,
131 ) -> Self {
132 self.client_ip = client_ip;
133 self.user_agent = user_agent;
134 self
135 }
136
137 pub fn connect(&mut self) {
139 self.status = SessionStatus::Connected;
140 self.last_active_at = Utc::now();
141 }
142
143 pub fn disconnect(&mut self) {
145 self.status = SessionStatus::Disconnected;
146 self.last_active_at = Utc::now();
147 }
148
149 pub fn reconnecting(&mut self) {
151 self.status = SessionStatus::Reconnecting;
152 self.last_active_at = Utc::now();
153 }
154
155 pub fn touch(&mut self) {
157 self.last_active_at = Utc::now();
158 }
159
160 pub fn is_connected(&self) -> bool {
162 matches!(self.status, SessionStatus::Connected)
163 }
164
165 pub fn is_authenticated(&self) -> bool {
167 self.user_id.is_some()
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn test_session_id_generation() {
177 let id1 = SessionId::new();
178 let id2 = SessionId::new();
179 assert_ne!(id1, id2);
180 }
181
182 #[test]
183 fn test_session_status_conversion() {
184 assert_eq!(
185 "connected".parse::<SessionStatus>(),
186 Ok(SessionStatus::Connected)
187 );
188 assert_eq!(
189 "disconnected".parse::<SessionStatus>(),
190 Ok(SessionStatus::Disconnected)
191 );
192 assert_eq!(SessionStatus::Connected.as_str(), "connected");
193 }
194
195 #[test]
196 fn test_session_info_creation() {
197 let node_id = NodeId::new();
198 let session = SessionInfo::new(node_id);
199
200 assert_eq!(session.status, SessionStatus::Connecting);
201 assert!(!session.is_connected());
202 assert!(!session.is_authenticated());
203 }
204
205 #[test]
206 fn test_session_lifecycle() {
207 let node_id = NodeId::new();
208 let mut session = SessionInfo::new(node_id);
209
210 session.connect();
212 assert!(session.is_connected());
213
214 session = session.with_user_id("user123");
216 assert!(session.is_authenticated());
217
218 session.disconnect();
220 assert!(!session.is_connected());
221 }
222}