1use gestura_core_config::AppConfig;
5use gestura_core_foundation::AppError;
6use std::collections::HashMap;
7use std::path::PathBuf;
8use std::sync::Arc;
9use tokio::sync::RwLock;
10
11#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
13pub enum DataCategory {
14 PersonalIdentifiers,
15 VoiceRecordings,
16 BiometricData,
17 DeviceData,
18 UsageAnalytics,
19 ConfigurationData,
20 LogData,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
25pub enum ConsentStatus {
26 Granted,
27 Denied,
28 Withdrawn,
29 Pending,
30}
31
32#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
34pub struct ConsentRecord {
35 pub user_id: String,
36 pub category: DataCategory,
37 pub status: ConsentStatus,
38 pub granted_at: Option<chrono::DateTime<chrono::Utc>>,
39 pub withdrawn_at: Option<chrono::DateTime<chrono::Utc>>,
40 pub purpose: String,
41 pub legal_basis: String,
42}
43
44#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
46pub struct DataAuditEntry {
47 pub timestamp: chrono::DateTime<chrono::Utc>,
48 pub user_id: String,
49 pub operation: DataOperation,
50 pub category: DataCategory,
51 pub details: String,
52 pub legal_basis: String,
53}
54
55#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
57pub enum DataOperation {
58 Collect,
59 Process,
60 Store,
61 Access,
62 Modify,
63 Delete,
64 Export,
65 Share,
66}
67
68pub struct GdprManager {
70 consent_records: Arc<RwLock<HashMap<String, Vec<ConsentRecord>>>>,
71 audit_trail: Arc<RwLock<Vec<DataAuditEntry>>>,
72 data_locations: Arc<RwLock<HashMap<DataCategory, Vec<PathBuf>>>>,
73 max_audit_entries: usize,
74}
75
76impl GdprManager {
77 pub fn new(max_audit_entries: usize) -> Self {
79 Self {
80 consent_records: Arc::new(RwLock::new(HashMap::new())),
81 audit_trail: Arc::new(RwLock::new(Vec::new())),
82 data_locations: Arc::new(RwLock::new(HashMap::new())),
83 max_audit_entries,
84 }
85 }
86
87 pub async fn register_consent(
89 &self,
90 user_id: String,
91 category: DataCategory,
92 purpose: String,
93 legal_basis: String,
94 ) -> Result<(), AppError> {
95 let consent = ConsentRecord {
96 user_id: user_id.clone(),
97 category: category.clone(),
98 status: ConsentStatus::Granted,
99 granted_at: Some(chrono::Utc::now()),
100 withdrawn_at: None,
101 purpose,
102 legal_basis: legal_basis.clone(),
103 };
104
105 let mut consents = self.consent_records.write().await;
106 consents
107 .entry(user_id.clone())
108 .or_insert_with(Vec::new)
109 .push(consent);
110
111 self.audit_data_operation(
113 user_id.clone(),
114 DataOperation::Collect,
115 category,
116 "Consent granted".to_string(),
117 legal_basis,
118 )
119 .await;
120
121 tracing::info!("Consent registered for user: {}", user_id);
122 Ok(())
123 }
124
125 pub async fn withdraw_consent(
127 &self,
128 user_id: &str,
129 category: &DataCategory,
130 ) -> Result<(), AppError> {
131 let mut consents = self.consent_records.write().await;
132
133 if let Some(user_consents) = consents.get_mut(user_id) {
134 for consent in user_consents.iter_mut() {
135 if consent.category == *category && consent.status == ConsentStatus::Granted {
136 consent.status = ConsentStatus::Withdrawn;
137 consent.withdrawn_at = Some(chrono::Utc::now());
138
139 self.audit_data_operation(
141 user_id.to_string(),
142 DataOperation::Modify,
143 category.clone(),
144 "Consent withdrawn".to_string(),
145 consent.legal_basis.clone(),
146 )
147 .await;
148
149 tracing::info!(
150 "Consent withdrawn for user: {} category: {:?}",
151 user_id,
152 category
153 );
154 return Ok(());
155 }
156 }
157 }
158
159 Err(AppError::Io(std::io::Error::new(
160 std::io::ErrorKind::NotFound,
161 "Consent record not found",
162 )))
163 }
164
165 pub async fn has_consent(&self, user_id: &str, category: &DataCategory) -> bool {
167 let consents = self.consent_records.read().await;
168
169 if let Some(user_consents) = consents.get(user_id) {
170 user_consents
171 .iter()
172 .any(|c| c.category == *category && c.status == ConsentStatus::Granted)
173 } else {
174 false
175 }
176 }
177
178 pub async fn export_user_data(&self, user_id: &str) -> Result<serde_json::Value, AppError> {
180 self.audit_data_operation(
182 user_id.to_string(),
183 DataOperation::Export,
184 DataCategory::PersonalIdentifiers,
185 "Data export requested".to_string(),
186 "GDPR Article 20".to_string(),
187 )
188 .await;
189
190 let mut export_data = serde_json::Map::new();
191
192 let user_consents: Vec<ConsentRecord> = {
194 let consents = self.consent_records.read().await;
195 consents.get(user_id).cloned().unwrap_or_default()
196 };
197 if !user_consents.is_empty() {
198 export_data.insert(
199 "consents".to_string(),
200 serde_json::to_value(&user_consents)?,
201 );
202 }
203
204 let config = AppConfig::load_from_path(AppConfig::default_path());
206 export_data.insert("configuration".to_string(), serde_json::to_value(&config)?);
207
208 let audit_entries = self.get_user_audit_trail(user_id).await;
210 export_data.insert(
211 "audit_trail".to_string(),
212 serde_json::to_value(&audit_entries)?,
213 );
214
215 let voice_locations: Vec<PathBuf> = {
217 let data_locations = self.data_locations.read().await;
218 data_locations
219 .get(&DataCategory::VoiceRecordings)
220 .cloned()
221 .unwrap_or_default()
222 };
223 if !voice_locations.is_empty() {
224 let voice_metadata: Vec<_> = voice_locations
225 .iter()
226 .map(|path| {
227 serde_json::json!({
228 "path": path.to_string_lossy(),
229 "category": "voice_recording"
230 })
231 })
232 .collect();
233 export_data.insert(
234 "voice_data_locations".to_string(),
235 serde_json::Value::Array(voice_metadata),
236 );
237 }
238
239 tracing::info!("Data export completed for user: {}", user_id);
240 Ok(serde_json::Value::Object(export_data))
241 }
242
243 pub async fn register_data_location(&self, category: DataCategory, path: PathBuf) {
245 let mut locations = self.data_locations.write().await;
246 locations
247 .entry(category)
248 .or_insert_with(Vec::new)
249 .push(path);
250 }
251
252 pub async fn audit_data_operation(
254 &self,
255 user_id: String,
256 operation: DataOperation,
257 category: DataCategory,
258 details: String,
259 legal_basis: String,
260 ) {
261 let entry = DataAuditEntry {
262 timestamp: chrono::Utc::now(),
263 user_id,
264 operation,
265 category,
266 details,
267 legal_basis,
268 };
269
270 let mut audit_trail = self.audit_trail.write().await;
271 audit_trail.push(entry);
272
273 if audit_trail.len() > self.max_audit_entries {
275 audit_trail.remove(0);
276 }
277 }
278
279 pub async fn get_user_audit_trail(&self, user_id: &str) -> Vec<DataAuditEntry> {
281 let audit_trail = self.audit_trail.read().await;
282 audit_trail
283 .iter()
284 .filter(|entry| entry.user_id == user_id)
285 .cloned()
286 .collect()
287 }
288
289 pub async fn get_audit_trail(&self, limit: Option<usize>) -> Vec<DataAuditEntry> {
291 let audit_trail = self.audit_trail.read().await;
292 if let Some(limit) = limit {
293 audit_trail.iter().rev().take(limit).cloned().collect()
294 } else {
295 audit_trail.clone()
296 }
297 }
298
299 pub async fn get_user_consents(&self, user_id: &str) -> Vec<ConsentRecord> {
301 let consents = self.consent_records.read().await;
302 consents.get(user_id).cloned().unwrap_or_default()
303 }
304}
305
306impl GdprManager {
307 pub async fn delete_user_data(
316 &self,
317 user_id: &str,
318 verify: bool,
319 ) -> Result<Vec<String>, AppError> {
320 let mut deleted_items = Vec::new();
321
322 self.audit_data_operation(
324 user_id.to_string(),
325 DataOperation::Delete,
326 DataCategory::PersonalIdentifiers,
327 "Data deletion requested".to_string(),
328 "GDPR Article 17".to_string(),
329 )
330 .await;
331
332 {
334 let mut consents = self.consent_records.write().await;
335 if consents.remove(user_id).is_some() {
336 deleted_items.push("Consent records".to_string());
337 }
338 }
339
340 let candidates: Vec<(DataCategory, PathBuf)> = {
342 let data_locations = self.data_locations.read().await;
343 data_locations
344 .iter()
345 .flat_map(|(category, locations)| {
346 locations
347 .iter()
348 .filter(|p| p.to_string_lossy().contains(user_id))
349 .cloned()
350 .map(|p| (category.clone(), p))
351 .collect::<Vec<_>>()
352 })
353 .collect()
354 };
355
356 for (category, path) in candidates {
357 if verify {
358 deleted_items.push(format!("{category:?}: {}", path.display()));
359 continue;
360 }
361
362 match tokio::fs::remove_file(&path).await {
363 Ok(()) => {
364 deleted_items.push(format!("{category:?}: {}", path.display()));
365 tracing::info!(category = ?category, path = %path.display(), "Deleted user data file");
366 }
367 Err(e) => {
368 tracing::error!(
370 category = ?category,
371 path = %path.display(),
372 error = %e,
373 "Failed to delete user data file"
374 );
375 }
376 }
377 }
378
379 if !verify {
381 let mut audit_trail = self.audit_trail.write().await;
382 for entry in audit_trail.iter_mut() {
383 if entry.user_id == user_id {
384 entry.user_id = "[DELETED]".to_string();
385 }
386 }
387 }
388
389 if verify {
390 tracing::info!(user_id = %user_id, "Data deletion verification completed");
391 } else {
392 tracing::info!(user_id = %user_id, "Data deletion completed");
393 }
394
395 Ok(deleted_items)
396 }
397}
398
399impl GdprManager {
400 pub async fn generate_privacy_report(&self) -> serde_json::Value {
406 let (total_users, total_consents, granted, withdrawn, denied, pending) = {
407 let consents = self.consent_records.read().await;
408 let total_users = consents.len();
409 let total_consents: usize = consents.values().map(|v| v.len()).sum();
410
411 let mut granted = 0usize;
412 let mut withdrawn = 0usize;
413 let mut denied = 0usize;
414 let mut pending = 0usize;
415
416 for c in consents.values().flatten() {
417 match c.status {
418 ConsentStatus::Granted => granted += 1,
419 ConsentStatus::Withdrawn => withdrawn += 1,
420 ConsentStatus::Denied => denied += 1,
421 ConsentStatus::Pending => pending += 1,
422 }
423 }
424
425 (
426 total_users,
427 total_consents,
428 granted,
429 withdrawn,
430 denied,
431 pending,
432 )
433 };
434
435 let total_audit_entries = {
436 let audit_trail = self.audit_trail.read().await;
437 audit_trail.len()
438 };
439
440 let (total_data_locations, categories) = {
441 let data_locations = self.data_locations.read().await;
442 let total_data_locations: usize = data_locations.values().map(|v| v.len()).sum();
443 let categories = data_locations.keys().cloned().collect::<Vec<_>>();
444 (total_data_locations, categories)
445 };
446
447 serde_json::json!({
448 "generated_at": chrono::Utc::now(),
449 "summary": {
450 "total_users": total_users,
451 "total_consents": total_consents,
452 "total_audit_entries": total_audit_entries,
453 "total_data_locations": total_data_locations
454 },
455 "consent_breakdown": {
456 "granted": granted,
457 "withdrawn": withdrawn,
458 "denied": denied,
459 "pending": pending
460 },
461 "data_categories": categories
462 })
463 }
464}
465
466static GDPR_MANAGER: tokio::sync::OnceCell<GdprManager> = tokio::sync::OnceCell::const_new();
468
469pub async fn get_gdpr_manager() -> &'static GdprManager {
471 GDPR_MANAGER
472 .get_or_init(|| async { GdprManager::new(50000) })
473 .await
474}
475
476#[cfg(test)]
477mod tests {
478 use super::*;
479 use chrono::TimeZone;
480
481 fn assert_json_roundtrip<T>(value: &T)
483 where
484 T: serde::Serialize + for<'de> serde::Deserialize<'de>,
485 {
486 let v1 = serde_json::to_value(value).expect("serialize");
487 let decoded: T = serde_json::from_value(v1.clone()).expect("deserialize");
488 let v2 = serde_json::to_value(&decoded).expect("serialize (round-tripped)");
489 assert_eq!(v1, v2);
490 }
491
492 #[tokio::test]
493 async fn test_gdpr_manager_basic_flow() {
494 let manager = GdprManager::new(1000);
495
496 manager
498 .register_consent(
499 "test-user".to_string(),
500 DataCategory::VoiceRecordings,
501 "Voice processing".to_string(),
502 "User consent".to_string(),
503 )
504 .await
505 .unwrap();
506
507 assert!(
508 manager
509 .has_consent("test-user", &DataCategory::VoiceRecordings)
510 .await
511 );
512
513 manager
515 .withdraw_consent("test-user", &DataCategory::VoiceRecordings)
516 .await
517 .unwrap();
518 assert!(
519 !manager
520 .has_consent("test-user", &DataCategory::VoiceRecordings)
521 .await
522 );
523
524 let export = manager.export_user_data("test-user").await.unwrap();
526 assert!(export.get("configuration").is_some());
527
528 let report = manager.generate_privacy_report().await;
530 assert!(report.get("summary").is_some());
531 }
532
533 #[tokio::test]
534 async fn test_delete_user_data_verify_and_delete() {
535 let manager = GdprManager::new(1000);
536 let user_id = "user-to-delete";
537
538 manager
539 .register_consent(
540 user_id.to_string(),
541 DataCategory::PersonalIdentifiers,
542 "Testing".to_string(),
543 "Consent".to_string(),
544 )
545 .await
546 .unwrap();
547
548 let mut tmp_path = std::env::temp_dir();
550 tmp_path.push(format!(
551 "gestura_gdpr_test_{user_id}_{}",
552 uuid::Uuid::new_v4()
553 ));
554 std::fs::write(&tmp_path, b"test").unwrap();
555
556 manager
557 .register_data_location(DataCategory::VoiceRecordings, tmp_path.clone())
558 .await;
559
560 let preview = manager.delete_user_data(user_id, true).await.unwrap();
562 assert!(preview.iter().any(|s| s.contains(user_id)));
563 assert!(tmp_path.exists(), "file should still exist in verify mode");
564
565 let deleted = manager.delete_user_data(user_id, false).await.unwrap();
567 assert!(deleted.iter().any(|s| s.contains(user_id)));
568 assert!(!tmp_path.exists(), "file should be deleted");
569 }
570
571 #[test]
572 fn test_gdpr_models_serde_roundtrip() {
573 assert_json_roundtrip(&DataCategory::VoiceRecordings);
574 assert_json_roundtrip(&ConsentStatus::Granted);
575 assert_json_roundtrip(&DataOperation::Collect);
576
577 let fixed_ts = chrono::Utc
578 .with_ymd_and_hms(2025, 1, 1, 0, 0, 0)
579 .single()
580 .expect("valid timestamp");
581
582 let consent = ConsentRecord {
583 user_id: "user-1".to_string(),
584 category: DataCategory::VoiceRecordings,
585 status: ConsentStatus::Granted,
586 granted_at: Some(fixed_ts),
587 withdrawn_at: None,
588 purpose: "Voice processing".to_string(),
589 legal_basis: "Consent".to_string(),
590 };
591 assert_json_roundtrip(&consent);
592
593 let audit = DataAuditEntry {
594 timestamp: fixed_ts,
595 user_id: "user-1".to_string(),
596 operation: DataOperation::Access,
597 category: DataCategory::VoiceRecordings,
598 details: "Accessed voice data".to_string(),
599 legal_basis: "Consent".to_string(),
600 };
601 assert_json_roundtrip(&audit);
602 }
603
604 #[tokio::test]
605 async fn test_consent_timestamps_invariants() {
606 let manager = GdprManager::new(1000);
607 let user_id = "test-user-invariants";
608
609 manager
610 .register_consent(
611 user_id.to_string(),
612 DataCategory::VoiceRecordings,
613 "Voice processing".to_string(),
614 "Consent".to_string(),
615 )
616 .await
617 .unwrap();
618
619 let consents = manager.get_user_consents(user_id).await;
620 let c = consents
621 .iter()
622 .find(|c| c.category == DataCategory::VoiceRecordings)
623 .unwrap();
624 assert_eq!(c.status, ConsentStatus::Granted);
625 assert!(c.granted_at.is_some());
626 assert!(c.withdrawn_at.is_none());
627
628 manager
629 .withdraw_consent(user_id, &DataCategory::VoiceRecordings)
630 .await
631 .unwrap();
632
633 let consents = manager.get_user_consents(user_id).await;
634 let c = consents
635 .iter()
636 .find(|c| c.category == DataCategory::VoiceRecordings)
637 .unwrap();
638 assert_eq!(c.status, ConsentStatus::Withdrawn);
639 assert!(c.granted_at.is_some());
640 assert!(c.withdrawn_at.is_some());
641 }
642}