1use axum::{
7 extract::{Path, Query, State},
8 http::StatusCode,
9 response::Json,
10};
11use mockforge_core::security::compliance_dashboard::{
12 ComplianceDashboardEngine, ComplianceGap, ComplianceAlert, GapSeverity, AlertType, ComplianceStandard,
13};
14use serde::{Deserialize, Serialize};
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
72 .get_dashboard_data()
73 .await
74 .map_err(|e| {
75 error!("Failed to get dashboard data: {}", e);
76 StatusCode::INTERNAL_SERVER_ERROR
77 })?;
78
79 Ok(Json(serde_json::to_value(&dashboard).unwrap()))
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
100 .get_gaps_by_severity(severity)
101 .await
102 .map_err(|e| {
103 error!("Failed to get gaps by severity: {}", e);
104 StatusCode::INTERNAL_SERVER_ERROR
105 })?
106 } else {
107 engine
108 .get_all_gaps()
109 .await
110 .map_err(|e| {
111 error!("Failed to get all gaps: {}", e);
112 StatusCode::INTERNAL_SERVER_ERROR
113 })?
114 };
115
116 Ok(Json(serde_json::to_value(&gaps).unwrap()))
117}
118
119pub async fn add_gap(
123 State(state): State<ComplianceDashboardState>,
124 Json(request): Json<AddGapRequest>,
125) -> Result<Json<serde_json::Value>, StatusCode> {
126 let gap_id = format!("GAP-{}", uuid::Uuid::new_v4().to_string().split('-').next().unwrap());
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
162 .update_gap_status(&gap_id, request.status)
163 .await
164 .map_err(|e| {
165 error!("Failed to update gap status: {}", e);
166 StatusCode::BAD_REQUEST
167 })?;
168
169 info!("Gap status updated: {}", gap_id);
170
171 Ok(Json(serde_json::json!({
172 "gap_id": gap_id,
173 "status": "updated"
174 })))
175}
176
177pub async fn get_alerts(
181 State(state): State<ComplianceDashboardState>,
182 Query(params): Query<HashMap<String, String>>,
183) -> Result<Json<serde_json::Value>, StatusCode> {
184 let engine = state.engine.read().await;
185
186 let alerts = if let Some(severity_str) = params.get("severity") {
187 let severity = match severity_str.as_str() {
188 "critical" => GapSeverity::Critical,
189 "high" => GapSeverity::High,
190 "medium" => GapSeverity::Medium,
191 "low" => GapSeverity::Low,
192 _ => return Err(StatusCode::BAD_REQUEST),
193 };
194 engine
195 .get_alerts_by_severity(severity)
196 .await
197 .map_err(|e| {
198 error!("Failed to get alerts by severity: {}", e);
199 StatusCode::INTERNAL_SERVER_ERROR
200 })?
201 } else {
202 engine
203 .get_all_alerts()
204 .await
205 .map_err(|e| {
206 error!("Failed to get all alerts: {}", e);
207 StatusCode::INTERNAL_SERVER_ERROR
208 })?
209 };
210
211 Ok(Json(serde_json::to_value(&alerts).unwrap()))
212}
213
214pub async fn add_alert(
218 State(state): State<ComplianceDashboardState>,
219 Json(request): Json<AddAlertRequest>,
220) -> Result<Json<serde_json::Value>, StatusCode> {
221 let alert_id = format!("ALERT-{}", uuid::Uuid::new_v4().to_string().split('-').next().unwrap());
222
223 let engine = state.engine.write().await;
224 engine
225 .add_alert(
226 alert_id.clone(),
227 request.alert_type,
228 request.severity,
229 request.message,
230 request.standard,
231 request.control_id,
232 )
233 .await
234 .map_err(|e| {
235 error!("Failed to add alert: {}", e);
236 StatusCode::INTERNAL_SERVER_ERROR
237 })?;
238
239 info!("Compliance alert added: {}", alert_id);
240
241 Ok(Json(serde_json::json!({
242 "alert_id": alert_id,
243 "status": "created"
244 })))
245}
246
247pub fn compliance_dashboard_router(state: ComplianceDashboardState) -> axum::Router {
249 use axum::routing::{get, post, patch};
250
251 axum::Router::new()
252 .route("/dashboard", get(get_dashboard))
253 .route("/gaps", get(get_gaps))
254 .route("/gaps", post(add_gap))
255 .route("/gaps/{gap_id}/status", patch(update_gap_status))
256 .route("/alerts", get(get_alerts))
257 .route("/alerts", post(add_alert))
258 .with_state(state)
259}