koprogo_api/infrastructure/web/handlers/
gdpr_art30_handlers.rs

1use crate::infrastructure::web::{AppState, AuthenticatedUser};
2use actix_web::{get, web, HttpResponse, Responder};
3use serde::Serialize;
4
5/// DTO for data processing activity (Art. 30 register)
6#[derive(Debug, Serialize)]
7pub struct DataProcessingActivityDto {
8    pub id: String,
9    pub activity_name: String,
10    pub controller_name: String,
11    pub purpose: String,
12    pub legal_basis: String,
13    pub data_categories: Vec<String>,
14    pub data_subjects: Vec<String>,
15    pub recipients: Vec<String>,
16    pub retention_period: String,
17    pub security_measures: String,
18    pub created_at: String,
19    pub updated_at: String,
20}
21
22/// DTO for data processor agreement (DPA with sub-processors)
23#[derive(Debug, Serialize)]
24pub struct DataProcessorAgreementDto {
25    pub id: String,
26    pub processor_name: String,
27    pub service_description: String,
28    pub dpa_signed_at: Option<String>,
29    pub dpa_url: Option<String>,
30    pub transfer_mechanism: Option<String>,
31    pub data_categories: Vec<String>,
32    pub certifications: Option<Vec<String>>,
33    pub created_at: String,
34    pub updated_at: String,
35}
36
37/// Response for paginated list of processing activities
38#[derive(Debug, Serialize)]
39pub struct ProcessingActivitiesResponse {
40    pub activities: Vec<DataProcessingActivityDto>,
41    pub total: i64,
42}
43
44/// Response for paginated list of sub-processors
45#[derive(Debug, Serialize)]
46pub struct ProcessorsResponse {
47    pub processors: Vec<DataProcessorAgreementDto>,
48    pub total: i64,
49}
50
51/// GET /api/v1/admin/gdpr/processing-register
52/// List all GDPR Article 30 data processing activities (SuperAdmin only)
53///
54/// # Returns
55/// * `200 OK` - List of all data processing activities
56/// * `401 Unauthorized` - Missing or invalid authentication
57/// * `403 Forbidden` - User is not SuperAdmin
58/// * `500 Internal Server Error` - Database error
59#[get("/admin/gdpr/processing-register")]
60pub async fn list_processing_activities(
61    data: web::Data<AppState>,
62    auth: AuthenticatedUser,
63) -> impl Responder {
64    // Only SuperAdmin can view processing register
65    if auth.role != "superadmin" {
66        return HttpResponse::Forbidden().json(serde_json::json!({
67            "error": "Access denied. SuperAdmin role required."
68        }));
69    }
70
71    // Fetch processing activities from database
72    match data.pool.acquire().await {
73        Ok(mut conn) => {
74            match sqlx::query_as!(
75                ProcessingActivityRow,
76                r#"
77                SELECT
78                    id,
79                    activity_name,
80                    controller_name,
81                    purpose,
82                    legal_basis,
83                    data_categories,
84                    data_subjects,
85                    recipients,
86                    retention_period,
87                    security_measures,
88                    created_at,
89                    updated_at
90                FROM data_processing_activities
91                ORDER BY created_at DESC
92                "#
93            )
94            .fetch_all(&mut *conn)
95            .await
96            {
97                Ok(rows) => {
98                    let total = rows.len() as i64;
99                    let activities: Vec<DataProcessingActivityDto> = rows
100                        .into_iter()
101                        .map(|row| DataProcessingActivityDto {
102                            id: row.id.to_string(),
103                            activity_name: row.activity_name,
104                            controller_name: row.controller_name,
105                            purpose: row.purpose,
106                            legal_basis: row.legal_basis,
107                            data_categories: row.data_categories.unwrap_or_default(),
108                            data_subjects: row.data_subjects.unwrap_or_default(),
109                            recipients: row.recipients.unwrap_or_default(),
110                            retention_period: row.retention_period,
111                            security_measures: row.security_measures,
112                            created_at: row.created_at.to_rfc3339(),
113                            updated_at: row.updated_at.to_rfc3339(),
114                        })
115                        .collect();
116
117                    HttpResponse::Ok().json(ProcessingActivitiesResponse { activities, total })
118                }
119                Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({
120                    "error": format!("Failed to fetch processing activities: {}", e)
121                })),
122            }
123        }
124        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({
125            "error": format!("Database connection error: {}", e)
126        })),
127    }
128}
129
130/// GET /api/v1/admin/gdpr/processors
131/// List all sub-processor agreements and DPA status (SuperAdmin only)
132///
133/// # Returns
134/// * `200 OK` - List of all sub-processors with DPA status
135/// * `401 Unauthorized` - Missing or invalid authentication
136/// * `403 Forbidden` - User is not SuperAdmin
137/// * `500 Internal Server Error` - Database error
138#[get("/admin/gdpr/processors")]
139pub async fn list_sub_processors(
140    data: web::Data<AppState>,
141    auth: AuthenticatedUser,
142) -> impl Responder {
143    // Only SuperAdmin can view sub-processor register
144    if auth.role != "superadmin" {
145        return HttpResponse::Forbidden().json(serde_json::json!({
146            "error": "Access denied. SuperAdmin role required."
147        }));
148    }
149
150    // Fetch sub-processor agreements from database
151    match data.pool.acquire().await {
152        Ok(mut conn) => {
153            match sqlx::query_as!(
154                ProcessorAgreementRow,
155                r#"
156                SELECT
157                    id,
158                    processor_name,
159                    service_description,
160                    dpa_signed_at,
161                    dpa_url,
162                    transfer_mechanism,
163                    data_categories,
164                    certifications,
165                    created_at,
166                    updated_at
167                FROM data_processor_agreements
168                ORDER BY processor_name ASC
169                "#
170            )
171            .fetch_all(&mut *conn)
172            .await
173            {
174                Ok(rows) => {
175                    let total = rows.len() as i64;
176                    let processors: Vec<DataProcessorAgreementDto> = rows
177                        .into_iter()
178                        .map(|row| DataProcessorAgreementDto {
179                            id: row.id.to_string(),
180                            processor_name: row.processor_name,
181                            service_description: row.service_description,
182                            dpa_signed_at: row.dpa_signed_at.map(|dt| dt.to_rfc3339()),
183                            dpa_url: row.dpa_url,
184                            transfer_mechanism: row.transfer_mechanism,
185                            data_categories: row.data_categories.unwrap_or_default(),
186                            certifications: row.certifications,
187                            created_at: row.created_at.to_rfc3339(),
188                            updated_at: row.updated_at.to_rfc3339(),
189                        })
190                        .collect();
191
192                    HttpResponse::Ok().json(ProcessorsResponse { processors, total })
193                }
194                Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({
195                    "error": format!("Failed to fetch sub-processors: {}", e)
196                })),
197            }
198        }
199        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({
200            "error": format!("Database connection error: {}", e)
201        })),
202    }
203}
204
205// Internal structs for sqlx query results
206struct ProcessingActivityRow {
207    id: uuid::Uuid,
208    activity_name: String,
209    controller_name: String,
210    purpose: String,
211    legal_basis: String,
212    data_categories: Option<Vec<String>>,
213    data_subjects: Option<Vec<String>>,
214    recipients: Option<Vec<String>>,
215    retention_period: String,
216    security_measures: String,
217    created_at: chrono::DateTime<chrono::Utc>,
218    updated_at: chrono::DateTime<chrono::Utc>,
219}
220
221struct ProcessorAgreementRow {
222    id: uuid::Uuid,
223    processor_name: String,
224    service_description: String,
225    dpa_signed_at: Option<chrono::DateTime<chrono::Utc>>,
226    dpa_url: Option<String>,
227    transfer_mechanism: Option<String>,
228    data_categories: Option<Vec<String>>,
229    certifications: Option<Vec<String>>,
230    created_at: chrono::DateTime<chrono::Utc>,
231    updated_at: chrono::DateTime<chrono::Utc>,
232}
233
234#[cfg(test)]
235mod tests {
236    #[test]
237    fn test_handler_structure_list_processing_activities() {
238        // Structural test - actual testing in E2E
239    }
240
241    #[test]
242    fn test_handler_structure_list_sub_processors() {
243        // Structural test - actual testing in E2E
244    }
245}