package database import ( "database/sql" "fmt" "log" "os" "strings" _ "github.com/lib/pq" ) var DB *sql.DB func InitDB() { var err error connStr, err := BuildConnectionString() if err != nil { log.Fatalf("Configuration error: %v", err) } 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 BuildConnectionString() (string, error) { // Prefer DATABASE_URL if set (standard format) if dbURL := os.Getenv("DATABASE_URL"); dbURL != "" { log.Println("Using DATABASE_URL for connection") return dbURL, nil } // Fallback to individual params for backward compatibility host := os.Getenv("DB_HOST") if host == "" { return "", fmt.Errorf("DATABASE_URL or DB_HOST environment variable not set") } user := os.Getenv("DB_USER") if user == "" { return "", fmt.Errorf("DB_USER environment variable not set") } password := os.Getenv("DB_PASSWORD") if password == "" { return "", fmt.Errorf("DB_PASSWORD environment variable not set") } dbname := os.Getenv("DB_NAME") if dbname == "" { return "", fmt.Errorf("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) log.Println("Using individual DB_* params for connection") return connStr, nil } 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") }