crypto_alert_bot/internal/repository/postgresql/postgresql.go
2026-02-26 16:30:56 +03:00

85 lines
2.3 KiB
Go

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
}