Skip to main content

reinhardt_auth/sessions/
models.rs

1//! ORM models for session storage
2//!
3//! This module provides ORM model definitions for database-backed session storage.
4//! The SessionModel can be used with reinhardt-orm to manage sessions in the database.
5//!
6//! ## Features
7//!
8//! - **SessionModel**: ORM model for database session storage
9//! - **Model trait implementation**: Full ORM integration
10//!
11//! ## Example
12//!
13//! ```rust
14//! use reinhardt_auth::sessions::models::SessionModel;
15//! use chrono::Utc;
16//! use serde_json::json;
17//!
18//! let session = SessionModel::new(
19//!     "session_key_123".to_string(),
20//!     json!({"user_id": 42}),
21//!     3600, // TTL in seconds
22//! );
23//!
24//! assert_eq!(session.session_key(), "session_key_123");
25//! assert!(session.expire_date() > &Utc::now());
26//! ```
27
28use chrono::{DateTime, Duration, Utc};
29use serde::{Deserialize, Serialize};
30
31#[cfg(feature = "database")]
32use reinhardt_db::orm::Model;
33
34/// Session model for database storage
35///
36/// Represents a session stored in the database with expiration information.
37/// This model implements the ORM Model trait when the `database` feature is enabled.
38///
39/// ## Example
40///
41/// ```rust
42/// use reinhardt_auth::sessions::models::SessionModel;
43/// use chrono::Utc;
44/// use serde_json::json;
45///
46/// // Create a new session model
47/// let session = SessionModel::new(
48///     "abc123".to_string(),
49///     json!({"user_id": 42, "authenticated": true}),
50///     3600,
51/// );
52///
53/// assert_eq!(session.session_key(), "abc123");
54/// assert!(session.is_valid());
55/// ```
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct SessionModel {
58	/// Unique session key (primary key)
59	session_key: String,
60	/// Session data stored as JSON
61	session_data: serde_json::Value,
62	/// Session expiration timestamp
63	expire_date: DateTime<Utc>,
64}
65
66impl SessionModel {
67	/// Create a new session model
68	///
69	/// # Arguments
70	///
71	/// * `session_key` - Unique session identifier
72	/// * `session_data` - Session data as JSON value
73	/// * `ttl_seconds` - Time to live in seconds
74	///
75	/// # Example
76	///
77	/// ```rust
78	/// use reinhardt_auth::sessions::models::SessionModel;
79	/// use serde_json::json;
80	///
81	/// let session = SessionModel::new(
82	///     "session_123".to_string(),
83	///     json!({"cart_total": 99.99}),
84	///     7200,
85	/// );
86	/// ```
87	pub fn new(session_key: String, session_data: serde_json::Value, ttl_seconds: i64) -> Self {
88		let expire_date = Utc::now() + Duration::seconds(ttl_seconds);
89		Self {
90			session_key,
91			session_data,
92			expire_date,
93		}
94	}
95
96	/// Create a session model with a specific expiration date
97	///
98	/// # Example
99	///
100	/// ```rust
101	/// use reinhardt_auth::sessions::models::SessionModel;
102	/// use chrono::{Utc, Duration};
103	/// use serde_json::json;
104	///
105	/// let expire_date = Utc::now() + Duration::hours(2);
106	/// let session = SessionModel::with_expire_date(
107	///     "session_xyz".to_string(),
108	///     json!({"preferences": {"theme": "dark"}}),
109	///     expire_date,
110	/// );
111	/// ```
112	pub fn with_expire_date(
113		session_key: String,
114		session_data: serde_json::Value,
115		expire_date: DateTime<Utc>,
116	) -> Self {
117		Self {
118			session_key,
119			session_data,
120			expire_date,
121		}
122	}
123
124	/// Get the session key
125	///
126	/// # Example
127	///
128	/// ```rust
129	/// use reinhardt_auth::sessions::models::SessionModel;
130	/// use serde_json::json;
131	///
132	/// let session = SessionModel::new("key_123".to_string(), json!({}), 3600);
133	/// assert_eq!(session.session_key(), "key_123");
134	/// ```
135	pub fn session_key(&self) -> &str {
136		&self.session_key
137	}
138
139	/// Get the session data
140	///
141	/// # Example
142	///
143	/// ```rust
144	/// use reinhardt_auth::sessions::models::SessionModel;
145	/// use serde_json::json;
146	///
147	/// let data = json!({"user": "alice"});
148	/// let session = SessionModel::new("key".to_string(), data.clone(), 3600);
149	/// assert_eq!(session.session_data(), &data);
150	/// ```
151	pub fn session_data(&self) -> &serde_json::Value {
152		&self.session_data
153	}
154
155	/// Get the expiration date
156	///
157	/// # Example
158	///
159	/// ```rust
160	/// use reinhardt_auth::sessions::models::SessionModel;
161	/// use chrono::Utc;
162	/// use serde_json::json;
163	///
164	/// let session = SessionModel::new("key".to_string(), json!({}), 3600);
165	/// assert!(session.expire_date() > &Utc::now());
166	/// ```
167	pub fn expire_date(&self) -> &DateTime<Utc> {
168		&self.expire_date
169	}
170
171	/// Set the session data
172	///
173	/// # Example
174	///
175	/// ```rust
176	/// use reinhardt_auth::sessions::models::SessionModel;
177	/// use serde_json::json;
178	///
179	/// let mut session = SessionModel::new("key".to_string(), json!({}), 3600);
180	/// session.set_session_data(json!({"updated": true}));
181	/// assert_eq!(session.session_data()["updated"], true);
182	/// ```
183	pub fn set_session_data(&mut self, data: serde_json::Value) {
184		self.session_data = data;
185	}
186
187	/// Set the expiration date
188	///
189	/// # Example
190	///
191	/// ```rust
192	/// use reinhardt_auth::sessions::models::SessionModel;
193	/// use chrono::{Utc, Duration};
194	/// use serde_json::json;
195	///
196	/// let mut session = SessionModel::new("key".to_string(), json!({}), 3600);
197	/// let new_expire = Utc::now() + Duration::hours(24);
198	/// session.set_expire_date(new_expire);
199	/// ```
200	pub fn set_expire_date(&mut self, expire_date: DateTime<Utc>) {
201		self.expire_date = expire_date;
202	}
203
204	/// Check if the session is still valid (not expired)
205	///
206	/// # Example
207	///
208	/// ```rust
209	/// use reinhardt_auth::sessions::models::SessionModel;
210	/// use serde_json::json;
211	///
212	/// let session = SessionModel::new("key".to_string(), json!({}), 3600);
213	/// assert!(session.is_valid());
214	///
215	/// let expired = SessionModel::new("old".to_string(), json!({}), -100);
216	/// assert!(!expired.is_valid());
217	/// ```
218	pub fn is_valid(&self) -> bool {
219		self.expire_date > Utc::now()
220	}
221
222	/// Check if the session has expired
223	///
224	/// # Example
225	///
226	/// ```rust
227	/// use reinhardt_auth::sessions::models::SessionModel;
228	/// use serde_json::json;
229	///
230	/// let session = SessionModel::new("key".to_string(), json!({}), 3600);
231	/// assert!(!session.is_expired());
232	///
233	/// let expired = SessionModel::new("old".to_string(), json!({}), -100);
234	/// assert!(expired.is_expired());
235	/// ```
236	pub fn is_expired(&self) -> bool {
237		self.expire_date <= Utc::now()
238	}
239
240	/// Extend the session expiration by the given number of seconds
241	///
242	/// # Example
243	///
244	/// ```rust
245	/// use reinhardt_auth::sessions::models::SessionModel;
246	/// use chrono::Utc;
247	/// use serde_json::json;
248	///
249	/// let mut session = SessionModel::new("key".to_string(), json!({}), 3600);
250	/// let old_expire = *session.expire_date();
251	///
252	/// session.extend(1800);
253	/// assert!(session.expire_date() > &old_expire);
254	/// ```
255	pub fn extend(&mut self, seconds: i64) {
256		self.expire_date += Duration::seconds(seconds);
257	}
258
259	/// Refresh the session with a new TTL from now
260	///
261	/// # Example
262	///
263	/// ```rust
264	/// use reinhardt_auth::sessions::models::SessionModel;
265	/// use chrono::Utc;
266	/// use serde_json::json;
267	///
268	/// let mut session = SessionModel::new("key".to_string(), json!({}), 3600);
269	/// session.refresh(7200);
270	///
271	/// // New expiration should be approximately 7200 seconds from now
272	/// let expected = Utc::now() + chrono::Duration::seconds(7200);
273	/// assert!(session.expire_date().signed_duration_since(expected).num_seconds().abs() < 2);
274	/// ```
275	pub fn refresh(&mut self, ttl_seconds: i64) {
276		self.expire_date = Utc::now() + Duration::seconds(ttl_seconds);
277	}
278}
279
280/// Query field descriptors for the `SessionModel`.
281#[cfg(feature = "database")]
282#[derive(Debug, Clone)]
283pub struct SessionModelFields {
284	/// The session key field.
285	pub session_key: reinhardt_db::orm::query_fields::Field<SessionModel, String>,
286	/// The session data field.
287	pub session_data: reinhardt_db::orm::query_fields::Field<SessionModel, serde_json::Value>,
288	/// The expiration date field.
289	pub expire_date: reinhardt_db::orm::query_fields::Field<SessionModel, DateTime<Utc>>,
290}
291
292#[cfg(feature = "database")]
293impl Default for SessionModelFields {
294	fn default() -> Self {
295		Self::new()
296	}
297}
298
299#[cfg(feature = "database")]
300impl SessionModelFields {
301	/// Creates a new `SessionModelFields` with default field mappings.
302	pub fn new() -> Self {
303		Self {
304			session_key: reinhardt_db::orm::query_fields::Field::new(vec!["session_key"]),
305			session_data: reinhardt_db::orm::query_fields::Field::new(vec!["session_data"]),
306			expire_date: reinhardt_db::orm::query_fields::Field::new(vec!["expire_date"]),
307		}
308	}
309}
310
311#[cfg(feature = "database")]
312impl reinhardt_db::orm::FieldSelector for SessionModelFields {
313	fn with_alias(mut self, alias: &str) -> Self {
314		self.session_key = self.session_key.with_alias(alias);
315		self.session_data = self.session_data.with_alias(alias);
316		self.expire_date = self.expire_date.with_alias(alias);
317		self
318	}
319}
320
321#[cfg(feature = "database")]
322impl Model for SessionModel {
323	type PrimaryKey = String;
324	type Fields = SessionModelFields;
325	type Objects = reinhardt_db::orm::Manager<Self>;
326
327	fn table_name() -> &'static str {
328		"sessions"
329	}
330
331	fn new_fields() -> Self::Fields {
332		SessionModelFields::new()
333	}
334
335	fn primary_key_field() -> &'static str {
336		"session_key"
337	}
338
339	fn primary_key(&self) -> Option<Self::PrimaryKey> {
340		Some(self.session_key.clone())
341	}
342
343	fn set_primary_key(&mut self, value: Self::PrimaryKey) {
344		self.session_key = value;
345	}
346}
347
348#[cfg(test)]
349mod tests {
350	use super::*;
351	use serde_json::json;
352
353	#[test]
354	fn test_session_model_new() {
355		let session = SessionModel::new("test_key".to_string(), json!({"test": "data"}), 3600);
356
357		assert_eq!(session.session_key(), "test_key");
358		assert_eq!(session.session_data(), &json!({"test": "data"}));
359		assert!(session.expire_date() > &Utc::now());
360	}
361
362	#[test]
363	fn test_session_model_with_expire_date() {
364		let expire_date = Utc::now() + Duration::hours(2);
365		let session = SessionModel::with_expire_date(
366			"custom_key".to_string(),
367			json!({"custom": "data"}),
368			expire_date,
369		);
370
371		assert_eq!(session.session_key(), "custom_key");
372		assert_eq!(session.expire_date(), &expire_date);
373	}
374
375	#[test]
376	fn test_session_model_getters() {
377		let data = json!({"user_id": 42});
378		let session = SessionModel::new("key_123".to_string(), data.clone(), 3600);
379
380		assert_eq!(session.session_key(), "key_123");
381		assert_eq!(session.session_data(), &data);
382		assert!(session.expire_date() > &Utc::now());
383	}
384
385	#[test]
386	fn test_session_model_set_session_data() {
387		let mut session = SessionModel::new("key".to_string(), json!({}), 3600);
388
389		let new_data = json!({"updated": true, "count": 42});
390		session.set_session_data(new_data.clone());
391
392		assert_eq!(session.session_data(), &new_data);
393	}
394
395	#[test]
396	fn test_session_model_set_expire_date() {
397		let mut session = SessionModel::new("key".to_string(), json!({}), 3600);
398
399		let new_expire = Utc::now() + Duration::days(7);
400		session.set_expire_date(new_expire);
401
402		assert_eq!(session.expire_date(), &new_expire);
403	}
404
405	#[test]
406	fn test_session_model_is_valid() {
407		let valid_session = SessionModel::new("valid".to_string(), json!({}), 3600);
408		assert!(valid_session.is_valid());
409
410		let expired_session = SessionModel::new("expired".to_string(), json!({}), -100);
411		assert!(!expired_session.is_valid());
412	}
413
414	#[test]
415	fn test_session_model_is_expired() {
416		let active_session = SessionModel::new("active".to_string(), json!({}), 3600);
417		assert!(!active_session.is_expired());
418
419		let expired_session = SessionModel::new("expired".to_string(), json!({}), -100);
420		assert!(expired_session.is_expired());
421	}
422
423	#[test]
424	fn test_session_model_extend() {
425		let mut session = SessionModel::new("key".to_string(), json!({}), 3600);
426		let original_expire = *session.expire_date();
427
428		session.extend(1800);
429
430		let expected_expire = original_expire + Duration::seconds(1800);
431		assert_eq!(session.expire_date(), &expected_expire);
432	}
433
434	#[test]
435	fn test_session_model_refresh() {
436		let mut session = SessionModel::new("key".to_string(), json!({}), 3600);
437
438		// Wait a moment to ensure time difference
439		std::thread::sleep(std::time::Duration::from_millis(10));
440
441		session.refresh(7200);
442
443		// New expiration should be approximately 7200 seconds from now
444		let expected = Utc::now() + Duration::seconds(7200);
445		let diff = session
446			.expire_date()
447			.signed_duration_since(expected)
448			.num_seconds()
449			.abs();
450		assert!(diff < 2);
451	}
452
453	#[test]
454	#[cfg(feature = "database")]
455	fn test_session_model_implements_model_trait() {
456		let session = SessionModel::new("model_key".to_string(), json!({}), 3600);
457
458		assert_eq!(SessionModel::table_name(), "sessions");
459		assert_eq!(SessionModel::primary_key_field(), "session_key");
460		assert_eq!(session.primary_key(), Some("model_key".to_string()));
461	}
462
463	#[test]
464	#[cfg(feature = "database")]
465	fn test_session_model_set_primary_key() {
466		let mut session = SessionModel::new("old_key".to_string(), json!({}), 3600);
467
468		session.set_primary_key("new_key".to_string());
469		assert_eq!(session.session_key(), "new_key");
470		assert_eq!(session.primary_key(), Some("new_key".to_string()));
471	}
472
473	#[test]
474	fn test_session_model_serialization() {
475		let session =
476			SessionModel::new("serialize_test".to_string(), json!({"data": "value"}), 3600);
477
478		// Serialize
479		let serialized = serde_json::to_string(&session).unwrap();
480		assert!(serialized.contains("serialize_test"));
481
482		// Deserialize
483		let deserialized: SessionModel = serde_json::from_str(&serialized).unwrap();
484		assert_eq!(deserialized.session_key(), "serialize_test");
485		assert_eq!(deserialized.session_data(), &json!({"data": "value"}));
486	}
487
488	#[test]
489	fn test_session_model_edge_cases() {
490		// Very short TTL
491		let short_ttl = SessionModel::new("short".to_string(), json!({}), 1);
492		assert!(short_ttl.is_valid());
493
494		// Very long TTL
495		let long_ttl = SessionModel::new("long".to_string(), json!({}), 86400 * 365);
496		assert!(long_ttl.is_valid());
497
498		// Zero TTL (immediately expired)
499		let zero_ttl = SessionModel::new("zero".to_string(), json!({}), 0);
500		assert!(!zero_ttl.is_valid());
501	}
502}