use axum::{ Json, Router, extract::{Path, Query, State}, http::StatusCode, response::IntoResponse, routing::{get, post}, }; use serde::Deserialize; use crate::access::{AccessControl, AgentRole}; pub fn router(ac: AccessControl) -> Router { Router::new() .route("/roles", get(list_roles)) .route("/roles", post(set_role)) // Scrum iter 11 / P13-001 finding: get_role was #[allow(dead_code)] // because nothing called it — dead until exposed. Route activates it. // Returns 404 when the agent isn't registered so clients can // distinguish "missing role" from "access denied." .route("/roles/{agent}", get(get_role)) .route("/enabled", get(enabled_status)) .route("/audit", get(query_audit)) .route("/check", post(check_access)) .with_state(ac) } async fn list_roles(State(ac): State) -> impl IntoResponse { Json(ac.list_roles().await) } async fn set_role( State(ac): State, Json(role): Json, ) -> impl IntoResponse { let name = role.agent_name.clone(); ac.set_role(role).await; (StatusCode::OK, format!("role set: {name}")) } #[derive(Deserialize)] struct AuditQuery { limit: Option, } async fn query_audit( State(ac): State, Query(q): Query, ) -> impl IntoResponse { Json(ac.recent_audit(q.limit.unwrap_or(50)).await) } #[derive(Deserialize)] struct CheckRequest { agent: String, sensitivity: shared::types::Sensitivity, } async fn check_access( State(ac): State, Json(req): Json, ) -> impl IntoResponse { let allowed = ac.can_access(&req.agent, &req.sensitivity).await; Json(serde_json::json!({ "agent": req.agent, "sensitivity": req.sensitivity, "allowed": allowed, })) } async fn get_role( State(ac): State, Path(agent): Path, ) -> impl IntoResponse { match ac.get_role(&agent).await { Some(role) => Ok(Json(role)), None => Err((StatusCode::NOT_FOUND, format!("no role registered for agent '{agent}'"))), } } async fn enabled_status(State(ac): State) -> impl IntoResponse { Json(serde_json::json!({ "enabled": ac.is_enabled() })) }