This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/graph"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// GraphHandlers manages the service dependency graph.
|
||||
type GraphHandlers struct {
|
||||
path string
|
||||
mu sync.RWMutex
|
||||
yamlData []byte
|
||||
loaded *graph.Graph
|
||||
}
|
||||
|
||||
// NewGraphHandlers loads the graph from the given YAML file path.
|
||||
func NewGraphHandlers(yamlPath string) *GraphHandlers {
|
||||
h := &GraphHandlers{path: yamlPath}
|
||||
if err := h.reload(); err != nil {
|
||||
if _, ok := err.(*os.PathError); ok {
|
||||
log.Printf("[graph] no graph file at %q, starting with empty graph", yamlPath)
|
||||
h.loaded = graph.New()
|
||||
h.yamlData = []byte("nodes: {}\n")
|
||||
} else {
|
||||
log.Fatalf("[graph] failed to load graph from %q: %v", yamlPath, err)
|
||||
}
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *GraphHandlers) reload() error {
|
||||
data, err := os.ReadFile(h.path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g, err := graph.ParseYAML(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.mu.Lock()
|
||||
h.yamlData = data
|
||||
h.loaded = g
|
||||
h.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetGraph returns the current parsed graph.
|
||||
func (h *GraphHandlers) GetGraph() *graph.Graph {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
return h.loaded
|
||||
}
|
||||
|
||||
// GetYAML returns the raw YAML content.
|
||||
// @Summary Get dependency graph YAML
|
||||
// @Description Returns the service dependency graph as raw YAML text
|
||||
// @Tags graph
|
||||
// @Produce plain
|
||||
// @Success 200 {string} string "YAML content"
|
||||
// @Security Bearer
|
||||
// @Router /graph [get]
|
||||
func (h *GraphHandlers) GetYAML(c *gin.Context) {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
c.Data(http.StatusOK, "text/yaml", h.yamlData)
|
||||
}
|
||||
|
||||
// UpdateYAML updates the graph from new YAML text.
|
||||
// @Summary Update dependency graph YAML
|
||||
// @Description Replaces the service dependency graph YAML and reloads it
|
||||
// @Tags graph
|
||||
// @Accept plain
|
||||
// @Produce json
|
||||
// @Param body body string true "New YAML content"
|
||||
// @Success 200 {object} map[string]string
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Security Bearer
|
||||
// @Router /graph [put]
|
||||
func (h *GraphHandlers) UpdateYAML(c *gin.Context) {
|
||||
body, err := io.ReadAll(c.Request.Body)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "failed to read body"})
|
||||
return
|
||||
}
|
||||
|
||||
g, err := graph.ParseYAML(body)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := os.WriteFile(h.path, body, 0o644); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to write graph file"})
|
||||
return
|
||||
}
|
||||
|
||||
h.mu.Lock()
|
||||
h.yamlData = body
|
||||
h.loaded = g
|
||||
h.mu.Unlock()
|
||||
|
||||
log.Printf("[graph] updated graph from admin, saved to %s", h.path)
|
||||
c.JSON(http.StatusOK, gin.H{"message": "graph updated"})
|
||||
}
|
||||
|
||||
// StartupOrder returns the computed service startup order.
|
||||
// @Summary Get startup order
|
||||
// @Description Returns the topologically sorted service startup order
|
||||
// @Tags graph
|
||||
// @Produce json
|
||||
// @Success 200 {array} string
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Security Bearer
|
||||
// @Router /graph/order [get]
|
||||
func (h *GraphHandlers) StartupOrder(c *gin.Context) {
|
||||
h.mu.RLock()
|
||||
g := h.loaded
|
||||
h.mu.RUnlock()
|
||||
|
||||
order, err := g.TopologicalSort()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, order)
|
||||
}
|
||||
|
||||
// CycleCheck checks if the graph has cycles.
|
||||
// @Summary Check for cycles
|
||||
// @Description Returns whether the dependency graph contains cycles
|
||||
// @Tags graph
|
||||
// @Produce json
|
||||
// @Success 200 {object} map[string]bool
|
||||
// @Security Bearer
|
||||
// @Router /graph/cycle [get]
|
||||
func (h *GraphHandlers) CycleCheck(c *gin.Context) {
|
||||
h.mu.RLock()
|
||||
g := h.loaded
|
||||
h.mu.RUnlock()
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"has_cycle": g.HasCycle()})
|
||||
}
|
||||
Reference in New Issue
Block a user