mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 02:32:57 +00:00
200 lines
4.5 KiB
Go
200 lines
4.5 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.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{},
|
|
)
|
|
}
|
|
|
|
// 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()
|
|
}
|