1use axum::{
7 extract::{Path, Query, State},
8 http::StatusCode,
9 response::Json,
10};
11use mockforge_core::security::compliance_dashboard::{
12 AlertType, ComplianceDashboardEngine, ComplianceStandard, GapSeverity,
13};
14use serde::Deserialize;
15use std::collections::HashMap;
16use std::sync::Arc;
17use tokio::sync::RwLock;
18use tracing::{error, info};
19
20#[derive(Clone)]
22pub struct ComplianceDashboardState {
23 pub engine: Arc<RwLock<ComplianceDashboardEngine>>,
25}
26
27#[derive(Debug, Deserialize)]
29pub struct AddGapRequest {
30 pub description: String,
32 pub severity: GapSeverity,
34 pub standard: ComplianceStandard,
36 pub control_id: Option<String>,
38 pub target_remediation_date: Option<chrono::DateTime<chrono::Utc>>,
40}
41
42#[derive(Debug, Deserialize)]
44pub struct AddAlertRequest {
45 pub alert_type: AlertType,
47 pub severity: GapSeverity,
49 pub message: String,
51 pub standard: Option<ComplianceStandard>,
53 pub control_id: Option<String>,
55}
56
57#[derive(Debug, Deserialize)]
59pub struct UpdateGapStatusRequest {
60 pub status: mockforge_core::security::compliance_dashboard::GapStatus,
62}
63
64pub async fn get_dashboard(
68 State(state): State<ComplianceDashboardState>,
69) -> Result<Json<serde_json::Value>, StatusCode> {
70 let engine = state.engine.read().await;
71 let dashboard = engine.get_dashboard_data().await.map_err(|e| {
72 error!("Failed to get dashboard data: {}", e);
73 StatusCode::INTERNAL_SERVER_ERROR
74 })?;
75
76 serde_json::to_value(&dashboard).map(Json).map_err(|e| {
77 error!("Failed to serialize dashboard: {}", e);
78 StatusCode::INTERNAL_SERVER_ERROR
79 })
80}
81
82pub async fn get_gaps(
86 State(state): State<ComplianceDashboardState>,
87 Query(params): Query<HashMap<String, String>>,
88) -> Result<Json<serde_json::Value>, StatusCode> {
89 let engine = state.engine.read().await;
90
91 let gaps = if let Some(severity_str) = params.get("severity") {
92 let severity = match severity_str.as_str() {
93 "critical" => GapSeverity::Critical,
94 "high" => GapSeverity::High,
95 "medium" => GapSeverity::Medium,
96 "low" => GapSeverity::Low,
97 _ => return Err(StatusCode::BAD_REQUEST),
98 };
99 engine.get_gaps_by_severity(severity).await.map_err(|e| {
100 error!("Failed to get gaps by severity: {}", e);
101 StatusCode::INTERNAL_SERVER_ERROR
102 })?
103 } else {
104 engine.get_all_gaps().await.map_err(|e| {
105 error!("Failed to get all gaps: {}", e);
106 StatusCode::INTERNAL_SERVER_ERROR
107 })?
108 };
109
110 serde_json::to_value(&gaps).map(Json).map_err(|e| {
111 error!("Failed to serialize gaps: {}", e);
112 StatusCode::INTERNAL_SERVER_ERROR
113 })
114}
115
116pub async fn add_gap(
120 State(state): State<ComplianceDashboardState>,
121 Json(request): Json<AddGapRequest>,
122) -> Result<Json<serde_json::Value>, StatusCode> {
123 let gap_id = format!(
124 "GAP-{}",
125 uuid::Uuid::new_v4().simple().to_string().get(..8).unwrap_or("00000000")
126 );
127
128 let engine = state.engine.write().await;
129 engine
130 .add_gap(
131 gap_id.clone(),
132 request.description,
133 request.severity,
134 request.standard,
135 request.control_id,
136 request.target_remediation_date,
137 )
138 .await
139 .map_err(|e| {
140 error!("Failed to add gap: {}", e);
141 StatusCode::INTERNAL_SERVER_ERROR
142 })?;
143
144 info!("Compliance gap added: {}", gap_id);
145
146 Ok(Json(serde_json::json!({
147 "gap_id": gap_id,
148 "status": "created"
149 })))
150}
151
152pub async fn update_gap_status(
156 State(state): State<ComplianceDashboardState>,
157 Path(gap_id): Path<String>,
158 Json(request): Json<UpdateGapStatusRequest>,
159) -> Result<Json<serde_json::Value>, StatusCode> {
160 let engine = state.engine.write().await;
161 engine.update_gap_status(&gap_id, request.status).await.map_err(|e| {
162 error!("Failed to update gap status: {}", e);
163 StatusCode::BAD_REQUEST
164 })?;
165
166 info!("Gap status updated: {}", gap_id);
167
168 Ok(Json(serde_json::json!({
169 "gap_id": gap_id,
170 "status": "updated"
171 })))
172}
173
174pub async fn get_alerts(
178 State(state): State<ComplianceDashboardState>,
179 Query(params): Query<HashMap<String, String>>,
180) -> Result<Json<serde_json::Value>, StatusCode> {
181 let engine = state.engine.read().await;
182
183 let alerts = if let Some(severity_str) = params.get("severity") {
184 let severity = match severity_str.as_str() {
185 "critical" => GapSeverity::Critical,
186 "high" => GapSeverity::High,
187 "medium" => GapSeverity::Medium,
188 "low" => GapSeverity::Low,
189 _ => return Err(StatusCode::BAD_REQUEST),
190 };
191 engine.get_alerts_by_severity(severity).await.map_err(|e| {
192 error!("Failed to get alerts by severity: {}", e);
193 StatusCode::INTERNAL_SERVER_ERROR
194 })?
195 } else {
196 engine.get_all_alerts().await.map_err(|e| {
197 error!("Failed to get all alerts: {}", e);
198 StatusCode::INTERNAL_SERVER_ERROR
199 })?
200 };
201
202 serde_json::to_value(&alerts).map(Json).map_err(|e| {
203 error!("Failed to serialize alerts: {}", e);
204 StatusCode::INTERNAL_SERVER_ERROR
205 })
206}
207
208pub async fn add_alert(
212 State(state): State<ComplianceDashboardState>,
213 Json(request): Json<AddAlertRequest>,
214) -> Result<Json<serde_json::Value>, StatusCode> {
215 let alert_id = format!(
216 "ALERT-{}",
217 uuid::Uuid::new_v4().simple().to_string().get(..8).unwrap_or("00000000")
218 );
219
220 let engine = state.engine.write().await;
221 engine
222 .add_alert(
223 alert_id.clone(),
224 request.alert_type,
225 request.severity,
226 request.message,
227 request.standard,
228 request.control_id,
229 )
230 .await
231 .map_err(|e| {
232 error!("Failed to add alert: {}", e);
233 StatusCode::INTERNAL_SERVER_ERROR
234 })?;
235
236 info!("Compliance alert added: {}", alert_id);
237
238 Ok(Json(serde_json::json!({
239 "alert_id": alert_id,
240 "status": "created"
241 })))
242}
243
244pub async fn get_compliance_status(
248 State(state): State<ComplianceDashboardState>,
249) -> Result<Json<serde_json::Value>, StatusCode> {
250 let engine = state.engine.read().await;
251 let dashboard = engine.get_dashboard_data().await.map_err(|e| {
252 error!("Failed to get dashboard data: {}", e);
253 StatusCode::INTERNAL_SERVER_ERROR
254 })?;
255
256 let mut by_area = serde_json::Map::new();
258 for (category, effectiveness) in &dashboard.control_effectiveness {
259 let category_name = match category {
260 mockforge_core::security::compliance_dashboard::ControlCategory::AccessControl => {
261 "access_control"
262 }
263 mockforge_core::security::compliance_dashboard::ControlCategory::Encryption => {
264 "encryption"
265 }
266 mockforge_core::security::compliance_dashboard::ControlCategory::Monitoring => {
267 "monitoring"
268 }
269 mockforge_core::security::compliance_dashboard::ControlCategory::ChangeManagement => {
270 "change_management"
271 }
272 mockforge_core::security::compliance_dashboard::ControlCategory::IncidentResponse => {
273 "incident_response"
274 }
275 };
276 by_area.insert(
277 category_name.to_string(),
278 serde_json::Value::Number(effectiveness.effectiveness.into()),
279 );
280 }
281
282 Ok(Json(serde_json::json!({
283 "overall_compliance": dashboard.overall_compliance,
284 "soc2_compliance": dashboard.soc2_compliance,
285 "iso27001_compliance": dashboard.iso27001_compliance,
286 "by_area": by_area,
287 "gaps": dashboard.gaps.total,
288 "remediation_in_progress": dashboard.remediation.in_progress
289 })))
290}
291
292pub async fn get_compliance_report(
296 State(state): State<ComplianceDashboardState>,
297 Path(period): Path<String>,
298 Query(params): Query<HashMap<String, String>>,
299) -> Result<Json<serde_json::Value>, StatusCode> {
300 let engine = state.engine.read().await;
301 let dashboard = engine.get_dashboard_data().await.map_err(|e| {
302 error!("Failed to get dashboard data: {}", e);
303 StatusCode::INTERNAL_SERVER_ERROR
304 })?;
305
306 let report_period = params
308 .get("month")
309 .or_else(|| params.get("period"))
310 .cloned()
311 .unwrap_or_else(|| {
312 let now = chrono::Utc::now();
314 now.format("%Y-%m").to_string()
315 });
316
317 let all_gaps = engine.get_all_gaps().await.map_err(|e| {
319 error!("Failed to get gaps: {}", e);
320 StatusCode::INTERNAL_SERVER_ERROR
321 })?;
322
323 let mut recommendations = Vec::new();
325 for gap in &all_gaps {
326 match gap.severity {
327 mockforge_core::security::compliance_dashboard::GapSeverity::Critical => {
328 recommendations.push(format!("Urgent: {}", gap.description));
329 }
330 mockforge_core::security::compliance_dashboard::GapSeverity::High => {
331 recommendations.push(format!("High priority: {}", gap.description));
332 }
333 _ => {}
334 }
335 }
336
337 if recommendations.is_empty() {
339 if dashboard
340 .control_effectiveness
341 .get(&mockforge_core::security::compliance_dashboard::ControlCategory::ChangeManagement)
342 .map(|e| e.effectiveness < 95)
343 .unwrap_or(false)
344 {
345 recommendations.push("Enhance change management procedures".to_string());
346 }
347 if dashboard
348 .control_effectiveness
349 .get(&mockforge_core::security::compliance_dashboard::ControlCategory::IncidentResponse)
350 .map(|e| e.effectiveness < 95)
351 .unwrap_or(false)
352 {
353 recommendations.push("Improve incident response time".to_string());
354 }
355 }
356
357 let gaps_summary: Vec<serde_json::Value> = all_gaps
359 .iter()
360 .take(10)
361 .map(|gap| {
362 serde_json::json!({
363 "id": gap.gap_id,
364 "severity": format!("{:?}", gap.severity).to_lowercase(),
365 "remediation_status": format!("{:?}", gap.status).to_lowercase()
366 })
367 })
368 .collect();
369
370 let mut control_effectiveness = serde_json::Map::new();
372 for (category, effectiveness) in &dashboard.control_effectiveness {
373 let category_name = match category {
374 mockforge_core::security::compliance_dashboard::ControlCategory::AccessControl => {
375 "access_control"
376 }
377 mockforge_core::security::compliance_dashboard::ControlCategory::Encryption => {
378 "encryption"
379 }
380 mockforge_core::security::compliance_dashboard::ControlCategory::Monitoring => {
381 "monitoring"
382 }
383 mockforge_core::security::compliance_dashboard::ControlCategory::ChangeManagement => {
384 "change_management"
385 }
386 mockforge_core::security::compliance_dashboard::ControlCategory::IncidentResponse => {
387 "incident_response"
388 }
389 };
390 control_effectiveness.insert(
391 category_name.to_string(),
392 serde_json::Value::Number(effectiveness.effectiveness.into()),
393 );
394 }
395
396 Ok(Json(serde_json::json!({
397 "report_period": report_period,
398 "overall_compliance": dashboard.overall_compliance,
399 "control_effectiveness": control_effectiveness,
400 "gaps": gaps_summary,
401 "recommendations": recommendations
402 })))
403}
404
405pub fn compliance_dashboard_router(state: ComplianceDashboardState) -> axum::Router {
407 use axum::routing::{get, patch, post};
408
409 axum::Router::new()
410 .route("/dashboard", get(get_dashboard))
411 .route("/status", get(get_compliance_status))
412 .route("/reports/:period", get(get_compliance_report))
413 .route("/gaps", get(get_gaps))
414 .route("/gaps", post(add_gap))
415 .route("/gaps/{gap_id}/status", patch(update_gap_status))
416 .route("/alerts", get(get_alerts))
417 .route("/alerts", post(add_alert))
418 .with_state(state)
419}