package db import ( "context" "database/sql" "fmt" "time" "music-server/internal/logging" "github.com/golang-migrate/migrate/v4" "github.com/golang-migrate/migrate/v4/database/postgres" _ "github.com/golang-migrate/migrate/v4/database/postgres" "github.com/golang-migrate/migrate/v4/source/iofs" "github.com/jackc/pgx/v5/pgxpool" _ "github.com/lib/pq" "go.uber.org/zap" ) // Database holds the database connection pool and context type Database struct { Pool *pgxpool.Pool Ctx context.Context } // NewDatabase creates a new Database instance with connection pool func NewDatabase(host, port, user, password, dbname string) (*Database, error) { ctx := context.Background() psqlInfo := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname) logging.GetLogger().Debug("Database connection info", zap.String("host", host), zap.String("port", port), zap.String("dbname", dbname)) pool, err := pgxpool.New(ctx, psqlInfo) if err != nil { return nil, fmt.Errorf("unable to connect to database: %w", err) } // Test connection var success string err = pool.QueryRow(ctx, "select 'Successfully connected!'").Scan(&success) if err != nil { pool.Close() return nil, fmt.Errorf("database query failed: %w", err) } logging.GetLogger().Info("Database connected", zap.String("status", success)) return &Database{Pool: pool, Ctx: ctx}, nil } // Close closes the database connection pool func (db *Database) Close() { if db.Pool != nil { logging.GetLogger().Info("Closing database connection") db.Pool.Close() } } // Health checks the health of the database connection by pinging the database. // It returns a map with keys indicating various health statistics. func (db *Database) Health() map[string]string { ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() stats := make(map[string]string) // Ping the database err := db.Pool.Ping(ctx) if err != nil { stats["status"] = "down" stats["error"] = err.Error() return stats } stats["status"] = "up" return stats } // RunMigrations runs all pending database migrations to the latest version. // Uses the existing pool to extract connection details. func (db *Database) RunMigrations() error { // Extract connection info from pool config connConfig := db.Pool.Config().ConnConfig migrationURL := fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=disable", connConfig.User, connConfig.Password, connConfig.Host, connConfig.Port, connConfig.Database) logging.GetLogger().Debug("Migration info", zap.String("url", migrationURL)) sqlDb, err := sql.Open("postgres", migrationURL) if err != nil { return fmt.Errorf("failed to open database for migration: %w", err) } defer sqlDb.Close() driver, err := postgres.WithInstance(sqlDb, &postgres.Config{}) if err != nil { return fmt.Errorf("failed to create migration driver: %w", err) } files, err := iofs.New(MigrationsFs, "migrations") if err != nil { return fmt.Errorf("failed to create migration files: %w", err) } m, err := migrate.NewWithInstance("iofs", files, "postgres", driver) if err != nil { return fmt.Errorf("failed to create migrator: %w", err) } // Get current version for logging version, _, err := m.Version() if err != nil { logging.GetLogger().Error("Failed to get migration version", zap.String("error", err.Error())) } logging.GetLogger().Info("Migration version before", zap.Uint("version", version)) // Run all pending migrations to latest version err = m.Up() if err != nil { if err == migrate.ErrNoChange { logging.GetLogger().Info("Database already up to date") } else { return fmt.Errorf("migration failed: %w", err) } } else { // Get new version after migration versionAfter, _, _ := m.Version() logging.GetLogger().Info("Migrated to version", zap.Uint("version", versionAfter)) } logging.GetLogger().Info("Migration completed") return nil }