Files
breakpilot-compliance/ai-compliance-sdk/internal/api/handlers/iace_handler_mitigations.go
T
Benjamin Admin e7f2f98da3 feat: IACE CE-Compliance Module — Normen, Risikobewertung, Production Lines
Major features:
- 215 norms library with section references + Beuth URLs (A/B1/B2/C norms)
- 173 hazard patterns with detail fields (scenario, trigger, harm, zone)
- Deterministic pattern matching: Component × Lifecycle × Pattern cross-product
- SIL/PL auto-calculation from S×E×P risk graph
- Risk assessment table with editable S/E/P dropdowns
- Production Line Dashboard with animated station flow (Running Dots)
- IACE process flow + norms coverage on start page
- Non-blocking cookie banner, ProcessFlow SSR fix
- 104 Playwright E2E tests passing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 10:53:26 +02:00

336 lines
9.8 KiB
Go

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
// ============================================================================
// ListProjectMitigations handles GET /projects/:id/mitigations
// Returns all mitigations for all hazards in a project.
func (h *IACEHandler) ListProjectMitigations(c *gin.Context) {
projectID, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"})
return
}
mitigations, err := h.store.ListMitigationsByProject(c.Request.Context(), projectID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if mitigations == nil {
mitigations = []iace.Mitigation{}
}
c.JSON(http.StatusOK, gin.H{
"mitigations": mitigations,
"total": len(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})
}
// DeleteMitigation handles DELETE /projects/:id/mitigations/:mid
// Deletes a mitigation by ID.
func (h *IACEHandler) DeleteMitigation(c *gin.Context) {
mitigationID, err := uuid.Parse(c.Param("mid"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid mitigation ID"})
return
}
if err := h.store.DeleteMitigation(c.Request.Context(), mitigationID); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "mitigation deleted"})
}
// 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"})
}