1use crate::base::entity::node::Node;
13use chrono::Utc;
14use serde::{Deserialize, Serialize};
15use std::hash::{Hash, Hasher};
16use uuid::Uuid;
17
18#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
20pub struct Email {
21 value: String,
22}
23
24impl Email {
25 pub fn new(email: String) -> Result<Self, UserError> {
27 if Self::is_valid(&email) {
28 Ok(Self {
29 value: email.to_lowercase(),
30 })
31 } else {
32 Err(UserError::InvalidEmail(email))
33 }
34 }
35
36 fn is_valid(email: &str) -> bool {
38 use regex::Regex;
39
40 let email_regex = Regex::new(
41 r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"
42 ).unwrap();
43
44 email_regex.is_match(email) && email.len() <= 254
45 }
46
47 pub fn value(&self) -> &str {
49 &self.value
50 }
51
52 pub fn domain(&self) -> Option<&str> {
54 self.value.split('@').nth(1)
55 }
56
57 pub fn local_part(&self) -> Option<&str> {
59 self.value.split('@').next()
60 }
61}
62
63impl std::fmt::Display for Email {
64 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65 write!(f, "{}", self.value)
66 }
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
71#[serde(rename_all = "lowercase")]
72pub enum UserRole {
73 Admin,
75 #[default]
77 User,
78}
79
80impl UserRole {
81 pub fn as_str(&self) -> &'static str {
83 match self {
84 UserRole::Admin => "admin",
85 UserRole::User => "user",
86 }
87 }
88
89 pub fn from_str_lossy(value: &str) -> Self {
92 match value.trim().to_lowercase().as_str() {
93 "admin" => UserRole::Admin,
94 _ => UserRole::User,
95 }
96 }
97}
98
99impl std::str::FromStr for UserRole {
100 type Err = UserError;
101
102 fn from_str(value: &str) -> Result<Self, Self::Err> {
103 match value.trim().to_lowercase().as_str() {
104 "admin" => Ok(UserRole::Admin),
105 "user" => Ok(UserRole::User),
106 other => Err(UserError::InvalidRole(other.to_string())),
107 }
108 }
109}
110
111impl std::fmt::Display for UserRole {
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113 write!(f, "{}", self.as_str())
114 }
115}
116
117#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
119pub struct UserData {
120 pub username: String,
121 pub email: Email,
122 pub password_hash: String,
123 pub is_active: bool,
124 pub is_verified: bool,
125 #[serde(default)]
126 pub role: UserRole,
127 pub profile: UserProfile,
128}
129
130#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
131pub struct UserProfile {
132 pub first_name: Option<String>,
133 pub last_name: Option<String>,
134 pub bio: Option<String>,
135 pub avatar_url: Option<String>,
136 pub timezone: Option<String>,
137 pub locale: Option<String>,
138}
139
140impl Default for UserProfile {
141 fn default() -> Self {
142 Self {
143 first_name: None,
144 last_name: None,
145 bio: None,
146 avatar_url: None,
147 timezone: Some("UTC".to_string()),
148 locale: Some("en-US".to_string()),
149 }
150 }
151}
152
153impl Hash for UserData {
154 fn hash<H: Hasher>(&self, state: &mut H) {
155 self.username.hash(state);
156 self.email.hash(state);
157 self.password_hash.hash(state);
158 self.is_active.hash(state);
159 self.is_verified.hash(state);
160 self.role.hash(state);
161 }
162}
163
164pub type User = Node<UserData>;
166
167impl User {
168 pub fn new_user(
170 username: String,
171 email: Email,
172 password_hash: String,
173 profile: Option<UserProfile>,
174 ) -> Self {
175 let user_data = UserData {
176 username,
177 email,
178 password_hash,
179 is_active: true,
180 is_verified: false,
181 role: UserRole::default(),
182 profile: profile.unwrap_or_default(),
183 };
184
185 let name = Some(format!("User: {}", user_data.username.clone()));
186 Node::new(user_data, name)
187 }
188
189 pub fn username(&self) -> &str {
191 &self.node.username
192 }
193
194 pub fn email(&self) -> &Email {
196 &self.node.email
197 }
198
199 pub fn password_hash(&self) -> &str {
201 &self.node.password_hash
202 }
203
204 pub fn is_active(&self) -> bool {
206 self.node.is_active
207 }
208
209 pub fn is_verified(&self) -> bool {
211 self.node.is_verified
212 }
213
214 pub fn profile(&self) -> &UserProfile {
216 &self.node.profile
217 }
218
219 pub fn role(&self) -> UserRole {
221 self.node.role
222 }
223
224 pub fn set_role(&mut self, role: UserRole) {
226 self.node.role = role;
227 self.modified = Utc::now();
228 }
229
230 pub fn update_username(&mut self, new_username: String) -> Result<(), UserError> {
232 if new_username.trim().is_empty() {
233 return Err(UserError::InvalidUsername(
234 "Username cannot be empty".to_string(),
235 ));
236 }
237 if new_username.len() < 3 {
238 return Err(UserError::InvalidUsername(
239 "Username must be at least 3 characters".to_string(),
240 ));
241 }
242 if new_username.len() > 50 {
243 return Err(UserError::InvalidUsername(
244 "Username cannot exceed 50 characters".to_string(),
245 ));
246 }
247
248 self.node.username = new_username;
249 self.modified = Utc::now(); Ok(())
251 }
252
253 pub fn update_email(&mut self, new_email: Email) -> Result<(), UserError> {
255 self.node.email = new_email;
256 self.node.is_verified = false; self.modified = Utc::now(); Ok(())
259 }
260
261 pub fn update_password_hash(&mut self, new_password_hash: String) {
263 self.node.password_hash = new_password_hash;
264 self.modified = Utc::now(); }
266
267 pub fn activate(&mut self) {
269 self.node.is_active = true;
270 self.modified = Utc::now(); }
272
273 pub fn deactivate(&mut self) {
275 self.node.is_active = false;
276 self.modified = Utc::now(); }
278
279 pub fn verify(&mut self) {
281 self.node.is_verified = true;
282 self.modified = Utc::now(); }
284
285 pub fn update_profile(&mut self, profile: UserProfile) {
287 self.node.profile = profile;
288 self.modified = Utc::now(); }
290}
291
292#[derive(Debug, thiserror::Error)]
294pub enum UserError {
295 #[error("Invalid email format: {0}")]
296 InvalidEmail(String),
297 #[error("Invalid username: {0}")]
298 InvalidUsername(String),
299 #[error("Invalid role: {0}")]
300 InvalidRole(String),
301 #[error("User not found with ID: {0}")]
302 UserNotFound(Uuid),
303 #[error("User not found with email: {0}")]
304 UserNotFoundByEmail(String),
305 #[error("Email already exists: {0}")]
306 EmailAlreadyExists(String),
307 #[error("Username already exists: {0}")]
308 UsernameAlreadyExists(String),
309 #[error("Invalid password: {0}")]
310 InvalidPassword(String),
311 #[error("Authentication failed")]
312 AuthenticationFailed,
313 #[error("User is not active")]
314 UserNotActive,
315 #[error("User is not verified")]
316 UserNotVerified,
317 #[error("Repository error: {0}")]
318 RepositoryError(String),
319 #[error("Hash error: {0}")]
320 HashError(String),
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326
327 #[test]
328 fn test_email_validation() {
329 assert!(Email::new("test@example.com".to_string()).is_ok());
331 assert!(Email::new("user.name+tag@domain.co.uk".to_string()).is_ok());
332 assert!(Email::new("123@test.org".to_string()).is_ok());
333
334 assert!(Email::new("invalid-email".to_string()).is_err());
336 assert!(Email::new("@domain.com".to_string()).is_err());
337 assert!(Email::new("user@".to_string()).is_err());
338 assert!(Email::new("".to_string()).is_err());
339 }
340
341 #[test]
342 fn test_email_methods() {
343 let email = Email::new("Test.User@Example.COM".to_string()).unwrap();
344
345 assert_eq!(email.value(), "test.user@example.com");
347 assert_eq!(email.domain(), Some("example.com"));
348 assert_eq!(email.local_part(), Some("test.user"));
349 assert_eq!(email.to_string(), "test.user@example.com");
350 }
351
352 #[test]
353 fn test_user_creation() {
354 let email = Email::new("user@example.com".to_string()).unwrap();
355 let user = User::new_user(
356 "testuser".to_string(),
357 email.clone(),
358 "password_hash".to_string(),
359 None,
360 );
361
362 assert_eq!(user.username(), "testuser");
363 assert_eq!(user.email(), &email);
364 assert_eq!(user.password_hash(), "password_hash");
365 assert!(user.is_active());
366 assert!(!user.is_verified());
367 }
368
369 #[test]
370 fn test_user_updates() {
371 let email = Email::new("user@example.com".to_string()).unwrap();
372 let mut user = User::new_user(
373 "testuser".to_string(),
374 email,
375 "password_hash".to_string(),
376 None,
377 );
378
379 let initial_modified = user.modified;
380
381 std::thread::sleep(std::time::Duration::from_millis(1));
383
384 assert!(user.update_username("newusername".to_string()).is_ok());
386 assert_eq!(user.username(), "newusername");
387 assert!(user.modified > initial_modified);
388
389 assert!(user.update_username("a".to_string()).is_err());
391 assert!(user.update_username("".to_string()).is_err());
392
393 let new_email = Email::new("new@example.com".to_string()).unwrap();
395 let before_email_update = user.modified;
396 std::thread::sleep(std::time::Duration::from_millis(1));
397
398 assert!(user.update_email(new_email.clone()).is_ok());
399 assert_eq!(user.email(), &new_email);
400 assert!(!user.is_verified()); assert!(user.modified > before_email_update);
402
403 let before_deactivate = user.modified;
405 std::thread::sleep(std::time::Duration::from_millis(1));
406
407 user.deactivate();
408 assert!(!user.is_active());
409 assert!(user.modified > before_deactivate);
410
411 let before_activate = user.modified;
412 std::thread::sleep(std::time::Duration::from_millis(1));
413
414 user.activate();
415 assert!(user.is_active());
416 assert!(user.modified > before_activate);
417
418 let before_verify = user.modified;
420 std::thread::sleep(std::time::Duration::from_millis(1));
421
422 user.verify();
423 assert!(user.is_verified());
424 assert!(user.modified > before_verify);
425 }
426
427 #[test]
428 fn test_user_profile_update() {
429 let email = Email::new("user@example.com".to_string()).unwrap();
430 let mut user = User::new_user(
431 "testuser".to_string(),
432 email,
433 "password_hash".to_string(),
434 None,
435 );
436
437 let new_profile = UserProfile {
438 first_name: Some("John".to_string()),
439 last_name: Some("Doe".to_string()),
440 bio: Some("Software developer".to_string()),
441 avatar_url: Some("https://example.com/avatar.jpg".to_string()),
442 timezone: Some("America/New_York".to_string()),
443 locale: Some("en-US".to_string()),
444 };
445
446 let before_update = user.modified;
447 std::thread::sleep(std::time::Duration::from_millis(1));
448
449 user.update_profile(new_profile.clone());
450 assert_eq!(user.profile(), &new_profile);
451 assert!(user.modified > before_update);
452 }
453
454 #[test]
455 fn test_username_validation() {
456 let email = Email::new("user@example.com".to_string()).unwrap();
457 let mut user = User::new_user(
458 "testuser".to_string(),
459 email,
460 "password_hash".to_string(),
461 None,
462 );
463
464 assert!(user.update_username("validuser".to_string()).is_ok());
466 assert!(user.update_username("user_123".to_string()).is_ok());
467 assert!(user.update_username("test-user".to_string()).is_ok());
468
469 assert!(user.update_username("".to_string()).is_err());
471 assert!(user.update_username("ab".to_string()).is_err());
472
473 let long_username = "a".repeat(51);
475 assert!(user.update_username(long_username).is_err());
476 }
477
478 #[test]
479 fn test_user_versioning() {
480 let email = Email::new("user@example.com".to_string()).unwrap();
481 let user = User::new_user(
482 "testuser".to_string(),
483 email,
484 "password_hash".to_string(),
485 None,
486 );
487
488 assert!(user.is_versioning_enabled());
490
491 assert!(!user.uuid.is_nil());
493
494 assert_eq!(user.created, user.modified);
496 }
497
498 #[test]
499 fn test_user_serialization() {
500 let email = Email::new("user@example.com".to_string()).unwrap();
501 let user = User::new_user(
502 "testuser".to_string(),
503 email,
504 "password_hash".to_string(),
505 Some(UserProfile {
506 first_name: Some("Test".to_string()),
507 last_name: Some("User".to_string()),
508 bio: None,
509 avatar_url: None,
510 timezone: Some("UTC".to_string()),
511 locale: Some("en-US".to_string()),
512 }),
513 );
514
515 let serialized = serde_json::to_string(&user).unwrap();
517 assert!(!serialized.is_empty());
518
519 let deserialized: User = serde_json::from_str(&serialized).unwrap();
521 assert_eq!(user.uuid, deserialized.uuid);
522 assert_eq!(user.node.username, deserialized.node.username);
523 assert_eq!(user.node.email.value(), deserialized.node.email.value());
524 }
525
526 #[test]
527 fn test_user_role_string_round_trip() {
528 use std::str::FromStr;
529
530 assert_eq!(UserRole::Admin.as_str(), "admin");
531 assert_eq!(UserRole::User.as_str(), "user");
532 assert_eq!(UserRole::from_str("admin").unwrap(), UserRole::Admin);
533 assert_eq!(UserRole::from_str(" USER ").unwrap(), UserRole::User);
534 assert!(UserRole::from_str("superuser").is_err());
535
536 assert_eq!(UserRole::from_str_lossy("admin"), UserRole::Admin);
538 assert_eq!(UserRole::from_str_lossy("nonsense"), UserRole::User);
539 }
540
541 #[test]
542 fn test_user_role_default_and_accessors() {
543 let email = Email::new("user@example.com".to_string()).unwrap();
544 let mut user = User::new_user(
545 "testuser".to_string(),
546 email,
547 "password_hash".to_string(),
548 None,
549 );
550
551 assert_eq!(user.role(), UserRole::User);
553
554 let before = user.modified;
555 std::thread::sleep(std::time::Duration::from_millis(1));
556 user.set_role(UserRole::Admin);
557 assert_eq!(user.role(), UserRole::Admin);
558 assert!(user.modified > before);
559 }
560}