koprogo_api/infrastructure/web/handlers/
consent_handlers.rs1use crate::infrastructure::web::{AppState, AuthenticatedUser};
2use actix_web::{get, post, web, HttpRequest, HttpResponse, Responder};
3use serde::{Deserialize, Serialize};
4
5fn extract_ip_address(req: &HttpRequest) -> Option<String> {
7 req.headers()
9 .get("X-Forwarded-For")
10 .and_then(|h| h.to_str().ok())
11 .map(|s| s.split(',').next().unwrap_or("").trim().to_string())
12 .filter(|s| !s.is_empty())
13 .or_else(|| {
14 req.headers()
16 .get("X-Real-IP")
17 .and_then(|h| h.to_str().ok())
18 .map(|s| s.to_string())
19 })
20 .or_else(|| {
21 req.peer_addr().map(|addr| addr.ip().to_string())
23 })
24}
25
26fn extract_user_agent(req: &HttpRequest) -> Option<String> {
28 req.headers()
29 .get("User-Agent")
30 .and_then(|h| h.to_str().ok())
31 .map(|s| s.to_string())
32}
33
34#[derive(Debug, Deserialize, utoipa::ToSchema)]
36pub struct RecordConsentRequest {
37 pub consent_type: String,
39 #[serde(default)]
41 pub policy_version: Option<String>,
42}
43
44#[derive(Debug, Serialize, utoipa::ToSchema)]
46pub struct ConsentStatusResponse {
47 pub privacy_policy_accepted: bool,
49 pub terms_accepted: bool,
51 pub privacy_policy_accepted_at: Option<String>,
53 pub terms_accepted_at: Option<String>,
55 pub user_id: String,
57}
58
59#[derive(Debug, Serialize, utoipa::ToSchema)]
61pub struct ConsentRecordedResponse {
62 pub message: String,
64 pub consent_type: String,
66 pub accepted_at: String,
68}
69
70#[utoipa::path(
86 post,
87 path = "/consent",
88 tag = "GDPR",
89 summary = "Record user consent to privacy policy or terms",
90 request_body = RecordConsentRequest,
91 responses(
92 (status = 200, description = "Consent recorded", body = ConsentRecordedResponse),
93 (status = 400, description = "Bad request"),
94 (status = 401, description = "Unauthorized"),
95 (status = 500, description = "Internal server error"),
96 ),
97 security(("bearer_auth" = []))
98)]
99#[post("/consent")]
100pub async fn record_consent(
101 req: HttpRequest,
102 data: web::Data<AppState>,
103 auth: AuthenticatedUser,
104 body: web::Json<RecordConsentRequest>,
105) -> impl Responder {
106 let ip_address = extract_ip_address(&req);
108 let user_agent = extract_user_agent(&req);
109
110 let organization_id = match auth.require_organization() {
112 Ok(org_id) => org_id,
113 Err(_) => {
114 return HttpResponse::BadRequest().json(serde_json::json!({
115 "error": "Organization context required to record consent"
116 }));
117 }
118 };
119
120 match data
122 .consent_use_cases
123 .record_consent(
124 auth.user_id,
125 organization_id,
126 &body.consent_type,
127 ip_address,
128 user_agent,
129 body.policy_version.clone(),
130 )
131 .await
132 {
133 Ok(response) => HttpResponse::Ok().json(ConsentRecordedResponse {
134 message: response.message,
135 consent_type: response.consent_type,
136 accepted_at: response.accepted_at.to_rfc3339(),
137 }),
138 Err(e) if e.contains("Invalid consent type") => {
139 HttpResponse::BadRequest().json(serde_json::json!({ "error": e }))
140 }
141 Err(e) => {
142 log::error!("Failed to record consent: {}", e);
143 HttpResponse::InternalServerError().json(serde_json::json!({ "error": e }))
144 }
145 }
146}
147
148#[utoipa::path(
159 get,
160 path = "/consent/status",
161 tag = "GDPR",
162 summary = "Check user consent status",
163 responses(
164 (status = 200, description = "Consent status", body = ConsentStatusResponse),
165 (status = 401, description = "Unauthorized"),
166 (status = 500, description = "Internal server error"),
167 ),
168 security(("bearer_auth" = []))
169)]
170#[get("/consent/status")]
171pub async fn get_consent_status(
172 _req: HttpRequest,
173 data: web::Data<AppState>,
174 auth: AuthenticatedUser,
175) -> impl Responder {
176 match data
177 .consent_use_cases
178 .get_consent_status(auth.user_id)
179 .await
180 {
181 Ok(status) => HttpResponse::Ok().json(ConsentStatusResponse {
182 privacy_policy_accepted: status.privacy_policy_accepted,
183 terms_accepted: status.terms_accepted,
184 privacy_policy_accepted_at: status.privacy_policy_accepted_at.map(|t| t.to_rfc3339()),
185 terms_accepted_at: status.terms_accepted_at.map(|t| t.to_rfc3339()),
186 user_id: auth.user_id.to_string(),
187 }),
188 Err(e) => {
189 log::error!("Failed to get consent status: {}", e);
190 HttpResponse::InternalServerError().json(serde_json::json!({ "error": e }))
191 }
192 }
193}