Files
breakpilot-compliance/ai-compliance-sdk/internal/api/handlers/roadmap_item_handlers.go
Sharang Parnerkar 3f2aff2389 refactor(go): split roadmap_handlers, academy/store, extract cmd/server/main to internal/app
roadmap_handlers.go (740 LOC) → roadmap_handlers.go, roadmap_item_handlers.go, roadmap_import_handlers.go
academy/store.go (683 LOC) → store_courses.go, store_enrollments.go
cmd/server/main.go (681 LOC) → internal/app/app.go (Run+buildRouter) + internal/app/routes.go (registerXxx helpers)
main.go reduced to 7 LOC thin entrypoint calling app.Run()

All files under 410 LOC. Zero behavior changes, same package declarations.
go vet passes on all directly-split packages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 09:51:11 +02:00

259 lines
6.7 KiB
Go

package handlers
import (
"net/http"
"time"
"github.com/breakpilot/ai-compliance-sdk/internal/roadmap"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
// ============================================================================
// RoadmapItem CRUD
// ============================================================================
// CreateItem creates a new roadmap item
// POST /sdk/v1/roadmaps/:id/items
func (h *RoadmapHandlers) CreateItem(c *gin.Context) {
roadmapID, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid roadmap ID"})
return
}
var input roadmap.RoadmapItemInput
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
item := &roadmap.RoadmapItem{
RoadmapID: roadmapID,
Title: input.Title,
Description: input.Description,
Category: input.Category,
Priority: input.Priority,
Status: input.Status,
ControlID: input.ControlID,
RegulationRef: input.RegulationRef,
GapID: input.GapID,
EffortDays: input.EffortDays,
AssigneeName: input.AssigneeName,
Department: input.Department,
PlannedStart: input.PlannedStart,
PlannedEnd: input.PlannedEnd,
Notes: input.Notes,
}
if err := h.store.CreateItem(c.Request.Context(), item); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Update roadmap progress
h.store.UpdateRoadmapProgress(c.Request.Context(), roadmapID)
c.JSON(http.StatusCreated, gin.H{"item": item})
}
// ListItems lists items for a roadmap
// GET /sdk/v1/roadmaps/:id/items
func (h *RoadmapHandlers) ListItems(c *gin.Context) {
roadmapID, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid roadmap ID"})
return
}
filters := &roadmap.RoadmapItemFilters{
SearchQuery: c.Query("search"),
Limit: 100,
}
if status := c.Query("status"); status != "" {
filters.Status = roadmap.ItemStatus(status)
}
if priority := c.Query("priority"); priority != "" {
filters.Priority = roadmap.ItemPriority(priority)
}
if category := c.Query("category"); category != "" {
filters.Category = roadmap.ItemCategory(category)
}
if controlID := c.Query("control_id"); controlID != "" {
filters.ControlID = controlID
}
items, err := h.store.ListItems(c.Request.Context(), roadmapID, filters)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"items": items,
"total": len(items),
})
}
// GetItem retrieves a roadmap item
// GET /sdk/v1/roadmap-items/:id
func (h *RoadmapHandlers) GetItem(c *gin.Context) {
id, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid ID"})
return
}
item, err := h.store.GetItem(c.Request.Context(), id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if item == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "item not found"})
return
}
c.JSON(http.StatusOK, gin.H{"item": item})
}
// UpdateItem updates a roadmap item
// PUT /sdk/v1/roadmap-items/:id
func (h *RoadmapHandlers) UpdateItem(c *gin.Context) {
id, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid ID"})
return
}
item, err := h.store.GetItem(c.Request.Context(), id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if item == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "item not found"})
return
}
var input roadmap.RoadmapItemInput
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Update fields
item.Title = input.Title
item.Description = input.Description
if input.Category != "" {
item.Category = input.Category
}
if input.Priority != "" {
item.Priority = input.Priority
}
if input.Status != "" {
item.Status = input.Status
}
item.ControlID = input.ControlID
item.RegulationRef = input.RegulationRef
item.GapID = input.GapID
item.EffortDays = input.EffortDays
item.AssigneeName = input.AssigneeName
item.Department = input.Department
item.PlannedStart = input.PlannedStart
item.PlannedEnd = input.PlannedEnd
item.Notes = input.Notes
if err := h.store.UpdateItem(c.Request.Context(), item); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Update roadmap progress
h.store.UpdateRoadmapProgress(c.Request.Context(), item.RoadmapID)
c.JSON(http.StatusOK, gin.H{"item": item})
}
// UpdateItemStatus updates just the status of a roadmap item
// PATCH /sdk/v1/roadmap-items/:id/status
func (h *RoadmapHandlers) UpdateItemStatus(c *gin.Context) {
id, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid ID"})
return
}
var req struct {
Status roadmap.ItemStatus `json:"status"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
item, err := h.store.GetItem(c.Request.Context(), id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if item == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "item not found"})
return
}
item.Status = req.Status
// Set actual dates
now := time.Now().UTC()
if req.Status == roadmap.ItemStatusInProgress && item.ActualStart == nil {
item.ActualStart = &now
}
if req.Status == roadmap.ItemStatusCompleted && item.ActualEnd == nil {
item.ActualEnd = &now
}
if err := h.store.UpdateItem(c.Request.Context(), item); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Update roadmap progress
h.store.UpdateRoadmapProgress(c.Request.Context(), item.RoadmapID)
c.JSON(http.StatusOK, gin.H{"item": item})
}
// DeleteItem deletes a roadmap item
// DELETE /sdk/v1/roadmap-items/:id
func (h *RoadmapHandlers) DeleteItem(c *gin.Context) {
id, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid ID"})
return
}
item, err := h.store.GetItem(c.Request.Context(), id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if item == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "item not found"})
return
}
roadmapID := item.RoadmapID
if err := h.store.DeleteItem(c.Request.Context(), id); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Update roadmap progress
h.store.UpdateRoadmapProgress(c.Request.Context(), roadmapID)
c.JSON(http.StatusOK, gin.H{"message": "item deleted"})
}