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
236 lines
5.6 KiB
Go
236 lines
5.6 KiB
Go
package handler
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"code.cubecraftcreations.com/CubeCraft-Creations/Control-Center/go-backend/internal/models"
|
|
)
|
|
|
|
// mockAgentRepo implements repository.AgentRepo in-memory for testing.
|
|
type mockAgentRepo struct {
|
|
mu sync.RWMutex
|
|
m map[string]models.AgentCardData
|
|
}
|
|
|
|
func newMockAgentRepo() *mockAgentRepo {
|
|
return &mockAgentRepo{m: make(map[string]models.AgentCardData)}
|
|
}
|
|
|
|
func (r *mockAgentRepo) Create(ctx context.Context, a models.AgentCardData) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
if _, ok := r.m[a.ID]; ok {
|
|
return fmt.Errorf("duplicate key: %s", a.ID)
|
|
}
|
|
r.m[a.ID] = a
|
|
return nil
|
|
}
|
|
|
|
func (r *mockAgentRepo) Get(ctx context.Context, id string) (models.AgentCardData, error) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
a, ok := r.m[id]
|
|
if !ok {
|
|
return a, fmt.Errorf("not found: %s", id)
|
|
}
|
|
return a, nil
|
|
}
|
|
|
|
func (r *mockAgentRepo) List(ctx context.Context, statusFilter models.AgentStatus) ([]models.AgentCardData, error) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
result := make([]models.AgentCardData, 0, len(r.m))
|
|
for _, a := range r.m {
|
|
if statusFilter != "" && a.Status != statusFilter {
|
|
continue
|
|
}
|
|
result = append(result, a)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (r *mockAgentRepo) Update(ctx context.Context, id string, req models.UpdateAgentRequest) (models.AgentCardData, error) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
a, ok := r.m[id]
|
|
if !ok {
|
|
return a, fmt.Errorf("not found: %s", id)
|
|
}
|
|
if req.Status != nil {
|
|
a.Status = *req.Status
|
|
}
|
|
if req.CurrentTask != nil {
|
|
a.CurrentTask = req.CurrentTask
|
|
}
|
|
if req.TaskProgress != nil {
|
|
a.TaskProgress = req.TaskProgress
|
|
}
|
|
if req.TaskElapsed != nil {
|
|
a.TaskElapsed = req.TaskElapsed
|
|
}
|
|
if req.Channel != nil {
|
|
a.Channel = *req.Channel
|
|
}
|
|
if req.ErrorMessage != nil {
|
|
a.ErrorMessage = req.ErrorMessage
|
|
}
|
|
a.LastActivity = time.Now().UTC().Format(time.RFC3339)
|
|
r.m[id] = a
|
|
return a, nil
|
|
}
|
|
|
|
func (r *mockAgentRepo) Delete(ctx context.Context, id string) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
if _, ok := r.m[id]; !ok {
|
|
return fmt.Errorf("not found: %s", id)
|
|
}
|
|
delete(r.m, id)
|
|
return nil
|
|
}
|
|
|
|
func (r *mockAgentRepo) Count(ctx context.Context) (int, error) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
return len(r.m), nil
|
|
}
|
|
|
|
// ─── Mock Session Repo ──────────────────────────────────────────────────────────
|
|
|
|
type mockSessionRepo struct {
|
|
mu sync.RWMutex
|
|
m map[string]models.Session
|
|
}
|
|
|
|
func newMockSessionRepo() *mockSessionRepo {
|
|
return &mockSessionRepo{m: make(map[string]models.Session)}
|
|
}
|
|
|
|
func (r *mockSessionRepo) Create(ctx context.Context, s models.Session) (models.Session, error) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
if s.ID == "" {
|
|
s.ID = fmt.Sprintf("sess-%d", len(r.m)+1)
|
|
}
|
|
if s.StartedAt.IsZero() {
|
|
s.StartedAt = time.Now().UTC()
|
|
}
|
|
if s.LastActivityAt.IsZero() {
|
|
s.LastActivityAt = s.StartedAt
|
|
}
|
|
r.m[s.ID] = s
|
|
return s, nil
|
|
}
|
|
|
|
func (r *mockSessionRepo) ListActive(ctx context.Context) ([]models.Session, error) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
result := make([]models.Session, 0)
|
|
for _, s := range r.m {
|
|
if s.Status == "running" || s.Status == "streaming" {
|
|
result = append(result, s)
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (r *mockSessionRepo) Count(ctx context.Context) (int, error) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
return len(r.m), nil
|
|
}
|
|
|
|
// ─── Mock Task Repo ─────────────────────────────────────────────────────────────
|
|
|
|
type mockTaskRepo struct {
|
|
mu sync.RWMutex
|
|
m map[string]models.Task
|
|
}
|
|
|
|
func newMockTaskRepo() *mockTaskRepo {
|
|
return &mockTaskRepo{m: make(map[string]models.Task)}
|
|
}
|
|
|
|
func (r *mockTaskRepo) Create(ctx context.Context, t models.Task) (models.Task, error) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
if t.ID == "" {
|
|
t.ID = fmt.Sprintf("task-%d", len(r.m)+1)
|
|
}
|
|
now := time.Now().UTC()
|
|
if t.CreatedAt.IsZero() {
|
|
t.CreatedAt = now
|
|
}
|
|
if t.UpdatedAt.IsZero() {
|
|
t.UpdatedAt = now
|
|
}
|
|
r.m[t.ID] = t
|
|
return t, nil
|
|
}
|
|
|
|
func (r *mockTaskRepo) ListRecent(ctx context.Context) ([]models.Task, error) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
result := make([]models.Task, 0, len(r.m))
|
|
for _, t := range r.m {
|
|
result = append(result, t)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (r *mockTaskRepo) Count(ctx context.Context) (int, error) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
return len(r.m), nil
|
|
}
|
|
|
|
// ─── Mock Project Repo ─────────────────────────────────────────────────────────
|
|
|
|
type mockProjectRepo struct {
|
|
mu sync.RWMutex
|
|
m map[string]models.Project
|
|
}
|
|
|
|
func newMockProjectRepo() *mockProjectRepo {
|
|
return &mockProjectRepo{m: make(map[string]models.Project)}
|
|
}
|
|
|
|
func (r *mockProjectRepo) Create(ctx context.Context, p models.Project) (models.Project, error) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
if p.ID == "" {
|
|
p.ID = fmt.Sprintf("proj-%d", len(r.m)+1)
|
|
}
|
|
now := time.Now().UTC()
|
|
if p.CreatedAt.IsZero() {
|
|
p.CreatedAt = now
|
|
}
|
|
if p.UpdatedAt.IsZero() {
|
|
p.UpdatedAt = now
|
|
}
|
|
if p.AgentIDs == nil {
|
|
p.AgentIDs = []string{}
|
|
}
|
|
r.m[p.ID] = p
|
|
return p, nil
|
|
}
|
|
|
|
func (r *mockProjectRepo) List(ctx context.Context) ([]models.Project, error) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
result := make([]models.Project, 0, len(r.m))
|
|
for _, p := range r.m {
|
|
result = append(result, p)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (r *mockProjectRepo) Count(ctx context.Context) (int, error) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
return len(r.m), nil
|
|
}
|