package handlers import ( "net/http" "strconv" "github.com/breakpilot/ai-compliance-sdk/internal/portfolio" "github.com/breakpilot/ai-compliance-sdk/internal/rbac" "github.com/gin-gonic/gin" "github.com/google/uuid" ) // PortfolioHandlers handles portfolio HTTP requests type PortfolioHandlers struct { store *portfolio.Store } // NewPortfolioHandlers creates new portfolio handlers func NewPortfolioHandlers(store *portfolio.Store) *PortfolioHandlers { return &PortfolioHandlers{store: store} } // ============================================================================ // Portfolio CRUD // ============================================================================ // CreatePortfolio creates a new portfolio // POST /sdk/v1/portfolios func (h *PortfolioHandlers) CreatePortfolio(c *gin.Context) { var req portfolio.CreatePortfolioRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } tenantID := rbac.GetTenantID(c) userID := rbac.GetUserID(c) p := &portfolio.Portfolio{ TenantID: tenantID, Name: req.Name, Description: req.Description, Status: portfolio.PortfolioStatusDraft, Department: req.Department, BusinessUnit: req.BusinessUnit, Owner: req.Owner, OwnerEmail: req.OwnerEmail, Settings: req.Settings, CreatedBy: userID, } if !p.Settings.AutoUpdateMetrics { p.Settings.AutoUpdateMetrics = true } if err := h.store.CreatePortfolio(c.Request.Context(), p); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, gin.H{"portfolio": p}) } // ListPortfolios lists portfolios // GET /sdk/v1/portfolios func (h *PortfolioHandlers) ListPortfolios(c *gin.Context) { tenantID := rbac.GetTenantID(c) filters := &portfolio.PortfolioFilters{ Limit: 50, } if status := c.Query("status"); status != "" { filters.Status = portfolio.PortfolioStatus(status) } if department := c.Query("department"); department != "" { filters.Department = department } if businessUnit := c.Query("business_unit"); businessUnit != "" { filters.BusinessUnit = businessUnit } if owner := c.Query("owner"); owner != "" { filters.Owner = owner } if limit := c.Query("limit"); limit != "" { if l, err := strconv.Atoi(limit); err == nil { filters.Limit = l } } if offset := c.Query("offset"); offset != "" { if o, err := strconv.Atoi(offset); err == nil { filters.Offset = o } } portfolios, err := h.store.ListPortfolios(c.Request.Context(), tenantID, filters) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "portfolios": portfolios, "total": len(portfolios), }) } // GetPortfolio retrieves a portfolio // GET /sdk/v1/portfolios/:id func (h *PortfolioHandlers) GetPortfolio(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid portfolio ID"}) return } summary, err := h.store.GetPortfolioSummary(c.Request.Context(), id) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if summary == nil { c.JSON(http.StatusNotFound, gin.H{"error": "portfolio not found"}) return } stats, _ := h.store.GetPortfolioStats(c.Request.Context(), id) c.JSON(http.StatusOK, gin.H{ "portfolio": summary.Portfolio, "items": summary.Items, "risk_distribution": summary.RiskDistribution, "feasibility_dist": summary.FeasibilityDist, "stats": stats, }) } // UpdatePortfolio updates a portfolio // PUT /sdk/v1/portfolios/:id func (h *PortfolioHandlers) UpdatePortfolio(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid portfolio ID"}) return } p, err := h.store.GetPortfolio(c.Request.Context(), id) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if p == nil { c.JSON(http.StatusNotFound, gin.H{"error": "portfolio not found"}) return } var req portfolio.UpdatePortfolioRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if req.Name != "" { p.Name = req.Name } if req.Description != "" { p.Description = req.Description } if req.Status != "" { p.Status = req.Status } if req.Department != "" { p.Department = req.Department } if req.BusinessUnit != "" { p.BusinessUnit = req.BusinessUnit } if req.Owner != "" { p.Owner = req.Owner } if req.OwnerEmail != "" { p.OwnerEmail = req.OwnerEmail } if req.Settings != nil { p.Settings = *req.Settings } if err := h.store.UpdatePortfolio(c.Request.Context(), p); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"portfolio": p}) } // DeletePortfolio deletes a portfolio // DELETE /sdk/v1/portfolios/:id func (h *PortfolioHandlers) DeletePortfolio(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid portfolio ID"}) return } if err := h.store.DeletePortfolio(c.Request.Context(), id); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "portfolio deleted"}) }