mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
refactor: migrations structure
fix: cascade for alias user foreign key constraint
This commit is contained in:
parent
4f7cc3c57e
commit
b6812ddc3a
1
go.sum
1
go.sum
@ -273,7 +273,6 @@ github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
|
|||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||||
github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0=
|
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.1 h1:L60q1+q7cXE4JeEJJKMnh2brFIe3rZxCihYAB61ypAY=
|
github.com/mitchellh/hashstructure/v2 v2.0.1 h1:L60q1+q7cXE4JeEJJKMnh2brFIe3rZxCihYAB61ypAY=
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.1/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
github.com/mitchellh/hashstructure/v2 v2.0.1/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||||
|
6
main.go
6
main.go
@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/gorilla/handlers"
|
"github.com/gorilla/handlers"
|
||||||
"github.com/markbates/pkger"
|
"github.com/markbates/pkger"
|
||||||
conf "github.com/muety/wakapi/config"
|
conf "github.com/muety/wakapi/config"
|
||||||
"github.com/muety/wakapi/migrations/common"
|
"github.com/muety/wakapi/migrations"
|
||||||
"github.com/muety/wakapi/repositories"
|
"github.com/muety/wakapi/repositories"
|
||||||
"gorm.io/gorm/logger"
|
"gorm.io/gorm/logger"
|
||||||
"log"
|
"log"
|
||||||
@ -96,9 +96,9 @@ func main() {
|
|||||||
defer sqlDb.Close()
|
defer sqlDb.Close()
|
||||||
|
|
||||||
// Migrate database schema
|
// Migrate database schema
|
||||||
common.RunCustomPreMigrations(db, config)
|
migrations.RunPreMigrations(db, config)
|
||||||
runDatabaseMigrations()
|
runDatabaseMigrations()
|
||||||
common.RunCustomPostMigrations(db, config)
|
migrations.RunCustomPostMigrations(db, config)
|
||||||
|
|
||||||
// Repositories
|
// Repositories
|
||||||
aliasRepository = repositories.NewAliasRepository(db)
|
aliasRepository = repositories.NewAliasRepository(db)
|
||||||
|
17
migrations/00000000_apply_fixtures.go
Normal file
17
migrations/00000000_apply_fixtures.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/muety/wakapi/config"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
f := migrationFunc{
|
||||||
|
name: "000-apply_fixtures",
|
||||||
|
f: func(db *gorm.DB, cfg *config.Config) error {
|
||||||
|
return cfg.GetFixturesFunc(cfg.Db.Dialect)(db)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
registerPostMigration(f)
|
||||||
|
}
|
32
migrations/20201103_rename_language_mappings_table.go
Normal file
32
migrations/20201103_rename_language_mappings_table.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/emvi/logbuch"
|
||||||
|
"github.com/muety/wakapi/config"
|
||||||
|
"github.com/muety/wakapi/models"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
f := migrationFunc{
|
||||||
|
name: "20201103-rename_language_mappings_table",
|
||||||
|
f: func(db *gorm.DB, cfg *config.Config) error {
|
||||||
|
migrator := db.Migrator()
|
||||||
|
oldTableName, newTableName := "custom_rules", "language_mappings"
|
||||||
|
oldIndexName, newIndexName := "idx_customrule_user", "idx_language_mapping_user"
|
||||||
|
|
||||||
|
if migrator.HasTable(oldTableName) {
|
||||||
|
logbuch.Info("renaming '%s' table to '%s'", oldTableName, newTableName)
|
||||||
|
if err := migrator.RenameTable(oldTableName, &models.LanguageMapping{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logbuch.Info("renaming '%s' index to '%s'", oldIndexName, newIndexName)
|
||||||
|
return migrator.RenameIndex(&models.LanguageMapping{}, oldIndexName, newIndexName)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
registerPreMigration(f)
|
||||||
|
}
|
79
migrations/20201106_migration_cascade_constraints.go
Normal file
79
migrations/20201106_migration_cascade_constraints.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/emvi/logbuch"
|
||||||
|
"github.com/muety/wakapi/config"
|
||||||
|
"github.com/muety/wakapi/models"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
const name = "20201106-migration_cascade_constraints"
|
||||||
|
|
||||||
|
f := migrationFunc{
|
||||||
|
name: name,
|
||||||
|
f: func(db *gorm.DB, cfg *config.Config) error {
|
||||||
|
// drop all already existing foreign key constraints
|
||||||
|
// afterwards let them be re-created by auto migrate with the newly introduced cascade settings,
|
||||||
|
|
||||||
|
migrator := db.Migrator()
|
||||||
|
|
||||||
|
if cfg.Db.Dialect == config.SQLDialectSqlite {
|
||||||
|
// https://stackoverflow.com/a/1884893/3112139
|
||||||
|
// unfortunately, we can't migrate existing sqlite databases to the newly introduced cascade settings
|
||||||
|
// things like deleting all summaries won't work in those cases unless an entirely new db is created
|
||||||
|
logbuch.Info("not attempting to drop and regenerate constraints on sqlite")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !migrator.HasTable(&models.KeyStringValue{}) {
|
||||||
|
logbuch.Info("key-value table not yet existing")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
condition := "key = ?"
|
||||||
|
if cfg.Db.Dialect == config.SQLDialectMysql {
|
||||||
|
condition = "`key` = ?"
|
||||||
|
}
|
||||||
|
lookupResult := db.Where(condition, name).First(&models.KeyStringValue{})
|
||||||
|
if lookupResult.Error == nil && lookupResult.RowsAffected > 0 {
|
||||||
|
logbuch.Info("no need to migrate '%s'", name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SELECT * FROM INFORMATION_SCHEMA.table_constraints;
|
||||||
|
constraints := map[string]interface{}{
|
||||||
|
"fk_summaries_editors": &models.SummaryItem{},
|
||||||
|
"fk_summaries_languages": &models.SummaryItem{},
|
||||||
|
"fk_summaries_machines": &models.SummaryItem{},
|
||||||
|
"fk_summaries_operating_systems": &models.SummaryItem{},
|
||||||
|
"fk_summaries_projects": &models.SummaryItem{},
|
||||||
|
"fk_summary_items_summary": &models.SummaryItem{},
|
||||||
|
"fk_summaries_user": &models.Summary{},
|
||||||
|
"fk_language_mappings_user": &models.LanguageMapping{},
|
||||||
|
"fk_heartbeats_user": &models.Heartbeat{},
|
||||||
|
"fk_aliases_user": &models.Alias{},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, table := range constraints {
|
||||||
|
if migrator.HasConstraint(table, name) {
|
||||||
|
logbuch.Info("dropping constraint '%s'", name)
|
||||||
|
if err := migrator.DropConstraint(table, name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Create(&models.KeyStringValue{
|
||||||
|
Key: name,
|
||||||
|
Value: "done",
|
||||||
|
}).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
registerPreMigration(f)
|
||||||
|
}
|
58
migrations/20210202_fix_cascade_for_alias_user_constraint.go
Normal file
58
migrations/20210202_fix_cascade_for_alias_user_constraint.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/emvi/logbuch"
|
||||||
|
"github.com/muety/wakapi/config"
|
||||||
|
"github.com/muety/wakapi/models"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
const name = "20210202-fix_cascade_for_alias_user_constraint"
|
||||||
|
|
||||||
|
f := migrationFunc{
|
||||||
|
name: name,
|
||||||
|
f: func(db *gorm.DB, cfg *config.Config) error {
|
||||||
|
migrator := db.Migrator()
|
||||||
|
|
||||||
|
if cfg.Db.Dialect == config.SQLDialectSqlite {
|
||||||
|
// see 20201106_migration_cascade_constraints
|
||||||
|
logbuch.Info("not attempting to drop and regenerate constraints on sqlite")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !migrator.HasTable(&models.KeyStringValue{}) {
|
||||||
|
logbuch.Info("key-value table not yet existing")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
condition := "key = ?"
|
||||||
|
if cfg.Db.Dialect == config.SQLDialectMysql {
|
||||||
|
condition = "`key` = ?"
|
||||||
|
}
|
||||||
|
lookupResult := db.Where(condition, name).First(&models.KeyStringValue{})
|
||||||
|
if lookupResult.Error == nil && lookupResult.RowsAffected > 0 {
|
||||||
|
logbuch.Info("no need to migrate '%s'", name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if migrator.HasConstraint(&models.Alias{}, "fk_aliases_user") {
|
||||||
|
logbuch.Info("dropping constraint 'fk_aliases_user'")
|
||||||
|
if err := migrator.DropConstraint(&models.Alias{}, "fk_aliases_user"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Create(&models.KeyStringValue{
|
||||||
|
Key: name,
|
||||||
|
Value: "done",
|
||||||
|
}).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
registerPreMigration(f)
|
||||||
|
}
|
@ -1,11 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/muety/wakapi/config"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type migrationFunc struct {
|
|
||||||
f func(db *gorm.DB, cfg *config.Config) error
|
|
||||||
name string
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/emvi/logbuch"
|
|
||||||
"github.com/muety/wakapi/config"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
var customPostMigrations []migrationFunc
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
customPostMigrations = []migrationFunc{
|
|
||||||
{
|
|
||||||
f: func(db *gorm.DB, cfg *config.Config) error {
|
|
||||||
return cfg.GetFixturesFunc(cfg.Db.Dialect)(db)
|
|
||||||
},
|
|
||||||
name: "apply fixtures",
|
|
||||||
},
|
|
||||||
// TODO: add function to modify aggregated summaries according to configured custom language mappings
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func RunCustomPostMigrations(db *gorm.DB, cfg *config.Config) {
|
|
||||||
for _, m := range customPostMigrations {
|
|
||||||
logbuch.Info("potentially running migration '%s'", m.name)
|
|
||||||
if err := m.f(db, cfg); err != nil {
|
|
||||||
logbuch.Fatal("migration '%s' failed – %v", m.name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,108 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/emvi/logbuch"
|
|
||||||
"github.com/muety/wakapi/config"
|
|
||||||
"github.com/muety/wakapi/models"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
var customPreMigrations []migrationFunc
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
customPreMigrations = []migrationFunc{
|
|
||||||
{
|
|
||||||
f: func(db *gorm.DB, cfg *config.Config) error {
|
|
||||||
migrator := db.Migrator()
|
|
||||||
oldTableName, newTableName := "custom_rules", "language_mappings"
|
|
||||||
oldIndexName, newIndexName := "idx_customrule_user", "idx_language_mapping_user"
|
|
||||||
|
|
||||||
if migrator.HasTable(oldTableName) {
|
|
||||||
logbuch.Info("renaming '%s' table to '%s'", oldTableName, newTableName)
|
|
||||||
if err := migrator.RenameTable(oldTableName, &models.LanguageMapping{}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
logbuch.Info("renaming '%s' index to '%s'", oldIndexName, newIndexName)
|
|
||||||
return migrator.RenameIndex(&models.LanguageMapping{}, oldIndexName, newIndexName)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
name: "rename language mappings table",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
f: func(db *gorm.DB, cfg *config.Config) error {
|
|
||||||
// drop all already existing foreign key constraints
|
|
||||||
// afterwards let them be re-created by auto migrate with the newly introduced cascade settings,
|
|
||||||
|
|
||||||
migrator := db.Migrator()
|
|
||||||
const lookupKey = "20201106-migration_cascade_constraints"
|
|
||||||
|
|
||||||
if cfg.Db.Dialect == config.SQLDialectSqlite {
|
|
||||||
// https://stackoverflow.com/a/1884893/3112139
|
|
||||||
// unfortunately, we can't migrate existing sqlite databases to the newly introduced cascade settings
|
|
||||||
// things like deleting all summaries won't work in those cases unless an entirely new db is created
|
|
||||||
logbuch.Info("not attempting to drop and regenerate constraints on sqlite")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !migrator.HasTable(&models.KeyStringValue{}) {
|
|
||||||
logbuch.Info("key-value table not yet existing")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
condition := "key = ?"
|
|
||||||
if cfg.Db.Dialect == config.SQLDialectMysql {
|
|
||||||
condition = "`key` = ?"
|
|
||||||
}
|
|
||||||
lookupResult := db.Where(condition, lookupKey).First(&models.KeyStringValue{})
|
|
||||||
if lookupResult.Error == nil && lookupResult.RowsAffected > 0 {
|
|
||||||
logbuch.Info("no need to migrate cascade constraints")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SELECT * FROM INFORMATION_SCHEMA.table_constraints;
|
|
||||||
constraints := map[string]interface{}{
|
|
||||||
"fk_summaries_editors": &models.SummaryItem{},
|
|
||||||
"fk_summaries_languages": &models.SummaryItem{},
|
|
||||||
"fk_summaries_machines": &models.SummaryItem{},
|
|
||||||
"fk_summaries_operating_systems": &models.SummaryItem{},
|
|
||||||
"fk_summaries_projects": &models.SummaryItem{},
|
|
||||||
"fk_summary_items_summary": &models.SummaryItem{},
|
|
||||||
"fk_summaries_user": &models.Summary{},
|
|
||||||
"fk_language_mappings_user": &models.LanguageMapping{},
|
|
||||||
"fk_heartbeats_user": &models.Heartbeat{},
|
|
||||||
"fk_aliases_user": &models.Alias{},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, table := range constraints {
|
|
||||||
if migrator.HasConstraint(table, name) {
|
|
||||||
logbuch.Info("dropping constraint '%s'", name)
|
|
||||||
if err := migrator.DropConstraint(table, name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := db.Create(&models.KeyStringValue{
|
|
||||||
Key: lookupKey,
|
|
||||||
Value: "done",
|
|
||||||
}).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
name: "add cascade constraints",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func RunCustomPreMigrations(db *gorm.DB, cfg *config.Config) {
|
|
||||||
for _, m := range customPreMigrations {
|
|
||||||
logbuch.Info("potentially running migration '%s'", m.name)
|
|
||||||
if err := m.f(db, cfg); err != nil {
|
|
||||||
logbuch.Fatal("migration '%s' failed – %v", m.name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/emvi/logbuch"
|
|
||||||
"github.com/muety/wakapi/config"
|
|
||||||
"github.com/muety/wakapi/models"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
func MigrateLanguages(db *gorm.DB) {
|
|
||||||
cfg := config.Get()
|
|
||||||
|
|
||||||
for k, v := range cfg.App.CustomLanguages {
|
|
||||||
result := db.Model(models.Heartbeat{}).
|
|
||||||
Where("language = ?", "").
|
|
||||||
Where("entity LIKE ?", "%."+k).
|
|
||||||
Updates(models.Heartbeat{Language: v})
|
|
||||||
if result.Error != nil {
|
|
||||||
logbuch.Fatal(result.Error.Error())
|
|
||||||
}
|
|
||||||
if result.RowsAffected > 0 {
|
|
||||||
logbuch.Info("migrated %+v rows for custom language %+s", result.RowsAffected, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
67
migrations/migrations.go
Normal file
67
migrations/migrations.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/emvi/logbuch"
|
||||||
|
"github.com/muety/wakapi/config"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type migrationFunc struct {
|
||||||
|
f func(db *gorm.DB, cfg *config.Config) error
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type migrationFuncs []migrationFunc
|
||||||
|
|
||||||
|
var (
|
||||||
|
preMigrations migrationFuncs
|
||||||
|
postMigrations migrationFuncs
|
||||||
|
)
|
||||||
|
|
||||||
|
func registerPreMigration(f migrationFunc) {
|
||||||
|
preMigrations = append(preMigrations, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerPostMigration(f migrationFunc) {
|
||||||
|
postMigrations = append(postMigrations, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Currently, migrations themselves keep track
|
||||||
|
// of whether they have run, yet or not, because some
|
||||||
|
// simply run on every start.
|
||||||
|
|
||||||
|
func RunPreMigrations(db *gorm.DB, cfg *config.Config) {
|
||||||
|
sort.Sort(preMigrations)
|
||||||
|
|
||||||
|
for _, m := range preMigrations {
|
||||||
|
logbuch.Info("potentially running migration '%s'", m.name)
|
||||||
|
if err := m.f(db, cfg); err != nil {
|
||||||
|
logbuch.Fatal("migration '%s' failed – %v", m.name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunCustomPostMigrations(db *gorm.DB, cfg *config.Config) {
|
||||||
|
sort.Sort(postMigrations)
|
||||||
|
|
||||||
|
for _, m := range postMigrations {
|
||||||
|
logbuch.Info("potentially running migration '%s'", m.name)
|
||||||
|
if err := m.f(db, cfg); err != nil {
|
||||||
|
logbuch.Fatal("migration '%s' failed – %v", m.name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m migrationFuncs) Len() int {
|
||||||
|
return len(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m migrationFuncs) Less(i, j int) bool {
|
||||||
|
return strings.Compare(m[i].name, m[j].name) < 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m migrationFuncs) Swap(i, j int) {
|
||||||
|
m[i], m[j] = m[j], m[i]
|
||||||
|
}
|
@ -2,8 +2,8 @@ package models
|
|||||||
|
|
||||||
type Alias struct {
|
type Alias struct {
|
||||||
ID uint `gorm:"primary_key"`
|
ID uint `gorm:"primary_key"`
|
||||||
Type uint8 `gorm:"not null; index:idx_alias_type_key; constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
|
Type uint8 `gorm:"not null; index:idx_alias_type_key"`
|
||||||
User *User `json:"-" gorm:"not null"`
|
User *User `json:"-" gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
|
||||||
UserID string `gorm:"not null; index:idx_alias_user"`
|
UserID string `gorm:"not null; index:idx_alias_user"`
|
||||||
Key string `gorm:"not null; index:idx_alias_type_key"`
|
Key string `gorm:"not null; index:idx_alias_type_key"`
|
||||||
Value string `gorm:"not null"`
|
Value string `gorm:"not null"`
|
||||||
|
Loading…
Reference in New Issue
Block a user