package postgresql import ( "context" "database/sql" "errors" "fmt" "log/slog" "strings" "gitea.computernetthings.ru/yash/crypto_alert_bot/internal/config" "gitea.computernetthings.ru/yash/crypto_alert_bot/internal/repository/postgresql/migrations" "github.com/jackc/pgx/v5/pgxpool" "github.com/golang-migrate/migrate/v4" pgx "github.com/golang-migrate/migrate/v4/database/pgx/v5" "github.com/golang-migrate/migrate/v4/source/iofs" _ "github.com/jackc/pgx/v5/stdlib" ) type Postgresql struct { db *pgxpool.Pool } // dsn builds a connection string that supports both TCP (host:port) and Unix // socket (path starting with "/") addresses. Unix socket mode skips the // password and uses OS peer authentication instead. func dsn(cfg *config.Postgresql) string { if strings.HasPrefix(cfg.Address, "/") { return fmt.Sprintf("host=%s user=%s dbname=%s sslmode=disable", cfg.Address, cfg.User, cfg.DBName) } return fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=disable", cfg.User, cfg.Password, cfg.Address, cfg.DBName) } func New(ctx context.Context, log *slog.Logger, cfg *config.Postgresql) (*Postgresql, error) { pool, err := pgxpool.New(ctx, dsn(cfg)) if err != nil { return nil, fmt.Errorf("failed to connect to postgres: %w", err) } if err = pool.Ping(ctx); err != nil { return nil, err } // apply migrations automatically if err = applyMigrations(cfg, log); err != nil { return nil, fmt.Errorf("failed to apply migrations: %w", err) } return &Postgresql{db: pool}, nil } func applyMigrations(cfg *config.Postgresql, log *slog.Logger) error { sqlDB, err := sql.Open("pgx", dsn(cfg)) if err != nil { return fmt.Errorf("failed to open sql db for migrations: %w", err) } defer sqlDB.Close() driver, err := pgx.WithInstance(sqlDB, &pgx.Config{}) if err != nil { return fmt.Errorf("failed to create pgx driver: %w", err) } d, err := iofs.New(migrations.Folder, ".") if err != nil { return fmt.Errorf("failed to create iofs source: %w", err) } m, err := migrate.NewWithInstance("iofs", d, "postgres", driver) if err != nil { return fmt.Errorf("failed to create postgresql db migration: %w", err) } if err := m.Up(); err != nil { if errors.Is(err, migrate.ErrNoChange) { log.Info("no migrations to process") return nil } return err } log.Info("migrations processed") return nil }