package handlers import ( "encoding/json" "net/http" "github.com/breakpilot/ai-compliance-sdk/internal/iace" "github.com/breakpilot/ai-compliance-sdk/internal/rbac" "github.com/gin-gonic/gin" "github.com/google/uuid" ) // ============================================================================ // Mitigations // ============================================================================ // CreateMitigation handles POST /projects/:id/hazards/:hid/mitigations // Creates a new mitigation measure for a hazard. func (h *IACEHandler) CreateMitigation(c *gin.Context) { projectID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"}) return } hazardID, err := uuid.Parse(c.Param("hid")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid hazard ID"}) return } var req iace.CreateMitigationRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Override hazard ID from URL path req.HazardID = hazardID mitigation, err := h.store.CreateMitigation(c.Request.Context(), req) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } // Update hazard status to mitigated h.store.UpdateHazard(c.Request.Context(), hazardID, map[string]interface{}{ "status": string(iace.HazardStatusMitigated), }) // Audit trail userID := rbac.GetUserID(c) newVals, _ := json.Marshal(mitigation) h.store.AddAuditEntry( c.Request.Context(), projectID, "mitigation", mitigation.ID, iace.AuditActionCreate, userID.String(), nil, newVals, ) c.JSON(http.StatusCreated, gin.H{"mitigation": mitigation}) } // UpdateMitigation handles PUT /mitigations/:mid // Updates a mitigation measure with the provided fields. func (h *IACEHandler) UpdateMitigation(c *gin.Context) { mitigationID, err := uuid.Parse(c.Param("mid")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid mitigation ID"}) return } var updates map[string]interface{} if err := c.ShouldBindJSON(&updates); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } mitigation, err := h.store.UpdateMitigation(c.Request.Context(), mitigationID, updates) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if mitigation == nil { c.JSON(http.StatusNotFound, gin.H{"error": "mitigation not found"}) return } c.JSON(http.StatusOK, gin.H{"mitigation": mitigation}) } // VerifyMitigation handles POST /mitigations/:mid/verify // Marks a mitigation as verified with a verification result. func (h *IACEHandler) VerifyMitigation(c *gin.Context) { mitigationID, err := uuid.Parse(c.Param("mid")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid mitigation ID"}) return } var req struct { VerificationResult string `json:"verification_result" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } userID := rbac.GetUserID(c) if err := h.store.VerifyMitigation( c.Request.Context(), mitigationID, req.VerificationResult, userID.String(), ); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "mitigation verified"}) } // ============================================================================ // Evidence & Verification // ============================================================================ // UploadEvidence handles POST /projects/:id/evidence // Creates a new evidence record for a project. func (h *IACEHandler) UploadEvidence(c *gin.Context) { projectID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"}) return } var req struct { MitigationID *uuid.UUID `json:"mitigation_id,omitempty"` VerificationPlanID *uuid.UUID `json:"verification_plan_id,omitempty"` FileName string `json:"file_name" binding:"required"` FilePath string `json:"file_path" binding:"required"` FileHash string `json:"file_hash" binding:"required"` FileSize int64 `json:"file_size" binding:"required"` MimeType string `json:"mime_type" binding:"required"` Description string `json:"description,omitempty"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } userID := rbac.GetUserID(c) evidence := &iace.Evidence{ ProjectID: projectID, MitigationID: req.MitigationID, VerificationPlanID: req.VerificationPlanID, FileName: req.FileName, FilePath: req.FilePath, FileHash: req.FileHash, FileSize: req.FileSize, MimeType: req.MimeType, Description: req.Description, UploadedBy: userID, } if err := h.store.CreateEvidence(c.Request.Context(), evidence); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } // Audit trail newVals, _ := json.Marshal(evidence) h.store.AddAuditEntry( c.Request.Context(), projectID, "evidence", evidence.ID, iace.AuditActionCreate, userID.String(), nil, newVals, ) c.JSON(http.StatusCreated, gin.H{"evidence": evidence}) } // ListEvidence handles GET /projects/:id/evidence // Lists all evidence records for a project. func (h *IACEHandler) ListEvidence(c *gin.Context) { projectID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"}) return } evidence, err := h.store.ListEvidence(c.Request.Context(), projectID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if evidence == nil { evidence = []iace.Evidence{} } c.JSON(http.StatusOK, gin.H{ "evidence": evidence, "total": len(evidence), }) } // CreateVerificationPlan handles POST /projects/:id/verification-plan // Creates a new verification plan for a project. func (h *IACEHandler) CreateVerificationPlan(c *gin.Context) { projectID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"}) return } var req iace.CreateVerificationPlanRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Override project ID from URL path req.ProjectID = projectID plan, err := h.store.CreateVerificationPlan(c.Request.Context(), req) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } // Audit trail userID := rbac.GetUserID(c) newVals, _ := json.Marshal(plan) h.store.AddAuditEntry( c.Request.Context(), projectID, "verification_plan", plan.ID, iace.AuditActionCreate, userID.String(), nil, newVals, ) c.JSON(http.StatusCreated, gin.H{"verification_plan": plan}) } // UpdateVerificationPlan handles PUT /verification-plan/:vid // Updates a verification plan with the provided fields. func (h *IACEHandler) UpdateVerificationPlan(c *gin.Context) { planID, err := uuid.Parse(c.Param("vid")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid verification plan ID"}) return } var updates map[string]interface{} if err := c.ShouldBindJSON(&updates); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } plan, err := h.store.UpdateVerificationPlan(c.Request.Context(), planID, updates) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if plan == nil { c.JSON(http.StatusNotFound, gin.H{"error": "verification plan not found"}) return } c.JSON(http.StatusOK, gin.H{"verification_plan": plan}) } // CompleteVerification handles POST /verification-plan/:vid/complete // Marks a verification plan as completed with a result. func (h *IACEHandler) CompleteVerification(c *gin.Context) { planID, err := uuid.Parse(c.Param("vid")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid verification plan ID"}) return } var req struct { Result string `json:"result" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } userID := rbac.GetUserID(c) if err := h.store.CompleteVerification( c.Request.Context(), planID, req.Result, userID.String(), ); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "verification completed"}) }