1use crate::activity::ActivityType;
20use crate::state::AppState;
21use axum::{
22 extract::{Path, State},
23 http::StatusCode,
24 response::IntoResponse,
25 Json,
26};
27use chrono::{DateTime, Utc};
28use parking_lot::RwLock;
29use serde::{Deserialize, Serialize};
30use sha2::{Digest, Sha256};
31use std::collections::HashMap;
32use std::sync::atomic::{AtomicU64, Ordering};
33use std::sync::Arc;
34
35#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
41#[serde(tag = "type", rename_all = "snake_case")]
42pub enum DeletionScope {
43 All,
45 SpecificCollections { collections: Vec<String> },
47 ExcludeAuditLogs,
49}
50
51impl Default for DeletionScope {
52 fn default() -> Self {
53 DeletionScope::All
54 }
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
63#[serde(rename_all = "lowercase")]
64pub enum ExportFormat {
65 Json,
67 Csv,
69}
70
71impl Default for ExportFormat {
72 fn default() -> Self {
73 ExportFormat::Json
74 }
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize, Default)]
79#[serde(tag = "type", rename_all = "snake_case")]
80pub enum ExportScope {
81 #[default]
83 All,
84 SpecificCollections { collections: Vec<String> },
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct DateRange {
91 #[serde(default)]
93 pub start: Option<String>,
94 #[serde(default)]
96 pub end: Option<String>,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct ExportRequest {
102 pub subject_id: String,
104 #[serde(default)]
106 pub format: ExportFormat,
107 #[serde(default)]
109 pub scope: ExportScope,
110 #[serde(default)]
112 pub date_range: Option<DateRange>,
113 #[serde(default)]
116 pub search_fields: Vec<String>,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct ExportedItem {
122 pub store_type: String,
124 pub location: String,
126 pub item_id: String,
128 pub data: serde_json::Value,
130 #[serde(skip_serializing_if = "Option::is_none")]
132 pub timestamp: Option<String>,
133}
134
135#[derive(Debug, Serialize)]
137pub struct ExportResponse {
138 pub export_id: String,
140 pub subject_id: String,
142 pub format: ExportFormat,
144 pub generated_at: String,
146 pub total_items: usize,
148 pub data: serde_json::Value,
150 pub scope: ExportScope,
152 #[serde(skip_serializing_if = "Option::is_none")]
154 pub date_range: Option<DateRange>,
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct DeletionRequest {
160 pub subject_id: String,
162 #[serde(default)]
164 pub scope: DeletionScope,
165 pub requestor: String,
167 pub reason: String,
169 #[serde(default)]
172 pub search_fields: Vec<String>,
173 #[serde(default)]
175 pub secure_erase: bool,
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct DeletedItem {
181 pub store_type: String,
183 pub location: String,
185 pub item_id: String,
187 pub size_bytes: Option<u64>,
189 pub deleted_at: String,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct DeletionCertificate {
196 pub id: String,
198 pub subject_id: String,
200 pub timestamp: String,
202 pub items_deleted: Vec<DeletedItem>,
204 pub total_items: usize,
206 pub total_bytes: u64,
208 pub scope: DeletionScope,
210 pub requestor: String,
212 pub reason: String,
214 pub verification_hash: String,
216 pub verified_by: String,
218 pub secure_erase_performed: bool,
220}
221
222impl DeletionCertificate {
223 pub fn new(
225 subject_id: String,
226 items: Vec<DeletedItem>,
227 scope: DeletionScope,
228 requestor: String,
229 reason: String,
230 node_id: String,
231 secure_erase: bool,
232 ) -> Self {
233 let timestamp = Utc::now().to_rfc3339();
234 let total_items = items.len();
235 let total_bytes = items.iter().filter_map(|i| i.size_bytes).sum();
236
237 let id = format!(
239 "DEL-{}-{}",
240 timestamp
241 .replace([':', '-', 'T', 'Z', '.'], "")
242 .chars()
243 .take(14)
244 .collect::<String>(),
245 &subject_id.chars().take(8).collect::<String>()
246 );
247
248 let mut cert = Self {
249 id,
250 subject_id,
251 timestamp,
252 items_deleted: items,
253 total_items,
254 total_bytes,
255 scope,
256 requestor,
257 reason,
258 verification_hash: String::new(),
259 verified_by: node_id,
260 secure_erase_performed: secure_erase,
261 };
262
263 cert.verification_hash = cert.compute_hash();
265 cert
266 }
267
268 fn compute_hash(&self) -> String {
270 let mut hasher = Sha256::new();
271
272 hasher.update(self.id.as_bytes());
274 hasher.update(self.subject_id.as_bytes());
275 hasher.update(self.timestamp.as_bytes());
276 hasher.update(self.total_items.to_string().as_bytes());
277 hasher.update(self.total_bytes.to_string().as_bytes());
278 hasher.update(self.requestor.as_bytes());
279 hasher.update(self.reason.as_bytes());
280 hasher.update(self.verified_by.as_bytes());
281
282 for item in &self.items_deleted {
284 hasher.update(item.store_type.as_bytes());
285 hasher.update(item.location.as_bytes());
286 hasher.update(item.item_id.as_bytes());
287 }
288
289 let result = hasher.finalize();
290 hex_encode(&result)
291 }
292
293 pub fn verify(&self) -> bool {
295 let mut cert_copy = self.clone();
296 cert_copy.verification_hash = String::new();
297 cert_copy.compute_hash() == self.verification_hash
298 }
299}
300
301#[derive(Debug, Clone, Serialize, Deserialize)]
303pub struct DeletionAuditEntry {
304 pub id: String,
306 pub event_type: DeletionEventType,
308 pub subject_id: String,
310 pub timestamp: String,
312 pub actor: String,
314 pub details: serde_json::Value,
316 pub prev_hash: String,
318 pub hash: String,
320}
321
322#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
324#[serde(rename_all = "snake_case")]
325pub enum DeletionEventType {
326 RequestReceived,
328 DeletionStarted,
330 ItemDeleted,
332 SecureErasePerformed,
334 DeletionCompleted,
336 DeletionFailed,
338 CertificateGenerated,
340 ExportRequestReceived,
342 ExportCompleted,
344 ExportFailed,
346}
347
348#[derive(Debug, Serialize)]
350pub struct DeletionResponse {
351 pub success: bool,
352 pub certificate: Option<DeletionCertificate>,
353 pub message: String,
354 #[serde(skip_serializing_if = "Option::is_none")]
355 pub error: Option<String>,
356}
357
358#[derive(Debug, Serialize)]
360pub struct ListCertificatesResponse {
361 pub certificates: Vec<DeletionCertificate>,
362 pub total: usize,
363}
364
365#[derive(Debug, Serialize)]
367pub struct VerifyCertificateResponse {
368 pub valid: bool,
369 pub certificate: Option<DeletionCertificate>,
370 pub message: String,
371}
372
373pub struct DeletionAuditLog {
380 entries: RwLock<Vec<DeletionAuditEntry>>,
381 certificates: RwLock<HashMap<String, DeletionCertificate>>,
382 next_id: AtomicU64,
383 last_hash: RwLock<String>,
384}
385
386impl DeletionAuditLog {
387 pub fn new() -> Self {
389 Self {
390 entries: RwLock::new(Vec::new()),
391 certificates: RwLock::new(HashMap::new()),
392 next_id: AtomicU64::new(1),
393 last_hash: RwLock::new("genesis".to_string()),
394 }
395 }
396
397 pub fn log_event(
399 &self,
400 event_type: DeletionEventType,
401 subject_id: &str,
402 actor: &str,
403 details: serde_json::Value,
404 ) -> String {
405 let id = format!("gdpr-{:08}", self.next_id.fetch_add(1, Ordering::SeqCst));
406 let timestamp = Utc::now().to_rfc3339();
407
408 let prev_hash = self.last_hash.read().clone();
409
410 let mut hasher = Sha256::new();
412 hasher.update(id.as_bytes());
413 hasher.update(format!("{:?}", event_type).as_bytes());
414 hasher.update(subject_id.as_bytes());
415 hasher.update(timestamp.as_bytes());
416 hasher.update(actor.as_bytes());
417 if let Ok(json) = serde_json::to_string(&details) {
418 hasher.update(json.as_bytes());
419 }
420 hasher.update(prev_hash.as_bytes());
421 let hash = hex_encode(&hasher.finalize());
422
423 let entry = DeletionAuditEntry {
424 id: id.clone(),
425 event_type,
426 subject_id: subject_id.to_string(),
427 timestamp,
428 actor: actor.to_string(),
429 details,
430 prev_hash,
431 hash: hash.clone(),
432 };
433
434 *self.last_hash.write() = hash;
436 self.entries.write().push(entry);
437
438 id
439 }
440
441 pub fn store_certificate(&self, cert: DeletionCertificate) {
443 self.certificates.write().insert(cert.id.clone(), cert);
444 }
445
446 pub fn get_certificate(&self, id: &str) -> Option<DeletionCertificate> {
448 self.certificates.read().get(id).cloned()
449 }
450
451 pub fn list_certificates(&self) -> Vec<DeletionCertificate> {
453 self.certificates.read().values().cloned().collect()
454 }
455
456 pub fn get_entries_for_subject(&self, subject_id: &str) -> Vec<DeletionAuditEntry> {
458 self.entries
459 .read()
460 .iter()
461 .filter(|e| e.subject_id == subject_id)
462 .cloned()
463 .collect()
464 }
465
466 pub fn verify_integrity(&self) -> Result<usize, String> {
468 let entries = self.entries.read();
469 let mut last_hash = "genesis".to_string();
470
471 for (idx, entry) in entries.iter().enumerate() {
472 if entry.prev_hash != last_hash {
474 return Err(format!(
475 "Hash chain broken at entry {}: expected prev_hash '{}', got '{}'",
476 idx, last_hash, entry.prev_hash
477 ));
478 }
479
480 let mut hasher = Sha256::new();
482 hasher.update(entry.id.as_bytes());
483 hasher.update(format!("{:?}", entry.event_type).as_bytes());
484 hasher.update(entry.subject_id.as_bytes());
485 hasher.update(entry.timestamp.as_bytes());
486 hasher.update(entry.actor.as_bytes());
487 if let Ok(json) = serde_json::to_string(&entry.details) {
488 hasher.update(json.as_bytes());
489 }
490 hasher.update(entry.prev_hash.as_bytes());
491 let computed_hash = hex_encode(&hasher.finalize());
492
493 if entry.hash != computed_hash {
494 return Err(format!(
495 "Hash mismatch at entry {}: computed '{}', stored '{}'",
496 idx, computed_hash, entry.hash
497 ));
498 }
499
500 last_hash = entry.hash.clone();
501 }
502
503 Ok(entries.len())
504 }
505}
506
507impl Default for DeletionAuditLog {
508 fn default() -> Self {
509 Self::new()
510 }
511}
512
513pub struct GdprService {
519 audit_log: Arc<DeletionAuditLog>,
520}
521
522impl GdprService {
523 pub fn new() -> Self {
525 Self {
526 audit_log: Arc::new(DeletionAuditLog::new()),
527 }
528 }
529
530 pub fn audit_log(&self) -> &Arc<DeletionAuditLog> {
532 &self.audit_log
533 }
534
535 fn default_search_fields() -> Vec<String> {
537 vec![
538 "email".to_string(),
539 "user_id".to_string(),
540 "customer_id".to_string(),
541 "subject_id".to_string(),
542 "id".to_string(),
543 "userId".to_string(),
544 "customerId".to_string(),
545 "user".to_string(),
546 "owner".to_string(),
547 ]
548 }
549
550 pub fn delete_from_kv(
552 &self,
553 state: &AppState,
554 subject_id: &str,
555 search_fields: &[String],
556 ) -> Vec<DeletedItem> {
557 let mut deleted = Vec::new();
558 let entries = state.kv_store.list(None, usize::MAX);
559
560 for entry in entries {
561 let should_delete =
562 self.value_contains_subject(&entry.value, subject_id, search_fields)
563 || entry.key.contains(subject_id);
564
565 if should_delete {
566 let size = serde_json::to_string(&entry.value)
567 .map(|s| s.len() as u64)
568 .ok();
569
570 if state.kv_store.delete(&entry.key).is_some() {
571 deleted.push(DeletedItem {
572 store_type: "kv".to_string(),
573 location: "kv_store".to_string(),
574 item_id: entry.key,
575 size_bytes: size,
576 deleted_at: Utc::now().to_rfc3339(),
577 });
578 }
579 }
580 }
581
582 deleted
583 }
584
585 pub fn delete_from_documents(
587 &self,
588 state: &AppState,
589 subject_id: &str,
590 search_fields: &[String],
591 specific_collections: Option<&[String]>,
592 ) -> Vec<DeletedItem> {
593 let mut deleted = Vec::new();
594 let collections = state.document_engine.list_collections();
595
596 for collection_name in collections {
597 if let Some(specific) = specific_collections {
599 if !specific.contains(&collection_name) {
600 continue;
601 }
602 }
603
604 let query = aegis_document::Query::new();
606 if let Ok(result) = state.document_engine.find(&collection_name, &query) {
607 for doc in &result.documents {
608 let should_delete =
610 self.document_belongs_to_subject(doc, subject_id, search_fields);
611
612 if should_delete {
613 let size = serde_json::to_string(&doc.data)
614 .map(|s| s.len() as u64)
615 .ok();
616
617 if state
618 .document_engine
619 .delete(&collection_name, &doc.id)
620 .is_ok()
621 {
622 deleted.push(DeletedItem {
623 store_type: "document".to_string(),
624 location: collection_name.clone(),
625 item_id: doc.id.to_string(),
626 size_bytes: size,
627 deleted_at: Utc::now().to_rfc3339(),
628 });
629 }
630 }
631 }
632 }
633 }
634
635 deleted
636 }
637
638 pub fn delete_from_sql(
640 &self,
641 state: &AppState,
642 subject_id: &str,
643 search_fields: &[String],
644 specific_tables: Option<&[String]>,
645 ) -> Vec<DeletedItem> {
646 let mut deleted = Vec::new();
647 let tables = state.query_engine.list_tables(None);
648
649 for table_name in tables {
650 if let Some(specific) = specific_tables {
652 if !specific.contains(&table_name) {
653 continue;
654 }
655 }
656
657 if let Some(table_info) = state.query_engine.get_table_info(&table_name, None) {
659 let searchable_columns: Vec<&String> = table_info
660 .columns
661 .iter()
662 .filter(|c| {
663 search_fields
664 .iter()
665 .any(|f| c.name.to_lowercase() == f.to_lowercase())
666 })
667 .map(|c| &c.name)
668 .collect();
669
670 for column in searchable_columns {
672 let escaped_subject = subject_id.replace('\'', "''");
674 let delete_sql = format!(
675 "DELETE FROM {} WHERE {} = '{}'",
676 table_name, column, escaped_subject
677 );
678
679 if let Ok(result) = state.query_engine.execute(&delete_sql, None) {
680 if result.rows_affected > 0 {
681 deleted.push(DeletedItem {
682 store_type: "sql".to_string(),
683 location: table_name.clone(),
684 item_id: format!("{}={}", column, subject_id),
685 size_bytes: None, deleted_at: Utc::now().to_rfc3339(),
687 });
688 }
689 }
690 }
691 }
692 }
693
694 deleted
695 }
696
697 pub fn delete_from_graph(
699 &self,
700 state: &AppState,
701 subject_id: &str,
702 search_fields: &[String],
703 ) -> Vec<DeletedItem> {
704 let mut deleted = Vec::new();
705 let nodes = state.graph_store.list_nodes();
706
707 for node in nodes {
708 let should_delete =
709 self.value_contains_subject(&node.properties, subject_id, search_fields)
710 || node.id.contains(subject_id)
711 || node.label.contains(subject_id);
712
713 if should_delete {
714 let size = serde_json::to_string(&node.properties)
715 .map(|s| s.len() as u64)
716 .ok();
717
718 if state.graph_store.delete_node(&node.id).is_ok() {
719 deleted.push(DeletedItem {
720 store_type: "graph".to_string(),
721 location: "graph_store".to_string(),
722 item_id: node.id,
723 size_bytes: size,
724 deleted_at: Utc::now().to_rfc3339(),
725 });
726 }
727 }
728 }
729
730 deleted
731 }
732
733 fn value_contains_subject(
735 &self,
736 value: &serde_json::Value,
737 subject_id: &str,
738 search_fields: &[String],
739 ) -> bool {
740 match value {
741 serde_json::Value::Object(map) => {
742 for field in search_fields {
743 if let Some(v) = map.get(field) {
744 if self.value_matches_subject(v, subject_id) {
745 return true;
746 }
747 }
748 }
749 for (_, v) in map {
751 if self.value_contains_subject(v, subject_id, search_fields) {
752 return true;
753 }
754 }
755 false
756 }
757 serde_json::Value::Array(arr) => arr
758 .iter()
759 .any(|v| self.value_contains_subject(v, subject_id, search_fields)),
760 _ => false,
761 }
762 }
763
764 fn value_matches_subject(&self, value: &serde_json::Value, subject_id: &str) -> bool {
766 match value {
767 serde_json::Value::String(s) => s == subject_id,
768 serde_json::Value::Number(n) => n.to_string() == subject_id,
769 _ => false,
770 }
771 }
772
773 fn document_belongs_to_subject(
775 &self,
776 doc: &aegis_document::Document,
777 subject_id: &str,
778 search_fields: &[String],
779 ) -> bool {
780 if doc.id.to_string() == subject_id {
782 return true;
783 }
784
785 for field in search_fields {
787 if let Some(value) = doc.get(field) {
788 if self.doc_value_matches_subject(value, subject_id) {
789 return true;
790 }
791 }
792 }
793
794 false
795 }
796
797 fn doc_value_matches_subject(&self, value: &aegis_document::Value, subject_id: &str) -> bool {
799 match value {
800 aegis_document::Value::String(s) => s == subject_id,
801 aegis_document::Value::Int(i) => i.to_string() == subject_id,
802 aegis_document::Value::Float(f) => f.to_string() == subject_id,
803 _ => false,
804 }
805 }
806
807 pub fn secure_erase(&self, _state: &AppState) -> bool {
810 true
821 }
822
823 pub fn execute_deletion(
825 &self,
826 state: &AppState,
827 request: DeletionRequest,
828 ) -> Result<DeletionCertificate, String> {
829 let node_id = state.config.node_id.clone();
830
831 self.audit_log.log_event(
833 DeletionEventType::RequestReceived,
834 &request.subject_id,
835 &request.requestor,
836 serde_json::json!({
837 "scope": request.scope,
838 "reason": request.reason,
839 "secure_erase": request.secure_erase,
840 }),
841 );
842
843 self.audit_log.log_event(
845 DeletionEventType::DeletionStarted,
846 &request.subject_id,
847 &request.requestor,
848 serde_json::json!({"timestamp": Utc::now().to_rfc3339()}),
849 );
850
851 let search_fields = if request.search_fields.is_empty() {
852 Self::default_search_fields()
853 } else {
854 request.search_fields.clone()
855 };
856
857 let mut all_deleted = Vec::new();
858
859 let specific_collections = match &request.scope {
861 DeletionScope::SpecificCollections { collections } => Some(collections.as_slice()),
862 _ => None,
863 };
864
865 let kv_deleted = self.delete_from_kv(state, &request.subject_id, &search_fields);
867 for item in &kv_deleted {
868 self.audit_log.log_event(
869 DeletionEventType::ItemDeleted,
870 &request.subject_id,
871 &request.requestor,
872 serde_json::json!({
873 "store": "kv",
874 "item_id": item.item_id,
875 }),
876 );
877 }
878 all_deleted.extend(kv_deleted);
879
880 let doc_deleted = self.delete_from_documents(
882 state,
883 &request.subject_id,
884 &search_fields,
885 specific_collections,
886 );
887 for item in &doc_deleted {
888 self.audit_log.log_event(
889 DeletionEventType::ItemDeleted,
890 &request.subject_id,
891 &request.requestor,
892 serde_json::json!({
893 "store": "document",
894 "collection": item.location,
895 "item_id": item.item_id,
896 }),
897 );
898 }
899 all_deleted.extend(doc_deleted);
900
901 let sql_deleted = self.delete_from_sql(
903 state,
904 &request.subject_id,
905 &search_fields,
906 specific_collections,
907 );
908 for item in &sql_deleted {
909 self.audit_log.log_event(
910 DeletionEventType::ItemDeleted,
911 &request.subject_id,
912 &request.requestor,
913 serde_json::json!({
914 "store": "sql",
915 "table": item.location,
916 "item_id": item.item_id,
917 }),
918 );
919 }
920 all_deleted.extend(sql_deleted);
921
922 let graph_deleted = self.delete_from_graph(state, &request.subject_id, &search_fields);
924 for item in &graph_deleted {
925 self.audit_log.log_event(
926 DeletionEventType::ItemDeleted,
927 &request.subject_id,
928 &request.requestor,
929 serde_json::json!({
930 "store": "graph",
931 "item_id": item.item_id,
932 }),
933 );
934 }
935 all_deleted.extend(graph_deleted);
936
937 let secure_erase_performed = if request.secure_erase {
939 let success = self.secure_erase(state);
940 self.audit_log.log_event(
941 DeletionEventType::SecureErasePerformed,
942 &request.subject_id,
943 &request.requestor,
944 serde_json::json!({
945 "success": success,
946 "timestamp": Utc::now().to_rfc3339(),
947 }),
948 );
949 success
950 } else {
951 false
952 };
953
954 if let Err(e) = state.save_to_disk() {
956 tracing::warn!("Failed to persist deletions to disk: {}", e);
957 }
958
959 self.audit_log.log_event(
961 DeletionEventType::DeletionCompleted,
962 &request.subject_id,
963 &request.requestor,
964 serde_json::json!({
965 "items_deleted": all_deleted.len(),
966 "timestamp": Utc::now().to_rfc3339(),
967 }),
968 );
969
970 let certificate = DeletionCertificate::new(
972 request.subject_id.clone(),
973 all_deleted,
974 request.scope,
975 request.requestor.clone(),
976 request.reason,
977 node_id,
978 secure_erase_performed,
979 );
980
981 self.audit_log.log_event(
983 DeletionEventType::CertificateGenerated,
984 &request.subject_id,
985 &request.requestor,
986 serde_json::json!({
987 "certificate_id": certificate.id,
988 "verification_hash": certificate.verification_hash,
989 }),
990 );
991
992 self.audit_log.store_certificate(certificate.clone());
994
995 Ok(certificate)
996 }
997
998 pub fn export_from_kv(
1004 &self,
1005 state: &AppState,
1006 subject_id: &str,
1007 search_fields: &[String],
1008 date_range: Option<&DateRange>,
1009 ) -> Vec<ExportedItem> {
1010 let mut exported = Vec::new();
1011 let entries = state.kv_store.list(None, usize::MAX);
1012
1013 for entry in entries {
1014 let belongs_to_subject =
1016 self.value_contains_subject(&entry.value, subject_id, search_fields)
1017 || entry.key.contains(subject_id);
1018
1019 if !belongs_to_subject {
1020 continue;
1021 }
1022
1023 if let Some(range) = date_range {
1025 if !self.is_within_date_range(&entry.updated_at.to_rfc3339(), range) {
1026 continue;
1027 }
1028 }
1029
1030 exported.push(ExportedItem {
1031 store_type: "kv".to_string(),
1032 location: "kv_store".to_string(),
1033 item_id: entry.key.clone(),
1034 data: serde_json::json!({
1035 "key": entry.key,
1036 "value": entry.value,
1037 "ttl": entry.ttl,
1038 }),
1039 timestamp: Some(entry.updated_at.to_rfc3339()),
1040 });
1041 }
1042
1043 exported
1044 }
1045
1046 pub fn export_from_documents(
1048 &self,
1049 state: &AppState,
1050 subject_id: &str,
1051 search_fields: &[String],
1052 specific_collections: Option<&[String]>,
1053 date_range: Option<&DateRange>,
1054 ) -> Vec<ExportedItem> {
1055 let mut exported = Vec::new();
1056 let collections = state.document_engine.list_collections();
1057
1058 for collection_name in collections {
1059 if let Some(specific) = specific_collections {
1061 if !specific.contains(&collection_name) {
1062 continue;
1063 }
1064 }
1065
1066 let query = aegis_document::Query::new();
1068 if let Ok(result) = state.document_engine.find(&collection_name, &query) {
1069 for doc in &result.documents {
1070 if !self.document_belongs_to_subject(doc, subject_id, search_fields) {
1072 continue;
1073 }
1074
1075 if let Some(range) = date_range {
1077 if let Some(aegis_document::Value::String(updated)) = doc.get("updated_at")
1078 {
1079 if !self.is_within_date_range(updated, range) {
1080 continue;
1081 }
1082 }
1083 }
1084
1085 let doc_data = self.document_to_json(doc);
1087
1088 exported.push(ExportedItem {
1089 store_type: "document".to_string(),
1090 location: collection_name.clone(),
1091 item_id: doc.id.to_string(),
1092 data: doc_data,
1093 timestamp: doc.get("updated_at").and_then(|v| {
1094 if let aegis_document::Value::String(s) = v {
1095 Some(s.clone())
1096 } else {
1097 None
1098 }
1099 }),
1100 });
1101 }
1102 }
1103 }
1104
1105 exported
1106 }
1107
1108 pub fn export_from_sql(
1110 &self,
1111 state: &AppState,
1112 subject_id: &str,
1113 search_fields: &[String],
1114 specific_tables: Option<&[String]>,
1115 _date_range: Option<&DateRange>,
1116 ) -> Vec<ExportedItem> {
1117 let mut exported = Vec::new();
1118 let tables = state.query_engine.list_tables(None);
1119
1120 for table_name in tables {
1121 if let Some(specific) = specific_tables {
1123 if !specific.contains(&table_name) {
1124 continue;
1125 }
1126 }
1127
1128 if let Some(table_info) = state.query_engine.get_table_info(&table_name, None) {
1130 let searchable_columns: Vec<&String> = table_info
1131 .columns
1132 .iter()
1133 .filter(|c| {
1134 search_fields
1135 .iter()
1136 .any(|f| c.name.to_lowercase() == f.to_lowercase())
1137 })
1138 .map(|c| &c.name)
1139 .collect();
1140
1141 for column in searchable_columns {
1143 let escaped_subject = subject_id.replace('\'', "''");
1145 let select_sql = format!(
1146 "SELECT * FROM {} WHERE {} = '{}'",
1147 table_name, column, escaped_subject
1148 );
1149
1150 if let Ok(result) = state.query_engine.execute(&select_sql, None) {
1151 for (idx, row) in result.rows.iter().enumerate() {
1152 let mut row_data = serde_json::Map::new();
1154 for (col_idx, col_name) in result.columns.iter().enumerate() {
1155 if let Some(value) = row.get(col_idx) {
1156 row_data.insert(col_name.clone(), value.clone());
1157 }
1158 }
1159
1160 exported.push(ExportedItem {
1161 store_type: "sql".to_string(),
1162 location: table_name.clone(),
1163 item_id: format!("{}={}/row-{}", column, subject_id, idx),
1164 data: serde_json::Value::Object(row_data),
1165 timestamp: None,
1166 });
1167 }
1168 }
1169 }
1170 }
1171 }
1172
1173 exported
1174 }
1175
1176 pub fn export_from_graph(
1178 &self,
1179 state: &AppState,
1180 subject_id: &str,
1181 search_fields: &[String],
1182 ) -> Vec<ExportedItem> {
1183 let mut exported = Vec::new();
1184 let nodes = state.graph_store.list_nodes();
1185
1186 for node in nodes {
1187 let belongs_to_subject =
1188 self.value_contains_subject(&node.properties, subject_id, search_fields)
1189 || node.id.contains(subject_id)
1190 || node.label.contains(subject_id);
1191
1192 if belongs_to_subject {
1193 let edges = state.graph_store.get_edges_for_node(&node.id);
1195 let edge_data: Vec<serde_json::Value> = edges
1196 .iter()
1197 .map(|e| {
1198 serde_json::json!({
1199 "id": e.id,
1200 "source": e.source,
1201 "target": e.target,
1202 "relationship": e.relationship,
1203 })
1204 })
1205 .collect();
1206
1207 exported.push(ExportedItem {
1208 store_type: "graph".to_string(),
1209 location: "graph_store".to_string(),
1210 item_id: node.id.clone(),
1211 data: serde_json::json!({
1212 "id": node.id,
1213 "label": node.label,
1214 "properties": node.properties,
1215 "edges": edge_data,
1216 }),
1217 timestamp: None,
1218 });
1219 }
1220 }
1221
1222 exported
1223 }
1224
1225 fn is_within_date_range(&self, timestamp: &str, range: &DateRange) -> bool {
1227 let ts = match DateTime::parse_from_rfc3339(timestamp) {
1228 Ok(dt) => dt.with_timezone(&Utc),
1229 Err(_) => return true, };
1231
1232 if let Some(ref start) = range.start {
1233 if let Ok(start_dt) = DateTime::parse_from_rfc3339(start) {
1234 if ts < start_dt.with_timezone(&Utc) {
1235 return false;
1236 }
1237 }
1238 }
1239
1240 if let Some(ref end) = range.end {
1241 if let Ok(end_dt) = DateTime::parse_from_rfc3339(end) {
1242 if ts > end_dt.with_timezone(&Utc) {
1243 return false;
1244 }
1245 }
1246 }
1247
1248 true
1249 }
1250
1251 fn document_to_json(&self, doc: &aegis_document::Document) -> serde_json::Value {
1253 let mut map = serde_json::Map::new();
1254 map.insert(
1255 "_id".to_string(),
1256 serde_json::Value::String(doc.id.to_string()),
1257 );
1258 for (key, value) in &doc.data {
1259 map.insert(key.clone(), self.doc_value_to_json(value));
1260 }
1261 serde_json::Value::Object(map)
1262 }
1263
1264 fn doc_value_to_json(&self, value: &aegis_document::Value) -> serde_json::Value {
1266 match value {
1267 aegis_document::Value::Null => serde_json::Value::Null,
1268 aegis_document::Value::Bool(b) => serde_json::Value::Bool(*b),
1269 aegis_document::Value::Int(i) => serde_json::Value::Number((*i).into()),
1270 aegis_document::Value::Float(f) => serde_json::Number::from_f64(*f)
1271 .map(serde_json::Value::Number)
1272 .unwrap_or(serde_json::Value::Null),
1273 aegis_document::Value::String(s) => serde_json::Value::String(s.clone()),
1274 aegis_document::Value::Array(arr) => {
1275 serde_json::Value::Array(arr.iter().map(|v| self.doc_value_to_json(v)).collect())
1276 }
1277 aegis_document::Value::Object(obj) => {
1278 let map: serde_json::Map<String, serde_json::Value> = obj
1279 .iter()
1280 .map(|(k, v)| (k.clone(), self.doc_value_to_json(v)))
1281 .collect();
1282 serde_json::Value::Object(map)
1283 }
1284 }
1285 }
1286
1287 fn items_to_csv(&self, items: &[ExportedItem]) -> String {
1289 if items.is_empty() {
1290 return "store_type,location,item_id,timestamp,data\n".to_string();
1291 }
1292
1293 let mut csv = String::new();
1294
1295 let mut all_data_keys: Vec<String> = Vec::new();
1297 for item in items {
1298 if let serde_json::Value::Object(map) = &item.data {
1299 for key in map.keys() {
1300 if !all_data_keys.contains(key) {
1301 all_data_keys.push(key.clone());
1302 }
1303 }
1304 }
1305 }
1306 all_data_keys.sort();
1307
1308 let mut header_parts = vec![
1310 "store_type".to_string(),
1311 "location".to_string(),
1312 "item_id".to_string(),
1313 "timestamp".to_string(),
1314 ];
1315 header_parts.extend(all_data_keys.iter().cloned());
1316 csv.push_str(&header_parts.join(","));
1317 csv.push('\n');
1318
1319 for item in items {
1321 let mut row_parts = vec![
1322 escape_csv_field(&item.store_type),
1323 escape_csv_field(&item.location),
1324 escape_csv_field(&item.item_id),
1325 escape_csv_field(&item.timestamp.clone().unwrap_or_default()),
1326 ];
1327
1328 for key in &all_data_keys {
1330 let value = if let serde_json::Value::Object(map) = &item.data {
1331 map.get(key)
1332 .map(json_value_to_csv_string)
1333 .unwrap_or_default()
1334 } else {
1335 String::new()
1336 };
1337 row_parts.push(escape_csv_field(&value));
1338 }
1339
1340 csv.push_str(&row_parts.join(","));
1341 csv.push('\n');
1342 }
1343
1344 csv
1345 }
1346
1347 pub fn execute_export(
1349 &self,
1350 state: &AppState,
1351 request: ExportRequest,
1352 ) -> Result<ExportResponse, String> {
1353 let timestamp = Utc::now();
1355 let export_id = format!(
1356 "EXP-{}-{}",
1357 timestamp.format("%Y%m%d%H%M%S"),
1358 &request.subject_id.chars().take(8).collect::<String>()
1359 );
1360
1361 self.audit_log.log_event(
1363 DeletionEventType::ExportRequestReceived,
1364 &request.subject_id,
1365 "system",
1366 serde_json::json!({
1367 "export_id": export_id,
1368 "format": request.format,
1369 "scope": request.scope,
1370 "date_range": request.date_range,
1371 }),
1372 );
1373
1374 let search_fields = if request.search_fields.is_empty() {
1375 Self::default_search_fields()
1376 } else {
1377 request.search_fields.clone()
1378 };
1379
1380 let mut all_exported = Vec::new();
1381
1382 let specific_collections = match &request.scope {
1384 ExportScope::SpecificCollections { collections } => Some(collections.as_slice()),
1385 _ => None,
1386 };
1387
1388 let kv_exported = self.export_from_kv(
1390 state,
1391 &request.subject_id,
1392 &search_fields,
1393 request.date_range.as_ref(),
1394 );
1395 all_exported.extend(kv_exported);
1396
1397 let doc_exported = self.export_from_documents(
1399 state,
1400 &request.subject_id,
1401 &search_fields,
1402 specific_collections,
1403 request.date_range.as_ref(),
1404 );
1405 all_exported.extend(doc_exported);
1406
1407 let sql_exported = self.export_from_sql(
1409 state,
1410 &request.subject_id,
1411 &search_fields,
1412 specific_collections,
1413 request.date_range.as_ref(),
1414 );
1415 all_exported.extend(sql_exported);
1416
1417 let graph_exported = self.export_from_graph(state, &request.subject_id, &search_fields);
1419 all_exported.extend(graph_exported);
1420
1421 let total_items = all_exported.len();
1422
1423 let data = match request.format {
1425 ExportFormat::Json => {
1426 let mut grouped: HashMap<String, Vec<&ExportedItem>> = HashMap::new();
1428 for item in &all_exported {
1429 grouped
1430 .entry(item.store_type.clone())
1431 .or_default()
1432 .push(item);
1433 }
1434
1435 let mut result = serde_json::Map::new();
1436 result.insert(
1437 "subject_id".to_string(),
1438 serde_json::Value::String(request.subject_id.clone()),
1439 );
1440 result.insert(
1441 "export_id".to_string(),
1442 serde_json::Value::String(export_id.clone()),
1443 );
1444 result.insert(
1445 "generated_at".to_string(),
1446 serde_json::Value::String(timestamp.to_rfc3339()),
1447 );
1448 result.insert(
1449 "total_items".to_string(),
1450 serde_json::Value::Number(total_items.into()),
1451 );
1452
1453 let mut data_section = serde_json::Map::new();
1454 for (store_type, items) in grouped {
1455 let items_json: Vec<serde_json::Value> = items
1456 .iter()
1457 .map(|item| {
1458 serde_json::json!({
1459 "location": item.location,
1460 "item_id": item.item_id,
1461 "data": item.data,
1462 "timestamp": item.timestamp,
1463 })
1464 })
1465 .collect();
1466 data_section.insert(store_type, serde_json::Value::Array(items_json));
1467 }
1468 result.insert("data".to_string(), serde_json::Value::Object(data_section));
1469
1470 serde_json::Value::Object(result)
1471 }
1472 ExportFormat::Csv => {
1473 let csv_content = self.items_to_csv(&all_exported);
1474 serde_json::Value::String(csv_content)
1475 }
1476 };
1477
1478 self.audit_log.log_event(
1480 DeletionEventType::ExportCompleted,
1481 &request.subject_id,
1482 "system",
1483 serde_json::json!({
1484 "export_id": export_id,
1485 "total_items": total_items,
1486 "format": request.format,
1487 "timestamp": timestamp.to_rfc3339(),
1488 }),
1489 );
1490
1491 Ok(ExportResponse {
1492 export_id,
1493 subject_id: request.subject_id,
1494 format: request.format,
1495 generated_at: timestamp.to_rfc3339(),
1496 total_items,
1497 data,
1498 scope: request.scope,
1499 date_range: request.date_range,
1500 })
1501 }
1502}
1503
1504impl Default for GdprService {
1505 fn default() -> Self {
1506 Self::new()
1507 }
1508}
1509
1510fn escape_csv_field(field: &str) -> String {
1512 if field.contains(',') || field.contains('"') || field.contains('\n') || field.contains('\r') {
1513 format!("\"{}\"", field.replace('"', "\"\""))
1514 } else {
1515 field.to_string()
1516 }
1517}
1518
1519fn json_value_to_csv_string(value: &serde_json::Value) -> String {
1521 match value {
1522 serde_json::Value::Null => String::new(),
1523 serde_json::Value::Bool(b) => b.to_string(),
1524 serde_json::Value::Number(n) => n.to_string(),
1525 serde_json::Value::String(s) => s.clone(),
1526 serde_json::Value::Array(_) | serde_json::Value::Object(_) => {
1527 serde_json::to_string(value).unwrap_or_default()
1528 }
1529 }
1530}
1531
1532pub async fn delete_data_subject(
1538 State(state): State<AppState>,
1539 Path(identifier): Path<String>,
1540 Json(mut request): Json<DeletionRequest>,
1541) -> impl IntoResponse {
1542 if request.subject_id.is_empty() {
1544 request.subject_id = identifier;
1545 }
1546
1547 state.activity.log_with_details(
1548 ActivityType::Delete,
1549 &format!("GDPR deletion request for subject: {}", request.subject_id),
1550 None,
1551 Some(&request.requestor),
1552 Some("gdpr"),
1553 Some(serde_json::json!({
1554 "scope": request.scope,
1555 "reason": request.reason,
1556 })),
1557 );
1558
1559 match state.gdpr.execute_deletion(&state, request) {
1560 Ok(certificate) => {
1561 state.activity.log_with_details(
1562 ActivityType::System,
1563 &format!("GDPR deletion completed. Certificate: {}", certificate.id),
1564 None,
1565 None,
1566 Some("gdpr"),
1567 Some(serde_json::json!({
1568 "items_deleted": certificate.total_items,
1569 "bytes_deleted": certificate.total_bytes,
1570 })),
1571 );
1572
1573 (
1574 StatusCode::OK,
1575 Json(DeletionResponse {
1576 success: true,
1577 certificate: Some(certificate),
1578 message: "Data deletion completed successfully".to_string(),
1579 error: None,
1580 }),
1581 )
1582 }
1583 Err(e) => {
1584 state.activity.log_with_details(
1585 ActivityType::System,
1586 &format!("GDPR deletion failed: {}", e),
1587 None,
1588 None,
1589 Some("gdpr"),
1590 None,
1591 );
1592
1593 (
1594 StatusCode::INTERNAL_SERVER_ERROR,
1595 Json(DeletionResponse {
1596 success: false,
1597 certificate: None,
1598 message: "Data deletion failed".to_string(),
1599 error: Some(e),
1600 }),
1601 )
1602 }
1603 }
1604}
1605
1606pub async fn list_deletion_certificates(
1608 State(state): State<AppState>,
1609) -> Json<ListCertificatesResponse> {
1610 state
1611 .activity
1612 .log(ActivityType::Query, "List GDPR deletion certificates");
1613
1614 let certificates = state.gdpr.audit_log().list_certificates();
1615 let total = certificates.len();
1616
1617 Json(ListCertificatesResponse {
1618 certificates,
1619 total,
1620 })
1621}
1622
1623pub async fn get_deletion_certificate(
1625 State(state): State<AppState>,
1626 Path(cert_id): Path<String>,
1627) -> impl IntoResponse {
1628 state.activity.log(
1629 ActivityType::Query,
1630 &format!("Get GDPR certificate: {}", cert_id),
1631 );
1632
1633 match state.gdpr.audit_log().get_certificate(&cert_id) {
1634 Some(cert) => (StatusCode::OK, Json(Some(cert))),
1635 None => (StatusCode::NOT_FOUND, Json(None)),
1636 }
1637}
1638
1639pub async fn verify_deletion_certificate(
1641 State(state): State<AppState>,
1642 Path(cert_id): Path<String>,
1643) -> Json<VerifyCertificateResponse> {
1644 state.activity.log(
1645 ActivityType::Query,
1646 &format!("Verify GDPR certificate: {}", cert_id),
1647 );
1648
1649 match state.gdpr.audit_log().get_certificate(&cert_id) {
1650 Some(cert) => {
1651 let valid = cert.verify();
1652 Json(VerifyCertificateResponse {
1653 valid,
1654 certificate: Some(cert),
1655 message: if valid {
1656 "Certificate is valid and has not been tampered with".to_string()
1657 } else {
1658 "Certificate verification failed - hash mismatch".to_string()
1659 },
1660 })
1661 }
1662 None => Json(VerifyCertificateResponse {
1663 valid: false,
1664 certificate: None,
1665 message: format!("Certificate '{}' not found", cert_id),
1666 }),
1667 }
1668}
1669
1670pub async fn get_deletion_audit(
1672 State(state): State<AppState>,
1673 Path(subject_id): Path<String>,
1674) -> Json<Vec<DeletionAuditEntry>> {
1675 state.activity.log(
1676 ActivityType::Query,
1677 &format!("Get GDPR audit for: {}", subject_id),
1678 );
1679
1680 Json(state.gdpr.audit_log().get_entries_for_subject(&subject_id))
1681}
1682
1683pub async fn verify_audit_integrity(State(state): State<AppState>) -> impl IntoResponse {
1685 state
1686 .activity
1687 .log(ActivityType::Query, "Verify GDPR audit log integrity");
1688
1689 match state.gdpr.audit_log().verify_integrity() {
1690 Ok(count) => (
1691 StatusCode::OK,
1692 Json(serde_json::json!({
1693 "valid": true,
1694 "entries_verified": count,
1695 "message": "Audit log integrity verified successfully",
1696 })),
1697 ),
1698 Err(e) => (
1699 StatusCode::OK,
1700 Json(serde_json::json!({
1701 "valid": false,
1702 "entries_verified": 0,
1703 "message": e,
1704 })),
1705 ),
1706 }
1707}
1708
1709pub async fn export_data_subject(
1725 State(state): State<AppState>,
1726 Json(request): Json<ExportRequest>,
1727) -> impl IntoResponse {
1728 if request.subject_id.is_empty() {
1730 return (
1731 StatusCode::BAD_REQUEST,
1732 Json(serde_json::json!({
1733 "success": false,
1734 "error": "subject_id is required",
1735 })),
1736 );
1737 }
1738
1739 state.activity.log_with_details(
1740 ActivityType::Query,
1741 &format!(
1742 "GDPR data export request for subject: {}",
1743 request.subject_id
1744 ),
1745 None,
1746 None,
1747 Some("gdpr"),
1748 Some(serde_json::json!({
1749 "format": request.format,
1750 "scope": request.scope,
1751 "date_range": request.date_range,
1752 })),
1753 );
1754
1755 match state.gdpr.execute_export(&state, request) {
1756 Ok(response) => {
1757 state.activity.log_with_details(
1758 ActivityType::System,
1759 &format!(
1760 "GDPR data export completed. Export ID: {}",
1761 response.export_id
1762 ),
1763 None,
1764 None,
1765 Some("gdpr"),
1766 Some(serde_json::json!({
1767 "export_id": response.export_id,
1768 "total_items": response.total_items,
1769 "format": response.format,
1770 })),
1771 );
1772
1773 (
1774 StatusCode::OK,
1775 Json(serde_json::json!({
1776 "success": true,
1777 "export_id": response.export_id,
1778 "subject_id": response.subject_id,
1779 "format": response.format,
1780 "generated_at": response.generated_at,
1781 "total_items": response.total_items,
1782 "data": response.data,
1783 "scope": response.scope,
1784 "date_range": response.date_range,
1785 })),
1786 )
1787 }
1788 Err(e) => {
1789 state.activity.log_with_details(
1790 ActivityType::System,
1791 &format!("GDPR data export failed: {}", e),
1792 None,
1793 None,
1794 Some("gdpr"),
1795 None,
1796 );
1797
1798 (
1799 StatusCode::INTERNAL_SERVER_ERROR,
1800 Json(serde_json::json!({
1801 "success": false,
1802 "error": e,
1803 })),
1804 )
1805 }
1806 }
1807}
1808
1809fn hex_encode(bytes: &[u8]) -> String {
1815 const HEX_CHARS: &[u8] = b"0123456789abcdef";
1816 let mut result = String::with_capacity(bytes.len() * 2);
1817 for &byte in bytes {
1818 result.push(HEX_CHARS[(byte >> 4) as usize] as char);
1819 result.push(HEX_CHARS[(byte & 0x0f) as usize] as char);
1820 }
1821 result
1822}
1823
1824#[cfg(test)]
1829mod tests {
1830 use super::*;
1831
1832 #[test]
1833 fn test_deletion_scope_default() {
1834 let scope = DeletionScope::default();
1835 assert_eq!(scope, DeletionScope::All);
1836 }
1837
1838 #[test]
1839 fn test_deletion_certificate_hash() {
1840 let items = vec![DeletedItem {
1841 store_type: "kv".to_string(),
1842 location: "kv_store".to_string(),
1843 item_id: "user:123".to_string(),
1844 size_bytes: Some(256),
1845 deleted_at: "2025-01-26T12:00:00Z".to_string(),
1846 }];
1847
1848 let cert = DeletionCertificate::new(
1849 "user@example.com".to_string(),
1850 items,
1851 DeletionScope::All,
1852 "admin".to_string(),
1853 "GDPR request".to_string(),
1854 "node-1".to_string(),
1855 false,
1856 );
1857
1858 assert!(!cert.verification_hash.is_empty());
1860
1861 assert!(cert.verify());
1863 }
1864
1865 #[test]
1866 fn test_deletion_certificate_tamper_detection() {
1867 let items = vec![DeletedItem {
1868 store_type: "kv".to_string(),
1869 location: "kv_store".to_string(),
1870 item_id: "user:123".to_string(),
1871 size_bytes: Some(256),
1872 deleted_at: "2025-01-26T12:00:00Z".to_string(),
1873 }];
1874
1875 let mut cert = DeletionCertificate::new(
1876 "user@example.com".to_string(),
1877 items,
1878 DeletionScope::All,
1879 "admin".to_string(),
1880 "GDPR request".to_string(),
1881 "node-1".to_string(),
1882 false,
1883 );
1884
1885 cert.total_items = 999;
1887
1888 assert!(!cert.verify());
1890 }
1891
1892 #[test]
1893 fn test_audit_log_integrity() {
1894 let audit_log = DeletionAuditLog::new();
1895
1896 audit_log.log_event(
1898 DeletionEventType::RequestReceived,
1899 "user@example.com",
1900 "admin",
1901 serde_json::json!({"test": true}),
1902 );
1903 audit_log.log_event(
1904 DeletionEventType::DeletionStarted,
1905 "user@example.com",
1906 "admin",
1907 serde_json::json!({}),
1908 );
1909 audit_log.log_event(
1910 DeletionEventType::DeletionCompleted,
1911 "user@example.com",
1912 "admin",
1913 serde_json::json!({"items": 5}),
1914 );
1915
1916 let result = audit_log.verify_integrity();
1918 assert!(result.is_ok());
1919 assert_eq!(result.unwrap(), 3);
1920 }
1921
1922 #[test]
1923 fn test_audit_log_get_entries_for_subject() {
1924 let audit_log = DeletionAuditLog::new();
1925
1926 audit_log.log_event(
1927 DeletionEventType::RequestReceived,
1928 "user1@example.com",
1929 "admin",
1930 serde_json::json!({}),
1931 );
1932 audit_log.log_event(
1933 DeletionEventType::RequestReceived,
1934 "user2@example.com",
1935 "admin",
1936 serde_json::json!({}),
1937 );
1938 audit_log.log_event(
1939 DeletionEventType::DeletionCompleted,
1940 "user1@example.com",
1941 "admin",
1942 serde_json::json!({}),
1943 );
1944
1945 let user1_entries = audit_log.get_entries_for_subject("user1@example.com");
1946 assert_eq!(user1_entries.len(), 2);
1947
1948 let user2_entries = audit_log.get_entries_for_subject("user2@example.com");
1949 assert_eq!(user2_entries.len(), 1);
1950 }
1951
1952 #[test]
1953 fn test_gdpr_service_default_search_fields() {
1954 let fields = GdprService::default_search_fields();
1955 assert!(fields.contains(&"email".to_string()));
1956 assert!(fields.contains(&"user_id".to_string()));
1957 assert!(fields.contains(&"customer_id".to_string()));
1958 }
1959
1960 #[test]
1961 fn test_hex_encode() {
1962 let data = [0x00, 0x01, 0x0a, 0xff, 0xab];
1963 let hex = hex_encode(&data);
1964 assert_eq!(hex, "00010affab");
1965 }
1966
1967 #[test]
1968 fn test_deletion_scope_serialization() {
1969 let scope = DeletionScope::SpecificCollections {
1970 collections: vec!["users".to_string(), "orders".to_string()],
1971 };
1972 let json = serde_json::to_string(&scope).unwrap();
1973 assert!(json.contains("specific_collections"));
1974 assert!(json.contains("users"));
1975
1976 let deserialized: DeletionScope = serde_json::from_str(&json).unwrap();
1977 match deserialized {
1978 DeletionScope::SpecificCollections { collections } => {
1979 assert_eq!(collections.len(), 2);
1980 assert!(collections.contains(&"users".to_string()));
1981 }
1982 _ => panic!("Wrong variant deserialized"),
1983 }
1984 }
1985
1986 #[test]
1991 fn test_export_format_default() {
1992 let format = ExportFormat::default();
1993 assert_eq!(format, ExportFormat::Json);
1994 }
1995
1996 #[test]
1997 fn test_export_format_serialization() {
1998 let format = ExportFormat::Json;
2000 let json = serde_json::to_string(&format).unwrap();
2001 assert_eq!(json, "\"json\"");
2002
2003 let deserialized: ExportFormat = serde_json::from_str(&json).unwrap();
2004 assert_eq!(deserialized, ExportFormat::Json);
2005
2006 let format = ExportFormat::Csv;
2008 let json = serde_json::to_string(&format).unwrap();
2009 assert_eq!(json, "\"csv\"");
2010
2011 let deserialized: ExportFormat = serde_json::from_str(&json).unwrap();
2012 assert_eq!(deserialized, ExportFormat::Csv);
2013 }
2014
2015 #[test]
2016 fn test_export_scope_default() {
2017 let scope = ExportScope::default();
2018 match scope {
2019 ExportScope::All => {} _ => panic!("Expected ExportScope::All as default"),
2021 }
2022 }
2023
2024 #[test]
2025 fn test_export_scope_serialization() {
2026 let scope = ExportScope::SpecificCollections {
2027 collections: vec!["users".to_string(), "orders".to_string()],
2028 };
2029 let json = serde_json::to_string(&scope).unwrap();
2030 assert!(json.contains("specific_collections"));
2031 assert!(json.contains("users"));
2032
2033 let deserialized: ExportScope = serde_json::from_str(&json).unwrap();
2034 match deserialized {
2035 ExportScope::SpecificCollections { collections } => {
2036 assert_eq!(collections.len(), 2);
2037 assert!(collections.contains(&"users".to_string()));
2038 }
2039 _ => panic!("Wrong variant deserialized"),
2040 }
2041 }
2042
2043 #[test]
2044 fn test_export_request_deserialization() {
2045 let json = r#"{"subject_id": "user@example.com"}"#;
2047 let request: ExportRequest = serde_json::from_str(json).unwrap();
2048 assert_eq!(request.subject_id, "user@example.com");
2049 assert_eq!(request.format, ExportFormat::Json);
2050 assert!(request.search_fields.is_empty());
2051
2052 let json = r#"{
2054 "subject_id": "user@example.com",
2055 "format": "csv",
2056 "scope": {"type": "specific_collections", "collections": ["users"]},
2057 "date_range": {"start": "2024-01-01T00:00:00Z", "end": "2024-12-31T23:59:59Z"},
2058 "search_fields": ["email", "user_id"]
2059 }"#;
2060 let request: ExportRequest = serde_json::from_str(json).unwrap();
2061 assert_eq!(request.subject_id, "user@example.com");
2062 assert_eq!(request.format, ExportFormat::Csv);
2063 assert_eq!(request.search_fields, vec!["email", "user_id"]);
2064 assert!(request.date_range.is_some());
2065 }
2066
2067 #[test]
2068 fn test_date_range() {
2069 let range = DateRange {
2070 start: Some("2024-01-01T00:00:00Z".to_string()),
2071 end: Some("2024-12-31T23:59:59Z".to_string()),
2072 };
2073
2074 let json = serde_json::to_string(&range).unwrap();
2075 assert!(json.contains("2024-01-01"));
2076 assert!(json.contains("2024-12-31"));
2077 }
2078
2079 #[test]
2080 fn test_escape_csv_field() {
2081 assert_eq!(escape_csv_field("hello"), "hello");
2083
2084 assert_eq!(escape_csv_field("hello,world"), "\"hello,world\"");
2086
2087 assert_eq!(escape_csv_field("hello\"world"), "\"hello\"\"world\"");
2089
2090 assert_eq!(escape_csv_field("hello\nworld"), "\"hello\nworld\"");
2092 }
2093
2094 #[test]
2095 fn test_json_value_to_csv_string() {
2096 assert_eq!(json_value_to_csv_string(&serde_json::Value::Null), "");
2097 assert_eq!(
2098 json_value_to_csv_string(&serde_json::Value::Bool(true)),
2099 "true"
2100 );
2101 assert_eq!(json_value_to_csv_string(&serde_json::json!(42)), "42");
2102 assert_eq!(
2103 json_value_to_csv_string(&serde_json::json!("hello")),
2104 "hello"
2105 );
2106 assert_eq!(
2107 json_value_to_csv_string(&serde_json::json!([1, 2, 3])),
2108 "[1,2,3]"
2109 );
2110 }
2111
2112 #[test]
2113 fn test_exported_item_structure() {
2114 let item = ExportedItem {
2115 store_type: "kv".to_string(),
2116 location: "kv_store".to_string(),
2117 item_id: "user:123".to_string(),
2118 data: serde_json::json!({"key": "user:123", "value": {"name": "John"}}),
2119 timestamp: Some("2024-01-26T12:00:00Z".to_string()),
2120 };
2121
2122 let json = serde_json::to_string(&item).unwrap();
2123 assert!(json.contains("kv_store"));
2124 assert!(json.contains("user:123"));
2125 assert!(json.contains("John"));
2126 }
2127
2128 #[test]
2129 fn test_export_event_types() {
2130 let event = DeletionEventType::ExportRequestReceived;
2132 let json = serde_json::to_string(&event).unwrap();
2133 assert_eq!(json, "\"export_request_received\"");
2134
2135 let event = DeletionEventType::ExportCompleted;
2136 let json = serde_json::to_string(&event).unwrap();
2137 assert_eq!(json, "\"export_completed\"");
2138
2139 let event = DeletionEventType::ExportFailed;
2140 let json = serde_json::to_string(&event).unwrap();
2141 assert_eq!(json, "\"export_failed\"");
2142 }
2143}