package database import ( "fmt" "log" "net/url" "strings" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/logger" "onixbyte.com/pipely/internal/model" ) // Connect opens a PostgreSQL connection via GORM. If the target database does // not exist it is created automatically. Tables are then migrated. func Connect(databaseURL string) (*gorm.DB, error) { dbName, err := ensureDatabase(databaseURL) if err != nil { return nil, err } db, err := gorm.Open(postgres.Open(databaseURL), &gorm.Config{ Logger: logger.Default.LogMode(logger.Warn), }) if err != nil { return nil, fmt.Errorf("failed to open database: %w", err) } sqlDB, err := db.DB() if err != nil { return nil, fmt.Errorf("failed to get underlying sql.DB: %w", err) } sqlDB.SetMaxOpenConns(50) sqlDB.SetMaxIdleConns(10) log.Printf("[database] Connected to PostgreSQL database: %s", dbName) return db, nil } // AutoMigrate creates or updates tables to match the model definitions. func AutoMigrate(db *gorm.DB) error { return db.AutoMigrate( &model.AdminUser{}, &model.Device{}, &model.Artefact{}, &model.Deployment{}, &model.DeploymentDevice{}, ) } // ensureDatabase creates the target database if it does not exist. func ensureDatabase(databaseURL string) (string, error) { u, err := url.Parse(databaseURL) if err != nil { return "", fmt.Errorf("invalid database URL: %w", err) } dbName := strings.TrimPrefix(u.Path, "/") adminURL := *u adminURL.Path = "/postgres" db, err := gorm.Open(postgres.Open(adminURL.String()), &gorm.Config{}) if err != nil { return "", fmt.Errorf("failed to connect to admin database: %w", err) } var exists bool db.Raw("SELECT EXISTS(SELECT 1 FROM pg_database WHERE datname = ?)", dbName).Scan(&exists) if !exists { if err := db.Exec(fmt.Sprintf(`CREATE DATABASE "%s"`, dbName)).Error; err != nil { return "", fmt.Errorf("failed to create database %s: %w", dbName, err) } log.Printf("[database] Created database: %s", dbName) } sqlDB, _ := db.DB() if sqlDB != nil { sqlDB.Close() } return dbName, nil }