1use 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#[derive(Clone)]
23pub struct ComplianceDashboardState {
24 pub engine: Arc<RwLock<ComplianceDashboardEngine>>,
26}
27
28#[derive(Debug, Deserialize)]
30pub struct AddGapRequest {
31 pub description: String,
33 pub severity: GapSeverity,
35 pub standard: ComplianceStandard,
37 pub control_id: Option<String>,
39 pub target_remediation_date: Option<chrono::DateTime<chrono::Utc>>,
41}
42
43#[derive(Debug, Deserialize)]
45pub struct AddAlertRequest {
46 pub alert_type: AlertType,
48 pub severity: GapSeverity,
50 pub message: String,
52 pub standard: Option<ComplianceStandard>,
54 pub control_id: Option<String>,
56}
57
58#[derive(Debug, Deserialize)]
60pub struct UpdateGapStatusRequest {
61 pub status: mockforge_core::security::compliance_dashboard::GapStatus,
63}
64
65pub 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
80pub 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
111pub 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
144pub 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
166pub 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
197pub 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
230pub 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}