All checks were successful
Dev Build / build-test (pull_request) Successful in 2m23s
- Create repository/ package with pgx-backed CRUD for agents, sessions, tasks, projects - Define AgentRepo/SessionRepo/TaskRepo/ProjectRepo interfaces - Update handler to use repository interfaces instead of in-memory stores - Add SSE broker with GET /api/events endpoint (text/event-stream) - Add gateway client that polls OpenClaw for agent states - Add GATEWAY_URL and GATEWAY_POLL_INTERVAL config fields - Seed 5 demo agents (Otto, Rex, Dex, Hex, Pip) on empty DB - Update router to wire SSE broker - All 21 handler tests pass with mock repos
163 lines
5.0 KiB
Go
163 lines
5.0 KiB
Go
// Package handler contains HTTP handlers for the Control Center API.
|
|
// Each handler is a method on a Handler struct that receives its
|
|
// dependencies through dependency injection — now wired to PostgreSQL-backed
|
|
// repository implementations instead of in-memory stores.
|
|
package handler
|
|
|
|
import (
|
|
"encoding/json"
|
|
"log/slog"
|
|
"net/http"
|
|
"time"
|
|
|
|
"code.cubecraftcreations.com/CubeCraft-Creations/Control-Center/go-backend/internal/models"
|
|
"code.cubecraftcreations.com/CubeCraft-Creations/Control-Center/go-backend/internal/repository"
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/go-playground/validator/v10"
|
|
)
|
|
|
|
// Handler groups all route handlers and their dependencies.
|
|
type Handler struct {
|
|
Agents repository.AgentRepo
|
|
Sessions repository.SessionRepo
|
|
Tasks repository.TaskRepo
|
|
Projects repository.ProjectRepo
|
|
validate *validator.Validate
|
|
}
|
|
|
|
// NewHandler returns a fully wired Handler with repository backends.
|
|
func NewHandler(
|
|
ar repository.AgentRepo,
|
|
sr repository.SessionRepo,
|
|
tr repository.TaskRepo,
|
|
pr repository.ProjectRepo,
|
|
) *Handler {
|
|
v := validator.New()
|
|
v.RegisterValidation("agentStatus", validateAgentStatus)
|
|
return &Handler{
|
|
Agents: ar,
|
|
Sessions: sr,
|
|
Tasks: tr,
|
|
Projects: pr,
|
|
validate: v,
|
|
}
|
|
}
|
|
|
|
// ─── Agent Handlers ────────────────────────────────────────────────────────────
|
|
|
|
// ListAgents handles GET /api/agents.
|
|
func (h *Handler) ListAgents(w http.ResponseWriter, r *http.Request) {
|
|
statusFilter := models.AgentStatus(r.URL.Query().Get("status"))
|
|
allAgents, err := h.Agents.List(r.Context(), statusFilter)
|
|
if err != nil {
|
|
slog.Error("list agents failed", "error", err)
|
|
writeJSON(w, http.StatusInternalServerError, models.ErrorResponse{Error: "failed to list agents"})
|
|
return
|
|
}
|
|
|
|
page, pageSize := parsePagination(r)
|
|
start, end := paginateSlice(len(allAgents), page, pageSize)
|
|
|
|
totalCount, _ := h.Agents.Count(r.Context())
|
|
writeJSON(w, http.StatusOK, models.PaginatedResponse{
|
|
Data: allAgents[start:end],
|
|
TotalCount: totalCount,
|
|
Page: page,
|
|
PageSize: pageSize,
|
|
HasMore: end < len(allAgents),
|
|
})
|
|
}
|
|
|
|
// GetAgent handles GET /api/agents/{id}.
|
|
func (h *Handler) GetAgent(w http.ResponseWriter, r *http.Request) {
|
|
id := chi.URLParam(r, "id")
|
|
agent, err := h.Agents.Get(r.Context(), id)
|
|
if err != nil {
|
|
writeJSON(w, http.StatusNotFound, models.ErrorResponse{Error: "agent not found"})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, agent)
|
|
}
|
|
|
|
// CreateAgent handles POST /api/agents.
|
|
func (h *Handler) CreateAgent(w http.ResponseWriter, r *http.Request) {
|
|
var req models.CreateAgentRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeJSON(w, http.StatusBadRequest, models.ErrorResponse{Error: "invalid request body"})
|
|
return
|
|
}
|
|
|
|
if err := h.validate.Struct(req); err != nil {
|
|
writeJSON(w, http.StatusUnprocessableEntity, models.ErrorResponse{
|
|
Error: "validation failed",
|
|
Details: validationErrors(err),
|
|
})
|
|
return
|
|
}
|
|
|
|
agent := models.AgentCardData{
|
|
ID: req.ID,
|
|
DisplayName: req.DisplayName,
|
|
Role: req.Role,
|
|
Status: req.Status,
|
|
CurrentTask: req.CurrentTask,
|
|
SessionKey: req.SessionKey,
|
|
Channel: req.Channel,
|
|
LastActivity: time.Now().UTC().Format(time.RFC3339),
|
|
}
|
|
|
|
if err := h.Agents.Create(r.Context(), agent); err != nil {
|
|
writeJSON(w, http.StatusConflict, models.ErrorResponse{Error: "agent with this ID already exists"})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusCreated, agent)
|
|
}
|
|
|
|
// UpdateAgent handles PUT /api/agents/{id}.
|
|
func (h *Handler) UpdateAgent(w http.ResponseWriter, r *http.Request) {
|
|
id := chi.URLParam(r, "id")
|
|
|
|
var req models.UpdateAgentRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeJSON(w, http.StatusBadRequest, models.ErrorResponse{Error: "invalid request body"})
|
|
return
|
|
}
|
|
|
|
if err := h.validate.Struct(req); err != nil {
|
|
writeJSON(w, http.StatusUnprocessableEntity, models.ErrorResponse{
|
|
Error: "validation failed",
|
|
Details: validationErrors(err),
|
|
})
|
|
return
|
|
}
|
|
|
|
agent, err := h.Agents.Update(r.Context(), id, req)
|
|
if err != nil {
|
|
writeJSON(w, http.StatusNotFound, models.ErrorResponse{Error: "agent not found"})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, agent)
|
|
}
|
|
|
|
// DeleteAgent handles DELETE /api/agents/{id}.
|
|
func (h *Handler) DeleteAgent(w http.ResponseWriter, r *http.Request) {
|
|
id := chi.URLParam(r, "id")
|
|
if err := h.Agents.Delete(r.Context(), id); err != nil {
|
|
writeJSON(w, http.StatusNotFound, models.ErrorResponse{Error: "agent not found"})
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
// AgentHistory handles GET /api/agents/{id}/history.
|
|
func (h *Handler) AgentHistory(w http.ResponseWriter, r *http.Request) {
|
|
id := chi.URLParam(r, "id")
|
|
if _, err := h.Agents.Get(r.Context(), id); err != nil {
|
|
writeJSON(w, http.StatusNotFound, models.ErrorResponse{Error: "agent not found"})
|
|
return
|
|
}
|
|
|
|
// History is not currently persisted in PostgreSQL — return stub.
|
|
writeJSON(w, http.StatusOK, []models.AgentStatusHistoryEntry{})
|
|
}
|