package database import ( "database/sql" "fmt" "log" "os" "path/filepath" "sort" "strings" "time" _ "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) } // Connection Pool Settings // Adjust these values based on your production load and database resources DB.SetMaxOpenConns(25) // Limit total connections DB.SetMaxIdleConns(25) // Keep connections open for reuse DB.SetConnMaxLifetime(5 * time.Minute) // Recycle connections every 5 min (avoids stale connections dropped by firewalls) DB.SetConnMaxIdleTime(5 * time.Minute) // Close idle connections after 5 min 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) { if dbURL := os.Getenv("DATABASE_URL"); dbURL != "" { log.Println("Using DATABASE_URL for connection") return dbURL, nil } return "", fmt.Errorf("DATABASE_URL environment variable not set") } func RunMigrations() { migrationDir, err := resolveMigrationDir() if err != nil { log.Printf("⚠️ Warning: Could not list migrations directory: %v", err) return } if err := ensureMigrationsTable(); err != nil { log.Fatalf("❌ Error ensuring migrations table: %v", err) } entries, err := os.ReadDir(migrationDir) if err != nil { log.Fatalf("❌ Error reading migrations directory: %v", err) } var files []os.DirEntry for _, entry := range entries { if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".sql") { continue } files = append(files, entry) } sort.Slice(files, func(i, j int) bool { return files[i].Name() < files[j].Name() }) warnDuplicateMigrationPrefixes(files) for _, file := range files { name := file.Name() if isMigrationApplied(name) { log.Printf("⏭️ Migration %s skipped (already tracked)", name) continue } path := filepath.Join(migrationDir, name) content, err := os.ReadFile(path) if err != nil { log.Fatalf("❌ Error reading migration file %s: %v", name, err) } log.Printf("📦 Running migration: %s", name) if err := executeMigration(name, string(content)); err != nil { log.Fatalf("❌ Error running migration %s: %v", name, err) } log.Printf("✅ Migration %s executed successfully", name) } log.Println("All migrations processed") } func resolveMigrationDir() (string, error) { candidateDirs := []string{"migrations", "../../migrations"} for _, dir := range candidateDirs { if _, err := os.ReadDir(dir); err == nil { return dir, nil } } return "", fmt.Errorf("migrations directory not found in any known location") } func ensureMigrationsTable() error { _, err := DB.Exec(` CREATE TABLE IF NOT EXISTS schema_migrations ( filename TEXT PRIMARY KEY, applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ) `) return err } func isMigrationApplied(filename string) bool { var exists bool err := DB.QueryRow("SELECT EXISTS(SELECT 1 FROM schema_migrations WHERE filename = $1)", filename).Scan(&exists) if err != nil { log.Fatalf("❌ Error checking migration %s: %v", filename, err) } return exists } func executeMigration(filename, sqlContent string) error { tx, err := DB.Begin() if err != nil { return err } defer tx.Rollback() if _, err := tx.Exec(sqlContent); err != nil { errStr := strings.ToLower(err.Error()) if !strings.Contains(errStr, "already exists") { return err } log.Printf("⏭️ Migration %s skipped due to existing resources", filename) } if _, err := tx.Exec("INSERT INTO schema_migrations (filename) VALUES ($1)", filename); err != nil { return err } return tx.Commit() } func warnDuplicateMigrationPrefixes(files []os.DirEntry) { prefixCount := make(map[string]int) for _, file := range files { parts := strings.SplitN(file.Name(), "_", 2) if len(parts) == 2 { prefixCount[parts[0]]++ } } for prefix, count := range prefixCount { if count > 1 { log.Printf("⚠️ Duplicate migration prefix detected (%s appears %d times)", prefix, count) } } }