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>
197 lines
5.2 KiB
Go
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,
|
|
})
|
|
}
|