koprogo_api/domain/entities/
user.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4use validator::Validate;
5
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
7pub enum UserRole {
8    SuperAdmin,
9    Syndic,
10    Accountant,
11    Owner,
12}
13
14impl std::fmt::Display for UserRole {
15    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16        match self {
17            UserRole::SuperAdmin => write!(f, "superadmin"),
18            UserRole::Syndic => write!(f, "syndic"),
19            UserRole::Accountant => write!(f, "accountant"),
20            UserRole::Owner => write!(f, "owner"),
21        }
22    }
23}
24
25impl std::str::FromStr for UserRole {
26    type Err = String;
27
28    fn from_str(s: &str) -> Result<Self, Self::Err> {
29        match s.to_lowercase().as_str() {
30            "superadmin" => Ok(UserRole::SuperAdmin),
31            "syndic" => Ok(UserRole::Syndic),
32            "accountant" => Ok(UserRole::Accountant),
33            "owner" => Ok(UserRole::Owner),
34            _ => Err(format!("Invalid user role: {}", s)),
35        }
36    }
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
40pub struct User {
41    pub id: Uuid,
42
43    #[validate(email(message = "Email must be valid"))]
44    pub email: String,
45
46    #[serde(skip_serializing)]
47    pub password_hash: String,
48
49    #[validate(length(min = 2, message = "First name must be at least 2 characters"))]
50    pub first_name: String,
51
52    #[validate(length(min = 2, message = "Last name must be at least 2 characters"))]
53    pub last_name: String,
54
55    pub role: UserRole,
56
57    pub organization_id: Option<Uuid>,
58
59    pub is_active: bool,
60
61    pub created_at: DateTime<Utc>,
62    pub updated_at: DateTime<Utc>,
63}
64
65impl User {
66    pub fn new(
67        email: String,
68        password_hash: String,
69        first_name: String,
70        last_name: String,
71        role: UserRole,
72        organization_id: Option<Uuid>,
73    ) -> Result<Self, String> {
74        let user = Self {
75            id: Uuid::new_v4(),
76            email: email.to_lowercase().trim().to_string(),
77            password_hash,
78            first_name: first_name.trim().to_string(),
79            last_name: last_name.trim().to_string(),
80            role,
81            organization_id,
82            is_active: true,
83            created_at: Utc::now(),
84            updated_at: Utc::now(),
85        };
86
87        user.validate()
88            .map_err(|e| format!("Validation error: {}", e))?;
89
90        Ok(user)
91    }
92
93    pub fn full_name(&self) -> String {
94        format!("{} {}", self.first_name, self.last_name)
95    }
96
97    pub fn update_profile(&mut self, first_name: String, last_name: String) -> Result<(), String> {
98        self.first_name = first_name.trim().to_string();
99        self.last_name = last_name.trim().to_string();
100        self.updated_at = Utc::now();
101
102        self.validate()
103            .map_err(|e| format!("Validation error: {}", e))?;
104
105        Ok(())
106    }
107
108    pub fn deactivate(&mut self) {
109        self.is_active = false;
110        self.updated_at = Utc::now();
111    }
112
113    pub fn activate(&mut self) {
114        self.is_active = true;
115        self.updated_at = Utc::now();
116    }
117
118    pub fn can_access_building(&self, building_org_id: Option<Uuid>) -> bool {
119        match self.role {
120            UserRole::SuperAdmin => true,
121            _ => self.organization_id == building_org_id,
122        }
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    #[test]
131    fn test_create_user_success() {
132        let user = User::new(
133            "test@example.com".to_string(),
134            "hashed_password".to_string(),
135            "John".to_string(),
136            "Doe".to_string(),
137            UserRole::Syndic,
138            Some(Uuid::new_v4()),
139        );
140
141        assert!(user.is_ok());
142        let user = user.unwrap();
143        assert_eq!(user.email, "test@example.com");
144        assert_eq!(user.full_name(), "John Doe");
145        assert!(user.is_active);
146    }
147
148    #[test]
149    fn test_create_user_invalid_email() {
150        let user = User::new(
151            "invalid-email".to_string(),
152            "hashed_password".to_string(),
153            "John".to_string(),
154            "Doe".to_string(),
155            UserRole::Syndic,
156            None,
157        );
158
159        assert!(user.is_err());
160    }
161
162    #[test]
163    fn test_update_profile() {
164        let mut user = User::new(
165            "test@example.com".to_string(),
166            "hashed_password".to_string(),
167            "John".to_string(),
168            "Doe".to_string(),
169            UserRole::Syndic,
170            None,
171        )
172        .unwrap();
173
174        let result = user.update_profile("Jane".to_string(), "Smith".to_string());
175        assert!(result.is_ok());
176        assert_eq!(user.full_name(), "Jane Smith");
177    }
178
179    #[test]
180    fn test_deactivate_user() {
181        let mut user = User::new(
182            "test@example.com".to_string(),
183            "hashed_password".to_string(),
184            "John".to_string(),
185            "Doe".to_string(),
186            UserRole::Syndic,
187            None,
188        )
189        .unwrap();
190
191        user.deactivate();
192        assert!(!user.is_active);
193    }
194
195    #[test]
196    fn test_superadmin_can_access_all_buildings() {
197        let user = User::new(
198            "admin@example.com".to_string(),
199            "hashed_password".to_string(),
200            "Admin".to_string(),
201            "User".to_string(),
202            UserRole::SuperAdmin,
203            None,
204        )
205        .unwrap();
206
207        assert!(user.can_access_building(Some(Uuid::new_v4())));
208        assert!(user.can_access_building(None));
209    }
210
211    #[test]
212    fn test_regular_user_access_control() {
213        let org_id = Uuid::new_v4();
214        let user = User::new(
215            "syndic@example.com".to_string(),
216            "hashed_password".to_string(),
217            "John".to_string(),
218            "Syndic".to_string(),
219            UserRole::Syndic,
220            Some(org_id),
221        )
222        .unwrap();
223
224        assert!(user.can_access_building(Some(org_id)));
225        assert!(!user.can_access_building(Some(Uuid::new_v4())));
226    }
227}