Files
kandev/apps/backend/internal/common/database/database.go
T
jcfs 20f5f43fb3 feat: complete acp-go-sdk integration with task state updates
- Use acp-go-sdk for all ACP communication in agentctl
- Mark agent as completed when Prompt() returns successfully
- Update task state to COMPLETED when agent completes
- Update task state to FAILED when agent fails and retries exhausted
- Enhanced E2E test to verify agent actually creates files
- E2E test now validates full flow: task creation -> agent work -> file creation -> task completion
2026-01-10 16:46:35 +00:00

153 lines
4.0 KiB
Go

// Package database provides PostgreSQL connection pooling and database operations.
package database
import (
"context"
"fmt"
"time"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/kandev/kandev/internal/common/config"
)
// DB wraps a pgxpool.Pool and provides helper methods for database operations.
type DB struct {
pool *pgxpool.Pool
}
// NewDB creates a new database connection pool using the provided configuration.
// It builds the connection string from config, configures pool settings,
// establishes the connection, and verifies it with a ping.
func NewDB(ctx context.Context, cfg config.DatabaseConfig) (*DB, error) {
connString := cfg.DSN()
poolConfig, err := pgxpool.ParseConfig(connString)
if err != nil {
return nil, fmt.Errorf("failed to parse database config: %w", err)
}
// Configure pool settings
poolConfig.MaxConns = int32(cfg.MaxConns)
poolConfig.MinConns = int32(cfg.MinConns)
// Set reasonable connection timeouts
poolConfig.ConnConfig.ConnectTimeout = 10 * time.Second
// Create the connection pool
pool, err := pgxpool.NewWithConfig(ctx, poolConfig)
if err != nil {
return nil, fmt.Errorf("failed to create connection pool: %w", err)
}
// Verify the connection
if err := pool.Ping(ctx); err != nil {
pool.Close()
return nil, fmt.Errorf("failed to ping database: %w", err)
}
return &DB{pool: pool}, nil
}
// Pool returns the underlying pgxpool.Pool.
func (db *DB) Pool() *pgxpool.Pool {
return db.pool
}
// Close closes the connection pool.
func (db *DB) Close() {
if db.pool != nil {
db.pool.Close()
}
}
// Ping verifies the database connection is still alive.
func (db *DB) Ping(ctx context.Context) error {
return db.pool.Ping(ctx)
}
// Exec executes a query that doesn't return rows.
func (db *DB) Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error) {
return db.pool.Exec(ctx, sql, args...)
}
// Query executes a query that returns rows.
func (db *DB) Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error) {
return db.pool.Query(ctx, sql, args...)
}
// QueryRow executes a query that returns at most one row.
func (db *DB) QueryRow(ctx context.Context, sql string, args ...any) pgx.Row {
return db.pool.QueryRow(ctx, sql, args...)
}
// BeginTx starts a transaction with the given options.
func (db *DB) BeginTx(ctx context.Context, opts pgx.TxOptions) (pgx.Tx, error) {
return db.pool.BeginTx(ctx, opts)
}
// WithTx executes the given function within a transaction.
// If the function returns an error, the transaction is rolled back.
// If the function succeeds, the transaction is committed.
func (db *DB) WithTx(ctx context.Context, fn func(tx pgx.Tx) error) error {
tx, err := db.pool.Begin(ctx)
if err != nil {
return fmt.Errorf("failed to begin transaction: %w", err)
}
defer func() {
if p := recover(); p != nil {
// Rollback on panic
_ = tx.Rollback(ctx)
panic(p)
}
}()
if err := fn(tx); err != nil {
if rbErr := tx.Rollback(ctx); rbErr != nil {
return fmt.Errorf("tx failed: %w, rollback failed: %v", err, rbErr)
}
return err
}
if err := tx.Commit(ctx); err != nil {
return fmt.Errorf("failed to commit transaction: %w", err)
}
return nil
}
// WithTxOptions executes the given function within a transaction with custom options.
// If the function returns an error, the transaction is rolled back.
// If the function succeeds, the transaction is committed.
func (db *DB) WithTxOptions(ctx context.Context, opts pgx.TxOptions, fn func(tx pgx.Tx) error) error {
tx, err := db.pool.BeginTx(ctx, opts)
if err != nil {
return fmt.Errorf("failed to begin transaction: %w", err)
}
defer func() {
if p := recover(); p != nil {
// Rollback on panic
_ = tx.Rollback(ctx)
panic(p)
}
}()
if err := fn(tx); err != nil {
if rbErr := tx.Rollback(ctx); rbErr != nil {
return fmt.Errorf("tx failed: %w, rollback failed: %v", err, rbErr)
}
return err
}
if err := tx.Commit(ctx); err != nil {
return fmt.Errorf("failed to commit transaction: %w", err)
}
return nil
}