package database import ( "context" "log" "time" _ "fotbal-club/database/goosemigrations" "fotbal-club/internal/config" "fotbal-club/internal/models" "github.com/pressly/goose/v3" "golang.org/x/crypto/bcrypt" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/logger" ) var ( // DB is the global database connection pool DB *gorm.DB // dbLogger is the logger instance for database operations dbLogger = logger.New( log.New(log.Writer(), "\r\n", log.LstdFlags), logger.Config{ SlowThreshold: time.Second, LogLevel: logger.Warn, Colorful: true, IgnoreRecordNotFoundError: true, }, ) ) // InitDB initializes the database connection with connection pooling func InitDB() (*gorm.DB, error) { var err error // Set up database connection with connection pooling DB, err = gorm.Open(postgres.Open(config.AppConfig.DatabaseURL), &gorm.Config{ Logger: dbLogger, PrepareStmt: true, DisableForeignKeyConstraintWhenMigrating: true, }) if err != nil { return nil, err } // Get the underlying sql.DB instance to configure connection pool sqlDB, err := DB.DB() if err != nil { return nil, err } // Set connection pool parameters sqlDB.SetMaxIdleConns(config.AppConfig.MaxIdleConnections) sqlDB.SetMaxOpenConns(config.AppConfig.MaxOpenConnections) sqlDB.SetConnMaxLifetime(config.AppConfig.ConnMaxLifetime) log.Println("Database connection established with connection pooling") return DB, nil } // GetDB returns the database instance func GetDB() *gorm.DB { return DB } // CloseDB closes the database connection func CloseDB() error { sqlDB, err := DB.DB() if err != nil { log.Printf("Error getting database instance: %v", err) return err } // Close the database connection if err := sqlDB.Close(); err != nil { log.Printf("Error closing database connection: %v", err) return err } log.Println("Database connection closed") return nil } // WithTransaction executes a function within a database transaction func WithTransaction(ctx context.Context, fn func(tx *gorm.DB) error) error { tx := DB.Begin() if tx.Error != nil { return tx.Error } defer func() { if r := recover(); r != nil { tx.Rollback() panic(r) // re-throw panic after Rollback } }() if err := fn(tx); err != nil { if rbErr := tx.Rollback().Error; rbErr != nil { return rbErr } return err } return tx.Commit().Error } // MigrateDB runs database migrations for all models func MigrateDB(db *gorm.DB) error { sqlDB, err := db.DB() if err != nil { return err } goose.SetLogger(log.New(log.Writer(), "[goose] ", log.LstdFlags)) if err := goose.SetDialect("postgres"); err != nil { return err } if err := goose.UpContext(context.Background(), sqlDB, "database/goosemigrations"); err != nil { return err } return nil } // SeedDB populates the database with initial data func SeedDB(db *gorm.DB) error { // Check if we already have data var count int64 if err := db.Model(&models.User{}).Count(&count).Error; err != nil { return err } // If we already have data, don't seed if count > 0 { log.Println("Database already seeded, skipping...") return nil } // Create admin user hashedPassword, err := bcrypt.GenerateFromPassword([]byte("admin123"), bcrypt.DefaultCost) if err != nil { return err } adminUser := models.User{ Email: "admin@example.com", Password: string(hashedPassword), FirstName: "Admin", LastName: "User", Role: "admin", } if err := db.Create(&adminUser).Error; err != nil { return err } log.Println("Database seeded successfully with admin user") log.Printf("Admin credentials: admin@example.com / admin123") return nil } // HealthCheck performs a simple database query to check if the connection is alive func HealthCheck() error { sqlDB, err := DB.DB() if err != nil { return err } return sqlDB.Ping() }