CUB-124: scaffold Control Center Go backend
All checks were successful
Dev Build / build-test (pull_request) Successful in 2m6s
All checks were successful
Dev Build / build-test (pull_request) Successful in 2m6s
This commit is contained in:
62
go-backend/internal/db/db.go
Normal file
62
go-backend/internal/db/db.go
Normal file
@@ -0,0 +1,62 @@
|
||||
// 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,14 +6,22 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"code.cubecraftcreations.com/CubeCraft-Creations/Control-Center/go-backend/internal/db"
|
||||
"code.cubecraftcreations.com/CubeCraft-Creations/Control-Center/go-backend/internal/handler"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"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.
|
||||
func New(h *handler.Handler) *chi.Mux {
|
||||
func New(deps *Dependencies) *chi.Mux {
|
||||
r := chi.NewRouter()
|
||||
|
||||
// ── Global middleware ──────────────────────────────────────────────────
|
||||
@@ -23,9 +31,13 @@ func New(h *handler.Handler) *chi.Mux {
|
||||
r.Use(middleware.Recoverer)
|
||||
r.Use(middleware.Timeout(30 * time.Second))
|
||||
|
||||
// ── CORS — permissive for development ──────────────────────────────────
|
||||
// ── CORS ───────────────────────────────────────────────────────────────
|
||||
corsOrigin := deps.CORSOrigin
|
||||
if corsOrigin == "" {
|
||||
corsOrigin = "*"
|
||||
}
|
||||
r.Use(cors.Handler(cors.Options{
|
||||
AllowedOrigins: []string{"*"},
|
||||
AllowedOrigins: []string{corsOrigin},
|
||||
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
||||
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"},
|
||||
ExposedHeaders: []string{"Link", "X-Total-Count"},
|
||||
@@ -33,32 +45,39 @@ func New(h *handler.Handler) *chi.Mux {
|
||||
MaxAge: 300,
|
||||
}))
|
||||
|
||||
// ── Health check ───────────────────────────────────────────────────────
|
||||
// ── Health check (with DB connectivity probe) ──────────────────────────
|
||||
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{"status":"ok"}`))
|
||||
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 ──────────────────────────────────────────────────────
|
||||
r.Route("/api", func(api chi.Router) {
|
||||
// Agents CRUD
|
||||
api.Route("/agents", func(agents chi.Router) {
|
||||
agents.Get("/", h.ListAgents) // GET /api/agents
|
||||
agents.Post("/", h.CreateAgent) // POST /api/agents
|
||||
agents.Get("/{id}", h.GetAgent) // GET /api/agents/{id}
|
||||
agents.Put("/{id}", h.UpdateAgent) // PUT /api/agents/{id}
|
||||
agents.Delete("/{id}", h.DeleteAgent) // DELETE /api/agents/{id}
|
||||
agents.Get("/{id}/history", h.AgentHistory) // GET /api/agents/{id}/history
|
||||
agents.Get("/", deps.Handler.ListAgents) // GET /api/agents
|
||||
agents.Post("/", deps.Handler.CreateAgent) // POST /api/agents
|
||||
agents.Get("/{id}", deps.Handler.GetAgent) // GET /api/agents/{id}
|
||||
agents.Put("/{id}", deps.Handler.UpdateAgent) // PUT /api/agents/{id}
|
||||
agents.Delete("/{id}", deps.Handler.DeleteAgent) // DELETE /api/agents/{id}
|
||||
agents.Get("/{id}/history", deps.Handler.AgentHistory) // GET /api/agents/{id}/history
|
||||
})
|
||||
|
||||
// Sessions
|
||||
api.Get("/sessions", h.ListSessions)
|
||||
api.Get("/sessions", deps.Handler.ListSessions)
|
||||
|
||||
// Tasks
|
||||
api.Get("/tasks", h.ListTasks)
|
||||
api.Get("/tasks", deps.Handler.ListTasks)
|
||||
|
||||
// Projects
|
||||
api.Get("/projects", h.ListProjects)
|
||||
api.Get("/projects", deps.Handler.ListProjects)
|
||||
})
|
||||
|
||||
return r
|
||||
|
||||
Reference in New Issue
Block a user