155 lines
3.5 KiB
Go
155 lines
3.5 KiB
Go
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)
|
|
}
|