Files
breakpilot-compliance/ai-compliance-sdk/internal/api/handlers/portfolio_items_handlers.go
Sharang Parnerkar 13f57c4519 refactor(go): split obligations, portfolio, rbac, whistleblower handlers and stores, roadmap parser
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>
2026-04-19 10:00:15 +02:00

197 lines
5.2 KiB
Go

package handlers
import (
"net/http"
"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"
)
// ============================================================================
// Portfolio Items
// ============================================================================
// AddItem adds an item to a portfolio
// POST /sdk/v1/portfolios/:id/items
func (h *PortfolioHandlers) AddItem(c *gin.Context) {
portfolioID, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid portfolio ID"})
return
}
var req portfolio.AddItemRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
userID := rbac.GetUserID(c)
item := &portfolio.PortfolioItem{
PortfolioID: portfolioID,
ItemType: req.ItemType,
ItemID: req.ItemID,
Tags: req.Tags,
Notes: req.Notes,
AddedBy: userID,
}
if err := h.store.AddItem(c.Request.Context(), item); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, gin.H{"item": item})
}
// ListItems lists items in a portfolio
// GET /sdk/v1/portfolios/:id/items
func (h *PortfolioHandlers) ListItems(c *gin.Context) {
portfolioID, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid portfolio ID"})
return
}
var itemType *portfolio.ItemType
if t := c.Query("type"); t != "" {
it := portfolio.ItemType(t)
itemType = &it
}
items, err := h.store.ListItems(c.Request.Context(), portfolioID, itemType)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"items": items,
"total": len(items),
})
}
// BulkAddItems adds multiple items to a portfolio
// POST /sdk/v1/portfolios/:id/items/bulk
func (h *PortfolioHandlers) BulkAddItems(c *gin.Context) {
portfolioID, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid portfolio ID"})
return
}
var req portfolio.BulkAddItemsRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
userID := rbac.GetUserID(c)
items := make([]portfolio.PortfolioItem, len(req.Items))
for i, r := range req.Items {
items[i] = portfolio.PortfolioItem{
ItemType: r.ItemType,
ItemID: r.ItemID,
Tags: r.Tags,
Notes: r.Notes,
}
}
result, err := h.store.BulkAddItems(c.Request.Context(), portfolioID, items, userID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, result)
}
// RemoveItem removes an item from a portfolio
// DELETE /sdk/v1/portfolios/:id/items/:itemId
func (h *PortfolioHandlers) RemoveItem(c *gin.Context) {
itemID, err := uuid.Parse(c.Param("itemId"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid item ID"})
return
}
if err := h.store.RemoveItem(c.Request.Context(), itemID); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "item removed"})
}
// ReorderItems updates the order of items
// PUT /sdk/v1/portfolios/:id/items/order
func (h *PortfolioHandlers) ReorderItems(c *gin.Context) {
portfolioID, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid portfolio ID"})
return
}
var req struct {
ItemIDs []uuid.UUID `json:"item_ids"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.store.UpdateItemOrder(c.Request.Context(), portfolioID, req.ItemIDs); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "items reordered"})
}
// ============================================================================
// Merge Operations
// ============================================================================
// MergePortfolios merges two portfolios
// POST /sdk/v1/portfolios/merge
func (h *PortfolioHandlers) MergePortfolios(c *gin.Context) {
var req portfolio.MergeRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
source, err := h.store.GetPortfolio(c.Request.Context(), req.SourcePortfolioID)
if err != nil || source == nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "source portfolio not found"})
return
}
target, err := h.store.GetPortfolio(c.Request.Context(), req.TargetPortfolioID)
if err != nil || target == nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "target portfolio not found"})
return
}
if req.Strategy == "" {
req.Strategy = portfolio.MergeStrategyUnion
}
userID := rbac.GetUserID(c)
result, err := h.store.MergePortfolios(c.Request.Context(), &req, userID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "portfolios merged",
"result": result,
})
}