photoprism/internal/migrate/version.go
Michael Mayer 74772aea97 Config: Always initialize fixtures, even when skipping migrations #3215
Signed-off-by: Michael Mayer <michael@photoprism.app>
2023-02-21 04:44:08 +01:00

168 lines
3.9 KiB
Go

package migrate
import (
"fmt"
"sync"
"time"
"github.com/jinzhu/gorm"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/txt"
)
var versionOnce = sync.Once{}
var versionMutex = sync.Mutex{}
// Version represents the application version.
type Version struct {
ID uint `gorm:"primary_key" yaml:"-"`
Version string `gorm:"size:255;unique_index:idx_version_edition;" json:"Version" yaml:"Version,omitempty"`
Edition string `gorm:"size:255;unique_index:idx_version_edition;" json:"Edition" yaml:"Edition,omitempty"`
Error string `gorm:"size:255;" json:"Error" yaml:"Error,omitempty"`
CreatedAt time.Time `yaml:"CreatedAt,omitempty"`
UpdatedAt time.Time `yaml:"UpdatedAt,omitempty"`
MigratedAt *time.Time `json:"MigratedAt" yaml:"MigratedAt,omitempty"`
}
// TableName returns the entity database table name.
func (Version) TableName() string {
return "versions"
}
var UnknownVersion = Version{
Version: "0.0.0",
Edition: "dev",
}
// NeedsMigration tests if the Version is not yet installed.
func (m *Version) NeedsMigration() bool {
if m == nil {
return true
} else if m.MigratedAt == nil || m.CreatedAt.IsZero() {
return true
} else if m.Unknown() {
return true
}
return m.MigratedAt.IsZero()
}
// Migrated flags the version as installed and migrated.
func (m *Version) Migrated(db *gorm.DB) error {
if err := m.CreateTable(db); err != nil {
return err
} else if m.Unknown() {
return nil
}
timeStamp := time.Now().UTC().Truncate(time.Second)
m.MigratedAt = &timeStamp
m.Error = ""
return db.Model(m).Updates(Values{"MigratedAt": m.MigratedAt, "Error": m.Error}).Error
}
// NewVersion creates a Version entity from a model name and a make name.
func NewVersion(version, edition string) *Version {
result := &Version{
Version: txt.Clip(version, txt.ClipLongName),
Edition: txt.Clip(edition, txt.ClipLongName),
}
return result
}
// Create inserts a new row to the database.
func (m *Version) Create(db *gorm.DB) error {
if err := m.CreateTable(db); err != nil {
return err
}
versionMutex.Lock()
defer versionMutex.Unlock()
return db.Create(m).Error
}
// Save saved the record in the database.
func (m *Version) Save(db *gorm.DB) error {
if err := m.CreateTable(db); err != nil {
return err
}
versionMutex.Lock()
defer versionMutex.Unlock()
return db.Save(m).Error
}
// Find fetches an existing record from the database.
func (m *Version) Find(db *gorm.DB) *Version {
if err := m.CreateTable(db); err != nil {
return nil
} else if m.Version == "" {
return nil
}
result := Version{}
if err := db.Where("version = ? AND edition = ?", m.Version, m.Edition).First(&result).Error; err != nil {
return nil
}
return &result
}
// FirstOrCreateVersion returns the existing row, inserts a new row or nil in case of errors.
func FirstOrCreateVersion(db *gorm.DB, m *Version) *Version {
if m == nil {
return &UnknownVersion
} else if err := m.CreateTable(db); err != nil {
return &UnknownVersion
} else if m.Version == "" {
return &UnknownVersion
}
// Find existing version or create new record.
if found := m.Find(db); found != nil {
return found
} else if err := m.Create(db); err != nil {
log.Errorf("version: %s (create)", err)
} else {
return m
}
return &UnknownVersion
}
// String returns an identifier that can be used in logs.
func (m *Version) String() string {
return clean.Log(m.Version + "-" + m.Edition)
}
// Unknown checks if the version is unknown.
func (m *Version) Unknown() bool {
if m == nil {
return true
} else if m.Version == "" {
return true
}
return m.Version == UnknownVersion.Version
}
// CreateTable creates the versions database table if needed.
func (m *Version) CreateTable(db *gorm.DB) (err error) {
if db == nil {
return fmt.Errorf("db is nil")
} else if m == nil {
return fmt.Errorf("version is nil")
}
versionOnce.Do(func() {
err = db.AutoMigrate(&Version{}).Error
})
return err
}