package handlers import ( "encoding/json" "fmt" "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" ) // ============================================================================ // Component Management // ============================================================================ // CreateComponent handles POST /projects/:id/components // Adds a new component to a project. func (h *IACEHandler) CreateComponent(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.CreateComponentRequest 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 component, err := h.store.CreateComponent(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(component) h.store.AddAuditEntry( c.Request.Context(), projectID, "component", component.ID, iace.AuditActionCreate, userID.String(), nil, newVals, ) c.JSON(http.StatusCreated, gin.H{"component": component}) } // ListComponents handles GET /projects/:id/components // Lists all components for a project. func (h *IACEHandler) ListComponents(c *gin.Context) { projectID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"}) return } components, err := h.store.ListComponents(c.Request.Context(), projectID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if components == nil { components = []iace.Component{} } c.JSON(http.StatusOK, gin.H{ "components": components, "total": len(components), }) } // UpdateComponent handles PUT /projects/:id/components/:cid // Updates a component with the provided fields. func (h *IACEHandler) UpdateComponent(c *gin.Context) { _, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"}) return } componentID, err := uuid.Parse(c.Param("cid")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid component ID"}) return } var updates map[string]interface{} if err := c.ShouldBindJSON(&updates); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } component, err := h.store.UpdateComponent(c.Request.Context(), componentID, updates) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if component == nil { c.JSON(http.StatusNotFound, gin.H{"error": "component not found"}) return } c.JSON(http.StatusOK, gin.H{"component": component}) } // DeleteComponent handles DELETE /projects/:id/components/:cid // Deletes a component from a project. func (h *IACEHandler) DeleteComponent(c *gin.Context) { _, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"}) return } componentID, err := uuid.Parse(c.Param("cid")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid component ID"}) return } if err := h.store.DeleteComponent(c.Request.Context(), componentID); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "component deleted"}) } // CheckCompleteness handles POST /projects/:id/completeness-check // Loads all project data, evaluates all 25 CE completeness gates, updates the // project's completeness score, and returns the result. func (h *IACEHandler) CheckCompleteness(c *gin.Context) { projectID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"}) return } project, err := h.store.GetProject(c.Request.Context(), projectID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if project == nil { c.JSON(http.StatusNotFound, gin.H{"error": "project not found"}) return } // Load all related entities components, _ := h.store.ListComponents(c.Request.Context(), projectID) classifications, _ := h.store.GetClassifications(c.Request.Context(), projectID) hazards, _ := h.store.ListHazards(c.Request.Context(), projectID) // Collect all assessments and mitigations across all hazards var allAssessments []iace.RiskAssessment var allMitigations []iace.Mitigation for _, hazard := range hazards { assessments, _ := h.store.ListAssessments(c.Request.Context(), hazard.ID) allAssessments = append(allAssessments, assessments...) mitigations, _ := h.store.ListMitigations(c.Request.Context(), hazard.ID) allMitigations = append(allMitigations, mitigations...) } evidence, _ := h.store.ListEvidence(c.Request.Context(), projectID) techFileSections, _ := h.store.ListTechFileSections(c.Request.Context(), projectID) // Determine if the project has AI components hasAI := false for _, comp := range components { if comp.ComponentType == iace.ComponentTypeAIModel { hasAI = true break } } // Check audit trail for pattern matching patternMatchingPerformed, _ := h.store.HasAuditEntryForType(c.Request.Context(), projectID, "pattern_matching") // Build completeness context completenessCtx := &iace.CompletenessContext{ Project: project, Components: components, Classifications: classifications, Hazards: hazards, Assessments: allAssessments, Mitigations: allMitigations, Evidence: evidence, TechFileSections: techFileSections, HasAI: hasAI, PatternMatchingPerformed: patternMatchingPerformed, } // Run the checker result := h.checker.Check(completenessCtx) // Build risk summary for the project update riskSummary := map[string]int{ "total_hazards": len(hazards), } for _, a := range allAssessments { riskSummary[string(a.RiskLevel)]++ } // Update project completeness score and risk summary if err := h.store.UpdateProjectCompleteness( c.Request.Context(), projectID, result.Score, riskSummary, ); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "completeness": result, }) } // ============================================================================ // Classification // ============================================================================ // Classify handles POST /projects/:id/classify // Runs all regulatory classifiers (AI Act, Machinery Regulation, CRA, NIS2), // upserts each result into the store, and returns classifications. func (h *IACEHandler) Classify(c *gin.Context) { projectID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"}) return } project, err := h.store.GetProject(c.Request.Context(), projectID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if project == nil { c.JSON(http.StatusNotFound, gin.H{"error": "project not found"}) return } components, err := h.store.ListComponents(c.Request.Context(), projectID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } // Run all classifiers results := h.classifier.ClassifyAll(project, components) // Upsert each classification result into the store var classifications []iace.RegulatoryClassification for _, r := range results { reqsJSON, _ := json.Marshal(r.Requirements) classification, err := h.store.UpsertClassification( c.Request.Context(), projectID, r.Regulation, r.ClassificationResult, r.RiskLevel, r.Confidence, r.Reasoning, nil, // ragSources - not available from rule-based classifier reqsJSON, ) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if classification != nil { classifications = append(classifications, *classification) } } // Advance project status to classification h.store.UpdateProjectStatus(c.Request.Context(), projectID, iace.ProjectStatusClassification) // Audit trail userID := rbac.GetUserID(c) newVals, _ := json.Marshal(classifications) h.store.AddAuditEntry( c.Request.Context(), projectID, "classification", projectID, iace.AuditActionCreate, userID.String(), nil, newVals, ) c.JSON(http.StatusOK, gin.H{ "classifications": classifications, "total": len(classifications), }) } // GetClassifications handles GET /projects/:id/classifications // Returns all regulatory classifications for a project. func (h *IACEHandler) GetClassifications(c *gin.Context) { projectID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"}) return } classifications, err := h.store.GetClassifications(c.Request.Context(), projectID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if classifications == nil { classifications = []iace.RegulatoryClassification{} } c.JSON(http.StatusOK, gin.H{ "classifications": classifications, "total": len(classifications), }) } // ClassifySingle handles POST /projects/:id/classify/:regulation // Runs a single regulatory classifier for the specified regulation type. func (h *IACEHandler) ClassifySingle(c *gin.Context) { projectID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"}) return } regulation := iace.RegulationType(c.Param("regulation")) project, err := h.store.GetProject(c.Request.Context(), projectID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if project == nil { c.JSON(http.StatusNotFound, gin.H{"error": "project not found"}) return } components, err := h.store.ListComponents(c.Request.Context(), projectID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } // Run the appropriate classifier var result iace.ClassificationResult switch regulation { case iace.RegulationAIAct: result = h.classifier.ClassifyAIAct(project, components) case iace.RegulationMachineryRegulation: result = h.classifier.ClassifyMachineryRegulation(project, components) case iace.RegulationCRA: result = h.classifier.ClassifyCRA(project, components) case iace.RegulationNIS2: result = h.classifier.ClassifyNIS2(project, components) default: c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("unknown regulation type: %s", regulation)}) return } // Upsert the classification result reqsJSON, _ := json.Marshal(result.Requirements) classification, err := h.store.UpsertClassification( c.Request.Context(), projectID, result.Regulation, result.ClassificationResult, result.RiskLevel, result.Confidence, result.Reasoning, nil, reqsJSON, ) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"classification": classification}) }