A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
626 lines
17 KiB
Go
626 lines
17 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,
|
|
}
|
|
|
|
// Set default settings
|
|
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
|
|
}
|
|
|
|
// Get stats
|
|
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"})
|
|
}
|
|
|
|
// ============================================================================
|
|
// 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)
|
|
|
|
// Convert AddItemRequest to PortfolioItem
|
|
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
|
|
}
|
|
|
|
// Validate portfolios exist
|
|
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
|
|
}
|
|
|
|
// Set defaults
|
|
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,
|
|
})
|
|
}
|
|
|
|
// ============================================================================
|
|
// Statistics & Reports
|
|
// ============================================================================
|
|
|
|
// GetPortfolioStats returns statistics for a portfolio
|
|
// GET /sdk/v1/portfolios/:id/stats
|
|
func (h *PortfolioHandlers) GetPortfolioStats(c *gin.Context) {
|
|
id, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid portfolio ID"})
|
|
return
|
|
}
|
|
|
|
stats, err := h.store.GetPortfolioStats(c.Request.Context(), id)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, stats)
|
|
}
|
|
|
|
// GetPortfolioActivity returns recent activity for a portfolio
|
|
// GET /sdk/v1/portfolios/:id/activity
|
|
func (h *PortfolioHandlers) GetPortfolioActivity(c *gin.Context) {
|
|
id, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid portfolio ID"})
|
|
return
|
|
}
|
|
|
|
limit := 20
|
|
if l := c.Query("limit"); l != "" {
|
|
if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 {
|
|
limit = parsed
|
|
}
|
|
}
|
|
|
|
activities, err := h.store.GetRecentActivity(c.Request.Context(), id, limit)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"activities": activities,
|
|
"total": len(activities),
|
|
})
|
|
}
|
|
|
|
// ComparePortfolios compares multiple portfolios
|
|
// POST /sdk/v1/portfolios/compare
|
|
func (h *PortfolioHandlers) ComparePortfolios(c *gin.Context) {
|
|
var req portfolio.ComparePortfoliosRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
if len(req.PortfolioIDs) < 2 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "at least 2 portfolios required for comparison"})
|
|
return
|
|
}
|
|
if len(req.PortfolioIDs) > 5 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "maximum 5 portfolios can be compared"})
|
|
return
|
|
}
|
|
|
|
// Get all portfolios
|
|
var portfolios []portfolio.Portfolio
|
|
comparison := portfolio.PortfolioComparison{
|
|
RiskScores: make(map[string]float64),
|
|
ComplianceScores: make(map[string]float64),
|
|
ItemCounts: make(map[string]int),
|
|
UniqueItems: make(map[string][]uuid.UUID),
|
|
}
|
|
|
|
allItems := make(map[uuid.UUID][]string) // item_id -> portfolio_ids
|
|
|
|
for _, id := range req.PortfolioIDs {
|
|
p, err := h.store.GetPortfolio(c.Request.Context(), id)
|
|
if err != nil || p == nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "portfolio not found: " + id.String()})
|
|
return
|
|
}
|
|
portfolios = append(portfolios, *p)
|
|
|
|
idStr := id.String()
|
|
comparison.RiskScores[idStr] = p.AvgRiskScore
|
|
comparison.ComplianceScores[idStr] = p.ComplianceScore
|
|
comparison.ItemCounts[idStr] = p.TotalAssessments + p.TotalRoadmaps + p.TotalWorkshops
|
|
|
|
// Get items for comparison
|
|
items, _ := h.store.ListItems(c.Request.Context(), id, nil)
|
|
for _, item := range items {
|
|
allItems[item.ItemID] = append(allItems[item.ItemID], idStr)
|
|
}
|
|
}
|
|
|
|
// Find common and unique items
|
|
for itemID, portfolioIDs := range allItems {
|
|
if len(portfolioIDs) > 1 {
|
|
comparison.CommonItems = append(comparison.CommonItems, itemID)
|
|
} else {
|
|
pid := portfolioIDs[0]
|
|
comparison.UniqueItems[pid] = append(comparison.UniqueItems[pid], itemID)
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, portfolio.ComparePortfoliosResponse{
|
|
Portfolios: portfolios,
|
|
Comparison: comparison,
|
|
})
|
|
}
|
|
|
|
// RecalculateMetrics manually recalculates portfolio metrics
|
|
// POST /sdk/v1/portfolios/:id/recalculate
|
|
func (h *PortfolioHandlers) RecalculateMetrics(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.RecalculateMetrics(c.Request.Context(), id); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Get updated portfolio
|
|
p, _ := h.store.GetPortfolio(c.Request.Context(), id)
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "metrics recalculated",
|
|
"portfolio": p,
|
|
})
|
|
}
|
|
|
|
// ============================================================================
|
|
// Approval Workflow
|
|
// ============================================================================
|
|
|
|
// ApprovePortfolio approves a portfolio
|
|
// POST /sdk/v1/portfolios/:id/approve
|
|
func (h *PortfolioHandlers) ApprovePortfolio(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
|
|
}
|
|
|
|
if p.Status != portfolio.PortfolioStatusReview {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "portfolio must be in REVIEW status to approve"})
|
|
return
|
|
}
|
|
|
|
userID := rbac.GetUserID(c)
|
|
now := c.Request.Context().Value("now")
|
|
if now == nil {
|
|
t := p.UpdatedAt
|
|
p.ApprovedAt = &t
|
|
}
|
|
p.ApprovedBy = &userID
|
|
p.Status = portfolio.PortfolioStatusApproved
|
|
|
|
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{
|
|
"message": "portfolio approved",
|
|
"portfolio": p,
|
|
})
|
|
}
|
|
|
|
// SubmitForReview submits a portfolio for review
|
|
// POST /sdk/v1/portfolios/:id/submit-review
|
|
func (h *PortfolioHandlers) SubmitForReview(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
|
|
}
|
|
|
|
if p.Status != portfolio.PortfolioStatusDraft && p.Status != portfolio.PortfolioStatusActive {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "portfolio must be in DRAFT or ACTIVE status to submit for review"})
|
|
return
|
|
}
|
|
|
|
p.Status = portfolio.PortfolioStatusReview
|
|
|
|
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{
|
|
"message": "portfolio submitted for review",
|
|
"portfolio": p,
|
|
})
|
|
}
|