Split 7 files exceeding the 500 LOC hard cap into 16 files, all under 500 LOC. No exported symbols renamed; zero behavior changes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
212 lines
5.4 KiB
Go
212 lines
5.4 KiB
Go
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"})
|
|
}
|