mockforge_http/handlers/
compliance_dashboard.rs

1//! HTTP handlers for compliance dashboard
2//!
3//! This module provides REST API endpoints for accessing compliance
4//! dashboard data, gaps, alerts, and control effectiveness metrics.
5
6use axum::{
7    extract::{Path, Query, State},
8    http::StatusCode,
9    response::Json,
10};
11use mockforge_core::security::compliance_dashboard::{
12    AlertType, ComplianceAlert, ComplianceDashboardEngine, ComplianceGap, ComplianceStandard,
13    GapSeverity,
14};
15use serde::{Deserialize, Serialize};
16use std::collections::HashMap;
17use std::sync::Arc;
18use tokio::sync::RwLock;
19use tracing::{error, info};
20
21/// State for compliance dashboard handlers
22#[derive(Clone)]
23pub struct ComplianceDashboardState {
24    /// Compliance dashboard engine
25    pub engine: Arc<RwLock<ComplianceDashboardEngine>>,
26}
27
28/// Request to add a compliance gap
29#[derive(Debug, Deserialize)]
30pub struct AddGapRequest {
31    /// Gap description
32    pub description: String,
33    /// Severity
34    pub severity: GapSeverity,
35    /// Standard
36    pub standard: ComplianceStandard,
37    /// Control ID (optional)
38    pub control_id: Option<String>,
39    /// Target remediation date (optional)
40    pub target_remediation_date: Option<chrono::DateTime<chrono::Utc>>,
41}
42
43/// Request to add a compliance alert
44#[derive(Debug, Deserialize)]
45pub struct AddAlertRequest {
46    /// Alert type
47    pub alert_type: AlertType,
48    /// Severity
49    pub severity: GapSeverity,
50    /// Message
51    pub message: String,
52    /// Standard (optional)
53    pub standard: Option<ComplianceStandard>,
54    /// Control ID (optional)
55    pub control_id: Option<String>,
56}
57
58/// Request to update gap status
59#[derive(Debug, Deserialize)]
60pub struct UpdateGapStatusRequest {
61    /// New status
62    pub status: mockforge_core::security::compliance_dashboard::GapStatus,
63}
64
65/// Get dashboard data
66///
67/// GET /api/v1/compliance/dashboard
68pub async fn get_dashboard(
69    State(state): State<ComplianceDashboardState>,
70) -> Result<Json<serde_json::Value>, StatusCode> {
71    let engine = state.engine.read().await;
72    let dashboard = engine.get_dashboard_data().await.map_err(|e| {
73        error!("Failed to get dashboard data: {}", e);
74        StatusCode::INTERNAL_SERVER_ERROR
75    })?;
76
77    Ok(Json(serde_json::to_value(&dashboard).unwrap()))
78}
79
80/// Get all compliance gaps
81///
82/// GET /api/v1/compliance/gaps
83pub async fn get_gaps(
84    State(state): State<ComplianceDashboardState>,
85    Query(params): Query<HashMap<String, String>>,
86) -> Result<Json<serde_json::Value>, StatusCode> {
87    let engine = state.engine.read().await;
88
89    let gaps = if let Some(severity_str) = params.get("severity") {
90        let severity = match severity_str.as_str() {
91            "critical" => GapSeverity::Critical,
92            "high" => GapSeverity::High,
93            "medium" => GapSeverity::Medium,
94            "low" => GapSeverity::Low,
95            _ => return Err(StatusCode::BAD_REQUEST),
96        };
97        engine.get_gaps_by_severity(severity).await.map_err(|e| {
98            error!("Failed to get gaps by severity: {}", e);
99            StatusCode::INTERNAL_SERVER_ERROR
100        })?
101    } else {
102        engine.get_all_gaps().await.map_err(|e| {
103            error!("Failed to get all gaps: {}", e);
104            StatusCode::INTERNAL_SERVER_ERROR
105        })?
106    };
107
108    Ok(Json(serde_json::to_value(&gaps).unwrap()))
109}
110
111/// Add a compliance gap
112///
113/// POST /api/v1/compliance/gaps
114pub async fn add_gap(
115    State(state): State<ComplianceDashboardState>,
116    Json(request): Json<AddGapRequest>,
117) -> Result<Json<serde_json::Value>, StatusCode> {
118    let gap_id = format!("GAP-{}", uuid::Uuid::new_v4().to_string().split('-').next().unwrap());
119
120    let engine = state.engine.write().await;
121    engine
122        .add_gap(
123            gap_id.clone(),
124            request.description,
125            request.severity,
126            request.standard,
127            request.control_id,
128            request.target_remediation_date,
129        )
130        .await
131        .map_err(|e| {
132            error!("Failed to add gap: {}", e);
133            StatusCode::INTERNAL_SERVER_ERROR
134        })?;
135
136    info!("Compliance gap added: {}", gap_id);
137
138    Ok(Json(serde_json::json!({
139        "gap_id": gap_id,
140        "status": "created"
141    })))
142}
143
144/// Update gap status
145///
146/// PATCH /api/v1/compliance/gaps/{gap_id}/status
147pub async fn update_gap_status(
148    State(state): State<ComplianceDashboardState>,
149    Path(gap_id): Path<String>,
150    Json(request): Json<UpdateGapStatusRequest>,
151) -> Result<Json<serde_json::Value>, StatusCode> {
152    let engine = state.engine.write().await;
153    engine.update_gap_status(&gap_id, request.status).await.map_err(|e| {
154        error!("Failed to update gap status: {}", e);
155        StatusCode::BAD_REQUEST
156    })?;
157
158    info!("Gap status updated: {}", gap_id);
159
160    Ok(Json(serde_json::json!({
161        "gap_id": gap_id,
162        "status": "updated"
163    })))
164}
165
166/// Get all compliance alerts
167///
168/// GET /api/v1/compliance/alerts
169pub async fn get_alerts(
170    State(state): State<ComplianceDashboardState>,
171    Query(params): Query<HashMap<String, String>>,
172) -> Result<Json<serde_json::Value>, StatusCode> {
173    let engine = state.engine.read().await;
174
175    let alerts = if let Some(severity_str) = params.get("severity") {
176        let severity = match severity_str.as_str() {
177            "critical" => GapSeverity::Critical,
178            "high" => GapSeverity::High,
179            "medium" => GapSeverity::Medium,
180            "low" => GapSeverity::Low,
181            _ => return Err(StatusCode::BAD_REQUEST),
182        };
183        engine.get_alerts_by_severity(severity).await.map_err(|e| {
184            error!("Failed to get alerts by severity: {}", e);
185            StatusCode::INTERNAL_SERVER_ERROR
186        })?
187    } else {
188        engine.get_all_alerts().await.map_err(|e| {
189            error!("Failed to get all alerts: {}", e);
190            StatusCode::INTERNAL_SERVER_ERROR
191        })?
192    };
193
194    Ok(Json(serde_json::to_value(&alerts).unwrap()))
195}
196
197/// Add a compliance alert
198///
199/// POST /api/v1/compliance/alerts
200pub async fn add_alert(
201    State(state): State<ComplianceDashboardState>,
202    Json(request): Json<AddAlertRequest>,
203) -> Result<Json<serde_json::Value>, StatusCode> {
204    let alert_id = format!("ALERT-{}", uuid::Uuid::new_v4().to_string().split('-').next().unwrap());
205
206    let engine = state.engine.write().await;
207    engine
208        .add_alert(
209            alert_id.clone(),
210            request.alert_type,
211            request.severity,
212            request.message,
213            request.standard,
214            request.control_id,
215        )
216        .await
217        .map_err(|e| {
218            error!("Failed to add alert: {}", e);
219            StatusCode::INTERNAL_SERVER_ERROR
220        })?;
221
222    info!("Compliance alert added: {}", alert_id);
223
224    Ok(Json(serde_json::json!({
225        "alert_id": alert_id,
226        "status": "created"
227    })))
228}
229
230/// Create compliance dashboard router
231pub fn compliance_dashboard_router(state: ComplianceDashboardState) -> axum::Router {
232    use axum::routing::{get, patch, post};
233
234    axum::Router::new()
235        .route("/dashboard", get(get_dashboard))
236        .route("/gaps", get(get_gaps))
237        .route("/gaps", post(add_gap))
238        .route("/gaps/{gap_id}/status", patch(update_gap_status))
239        .route("/alerts", get(get_alerts))
240        .route("/alerts", post(add_alert))
241        .with_state(state)
242}