package database import ( "context" "fmt" "log" "os" "path/filepath" "time" "github.com/jackc/pgx/v5/pgxpool" ) // DB holds the database connection pool type DB struct { Pool *pgxpool.Pool } // Config holds database configuration type Config struct { Host string Port string User string Password string DBName string SSLMode string } // NewConfig creates a new database config from environment variables func NewConfig() *Config { return &Config{ Host: getEnv("DB_HOST", "localhost"), Port: getEnv("DB_PORT", "5432"), User: getEnv("DB_USER", "postgres"), Password: getEnv("DB_PASSWORD", "postgres"), DBName: getEnv("DB_NAME", "breakpilot"), SSLMode: getEnv("DB_SSLMODE", "disable"), } } func getEnv(key, defaultValue string) string { if value := os.Getenv(key); value != "" { return value } return defaultValue } // ConnectionString returns the PostgreSQL connection string func (c *Config) ConnectionString() string { return fmt.Sprintf( "postgres://%s:%s@%s:%s/%s?sslmode=%s", c.User, c.Password, c.Host, c.Port, c.DBName, c.SSLMode, ) } // New creates a new database connection func New(ctx context.Context, cfg *Config) (*DB, error) { config, err := pgxpool.ParseConfig(cfg.ConnectionString()) if err != nil { return nil, fmt.Errorf("failed to parse database config: %w", err) } // Configure connection pool config.MaxConns = 10 config.MinConns = 2 config.MaxConnLifetime = time.Hour config.MaxConnIdleTime = 30 * time.Minute pool, err := pgxpool.NewWithConfig(ctx, config) if err != nil { return nil, fmt.Errorf("failed to create connection pool: %w", err) } // Test connection if err := pool.Ping(ctx); err != nil { pool.Close() return nil, fmt.Errorf("failed to ping database: %w", err) } log.Printf("Connected to database %s on %s:%s", cfg.DBName, cfg.Host, cfg.Port) return &DB{Pool: pool}, nil } // Close closes the database connection pool func (db *DB) Close() { if db.Pool != nil { db.Pool.Close() } } // RunMigrations executes all SQL migrations func (db *DB) RunMigrations(ctx context.Context) error { // Try multiple paths for migration file migrationPaths := []string{ "migrations/001_university_staff.sql", "../migrations/001_university_staff.sql", "../../migrations/001_university_staff.sql", } var content []byte var err error var foundPath string for _, path := range migrationPaths { absPath, _ := filepath.Abs(path) content, err = os.ReadFile(absPath) if err == nil { foundPath = absPath break } } if content == nil { return fmt.Errorf("failed to read migration file from any path: %w", err) } log.Printf("Running migrations from: %s", foundPath) // Execute migration _, err = db.Pool.Exec(ctx, string(content)) if err != nil { return fmt.Errorf("failed to execute migration: %w", err) } log.Println("Database migrations completed successfully") return nil } // Health checks if the database is healthy func (db *DB) Health(ctx context.Context) error { return db.Pool.Ping(ctx) }