171 lines
4.4 KiB
Go
171 lines
4.4 KiB
Go
package infrastructure
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
|
|
"github.com/golang-migrate/migrate/v4"
|
|
"github.com/golang-migrate/migrate/v4/database/postgres"
|
|
_ "github.com/golang-migrate/migrate/v4/source/file"
|
|
_ "github.com/lib/pq"
|
|
)
|
|
|
|
// Config holds database configuration
|
|
type Config struct {
|
|
Host string
|
|
Port string
|
|
User string
|
|
Password string
|
|
DBName string
|
|
SSLMode string
|
|
}
|
|
|
|
// NewConnection creates a new database connection
|
|
func NewConnection(config Config) (*sql.DB, error) {
|
|
dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s",
|
|
config.Host, config.Port, config.User, config.Password, config.DBName, config.SSLMode)
|
|
|
|
db, err := sql.Open("postgres", dsn)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open database connection: %w", err)
|
|
}
|
|
|
|
if err := db.Ping(); err != nil {
|
|
return nil, fmt.Errorf("failed to ping database: %w", err)
|
|
}
|
|
|
|
return db, nil
|
|
}
|
|
|
|
// LoadConfigFromEnv loads database configuration from environment variables
|
|
func LoadConfigFromEnv() Config {
|
|
return Config{
|
|
Host: getEnv("DB_HOST", "localhost"),
|
|
Port: getEnv("DB_PORT", "5432"),
|
|
User: getEnv("DB_USER", "postgres"),
|
|
Password: getEnv("DB_PASSWORD", "password"),
|
|
DBName: getEnv("DB_NAME", "linkedin_jobs"),
|
|
SSLMode: getEnv("DB_SSLMODE", "disable"),
|
|
}
|
|
}
|
|
|
|
func getEnv(key, defaultValue string) string {
|
|
if value := os.Getenv(key); value != "" {
|
|
return value
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
// RunMigrations runs all pending migrations
|
|
func RunMigrations(db *sql.DB) error {
|
|
driver, err := postgres.WithInstance(db, &postgres.Config{})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create postgres driver: %w", err)
|
|
}
|
|
|
|
// Get the migrations directory path
|
|
migrationsPath, err := getMigrationsPath()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get migrations path: %w", err)
|
|
}
|
|
|
|
m, err := migrate.NewWithDatabaseInstance(
|
|
fmt.Sprintf("file://%s", migrationsPath),
|
|
"postgres",
|
|
driver,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create migrate instance: %w", err)
|
|
}
|
|
|
|
if err := m.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
|
|
return fmt.Errorf("failed to run migrations: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RollbackMigrations rolls back migrations by the specified number of steps
|
|
func RollbackMigrations(db *sql.DB, steps int) error {
|
|
driver, err := postgres.WithInstance(db, &postgres.Config{})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create postgres driver: %w", err)
|
|
}
|
|
|
|
migrationsPath, err := getMigrationsPath()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get migrations path: %w", err)
|
|
}
|
|
|
|
m, err := migrate.NewWithDatabaseInstance(
|
|
fmt.Sprintf("file://%s", migrationsPath),
|
|
"postgres",
|
|
driver,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create migrate instance: %w", err)
|
|
}
|
|
defer m.Close()
|
|
|
|
if err := m.Steps(-steps); err != nil && !errors.Is(err, migrate.ErrNoChange) {
|
|
return fmt.Errorf("failed to rollback migrations: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetMigrationVersion returns the current migration version
|
|
func GetMigrationVersion(db *sql.DB) (uint, bool, error) {
|
|
driver, err := postgres.WithInstance(db, &postgres.Config{})
|
|
if err != nil {
|
|
return 0, false, fmt.Errorf("failed to create postgres driver: %w", err)
|
|
}
|
|
|
|
migrationsPath, err := getMigrationsPath()
|
|
if err != nil {
|
|
return 0, false, fmt.Errorf("failed to get migrations path: %w", err)
|
|
}
|
|
|
|
m, err := migrate.NewWithDatabaseInstance(
|
|
fmt.Sprintf("file://%s", migrationsPath),
|
|
"postgres",
|
|
driver,
|
|
)
|
|
if err != nil {
|
|
return 0, false, fmt.Errorf("failed to create migrate instance: %w", err)
|
|
}
|
|
defer m.Close()
|
|
|
|
version, dirty, err := m.Version()
|
|
if err != nil {
|
|
if errors.Is(err, migrate.ErrNilVersion) {
|
|
return 0, false, nil
|
|
}
|
|
return 0, false, fmt.Errorf("failed to get migration version: %w", err)
|
|
}
|
|
|
|
return version, dirty, nil
|
|
}
|
|
|
|
func getMigrationsPath() (string, error) {
|
|
// Get the directory of the current source file
|
|
_, filename, _, ok := runtime.Caller(0)
|
|
if !ok {
|
|
return "", fmt.Errorf("failed to get current file path")
|
|
}
|
|
|
|
// Get the project root (go up from infrastructure/ to project root)
|
|
projectRoot := filepath.Dir(filepath.Dir(filename))
|
|
migrationsPath := filepath.Join(projectRoot, "migrations")
|
|
|
|
// Verify the migrations directory exists
|
|
if _, err := os.Stat(migrationsPath); err != nil {
|
|
return "", fmt.Errorf("migrations directory not found at %s: %w", migrationsPath, err)
|
|
}
|
|
|
|
return migrationsPath, nil
|
|
}
|