package database import ( "context" "log" "time" "fotbal-club/internal/config" "fotbal-club/internal/models" "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 { // Enable UUID extension err := db.Exec(`CREATE EXTENSION IF NOT EXISTS "uuid-ossp";`).Error if err != nil { return err } // Run migrations for all models return db.AutoMigrate( &models.User{}, &models.Article{}, &models.Category{}, &models.Team{}, &models.Player{}, &models.Sponsor{}, &models.Banner{}, &models.Settings{}, &models.MatchOverride{}, &models.TeamLogoOverride{}, &models.ContactMessage{}, &models.ContactCategory{}, &models.Contact{}, &models.NewsletterSubscription{}, &models.VisitorEvent{}, &models.ArticleTeamLink{}, &models.ArticleMatchLink{}, &models.CompetitionAlias{}, &models.EmailLog{}, &models.EmailEvent{}, &models.NewsletterSentLog{}, &models.MatchNotification{}, &models.BlogNotification{}, &models.PasswordReset{}, &models.AboutPage{}, // Add event tables so public endpoints don't fail before any writes occur &models.Event{}, &models.EventAttachment{}, &models.UploadedFile{}, &models.FileUsage{}, &models.ShortLink{}, &models.LinkClick{}, &models.Comment{}, &models.CommentReaction{}, &models.CommentBan{}, &models.UnbanRequest{}, &models.CommentReport{}, &models.UserProfile{}, &models.PointsTransaction{}, &models.Achievement{}, &models.UserAchievement{}, &models.RewardItem{}, &models.RewardRedemption{}, // E-shop core models &models.EshopProductCategory{}, &models.EshopProduct{}, &models.EshopProductVariant{}, &models.EshopCart{}, &models.EshopCartItem{}, &models.EshopOrder{}, &models.EshopOrderItem{}, &models.EshopPayment{}, &models.EshopShippingLabel{}, &models.EshopSettings{}, // Financial management models &models.Budget{}, &models.Sponsorship{}, &models.SponsorshipPayment{}, &models.SponsorshipDocument{}, &models.Expense{}, &models.ExpenseDocument{}, &models.FinancialReport{}, &models.FinancialSettings{}, // Invoice system models &models.Invoice{}, &models.InvoiceItem{}, &models.InvoicePayment{}, &models.InvoiceCustomer{}, &models.InvoiceTemplate{}, &models.InvoiceSettings{}, &models.InvoiceSequence{}, // Facility management models &models.Facility{}, &models.FacilityAvailabilityRule{}, &models.FacilityBooking{}, &models.FacilityEquipment{}, &models.FacilityMaintenance{}, &models.WeatherCondition{}, &models.FacilityBookingTemplate{}, ) } // 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() }