e070a3fb5f
OTA update server designed for high-concurrency, low-bandwidth deployments. GORM-backed PostgreSQL, JWT auth, device management, artefact versioning, deployment rollout with gated rate-limited downloads, and a React admin panel.
84 lines
2.1 KiB
Go
84 lines
2.1 KiB
Go
package database
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"gorm.io/driver/postgres"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/logger"
|
|
|
|
"onixbyte.com/message-converter-manifest/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
|
|
}
|