Files
MyClub/pkg/database/database.go
T
Tomas Dvorak b9cea0cd77 dev day #79
2025-11-02 01:04:02 +01:00

212 lines
4.8 KiB
Go

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{},
)
}
// 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()
}