All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 34s
CI / test-python-backend-compliance (push) Successful in 28s
CI / test-python-document-crawler (push) Successful in 24s
CI / test-python-dsms-gateway (push) Successful in 17s
Implements the 4-level GCI scoring model (Module -> Risk-Weighted -> Regulation Area -> Final GCI) with DSGVO, NIS2, ISO 27001, and EU AI Act integration. Backend: - 9 Go files: engine, models, weights, validity, NIS2 roles/scoring, ISO mapping/gap-analysis, mock data - GCI handlers with 13 API endpoints under /sdk/v1/gci/ - Routes registered in main.go Frontend: - TypeScript types, API client, Next.js API proxy - Dashboard page with 6 tabs (Overview, Breakdown, NIS2, ISO 27001, Matrix, Audit Trail) - Sidebar navigation entry Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
189 lines
4.9 KiB
Go
189 lines
4.9 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/breakpilot/ai-compliance-sdk/internal/gci"
|
|
"github.com/breakpilot/ai-compliance-sdk/internal/rbac"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
type GCIHandlers struct {
|
|
engine *gci.Engine
|
|
}
|
|
|
|
func NewGCIHandlers(engine *gci.Engine) *GCIHandlers {
|
|
return &GCIHandlers{engine: engine}
|
|
}
|
|
|
|
// GetScore returns the GCI score for the current tenant
|
|
// GET /sdk/v1/gci/score
|
|
func (h *GCIHandlers) GetScore(c *gin.Context) {
|
|
tenantID := rbac.GetTenantID(c).String()
|
|
profile := c.DefaultQuery("profile", "default")
|
|
|
|
result := h.engine.Calculate(tenantID, profile)
|
|
c.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
// GetScoreBreakdown returns the detailed 4-level GCI breakdown
|
|
// GET /sdk/v1/gci/score/breakdown
|
|
func (h *GCIHandlers) GetScoreBreakdown(c *gin.Context) {
|
|
tenantID := rbac.GetTenantID(c).String()
|
|
profile := c.DefaultQuery("profile", "default")
|
|
|
|
breakdown := h.engine.CalculateBreakdown(tenantID, profile)
|
|
c.JSON(http.StatusOK, breakdown)
|
|
}
|
|
|
|
// GetHistory returns historical GCI snapshots for trend analysis
|
|
// GET /sdk/v1/gci/score/history
|
|
func (h *GCIHandlers) GetHistory(c *gin.Context) {
|
|
tenantID := rbac.GetTenantID(c).String()
|
|
|
|
history := h.engine.GetHistory(tenantID)
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"tenant_id": tenantID,
|
|
"snapshots": history,
|
|
"total": len(history),
|
|
})
|
|
}
|
|
|
|
// GetMatrix returns the compliance matrix (roles x regulations)
|
|
// GET /sdk/v1/gci/matrix
|
|
func (h *GCIHandlers) GetMatrix(c *gin.Context) {
|
|
tenantID := rbac.GetTenantID(c).String()
|
|
|
|
matrix := h.engine.GetMatrix(tenantID)
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"tenant_id": tenantID,
|
|
"matrix": matrix,
|
|
})
|
|
}
|
|
|
|
// GetAuditTrail returns the audit trail for the latest GCI calculation
|
|
// GET /sdk/v1/gci/audit-trail
|
|
func (h *GCIHandlers) GetAuditTrail(c *gin.Context) {
|
|
tenantID := rbac.GetTenantID(c).String()
|
|
profile := c.DefaultQuery("profile", "default")
|
|
|
|
result := h.engine.Calculate(tenantID, profile)
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"tenant_id": tenantID,
|
|
"gci_score": result.GCIScore,
|
|
"audit_trail": result.AuditTrail,
|
|
})
|
|
}
|
|
|
|
// GetNIS2Score returns the NIS2-specific compliance score
|
|
// GET /sdk/v1/gci/nis2/score
|
|
func (h *GCIHandlers) GetNIS2Score(c *gin.Context) {
|
|
tenantID := rbac.GetTenantID(c).String()
|
|
|
|
score := gci.CalculateNIS2Score(tenantID)
|
|
c.JSON(http.StatusOK, score)
|
|
}
|
|
|
|
// ListNIS2Roles returns available NIS2 responsibility roles
|
|
// GET /sdk/v1/gci/nis2/roles
|
|
func (h *GCIHandlers) ListNIS2Roles(c *gin.Context) {
|
|
roles := gci.ListNIS2Roles()
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"roles": roles,
|
|
"total": len(roles),
|
|
})
|
|
}
|
|
|
|
// AssignNIS2Role assigns a NIS2 role to a user (stub - returns mock)
|
|
// POST /sdk/v1/gci/nis2/roles/assign
|
|
func (h *GCIHandlers) AssignNIS2Role(c *gin.Context) {
|
|
var req struct {
|
|
RoleID string `json:"role_id" binding:"required"`
|
|
UserID string `json:"user_id" binding:"required"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
role, found := gci.GetNIS2Role(req.RoleID)
|
|
if !found {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "NIS2 role not found"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"status": "assigned",
|
|
"role": role,
|
|
"user_id": req.UserID,
|
|
})
|
|
}
|
|
|
|
// GetISOGapAnalysis returns the ISO 27001 gap analysis
|
|
// GET /sdk/v1/gci/iso/gap-analysis
|
|
func (h *GCIHandlers) GetISOGapAnalysis(c *gin.Context) {
|
|
tenantID := rbac.GetTenantID(c).String()
|
|
|
|
analysis := gci.CalculateISOGapAnalysis(tenantID)
|
|
c.JSON(http.StatusOK, analysis)
|
|
}
|
|
|
|
// ListISOMappings returns all ISO 27001 control mappings
|
|
// GET /sdk/v1/gci/iso/mappings
|
|
func (h *GCIHandlers) ListISOMappings(c *gin.Context) {
|
|
category := c.Query("category")
|
|
|
|
if category != "" {
|
|
controls := gci.GetISOControlsByCategory(category)
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"controls": controls,
|
|
"total": len(controls),
|
|
"category": category,
|
|
})
|
|
return
|
|
}
|
|
|
|
categories := []string{"A.5", "A.6", "A.7", "A.8"}
|
|
result := make(map[string][]gci.ISOControl)
|
|
total := 0
|
|
for _, cat := range categories {
|
|
controls := gci.GetISOControlsByCategory(cat)
|
|
if len(controls) > 0 {
|
|
result[cat] = controls
|
|
total += len(controls)
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"categories": result,
|
|
"total": total,
|
|
})
|
|
}
|
|
|
|
// GetISOMapping returns a single ISO control by ID
|
|
// GET /sdk/v1/gci/iso/mappings/:controlId
|
|
func (h *GCIHandlers) GetISOMapping(c *gin.Context) {
|
|
controlID := c.Param("controlId")
|
|
|
|
control, found := gci.GetISOControlByID(controlID)
|
|
if !found {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "ISO control not found"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, control)
|
|
}
|
|
|
|
// GetWeightProfiles returns available weighting profiles
|
|
// GET /sdk/v1/gci/profiles
|
|
func (h *GCIHandlers) GetWeightProfiles(c *gin.Context) {
|
|
profiles := []string{"default", "nis2_relevant", "ki_nutzer"}
|
|
result := make([]gci.WeightProfile, 0, len(profiles))
|
|
for _, id := range profiles {
|
|
result = append(result, gci.GetProfile(id))
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"profiles": result,
|
|
})
|
|
}
|