koprogo_api/domain/entities/
gdpr_objection.rs1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
10pub struct GdprObjectionRequest {
11 pub id: Uuid,
12 pub user_id: Uuid,
13 pub organization_id: Option<Uuid>,
14 pub requested_at: DateTime<Utc>,
15 pub status: ObjectionStatus,
16 pub objection_type: ObjectionType,
17 pub processing_purposes: Vec<ProcessingPurpose>,
18 pub justification: Option<String>,
19 pub processed_at: Option<DateTime<Utc>>,
20 pub processed_by: Option<Uuid>,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
24pub enum ObjectionStatus {
25 Pending,
26 Accepted, Rejected, Partial, }
30
31#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
33pub enum ObjectionType {
34 LegitimateInterests,
36 DirectMarketing,
38 Profiling,
40 AutomatedDecisionMaking,
42 ScientificResearch,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
47pub struct ProcessingPurpose {
48 pub purpose: String,
49 pub accepted: Option<bool>, }
51
52impl GdprObjectionRequest {
53 pub fn new(
55 user_id: Uuid,
56 organization_id: Option<Uuid>,
57 objection_type: ObjectionType,
58 processing_purposes: Vec<String>,
59 justification: Option<String>,
60 ) -> Self {
61 Self {
62 id: Uuid::new_v4(),
63 user_id,
64 organization_id,
65 requested_at: Utc::now(),
66 status: ObjectionStatus::Pending,
67 objection_type,
68 processing_purposes: processing_purposes
69 .into_iter()
70 .map(|purpose| ProcessingPurpose {
71 purpose,
72 accepted: None,
73 })
74 .collect(),
75 justification,
76 processed_at: None,
77 processed_by: None,
78 }
79 }
80
81 pub fn accept(&mut self, admin_id: Uuid) {
83 self.status = ObjectionStatus::Accepted;
84 for purpose in &mut self.processing_purposes {
85 purpose.accepted = Some(true);
86 }
87 self.processed_at = Some(Utc::now());
88 self.processed_by = Some(admin_id);
89 }
90
91 pub fn reject(&mut self, admin_id: Uuid) {
93 self.status = ObjectionStatus::Rejected;
94 for purpose in &mut self.processing_purposes {
95 purpose.accepted = Some(false);
96 }
97 self.processed_at = Some(Utc::now());
98 self.processed_by = Some(admin_id);
99 }
100
101 pub fn partial_accept(&mut self, admin_id: Uuid, accepted_purposes: Vec<String>) {
103 self.status = ObjectionStatus::Partial;
104 for purpose in &mut self.processing_purposes {
105 purpose.accepted = Some(accepted_purposes.contains(&purpose.purpose));
106 }
107 self.processed_at = Some(Utc::now());
108 self.processed_by = Some(admin_id);
109 }
110
111 pub fn is_marketing_objection(&self) -> bool {
113 matches!(self.objection_type, ObjectionType::DirectMarketing)
114 }
115
116 pub fn is_pending(&self) -> bool {
118 matches!(self.status, ObjectionStatus::Pending)
119 }
120
121 pub fn get_accepted_purposes(&self) -> Vec<String> {
123 self.processing_purposes
124 .iter()
125 .filter(|p| p.accepted == Some(true))
126 .map(|p| p.purpose.clone())
127 .collect()
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[test]
136 fn test_create_objection_request() {
137 let user_id = Uuid::new_v4();
138 let org_id = Uuid::new_v4();
139 let purposes = vec!["email_marketing".to_string(), "sms_marketing".to_string()];
140
141 let request = GdprObjectionRequest::new(
142 user_id,
143 Some(org_id),
144 ObjectionType::DirectMarketing,
145 purposes.clone(),
146 Some("I don't want to receive marketing emails".to_string()),
147 );
148
149 assert_eq!(request.user_id, user_id);
150 assert_eq!(request.organization_id, Some(org_id));
151 assert!(request.is_pending());
152 assert!(request.is_marketing_objection());
153 assert_eq!(request.processing_purposes.len(), 2);
154 }
155
156 #[test]
157 fn test_accept_objection() {
158 let user_id = Uuid::new_v4();
159 let admin_id = Uuid::new_v4();
160 let purposes = vec!["profiling".to_string()];
161
162 let mut request =
163 GdprObjectionRequest::new(user_id, None, ObjectionType::Profiling, purposes, None);
164
165 request.accept(admin_id);
166
167 assert_eq!(request.status, ObjectionStatus::Accepted);
168 assert!(request
169 .processing_purposes
170 .iter()
171 .all(|p| p.accepted == Some(true)));
172 assert_eq!(request.get_accepted_purposes().len(), 1);
173 }
174
175 #[test]
176 fn test_reject_objection() {
177 let user_id = Uuid::new_v4();
178 let admin_id = Uuid::new_v4();
179 let purposes = vec!["legitimate_interest".to_string()];
180
181 let mut request = GdprObjectionRequest::new(
182 user_id,
183 None,
184 ObjectionType::LegitimateInterests,
185 purposes,
186 Some("Compelling legitimate grounds".to_string()),
187 );
188
189 request.reject(admin_id);
190
191 assert_eq!(request.status, ObjectionStatus::Rejected);
192 assert!(request
193 .processing_purposes
194 .iter()
195 .all(|p| p.accepted == Some(false)));
196 assert_eq!(request.get_accepted_purposes().len(), 0);
197 }
198
199 #[test]
200 fn test_partial_accept() {
201 let user_id = Uuid::new_v4();
202 let admin_id = Uuid::new_v4();
203 let purposes = vec![
204 "email_marketing".to_string(),
205 "analytics".to_string(),
206 "sms_marketing".to_string(),
207 ];
208
209 let mut request = GdprObjectionRequest::new(
210 user_id,
211 None,
212 ObjectionType::DirectMarketing,
213 purposes,
214 None,
215 );
216
217 request.partial_accept(
218 admin_id,
219 vec!["email_marketing".to_string(), "sms_marketing".to_string()],
220 );
221
222 assert_eq!(request.status, ObjectionStatus::Partial);
223 assert_eq!(request.get_accepted_purposes().len(), 2);
224 assert!(request
225 .get_accepted_purposes()
226 .contains(&"email_marketing".to_string()));
227 assert!(!request
228 .get_accepted_purposes()
229 .contains(&"analytics".to_string()));
230 }
231
232 #[test]
233 fn test_marketing_objection_detection() {
234 let user_id = Uuid::new_v4();
235 let purposes = vec!["marketing".to_string()];
236
237 let marketing_request = GdprObjectionRequest::new(
238 user_id,
239 None,
240 ObjectionType::DirectMarketing,
241 purposes.clone(),
242 None,
243 );
244
245 let profiling_request =
246 GdprObjectionRequest::new(user_id, None, ObjectionType::Profiling, purposes, None);
247
248 assert!(marketing_request.is_marketing_objection());
249 assert!(!profiling_request.is_marketing_objection());
250 }
251}