- Show ⏭️ 'skipped (already applied)' for migrations that already exist - Add emojis for better log readability (✅ success, ❌ error, 📦 running) - Avoid confusing 'Error' messages when migrations are simply re-applied
103 lines
2.5 KiB
Go
Executable file
103 lines
2.5 KiB
Go
Executable file
package database
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"strings"
|
|
|
|
_ "github.com/lib/pq"
|
|
)
|
|
|
|
var DB *sql.DB
|
|
|
|
func InitDB() {
|
|
var err error
|
|
host := os.Getenv("DB_HOST")
|
|
if host == "" {
|
|
log.Fatal("DB_HOST environment variable not set")
|
|
}
|
|
user := os.Getenv("DB_USER")
|
|
if user == "" {
|
|
log.Fatal("DB_USER environment variable not set")
|
|
}
|
|
password := os.Getenv("DB_PASSWORD")
|
|
if password == "" {
|
|
log.Fatal("DB_PASSWORD environment variable not set")
|
|
}
|
|
dbname := os.Getenv("DB_NAME")
|
|
if dbname == "" {
|
|
log.Fatal("DB_NAME environment variable not set")
|
|
}
|
|
port := os.Getenv("DB_PORT")
|
|
if port == "" {
|
|
port = "5432"
|
|
}
|
|
|
|
sslmode := os.Getenv("DB_SSLMODE")
|
|
if sslmode == "" {
|
|
sslmode = "require" // Default to require for production security
|
|
}
|
|
|
|
connStr := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s",
|
|
host, port, user, password, dbname, sslmode)
|
|
|
|
DB, err = sql.Open("postgres", connStr)
|
|
if err != nil {
|
|
log.Fatalf("Error opening database: %v", err)
|
|
}
|
|
|
|
if err = DB.Ping(); err != nil {
|
|
log.Fatalf("Error connecting to database: %v", err)
|
|
}
|
|
|
|
log.Println("✅ Successfully connected to the database")
|
|
}
|
|
|
|
func RunMigrations() {
|
|
files, err := os.ReadDir("migrations")
|
|
if err != nil {
|
|
// Try fallback to relative path if running from cmd/api
|
|
files, err = os.ReadDir("../../migrations")
|
|
if err != nil {
|
|
log.Printf("⚠️ Warning: Could not list migrations directory: %v", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Sort files by name to ensure order (001, 002, ...)
|
|
// ReadDir returns sorted by name automatically, but let's be safe if logic changes?
|
|
// Actually ReadDir result is sorted by filename.
|
|
|
|
for _, file := range files {
|
|
if file.IsDir() {
|
|
continue
|
|
}
|
|
|
|
log.Printf("📦 Running migration: %s", file.Name())
|
|
content, err := os.ReadFile("migrations/" + file.Name())
|
|
if err != nil {
|
|
// Try fallback
|
|
content, err = os.ReadFile("../../migrations/" + file.Name())
|
|
if err != nil {
|
|
log.Fatalf("❌ Error reading migration file %s: %v", file.Name(), err)
|
|
}
|
|
}
|
|
|
|
_, err = DB.Exec(string(content))
|
|
if err != nil {
|
|
errStr := err.Error()
|
|
// Check if it's an "already exists" error - these are safe to skip
|
|
if strings.Contains(errStr, "already exists") {
|
|
log.Printf("⏭️ Migration %s skipped (already applied)", file.Name())
|
|
} else {
|
|
// Real error - log it
|
|
log.Printf("❌ Error running migration %s: %v", file.Name(), err)
|
|
}
|
|
} else {
|
|
log.Printf("✅ Migration %s executed successfully", file.Name())
|
|
}
|
|
}
|
|
log.Println("All migrations processed")
|
|
}
|