package database import ( "context" "database/sql" "fmt" "sync" "time" "starter-kit/internal/helper/config" "starter-kit/internal/helper/logger" "github.com/sirupsen/logrus" "gorm.io/driver/mysql" "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" ) var ( // dbInstance is the singleton database instance dbInstance *gorm.DB dbOnce sync.Once dbErr error ) // Database wraps the database connection type Database struct { DB *gorm.DB Config *config.DatabaseConfig Logger *logrus.Logger } // NewConnection creates a new database connection based on configuration func NewConnection(cfg *config.DatabaseConfig) (*gorm.DB, error) { dbOnce.Do(func() { switch cfg.Driver { case "postgres": dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s", cfg.Host, cfg.Port, cfg.Username, cfg.Password, cfg.Database, cfg.SSLMode) dbInstance, dbErr = gorm.Open(postgres.Open(dsn), &gorm.Config{}) case "mysql": dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", cfg.Username, cfg.Password, cfg.Host, cfg.Port, cfg.Database) dbInstance, dbErr = gorm.Open(mysql.Open(dsn), &gorm.Config{}) case "sqlite": dbInstance, dbErr = gorm.Open(sqlite.Open(cfg.Database), &gorm.Config{}) default: dbErr = fmt.Errorf("unsupported database driver: %s", cfg.Driver) return } if dbErr != nil { dbErr = fmt.Errorf("failed to connect to database: %w", dbErr) return } // Set connection pool settings var sqlDB *sql.DB sqlDB, dbErr = dbInstance.DB() if dbErr != nil { dbErr = fmt.Errorf("failed to get database instance: %w", dbErr) return } sqlDB.SetMaxOpenConns(cfg.MaxOpenConns) sqlDB.SetMaxIdleConns(cfg.MaxIdleConns) sqlDB.SetConnMaxLifetime(time.Duration(cfg.ConnMaxLifetime) * time.Second) logger.Info("Database connection established successfully") }) return dbInstance, dbErr } // Close closes the database connection func Close() error { if dbInstance == nil { return nil } sqlDB, err := dbInstance.DB() if err != nil { return fmt.Errorf("failed to get database instance: %w", err) } if err := sqlDB.Close(); err != nil { return fmt.Errorf("error closing database: %w", err) } logger.Info("Database connection closed") return nil } // Migrate runs database migrations func Migrate(cfg config.DatabaseConfig) error { _, err := NewConnection(&cfg) if err != nil { return fmt.Errorf("failed to connect to database: %w", err) } // TODO: Add your database migrations here // Example: return dbInstance.AutoMigrate(&models.User{}, &models.Post{}) return nil } // GetDB returns the database instance func GetDB() *gorm.DB { return dbInstance } // WithTransaction executes a function within a database transaction func WithTransaction(fn func(tx *gorm.DB) error) error { tx := dbInstance.Begin() if tx.Error != nil { return tx.Error } defer func() { if r := recover(); r != nil { tx.Rollback() panic(r) } }() if err := fn(tx); err != nil { if rbErr := tx.Rollback().Error; rbErr != nil { return fmt.Errorf("tx: %v, rb err: %v", err, rbErr) } return err } return tx.Commit().Error } // HealthCheck checks if the database is reachable func HealthCheck() error { if dbInstance == nil { return fmt.Errorf("database not initialized") } sqlDB, err := dbInstance.DB() if err != nil { return fmt.Errorf("failed to get database instance: %w", err) } ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() return sqlDB.PingContext(ctx) }