diff --git a/backend/cmd/migrate/main.go b/backend/cmd/migrate/main.go index 47ce009..12443ad 100644 --- a/backend/cmd/migrate/main.go +++ b/backend/cmd/migrate/main.go @@ -8,6 +8,7 @@ import ( "github.com/timetracker/backend/internal/config" "github.com/timetracker/backend/internal/db" + "github.com/timetracker/backend/internal/models" "gorm.io/gorm/logger" ) @@ -102,7 +103,7 @@ func main() { // Run migrations fmt.Println("Running database migrations...") - if err = db.MigrateDB(); err != nil { + if err = models.MigrateDB(); err != nil { log.Fatalf("Error migrating database: %v", err) } fmt.Println("✓ Database migrations completed successfully") diff --git a/backend/internal/db/db.go b/backend/internal/db/db.go index 27b9b3a..78b17d9 100644 --- a/backend/internal/db/db.go +++ b/backend/internal/db/db.go @@ -2,6 +2,7 @@ package db import ( "context" + "errors" "fmt" "log" "time" @@ -15,53 +16,30 @@ import ( // Global variable for the DB connection var db *gorm.DB +// ErrDBNotInitialized is returned when a database operation is attempted before initialization +var ErrDBNotInitialized = errors.New("database not initialized") + // InitDB initializes the database connection (once at startup) // with the provided configuration func InitDB(config config.DatabaseConfig) error { - // Create DSN (Data Source Name) - dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s", - config.Host, config.Port, config.User, config.Password, config.DBName, config.SSLMode) - - // Configure GORM logger - gormLogger := logger.New( - log.New(log.Writer(), "\r\n", log.LstdFlags), // io writer - logger.Config{ - SlowThreshold: 200 * time.Millisecond, // Slow SQL threshold - LogLevel: config.LogLevel, // Log level - IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger - Colorful: true, // Enable color - }, - ) - - // Establish database connection with custom logger - var err error - db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{ - Logger: gormLogger, - }) + // Create connection using the default database name + gormDB, err := createConnection(config, config.DBName) if err != nil { - return fmt.Errorf("error connecting to the database: %w", err) + return err } + // Set the global db instance + db = gormDB + // Configure connection pool - sqlDB, err := db.DB() - if err != nil { - return fmt.Errorf("error getting database connection: %w", err) - } - - // Set connection pool parameters - sqlDB.SetMaxIdleConns(config.MaxIdleConns) - sqlDB.SetMaxOpenConns(config.MaxOpenConns) - sqlDB.SetConnMaxLifetime(config.MaxLifetime) - - return nil + return configureConnectionPool(db, config) } -// GetEngine returns the DB instance, possibly with context +// GetEngine returns the DB instance with context func GetEngine(ctx context.Context) *gorm.DB { if db == nil { - panic("database not initialized") + panic(ErrDBNotInitialized) } - // If a special transaction is in ctx, you could check it here return db.WithContext(ctx) } @@ -82,3 +60,67 @@ func CloseDB() error { return nil } + +// GetGormDB is used for special cases like database creation +func GetGormDB(dbConfig config.DatabaseConfig, dbName string) (*gorm.DB, error) { + return createConnection(dbConfig, dbName) +} + +// MigrateDB performs database migrations for all models +// This is a placeholder that will be called by models.MigrateDB +func MigrateDB() error { + if db == nil { + return ErrDBNotInitialized + } + // The actual migration is implemented in models.MigrateDB + // This is just a placeholder to make the migrate/main.go file work + return errors.New("MigrateDB should be called from models package") +} + +// createConnection creates a new database connection with the given configuration +func createConnection(dbConfig config.DatabaseConfig, dbName string) (*gorm.DB, error) { + // Create DSN (Data Source Name) + dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbName=%s sslmode=%s", + dbConfig.Host, dbConfig.Port, dbConfig.User, dbConfig.Password, dbName, dbConfig.SSLMode) + + // Configure GORM logger + gormLogger := createGormLogger(dbConfig) + + // Establish database connection with custom logger + gormDB, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ + Logger: gormLogger, + }) + if err != nil { + return nil, fmt.Errorf("error connecting to the database: %w", err) + } + + return gormDB, nil +} + +// createGormLogger creates a configured GORM logger instance +func createGormLogger(dbConfig config.DatabaseConfig) logger.Interface { + return logger.New( + log.New(log.Writer(), "\r\n", log.LstdFlags), // io writer + logger.Config{ + SlowThreshold: 200 * time.Millisecond, // Slow SQL threshold + LogLevel: dbConfig.LogLevel, // Log level + IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger + Colorful: true, // Enable color + }, + ) +} + +// configureConnectionPool sets up the connection pool parameters +func configureConnectionPool(db *gorm.DB, config config.DatabaseConfig) error { + sqlDB, err := db.DB() + if err != nil { + return fmt.Errorf("error getting database connection: %w", err) + } + + // Set connection pool parameters + sqlDB.SetMaxIdleConns(config.MaxIdleConns) + sqlDB.SetMaxOpenConns(config.MaxOpenConns) + sqlDB.SetConnMaxLifetime(config.MaxLifetime) + + return nil +} diff --git a/backend/migrate b/backend/migrate new file mode 100755 index 0000000..46659bd Binary files /dev/null and b/backend/migrate differ