koprogo_api/domain/entities/
gdpr_export.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5/// Complete GDPR data export for a user
6/// Aggregates all personal data for GDPR Article 15 (Right to Access) compliance
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
8pub struct GdprExport {
9    pub export_date: DateTime<Utc>,
10    pub user_data: UserData,
11    pub owner_profiles: Vec<OwnerData>,
12    pub related_data: RelatedData,
13}
14
15/// User account information
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
17pub struct UserData {
18    pub id: Uuid,
19    pub email: String,
20    pub first_name: String,
21    pub last_name: String,
22    pub organization_id: Option<Uuid>,
23    pub is_active: bool,
24    pub is_anonymized: bool,
25    pub created_at: DateTime<Utc>,
26    pub updated_at: DateTime<Utc>,
27}
28
29/// Owner profile information
30#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
31pub struct OwnerData {
32    pub id: Uuid,
33    pub organization_id: Option<Uuid>,
34    pub first_name: String,
35    pub last_name: String,
36    pub email: Option<String>,
37    pub phone: Option<String>,
38    pub address: Option<String>,
39    pub city: Option<String>,
40    pub postal_code: Option<String>,
41    pub country: Option<String>,
42    pub is_anonymized: bool,
43    pub created_at: DateTime<Utc>,
44    pub updated_at: DateTime<Utc>,
45}
46
47/// Related data (units, expenses, documents, meetings)
48#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
49pub struct RelatedData {
50    pub units: Vec<UnitOwnershipData>,
51    pub expenses: Vec<ExpenseData>,
52    pub documents: Vec<DocumentData>,
53    pub meetings: Vec<MeetingData>,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
57pub struct UnitOwnershipData {
58    pub building_name: String,
59    pub building_address: String,
60    pub unit_number: String,
61    pub floor: Option<i32>,
62    pub ownership_percentage: f64,
63    pub start_date: DateTime<Utc>,
64    pub end_date: Option<DateTime<Utc>>,
65    pub is_primary_contact: bool,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
69pub struct ExpenseData {
70    pub description: String,
71    pub amount: f64,
72    pub due_date: DateTime<Utc>,
73    pub paid: bool,
74    pub building_name: String,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
78pub struct DocumentData {
79    pub title: String,
80    pub document_type: String,
81    pub uploaded_at: DateTime<Utc>,
82    pub building_name: Option<String>,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
86pub struct MeetingData {
87    pub title: String,
88    pub meeting_date: DateTime<Utc>,
89    pub agenda: Option<String>,
90    pub building_name: String,
91}
92
93impl GdprExport {
94    /// Create a new GDPR export
95    pub fn new(user_data: UserData) -> Self {
96        Self {
97            export_date: Utc::now(),
98            user_data,
99            owner_profiles: Vec::new(),
100            related_data: RelatedData::default(),
101        }
102    }
103
104    /// Add owner profile to export
105    pub fn add_owner_profile(&mut self, owner: OwnerData) {
106        self.owner_profiles.push(owner);
107    }
108
109    /// Add unit ownership data
110    pub fn add_unit_ownership(&mut self, unit: UnitOwnershipData) {
111        self.related_data.units.push(unit);
112    }
113
114    /// Add expense data
115    pub fn add_expense(&mut self, expense: ExpenseData) {
116        self.related_data.expenses.push(expense);
117    }
118
119    /// Add document data
120    pub fn add_document(&mut self, document: DocumentData) {
121        self.related_data.documents.push(document);
122    }
123
124    /// Add meeting data
125    pub fn add_meeting(&mut self, meeting: MeetingData) {
126        self.related_data.meetings.push(meeting);
127    }
128
129    /// Check if user data is anonymized
130    pub fn is_anonymized(&self) -> bool {
131        self.user_data.is_anonymized
132    }
133
134    /// Get total number of data items
135    pub fn total_items(&self) -> usize {
136        1 // user
137            + self.owner_profiles.len()
138            + self.related_data.units.len()
139            + self.related_data.expenses.len()
140            + self.related_data.documents.len()
141            + self.related_data.meetings.len()
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    fn create_test_user_data() -> UserData {
150        UserData {
151            id: Uuid::new_v4(),
152            email: "test@example.com".to_string(),
153            first_name: "John".to_string(),
154            last_name: "Doe".to_string(),
155            organization_id: Some(Uuid::new_v4()),
156            is_active: true,
157            is_anonymized: false,
158            created_at: Utc::now(),
159            updated_at: Utc::now(),
160        }
161    }
162
163    fn create_test_owner_data() -> OwnerData {
164        OwnerData {
165            id: Uuid::new_v4(),
166            organization_id: Some(Uuid::new_v4()),
167            first_name: "John".to_string(),
168            last_name: "Doe".to_string(),
169            email: Some("john.doe@example.com".to_string()),
170            phone: Some("+1234567890".to_string()),
171            address: Some("123 Main St".to_string()),
172            city: Some("Brussels".to_string()),
173            postal_code: Some("1000".to_string()),
174            country: Some("Belgium".to_string()),
175            is_anonymized: false,
176            created_at: Utc::now(),
177            updated_at: Utc::now(),
178        }
179    }
180
181    #[test]
182    fn test_create_gdpr_export() {
183        let user_data = create_test_user_data();
184        let export = GdprExport::new(user_data.clone());
185
186        assert_eq!(export.user_data, user_data);
187        assert_eq!(export.owner_profiles.len(), 0);
188        assert_eq!(export.related_data.units.len(), 0);
189        assert!(!export.is_anonymized());
190    }
191
192    #[test]
193    fn test_add_owner_profile() {
194        let user_data = create_test_user_data();
195        let mut export = GdprExport::new(user_data);
196        let owner = create_test_owner_data();
197
198        export.add_owner_profile(owner.clone());
199
200        assert_eq!(export.owner_profiles.len(), 1);
201        assert_eq!(export.owner_profiles[0], owner);
202    }
203
204    #[test]
205    fn test_add_unit_ownership() {
206        let user_data = create_test_user_data();
207        let mut export = GdprExport::new(user_data);
208        let unit = UnitOwnershipData {
209            building_name: "Building A".to_string(),
210            building_address: "123 Main St".to_string(),
211            unit_number: "101".to_string(),
212            floor: Some(1),
213            ownership_percentage: 50.0,
214            start_date: Utc::now(),
215            end_date: None,
216            is_primary_contact: true,
217        };
218
219        export.add_unit_ownership(unit.clone());
220
221        assert_eq!(export.related_data.units.len(), 1);
222        assert_eq!(export.related_data.units[0], unit);
223    }
224
225    #[test]
226    fn test_add_expense() {
227        let user_data = create_test_user_data();
228        let mut export = GdprExport::new(user_data);
229        let expense = ExpenseData {
230            description: "Monthly maintenance".to_string(),
231            amount: 100.0,
232            due_date: Utc::now(),
233            paid: true,
234            building_name: "Building A".to_string(),
235        };
236
237        export.add_expense(expense.clone());
238
239        assert_eq!(export.related_data.expenses.len(), 1);
240        assert_eq!(export.related_data.expenses[0], expense);
241    }
242
243    #[test]
244    fn test_add_document() {
245        let user_data = create_test_user_data();
246        let mut export = GdprExport::new(user_data);
247        let document = DocumentData {
248            title: "Meeting Minutes".to_string(),
249            document_type: "PDF".to_string(),
250            uploaded_at: Utc::now(),
251            building_name: Some("Building A".to_string()),
252        };
253
254        export.add_document(document.clone());
255
256        assert_eq!(export.related_data.documents.len(), 1);
257        assert_eq!(export.related_data.documents[0], document);
258    }
259
260    #[test]
261    fn test_add_meeting() {
262        let user_data = create_test_user_data();
263        let mut export = GdprExport::new(user_data);
264        let meeting = MeetingData {
265            title: "Annual General Meeting".to_string(),
266            meeting_date: Utc::now(),
267            agenda: Some("Budget approval".to_string()),
268            building_name: "Building A".to_string(),
269        };
270
271        export.add_meeting(meeting.clone());
272
273        assert_eq!(export.related_data.meetings.len(), 1);
274        assert_eq!(export.related_data.meetings[0], meeting);
275    }
276
277    #[test]
278    fn test_is_anonymized() {
279        let mut user_data = create_test_user_data();
280        user_data.is_anonymized = true;
281        let export = GdprExport::new(user_data);
282
283        assert!(export.is_anonymized());
284    }
285
286    #[test]
287    fn test_total_items() {
288        let user_data = create_test_user_data();
289        let mut export = GdprExport::new(user_data);
290
291        // Initially 1 (user only)
292        assert_eq!(export.total_items(), 1);
293
294        export.add_owner_profile(create_test_owner_data());
295        assert_eq!(export.total_items(), 2);
296
297        export.add_unit_ownership(UnitOwnershipData {
298            building_name: "Building A".to_string(),
299            building_address: "123 Main St".to_string(),
300            unit_number: "101".to_string(),
301            floor: Some(1),
302            ownership_percentage: 50.0,
303            start_date: Utc::now(),
304            end_date: None,
305            is_primary_contact: true,
306        });
307        assert_eq!(export.total_items(), 3);
308    }
309
310    #[test]
311    fn test_serialization() {
312        let user_data = create_test_user_data();
313        let export = GdprExport::new(user_data);
314
315        // Test JSON serialization
316        let json = serde_json::to_string(&export).expect("Should serialize to JSON");
317        assert!(json.contains("export_date"));
318        assert!(json.contains("user_data"));
319        assert!(json.contains("test@example.com"));
320
321        // Test JSON deserialization
322        let deserialized: GdprExport =
323            serde_json::from_str(&json).expect("Should deserialize from JSON");
324        assert_eq!(deserialized.user_data.email, export.user_data.email);
325    }
326}