// Package repository provides PostgreSQL-backed CRUD implementations // for the Control Center domain entities. Each repository takes a // *pgxpool.Pool in its constructor and uses pgx.CollectRows() for scanning. package repository import ( "context" "fmt" "time" "code.cubecraftcreations.com/CubeCraft-Creations/Control-Center/go-backend/internal/models" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" ) // AgentRepository provides PostgreSQL-backed CRUD for agents. type AgentRepository struct { pool *pgxpool.Pool } // NewAgentRepository returns a repository wired to the given connection pool. func NewAgentRepository(pool *pgxpool.Pool) *AgentRepository { return &AgentRepository{pool: pool} } // Create inserts a new agent. It maps the models.AgentCardData fields onto // the agents table columns (uuid id, text name, text status, text task, // int progress, text session_key, text channel). func (r *AgentRepository) Create(ctx context.Context, a models.AgentCardData) error { prog := 0 if a.TaskProgress != nil { prog = *a.TaskProgress } _, err := r.pool.Exec(ctx, ` INSERT INTO agents (id, name, status, task, progress, session_key, channel, last_activity) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) `, a.ID, a.DisplayName, string(a.Status), a.CurrentTask, prog, a.SessionKey, a.Channel, time.Now().UTC()) return err } // Get returns a single agent by its string id. func (r *AgentRepository) Get(ctx context.Context, id string) (models.AgentCardData, error) { var a models.AgentCardData var task *string var prog int var lastActivity time.Time err := r.pool.QueryRow(ctx, ` SELECT id, name, status, task, progress, session_key, channel, last_activity FROM agents WHERE id = $1 `, id).Scan(&a.ID, &a.DisplayName, &a.Status, &task, &prog, &a.SessionKey, &a.Channel, &lastActivity) if err != nil { return a, err } a.CurrentTask = task if prog > 0 || task != nil { p := prog a.TaskProgress = &p } a.LastActivity = lastActivity.UTC().Format(time.RFC3339) // Role is not persisted in the current schema — set a sensible default. a.Role = "agent" return a, nil } // List returns all agents, optionally filtered by status. // Results are ordered by name (display_name). func (r *AgentRepository) List(ctx context.Context, statusFilter models.AgentStatus) ([]models.AgentCardData, error) { var rows pgx.Rows var err error if statusFilter != "" { rows, err = r.pool.Query(ctx, ` SELECT id, name, status, task, progress, session_key, channel, last_activity FROM agents WHERE status = $1 ORDER BY name `, string(statusFilter)) } else { rows, err = r.pool.Query(ctx, ` SELECT id, name, status, task, progress, session_key, channel, last_activity FROM agents ORDER BY name `) } if err != nil { return nil, err } defer rows.Close() return pgx.CollectRows(rows, func(row pgx.CollectableRow) (models.AgentCardData, error) { var a models.AgentCardData var task *string var prog int var lastActivity time.Time if err := row.Scan(&a.ID, &a.DisplayName, &a.Status, &task, &prog, &a.SessionKey, &a.Channel, &lastActivity); err != nil { return a, err } a.CurrentTask = task if prog > 0 || task != nil { p := prog a.TaskProgress = &p } a.LastActivity = lastActivity.UTC().Format(time.RFC3339) a.Role = "agent" return a, nil }) } // Update applies partial updates to an agent. Returns the updated agent. func (r *AgentRepository) Update(ctx context.Context, id string, req models.UpdateAgentRequest) (models.AgentCardData, error) { // Build dynamic SET clause. setClauses := []string{"last_activity = $2"} args := []any{id, time.Now().UTC()} argIdx := 3 if req.Status != nil { setClauses = append(setClauses, fmt.Sprintf("status = $%d", argIdx)) args = append(args, string(*req.Status)) argIdx++ } if req.CurrentTask != nil { setClauses = append(setClauses, fmt.Sprintf("task = $%d", argIdx)) args = append(args, *req.CurrentTask) argIdx++ } if req.TaskProgress != nil { setClauses = append(setClauses, fmt.Sprintf("progress = $%d", argIdx)) args = append(args, *req.TaskProgress) argIdx++ } if req.Channel != nil { setClauses = append(setClauses, fmt.Sprintf("channel = $%d", argIdx)) args = append(args, *req.Channel) argIdx++ } // Build and execute query := "UPDATE agents SET " for i, clause := range setClauses { if i > 0 { query += ", " } query += clause } query += " WHERE id = $1" ct, err := r.pool.Exec(ctx, query, args...) if err != nil { return models.AgentCardData{}, err } if ct.RowsAffected() == 0 { return models.AgentCardData{}, fmt.Errorf("agent not found: %s", id) } return r.Get(ctx, id) } // Delete removes an agent. Returns nil even if the agent doesn't exist // (idempotent). Returns a wrapped error only on transport failures. func (r *AgentRepository) Delete(ctx context.Context, id string) error { _, err := r.pool.Exec(ctx, `DELETE FROM agents WHERE id = $1`, id) return err } // Count returns the total number of agents. func (r *AgentRepository) Count(ctx context.Context) (int, error) { var n int err := r.pool.QueryRow(ctx, `SELECT COUNT(*) FROM agents`).Scan(&n) return n, err } // CountByStatus returns the number of agents with the given status. func (r *AgentRepository) CountByStatus(ctx context.Context, status models.AgentStatus) (int, error) { var n int err := r.pool.QueryRow(ctx, `SELECT COUNT(*) FROM agents WHERE status = $1`, string(status)).Scan(&n) return n, err }