package handlers import ( "net/http" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgxpool" "github.com/breakpilot/ai-compliance-sdk/internal/gap" ) // GapHandler handles regulatory gap analysis endpoints. type GapHandler struct { engine *gap.Engine store *gap.Store } // NewGapHandler creates a new GapHandler. func NewGapHandler(pool *pgxpool.Pool) *GapHandler { store := gap.NewStore(pool) return &GapHandler{ engine: gap.NewEngine(store), store: store, } } // CreateProject creates a new gap analysis project. // POST /sdk/v1/gap/projects func (h *GapHandler) CreateProject(c *gin.Context) { var profile gap.ProductProfile if err := c.ShouldBindJSON(&profile); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } tenantID := c.GetHeader("X-Tenant-ID") if tenantID != "" { profile.TenantID, _ = uuid.Parse(tenantID) } if err := h.store.CreateProfile(&profile); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create project"}) return } c.JSON(http.StatusCreated, gin.H{"project": profile}) } // GetProject returns a gap project by ID. // GET /sdk/v1/gap/projects/:id func (h *GapHandler) GetProject(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"}) return } profile, err := h.store.GetProfile(id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "project not found"}) return } c.JSON(http.StatusOK, gin.H{"project": profile}) } // ListProjects lists gap projects for a tenant. // GET /sdk/v1/gap/projects func (h *GapHandler) ListProjects(c *gin.Context) { tenantID, err := uuid.Parse(c.GetHeader("X-Tenant-ID")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "X-Tenant-ID required"}) return } profiles, err := h.store.ListProfiles(tenantID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list projects"}) return } c.JSON(http.StatusOK, gin.H{"projects": profiles, "total": len(profiles)}) } // AnalyzeProject runs the full gap analysis. // POST /sdk/v1/gap/projects/:id/analyze func (h *GapHandler) AnalyzeProject(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"}) return } profile, err := h.store.GetProfile(id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "project not found"}) return } report, err := h.engine.Analyze(profile) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, report) } // QuickAnalyze runs gap analysis without saving a project. // POST /sdk/v1/gap/analyze func (h *GapHandler) QuickAnalyze(c *gin.Context) { var profile gap.ProductProfile if err := c.ShouldBindJSON(&profile); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } profile.ID = uuid.New() report, err := h.engine.Analyze(&profile) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, report) } // GetTemplates returns available industry templates. // GET /sdk/v1/gap/templates func (h *GapHandler) GetTemplates(c *gin.Context) { templates := make([]gin.H, 0, len(gap.IndustryTemplates)) for key, tmpl := range gap.IndustryTemplates { templates = append(templates, gin.H{ "key": key, "name": tmpl.Name, "description": tmpl.Description, "product_type": tmpl.ProductType, }) } c.JSON(http.StatusOK, gin.H{"templates": templates}) } // GetTemplate returns a specific template by key. // GET /sdk/v1/gap/templates/:key func (h *GapHandler) GetTemplate(c *gin.Context) { key := c.Param("key") tmpl, ok := gap.IndustryTemplates[key] if !ok { c.JSON(http.StatusNotFound, gin.H{"error": "template not found"}) return } c.JSON(http.StatusOK, gin.H{"template": tmpl}) } // GetRegulations returns all supported regulations with deadlines. // GET /sdk/v1/gap/regulations func (h *GapHandler) GetRegulations(c *gin.Context) { regs := make([]gin.H, 0, len(gap.RegulationNames)) for id, name := range gap.RegulationNames { entry := gin.H{"id": id, "name": name} if dl, ok := gap.RegulationDeadlines[id]; ok { entry["deadline"] = dl } regs = append(regs, entry) } c.JSON(http.StatusOK, gin.H{"regulations": regs}) }