Compare commits
1 Commits
agent/dex/
...
agent/hex/
| Author | SHA1 | Date | |
|---|---|---|---|
| 437a519c36 |
106
backend/internal/models/models.go
Normal file
106
backend/internal/models/models.go
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
// Package models defines the database entities for the Control Center Go backend.
|
||||||
|
// Structs map 1:1 to the PostgreSQL schema defined in backend/migrations/.
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AgentStatus represents the possible lifecycle states of an agent.
|
||||||
|
type AgentStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
AgentStatusActive AgentStatus = "active"
|
||||||
|
AgentStatusIdle AgentStatus = "idle"
|
||||||
|
AgentStatusThinking AgentStatus = "thinking"
|
||||||
|
AgentStatusError AgentStatus = "error"
|
||||||
|
AgentStatusOffline AgentStatus = "offline"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Agent represents a registered agent and its current state.
|
||||||
|
type Agent struct {
|
||||||
|
ID pgtype.UUID `db:"id" json:"id"`
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
Status AgentStatus `db:"status" json:"status"`
|
||||||
|
Task *string `db:"task" json:"task,omitempty"`
|
||||||
|
Progress int32 `db:"progress" json:"progress"`
|
||||||
|
SessionKey *string `db:"session_key" json:"session_key,omitempty"`
|
||||||
|
Channel *string `db:"channel" json:"channel,omitempty"`
|
||||||
|
LastActivity time.Time `db:"last_activity" json:"last_activity"`
|
||||||
|
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionStatus represents the possible states of an agent session.
|
||||||
|
type SessionStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
SessionStatusRunning SessionStatus = "running"
|
||||||
|
SessionStatusCompleted SessionStatus = "completed"
|
||||||
|
SessionStatusCrashed SessionStatus = "crashed"
|
||||||
|
SessionStatusTerminated SessionStatus = "terminated"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Session tracks an agent session over time.
|
||||||
|
type Session struct {
|
||||||
|
ID pgtype.UUID `db:"id" json:"id"`
|
||||||
|
AgentID pgtype.UUID `db:"agent_id" json:"agent_id"`
|
||||||
|
StartedAt time.Time `db:"started_at" json:"started_at"`
|
||||||
|
EndedAt *time.Time `db:"ended_at" json:"ended_at,omitempty"`
|
||||||
|
Status SessionStatus `db:"status" json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskLogStatus represents the possible states of a task log entry.
|
||||||
|
type TaskLogStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TaskLogStatusPending TaskLogStatus = "pending"
|
||||||
|
TaskLogStatusRunning TaskLogStatus = "running"
|
||||||
|
TaskLogStatusCompleted TaskLogStatus = "completed"
|
||||||
|
TaskLogStatusFailed TaskLogStatus = "failed"
|
||||||
|
TaskLogStatusCancelled TaskLogStatus = "cancelled"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TaskLog records a historical task assigned to an agent.
|
||||||
|
type TaskLog struct {
|
||||||
|
ID pgtype.UUID `db:"id" json:"id"`
|
||||||
|
AgentID pgtype.UUID `db:"agent_id" json:"agent_id"`
|
||||||
|
Task string `db:"task" json:"task"`
|
||||||
|
Status TaskLogStatus `db:"status" json:"status"`
|
||||||
|
StartedAt time.Time `db:"started_at" json:"started_at"`
|
||||||
|
CompletedAt *time.Time `db:"completed_at" json:"completed_at,omitempty"`
|
||||||
|
ErrorMessage *string `db:"error_message" json:"error_message,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProjectStatus represents the possible states of a project.
|
||||||
|
type ProjectStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ProjectStatusPlanned ProjectStatus = "planned"
|
||||||
|
ProjectStatusInProgress ProjectStatus = "in_progress"
|
||||||
|
ProjectStatusCompleted ProjectStatus = "completed"
|
||||||
|
ProjectStatusPaused ProjectStatus = "paused"
|
||||||
|
ProjectStatusCancelled ProjectStatus = "cancelled"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Project represents a project managed by the Control Center.
|
||||||
|
type Project struct {
|
||||||
|
ID pgtype.UUID `db:"id" json:"id"`
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
Description *string `db:"description" json:"description,omitempty"`
|
||||||
|
Status ProjectStatus `db:"status" json:"status"`
|
||||||
|
AgentID *pgtype.UUID `db:"agent_id" json:"agent_id,omitempty"`
|
||||||
|
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AgentEvent represents an event in the agent lifecycle or telemetry stream.
|
||||||
|
type AgentEvent struct {
|
||||||
|
ID pgtype.UUID `db:"id" json:"id"`
|
||||||
|
AgentID pgtype.UUID `db:"agent_id" json:"agent_id"`
|
||||||
|
EventType string `db:"event_type" json:"event_type"`
|
||||||
|
Payload *map[string]interface{} `db:"payload" json:"payload,omitempty"`
|
||||||
|
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||||
|
}
|
||||||
9
backend/migrations/001_initial_schema.down.sql
Normal file
9
backend/migrations/001_initial_schema.down.sql
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
-- Migration: 001_initial_schema (down)
|
||||||
|
-- Description: Reverts the core Control Center database schema.
|
||||||
|
|
||||||
|
-- Drop in reverse dependency order to avoid FK conflicts
|
||||||
|
DROP TABLE IF EXISTS agent_events;
|
||||||
|
DROP TABLE IF EXISTS task_logs;
|
||||||
|
DROP TABLE IF EXISTS sessions;
|
||||||
|
DROP TABLE IF EXISTS projects;
|
||||||
|
DROP TABLE IF EXISTS agents;
|
||||||
97
backend/migrations/001_initial_schema.up.sql
Normal file
97
backend/migrations/001_initial_schema.up.sql
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
-- Migration: 001_initial_schema
|
||||||
|
-- Description: Creates the core Control Center database schema.
|
||||||
|
|
||||||
|
-- Enable UUID extension
|
||||||
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- Table: agents
|
||||||
|
-- ============================================
|
||||||
|
CREATE TABLE agents (
|
||||||
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
status TEXT NOT NULL DEFAULT 'idle'
|
||||||
|
CHECK (status IN ('active', 'idle', 'thinking', 'error', 'offline')),
|
||||||
|
task TEXT,
|
||||||
|
progress INT NOT NULL DEFAULT 0
|
||||||
|
CHECK (progress >= 0 AND progress <= 100),
|
||||||
|
session_key TEXT,
|
||||||
|
channel TEXT,
|
||||||
|
last_activity TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMENT ON TABLE agents IS 'Registered agents and their current state';
|
||||||
|
COMMENT ON COLUMN agents.status IS 'Agent lifecycle status: active, idle, thinking, error, offline';
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- Table: sessions
|
||||||
|
-- ============================================
|
||||||
|
CREATE TABLE sessions (
|
||||||
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
agent_id UUID NOT NULL,
|
||||||
|
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
ended_at TIMESTAMPTZ,
|
||||||
|
status TEXT NOT NULL DEFAULT 'running'
|
||||||
|
CHECK (status IN ('running', 'completed', 'crashed', 'terminated')),
|
||||||
|
CONSTRAINT fk_sessions_agent
|
||||||
|
FOREIGN KEY (agent_id) REFERENCES agents(id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMENT ON TABLE sessions IS 'Agent session history';
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- Table: task_logs
|
||||||
|
-- ============================================
|
||||||
|
CREATE TABLE task_logs (
|
||||||
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
agent_id UUID NOT NULL,
|
||||||
|
task TEXT NOT NULL,
|
||||||
|
status TEXT NOT NULL DEFAULT 'pending'
|
||||||
|
CHECK (status IN ('pending', 'running', 'completed', 'failed', 'cancelled')),
|
||||||
|
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
completed_at TIMESTAMPTZ,
|
||||||
|
error_message TEXT,
|
||||||
|
CONSTRAINT fk_task_logs_agent
|
||||||
|
FOREIGN KEY (agent_id) REFERENCES agents(id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMENT ON TABLE task_logs IS 'Historical record of tasks assigned to agents';
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- Table: projects
|
||||||
|
-- ============================================
|
||||||
|
CREATE TABLE projects (
|
||||||
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
status TEXT NOT NULL DEFAULT 'planned'
|
||||||
|
CHECK (status IN ('planned', 'in_progress', 'completed', 'paused', 'cancelled')),
|
||||||
|
agent_id UUID,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
CONSTRAINT fk_projects_agent
|
||||||
|
FOREIGN KEY (agent_id) REFERENCES agents(id)
|
||||||
|
ON DELETE SET NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMENT ON TABLE projects IS 'Projects managed by the Control Center';
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- Table: agent_events
|
||||||
|
-- ============================================
|
||||||
|
CREATE TABLE agent_events (
|
||||||
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
agent_id UUID NOT NULL,
|
||||||
|
event_type TEXT NOT NULL,
|
||||||
|
payload JSONB,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
CONSTRAINT fk_agent_events_agent
|
||||||
|
FOREIGN KEY (agent_id) REFERENCES agents(id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMENT ON TABLE agent_events IS 'Event stream for agent lifecycle and telemetry';
|
||||||
20
backend/migrations/002_add_indexes.down.sql
Normal file
20
backend/migrations/002_add_indexes.down.sql
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
-- Migration: 002_add_indexes (down)
|
||||||
|
-- Description: Remove all indexes added in 002_add_indexes.
|
||||||
|
|
||||||
|
DROP INDEX IF EXISTS idx_agents_status;
|
||||||
|
DROP INDEX IF EXISTS idx_agents_last_activity;
|
||||||
|
DROP INDEX IF EXISTS idx_agents_created_at;
|
||||||
|
|
||||||
|
DROP INDEX IF EXISTS idx_sessions_agent_id;
|
||||||
|
DROP INDEX IF EXISTS idx_sessions_status;
|
||||||
|
DROP INDEX IF EXISTS idx_sessions_started_at;
|
||||||
|
|
||||||
|
DROP INDEX IF EXISTS idx_task_logs_agent_started;
|
||||||
|
DROP INDEX IF EXISTS idx_task_logs_status;
|
||||||
|
|
||||||
|
DROP INDEX IF EXISTS idx_agent_events_agent_created;
|
||||||
|
DROP INDEX IF EXISTS idx_agent_events_event_type;
|
||||||
|
|
||||||
|
DROP INDEX IF EXISTS idx_projects_status;
|
||||||
|
DROP INDEX IF EXISTS idx_projects_agent_id;
|
||||||
|
DROP INDEX IF EXISTS idx_projects_created;
|
||||||
25
backend/migrations/002_add_indexes.up.sql
Normal file
25
backend/migrations/002_add_indexes.up.sql
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
-- Migration: 002_add_indexes
|
||||||
|
-- Description: Add performance indexes for common query patterns.
|
||||||
|
|
||||||
|
-- agents: status filtering, activity ordering
|
||||||
|
CREATE INDEX idx_agents_status ON agents(status);
|
||||||
|
CREATE INDEX idx_agents_last_activity ON agents(last_activity DESC);
|
||||||
|
CREATE INDEX idx_agents_created_at ON agents(created_at DESC);
|
||||||
|
|
||||||
|
-- sessions: agent session lookups, active session checks
|
||||||
|
CREATE INDEX idx_sessions_agent_id ON sessions(agent_id);
|
||||||
|
CREATE INDEX idx_sessions_status ON sessions(status);
|
||||||
|
CREATE INDEX idx_sessions_started_at ON sessions(started_at DESC);
|
||||||
|
|
||||||
|
-- task_logs: agent task history, chronological ordering
|
||||||
|
CREATE INDEX idx_task_logs_agent_started ON task_logs(agent_id, started_at DESC);
|
||||||
|
CREATE INDEX idx_task_logs_status ON task_logs(status);
|
||||||
|
|
||||||
|
-- agent_events: event stream queries
|
||||||
|
CREATE INDEX idx_agent_events_agent_created ON agent_events(agent_id, created_at DESC);
|
||||||
|
CREATE INDEX idx_agent_events_event_type ON agent_events(event_type);
|
||||||
|
|
||||||
|
-- projects: status filtering, agent assignment
|
||||||
|
CREATE INDEX idx_projects_status ON projects(status);
|
||||||
|
CREATE INDEX idx_projects_agent_id ON projects(agent_id);
|
||||||
|
CREATE INDEX idx_projects_created ON projects(created_at DESC);
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
# Ignore local build artifacts and version-control files
|
|
||||||
*.exe
|
|
||||||
*.dll
|
|
||||||
*.so
|
|
||||||
*.dylib
|
|
||||||
*.test
|
|
||||||
*.out
|
|
||||||
bin/
|
|
||||||
dist/
|
|
||||||
|
|
||||||
# Version control
|
|
||||||
.git
|
|
||||||
.gitignore
|
|
||||||
|
|
||||||
# IDE / editor
|
|
||||||
.idea
|
|
||||||
.vscode
|
|
||||||
*.swp
|
|
||||||
*.swo
|
|
||||||
*~
|
|
||||||
|
|
||||||
# OS
|
|
||||||
.DS_Store
|
|
||||||
Thumbs.db
|
|
||||||
|
|
||||||
# Dependency cache (already fetched in Dockerfile)
|
|
||||||
vendor/
|
|
||||||
|
|
||||||
# Documentation
|
|
||||||
README.md
|
|
||||||
*.md
|
|
||||||
|
|
||||||
# CI / CD
|
|
||||||
.github/
|
|
||||||
.gitea/
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
# Build stage
|
|
||||||
FROM golang:1.23-alpine AS builder
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Install build dependencies
|
|
||||||
RUN apk add --no-cache git ca-certificates
|
|
||||||
|
|
||||||
# Copy dependency files first for better layer caching
|
|
||||||
COPY go.mod go.sum ./
|
|
||||||
RUN go mod download
|
|
||||||
|
|
||||||
# Copy source code
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Build the binary
|
|
||||||
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /bin/server ./cmd/server
|
|
||||||
|
|
||||||
# ── Final stage ─────────────────────────────────────────────────────────
|
|
||||||
FROM alpine:latest
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Install ca-certificates for HTTPS outbound calls
|
|
||||||
RUN apk --no-cache add ca-certificates
|
|
||||||
|
|
||||||
# Copy binary from builder
|
|
||||||
COPY --from=builder /bin/server /app/server
|
|
||||||
|
|
||||||
# Expose the default port (overridden by PORT env var)
|
|
||||||
EXPOSE 8080
|
|
||||||
|
|
||||||
# Run as non-root
|
|
||||||
RUN adduser -D -s /bin/sh appuser
|
|
||||||
USER appuser
|
|
||||||
|
|
||||||
ENTRYPOINT ["/app/server"]
|
|
||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.cubecraftcreations.com/CubeCraft-Creations/Control-Center/go-backend/internal/config"
|
"code.cubecraftcreations.com/CubeCraft-Creations/Control-Center/go-backend/internal/config"
|
||||||
"code.cubecraftcreations.com/CubeCraft-Creations/Control-Center/go-backend/internal/db"
|
|
||||||
"code.cubecraftcreations.com/CubeCraft-Creations/Control-Center/go-backend/internal/handler"
|
"code.cubecraftcreations.com/CubeCraft-Creations/Control-Center/go-backend/internal/handler"
|
||||||
"code.cubecraftcreations.com/CubeCraft-Creations/Control-Center/go-backend/internal/router"
|
"code.cubecraftcreations.com/CubeCraft-Creations/Control-Center/go-backend/internal/router"
|
||||||
"code.cubecraftcreations.com/CubeCraft-Creations/Control-Center/go-backend/internal/store"
|
"code.cubecraftcreations.com/CubeCraft-Creations/Control-Center/go-backend/internal/store"
|
||||||
@@ -28,16 +27,6 @@ func main() {
|
|||||||
}))
|
}))
|
||||||
slog.SetDefault(logger)
|
slog.SetDefault(logger)
|
||||||
|
|
||||||
// ── Database (optional until CUB-120 schema is ready) ──────────────────
|
|
||||||
var pool *db.Pool
|
|
||||||
if cfg.DatabaseURL != "" {
|
|
||||||
var err error
|
|
||||||
pool, err = db.New(cfg.DatabaseURL)
|
|
||||||
if err != nil {
|
|
||||||
slog.Warn("database connection failed; running without DB", "error", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Stores (in-memory for now; PostgreSQL after CUB-120) ────────────────
|
// ── Stores (in-memory for now; PostgreSQL after CUB-120) ────────────────
|
||||||
agentStore := store.NewAgentStore()
|
agentStore := store.NewAgentStore()
|
||||||
sessionStore := store.NewSessionStore()
|
sessionStore := store.NewSessionStore()
|
||||||
@@ -48,11 +37,7 @@ func main() {
|
|||||||
h := handler.NewHandler(agentStore, sessionStore, taskStore, projectStore)
|
h := handler.NewHandler(agentStore, sessionStore, taskStore, projectStore)
|
||||||
|
|
||||||
// ── Router ─────────────────────────────────────────────────────────────
|
// ── Router ─────────────────────────────────────────────────────────────
|
||||||
r := router.New(&router.Dependencies{
|
r := router.New(h)
|
||||||
Handler: h,
|
|
||||||
DB: pool,
|
|
||||||
CORSOrigin: cfg.CORSOrigin,
|
|
||||||
})
|
|
||||||
|
|
||||||
// ── Server ─────────────────────────────────────────────────────────────
|
// ── Server ─────────────────────────────────────────────────────────────
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
@@ -86,10 +71,6 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if pool != nil {
|
|
||||||
pool.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("server exited cleanly")
|
slog.Info("server exited cleanly")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,20 +7,15 @@ require (
|
|||||||
github.com/go-chi/cors v1.2.1
|
github.com/go-chi/cors v1.2.1
|
||||||
github.com/go-playground/validator/v10 v10.24.0
|
github.com/go-playground/validator/v10 v10.24.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/jackc/pgx/v5 v5.7.2
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
golang.org/x/crypto v0.32.0 // indirect
|
golang.org/x/crypto v0.32.0 // indirect
|
||||||
golang.org/x/net v0.34.0 // indirect
|
golang.org/x/net v0.34.0 // indirect
|
||||||
golang.org/x/sync v0.10.0 // indirect
|
|
||||||
golang.org/x/sys v0.29.0 // indirect
|
golang.org/x/sys v0.29.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/text v0.21.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||||
@@ -17,34 +16,19 @@ github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE
|
|||||||
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
|
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
|
||||||
github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI=
|
|
||||||
github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
|
|
||||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
|
||||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
|
||||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
// Package db provides PostgreSQL connection management using pgx.
|
|
||||||
package db
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5/pgxpool"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Pool wraps a pgx connection pool with lifecycle helpers.
|
|
||||||
type Pool struct {
|
|
||||||
*pgxpool.Pool
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a connection pool from a PostgreSQL DSN.
|
|
||||||
func New(dsn string) (*Pool, error) {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
cfg, err := pgxpool.ParseConfig(dsn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parse pgx config: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sensible defaults
|
|
||||||
cfg.MaxConns = 20
|
|
||||||
cfg.MinConns = 2
|
|
||||||
cfg.MaxConnLifetime = 30 * time.Minute
|
|
||||||
cfg.MaxConnIdleTime = 10 * time.Minute
|
|
||||||
cfg.HealthCheckPeriod = 5 * time.Second
|
|
||||||
|
|
||||||
pool, err := pgxpool.NewWithConfig(ctx, cfg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("create pgx pool: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pool.Ping(ctx); err != nil {
|
|
||||||
pool.Close()
|
|
||||||
return nil, fmt.Errorf("ping database: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("database connected", "pool", cfg.ConnConfig.Database)
|
|
||||||
return &Pool{Pool: pool}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close shuts down the pool gracefully.
|
|
||||||
func (p *Pool) Close() {
|
|
||||||
if p.Pool != nil {
|
|
||||||
p.Pool.Close()
|
|
||||||
slog.Info("database pool closed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Health returns nil if the database is reachable.
|
|
||||||
func (p *Pool) Health(ctx context.Context) error {
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
return p.Ping(ctx)
|
|
||||||
}
|
|
||||||
@@ -6,22 +6,14 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.cubecraftcreations.com/CubeCraft-Creations/Control-Center/go-backend/internal/db"
|
|
||||||
"code.cubecraftcreations.com/CubeCraft-Creations/Control-Center/go-backend/internal/handler"
|
"code.cubecraftcreations.com/CubeCraft-Creations/Control-Center/go-backend/internal/handler"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
"github.com/go-chi/cors"
|
"github.com/go-chi/cors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Dependencies carries the handler and database pool into the router.
|
|
||||||
type Dependencies struct {
|
|
||||||
Handler *handler.Handler
|
|
||||||
DB *db.Pool
|
|
||||||
CORSOrigin string
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a fully-configured chi router with all API routes mounted.
|
// New creates a fully-configured chi router with all API routes mounted.
|
||||||
func New(deps *Dependencies) *chi.Mux {
|
func New(h *handler.Handler) *chi.Mux {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
|
|
||||||
// ── Global middleware ──────────────────────────────────────────────────
|
// ── Global middleware ──────────────────────────────────────────────────
|
||||||
@@ -31,13 +23,9 @@ func New(deps *Dependencies) *chi.Mux {
|
|||||||
r.Use(middleware.Recoverer)
|
r.Use(middleware.Recoverer)
|
||||||
r.Use(middleware.Timeout(30 * time.Second))
|
r.Use(middleware.Timeout(30 * time.Second))
|
||||||
|
|
||||||
// ── CORS ───────────────────────────────────────────────────────────────
|
// ── CORS — permissive for development ──────────────────────────────────
|
||||||
corsOrigin := deps.CORSOrigin
|
|
||||||
if corsOrigin == "" {
|
|
||||||
corsOrigin = "*"
|
|
||||||
}
|
|
||||||
r.Use(cors.Handler(cors.Options{
|
r.Use(cors.Handler(cors.Options{
|
||||||
AllowedOrigins: []string{corsOrigin},
|
AllowedOrigins: []string{"*"},
|
||||||
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
||||||
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"},
|
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"},
|
||||||
ExposedHeaders: []string{"Link", "X-Total-Count"},
|
ExposedHeaders: []string{"Link", "X-Total-Count"},
|
||||||
@@ -45,39 +33,32 @@ func New(deps *Dependencies) *chi.Mux {
|
|||||||
MaxAge: 300,
|
MaxAge: 300,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// ── Health check (with DB connectivity probe) ──────────────────────────
|
// ── Health check ───────────────────────────────────────────────────────
|
||||||
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
|
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
status := "ok"
|
w.Write([]byte(`{"status":"ok"}`))
|
||||||
if deps.DB != nil {
|
|
||||||
if err := deps.DB.Health(r.Context()); err != nil {
|
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
|
||||||
status = "db_unhealthy"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.Write([]byte(`{"status":"` + status + `"}`))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// ── API v1 routes ──────────────────────────────────────────────────────
|
// ── API v1 routes ──────────────────────────────────────────────────────
|
||||||
r.Route("/api", func(api chi.Router) {
|
r.Route("/api", func(api chi.Router) {
|
||||||
// Agents CRUD
|
// Agents CRUD
|
||||||
api.Route("/agents", func(agents chi.Router) {
|
api.Route("/agents", func(agents chi.Router) {
|
||||||
agents.Get("/", deps.Handler.ListAgents) // GET /api/agents
|
agents.Get("/", h.ListAgents) // GET /api/agents
|
||||||
agents.Post("/", deps.Handler.CreateAgent) // POST /api/agents
|
agents.Post("/", h.CreateAgent) // POST /api/agents
|
||||||
agents.Get("/{id}", deps.Handler.GetAgent) // GET /api/agents/{id}
|
agents.Get("/{id}", h.GetAgent) // GET /api/agents/{id}
|
||||||
agents.Put("/{id}", deps.Handler.UpdateAgent) // PUT /api/agents/{id}
|
agents.Put("/{id}", h.UpdateAgent) // PUT /api/agents/{id}
|
||||||
agents.Delete("/{id}", deps.Handler.DeleteAgent) // DELETE /api/agents/{id}
|
agents.Delete("/{id}", h.DeleteAgent) // DELETE /api/agents/{id}
|
||||||
agents.Get("/{id}/history", deps.Handler.AgentHistory) // GET /api/agents/{id}/history
|
agents.Get("/{id}/history", h.AgentHistory) // GET /api/agents/{id}/history
|
||||||
})
|
})
|
||||||
|
|
||||||
// Sessions
|
// Sessions
|
||||||
api.Get("/sessions", deps.Handler.ListSessions)
|
api.Get("/sessions", h.ListSessions)
|
||||||
|
|
||||||
// Tasks
|
// Tasks
|
||||||
api.Get("/tasks", deps.Handler.ListTasks)
|
api.Get("/tasks", h.ListTasks)
|
||||||
|
|
||||||
// Projects
|
// Projects
|
||||||
api.Get("/projects", deps.Handler.ListProjects)
|
api.Get("/projects", h.ListProjects)
|
||||||
})
|
})
|
||||||
|
|
||||||
return r
|
return r
|
||||||
|
|||||||
Reference in New Issue
Block a user