iace_handler.go (2706 LOC) split into 9 files: - iace_handler.go: struct, constructor, shared helpers (~156 LOC) - iace_handler_projects.go: project CRUD + InitFromProfile (~310 LOC) - iace_handler_components.go: components + classification (~387 LOC) - iace_handler_hazards.go: hazard library, CRUD, risk assessment (~469 LOC) - iace_handler_mitigations.go: mitigations, evidence, verification plans (~293 LOC) - iace_handler_techfile.go: CE tech file generation/export (~452 LOC) - iace_handler_monitoring.go: monitoring events + audit trail (~134 LOC) - iace_handler_refdata.go: ISO 12100 ref data, patterns, suggestions (~465 LOC) - iace_handler_rag.go: RAG library search + section enrichment (~142 LOC) training_handlers.go (1864 LOC) split into 9 files: - training_handlers.go: struct + constructor (~23 LOC) - training_handlers_modules.go: module CRUD (~226 LOC) - training_handlers_matrix.go: CTM matrix endpoints (~95 LOC) - training_handlers_assignments.go: assignment lifecycle (~243 LOC) - training_handlers_quiz.go: quiz submit/grade/attempts (~185 LOC) - training_handlers_content.go: LLM content/audio/video generation (~274 LOC) - training_handlers_media.go: media, streaming, interactive video (~325 LOC) - training_handlers_blocks.go: block configs + canonical controls (~280 LOC) - training_handlers_stats.go: deadlines, escalation, audit, certificates (~290 LOC) All files remain in package handlers. Zero behavior changes. All exported function names preserved. All files under 500 LOC hard cap. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
294 lines
8.6 KiB
Go
294 lines
8.6 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
|
|
// ============================================================================
|
|
|
|
// 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"})
|
|
}
|