chore: Migrate github.com/jinzhu/gorm to gorm.io/gorm (#863)

* chore: Migrate github.com/jinzhu/gorm to gorm.io/gorm

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>

* chore: drop singleton connection limit on sqlite3 backend

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>

* enhance: database logging

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>

* Revert "chore: drop singleton connection limit on sqlite3 backend"

This reverts commit b494a3bd1f2e18dc9b5e57bbb87e755a6247452f.

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>

* typo

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>

* rename unique_index -> uniqueIndex

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>

* drop uniqueIndex on primary key

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>

* migrate fully to new gorm tag format

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>

* specify unique index name

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>

* remove pluginConf duplicate index

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>

* disable auto migrate FK

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>

---------

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
This commit is contained in:
饺子w (Yumechi) 2025-10-29 22:23:10 +00:00 committed by GitHub
parent 6de77209a6
commit 496c166981
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 151 additions and 150 deletions

View File

@ -611,16 +611,16 @@ func upload(values map[string]*os.File) (contentType string, buffer bytes.Buffer
for key, r := range values { for key, r := range values {
var fw io.Writer var fw io.Writer
if fw, err = w.CreateFormFile(key, r.Name()); err != nil { if fw, err = w.CreateFormFile(key, r.Name()); err != nil {
return return contentType, buffer, err
} }
if _, err = io.Copy(fw, r); err != nil { if _, err = io.Copy(fw, r); err != nil {
return return contentType, buffer, err
} }
} }
contentType = w.FormDataContentType() contentType = w.FormDataContentType()
w.Close() w.Close()
return return contentType, buffer, err
} }
func mustOpen(f string) *os.File { func mustOpen(f string) *os.File {

View File

@ -19,7 +19,7 @@ type UserDatabase interface {
DeleteUserByID(id uint) error DeleteUserByID(id uint) error
UpdateUser(user *model.User) error UpdateUser(user *model.User) error
CreateUser(user *model.User) error CreateUser(user *model.User) error
CountUser(condition ...interface{}) (int, error) CountUser(condition ...interface{}) (int64, error)
} }
// UserChangeNotifier notifies listeners for user changes. // UserChangeNotifier notifies listeners for user changes.

View File

@ -84,7 +84,7 @@ func (s *AuthenticationSuite) assertQueryRequest(key, value string, f fMiddlewar
ctx.Request = httptest.NewRequest("GET", fmt.Sprintf("/?%s=%s", key, value), nil) ctx.Request = httptest.NewRequest("GET", fmt.Sprintf("/?%s=%s", key, value), nil)
f()(ctx) f()(ctx)
assert.Equal(s.T(), code, recorder.Code) assert.Equal(s.T(), code, recorder.Code)
return return ctx
} }
func (s *AuthenticationSuite) TestNothingProvided() { func (s *AuthenticationSuite) TestNothingProvided() {
@ -217,7 +217,7 @@ func (s *AuthenticationSuite) assertHeaderRequest(key, value string, f fMiddlewa
ctx.Request.Header.Set(key, value) ctx.Request.Header.Set(key, value)
f()(ctx) f()(ctx)
assert.Equal(s.T(), code, recorder.Code) assert.Equal(s.T(), code, recorder.Code)
return return ctx
} }
type fMiddleware func() gin.HandlerFunc type fMiddleware func() gin.HandlerFunc

View File

@ -4,7 +4,7 @@ import (
"time" "time"
"github.com/gotify/server/v2/model" "github.com/gotify/server/v2/model"
"github.com/jinzhu/gorm" "gorm.io/gorm"
) )
// GetApplicationByToken returns the application for the given token or nil. // GetApplicationByToken returns the application for the given token or nil.

View File

@ -4,7 +4,7 @@ import (
"time" "time"
"github.com/gotify/server/v2/model" "github.com/gotify/server/v2/model"
"github.com/jinzhu/gorm" "gorm.io/gorm"
) )
// GetClientByID returns the client for the given id or nil. // GetClientByID returns the client for the given id or nil.

View File

@ -1,16 +1,21 @@
package database package database
import ( import (
"errors"
"log"
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
"github.com/gotify/server/v2/auth/password" "github.com/gotify/server/v2/auth/password"
"github.com/gotify/server/v2/mode"
"github.com/gotify/server/v2/model" "github.com/gotify/server/v2/model"
"github.com/jinzhu/gorm" "github.com/mattn/go-isatty"
_ "github.com/jinzhu/gorm/dialects/mysql" // enable the mysql dialect. "gorm.io/driver/mysql"
_ "github.com/jinzhu/gorm/dialects/postgres" // enable the postgres dialect. "gorm.io/driver/postgres"
_ "github.com/jinzhu/gorm/dialects/sqlite" // enable the sqlite3 dialect. "gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
) )
var mkdirAll = os.MkdirAll var mkdirAll = os.MkdirAll
@ -19,21 +24,53 @@ var mkdirAll = os.MkdirAll
func New(dialect, connection, defaultUser, defaultPass string, strength int, createDefaultUserIfNotExist bool) (*GormDatabase, error) { func New(dialect, connection, defaultUser, defaultPass string, strength int, createDefaultUserIfNotExist bool) (*GormDatabase, error) {
createDirectoryIfSqlite(dialect, connection) createDirectoryIfSqlite(dialect, connection)
db, err := gorm.Open(dialect, connection) logLevel := logger.Info
if mode.Get() == mode.Prod {
logLevel = logger.Warn
}
dbLogger := logger.New(log.New(os.Stderr, "\r\n", log.LstdFlags), logger.Config{
SlowThreshold: 200 * time.Millisecond,
LogLevel: logLevel,
IgnoreRecordNotFoundError: true,
Colorful: isatty.IsTerminal(os.Stderr.Fd()),
})
gormConfig := &gorm.Config{
Logger: dbLogger,
DisableForeignKeyConstraintWhenMigrating: true,
}
var db *gorm.DB
err := errors.New("unsupported dialect: " + dialect)
switch dialect {
case "mysql":
db, err = gorm.Open(mysql.Open(connection), gormConfig)
case "postgres":
db, err = gorm.Open(postgres.Open(connection), gormConfig)
case "sqlite3":
db, err = gorm.Open(sqlite.Open(connection), gormConfig)
}
if err != nil {
return nil, err
}
sqldb, err := db.DB()
if err != nil { if err != nil {
return nil, err return nil, err
} }
// We normally don't need that much connections, so we limit them. F.ex. mysql complains about // We normally don't need that much connections, so we limit them. F.ex. mysql complains about
// "too many connections", while load testing Gotify. // "too many connections", while load testing Gotify.
db.DB().SetMaxOpenConns(10) sqldb.SetMaxOpenConns(10)
if dialect == "sqlite3" { if dialect == "sqlite3" {
// We use the database connection inside the handlers from the http // We use the database connection inside the handlers from the http
// framework, therefore concurrent access occurs. Sqlite cannot handle // framework, therefore concurrent access occurs. Sqlite cannot handle
// concurrent writes, so we limit sqlite to one connection. // concurrent writes, so we limit sqlite to one connection.
// see https://github.com/mattn/go-sqlite3/issues/274 // see https://github.com/mattn/go-sqlite3/issues/274
db.DB().SetMaxOpenConns(1) sqldb.SetMaxOpenConns(1)
} }
if dialect == "mysql" { if dialect == "mysql" {
@ -41,18 +78,14 @@ func New(dialect, connection, defaultUser, defaultPass string, strength int, cre
// after which a connection may not be used anymore. // after which a connection may not be used anymore.
// The default for this setting on mariadb is 10 minutes. // The default for this setting on mariadb is 10 minutes.
// See https://github.com/docker-library/mariadb/issues/113 // See https://github.com/docker-library/mariadb/issues/113
db.DB().SetConnMaxLifetime(9 * time.Minute) sqldb.SetConnMaxLifetime(9 * time.Minute)
} }
if err := db.AutoMigrate(new(model.User), new(model.Application), new(model.Message), new(model.Client), new(model.PluginConf)).Error; err != nil { if err := db.AutoMigrate(new(model.User), new(model.Application), new(model.Message), new(model.Client), new(model.PluginConf)); err != nil {
return nil, err return nil, err
} }
if err := prepareBlobColumn(dialect, db); err != nil { userCount := int64(0)
return nil, err
}
userCount := 0
db.Find(new(model.User)).Count(&userCount) db.Find(new(model.User)).Count(&userCount)
if createDefaultUserIfNotExist && userCount == 0 { if createDefaultUserIfNotExist && userCount == 0 {
db.Create(&model.User{Name: defaultUser, Pass: password.CreatePassword(defaultPass, strength), Admin: true}) db.Create(&model.User{Name: defaultUser, Pass: password.CreatePassword(defaultPass, strength), Admin: true})
@ -61,31 +94,6 @@ func New(dialect, connection, defaultUser, defaultPass string, strength int, cre
return &GormDatabase{DB: db}, nil return &GormDatabase{DB: db}, nil
} }
func prepareBlobColumn(dialect string, db *gorm.DB) error {
blobType := ""
switch dialect {
case "mysql":
blobType = "longblob"
case "postgres":
blobType = "bytea"
}
if blobType != "" {
for _, target := range []struct {
Table interface{}
Column string
}{
{model.Message{}, "extras"},
{model.PluginConf{}, "config"},
{model.PluginConf{}, "storage"},
} {
if err := db.Model(target.Table).ModifyColumn(target.Column, blobType).Error; err != nil {
return err
}
}
}
return nil
}
func createDirectoryIfSqlite(dialect, connection string) { func createDirectoryIfSqlite(dialect, connection string) {
if dialect == "sqlite3" { if dialect == "sqlite3" {
if _, err := os.Stat(filepath.Dir(connection)); os.IsNotExist(err) { if _, err := os.Stat(filepath.Dir(connection)); os.IsNotExist(err) {
@ -103,5 +111,9 @@ type GormDatabase struct {
// Close closes the gorm database connection. // Close closes the gorm database connection.
func (d *GormDatabase) Close() { func (d *GormDatabase) Close() {
d.DB.Close() sqldb, err := d.DB.DB()
if err != nil {
return
}
sqldb.Close()
} }

View File

@ -2,7 +2,7 @@ package database
import ( import (
"github.com/gotify/server/v2/model" "github.com/gotify/server/v2/model"
"github.com/jinzhu/gorm" "gorm.io/gorm"
) )
// GetMessageByID returns the messages for the given id or nil. // GetMessageByID returns the messages for the given id or nil.
@ -27,7 +27,7 @@ func (d *GormDatabase) CreateMessage(message *model.Message) error {
func (d *GormDatabase) GetMessagesByUser(userID uint) ([]*model.Message, error) { func (d *GormDatabase) GetMessagesByUser(userID uint) ([]*model.Message, error) {
var messages []*model.Message var messages []*model.Message
err := d.DB.Joins("JOIN applications ON applications.user_id = ?", userID). err := d.DB.Joins("JOIN applications ON applications.user_id = ?", userID).
Where("messages.application_id = applications.id").Order("id desc").Find(&messages).Error Where("messages.application_id = applications.id").Order("messages.id desc").Find(&messages).Error
if err == gorm.ErrRecordNotFound { if err == gorm.ErrRecordNotFound {
err = nil err = nil
} }
@ -39,7 +39,7 @@ func (d *GormDatabase) GetMessagesByUser(userID uint) ([]*model.Message, error)
func (d *GormDatabase) GetMessagesByUserSince(userID uint, limit int, since uint) ([]*model.Message, error) { func (d *GormDatabase) GetMessagesByUserSince(userID uint, limit int, since uint) ([]*model.Message, error) {
var messages []*model.Message var messages []*model.Message
db := d.DB.Joins("JOIN applications ON applications.user_id = ?", userID). db := d.DB.Joins("JOIN applications ON applications.user_id = ?", userID).
Where("messages.application_id = applications.id").Order("id desc").Limit(limit) Where("messages.application_id = applications.id").Order("messages.id desc").Limit(limit)
if since != 0 { if since != 0 {
db = db.Where("messages.id < ?", since) db = db.Where("messages.id < ?", since)
} }
@ -53,7 +53,7 @@ func (d *GormDatabase) GetMessagesByUserSince(userID uint, limit int, since uint
// GetMessagesByApplication returns all messages from an application. // GetMessagesByApplication returns all messages from an application.
func (d *GormDatabase) GetMessagesByApplication(tokenID uint) ([]*model.Message, error) { func (d *GormDatabase) GetMessagesByApplication(tokenID uint) ([]*model.Message, error) {
var messages []*model.Message var messages []*model.Message
err := d.DB.Where("application_id = ?", tokenID).Order("id desc").Find(&messages).Error err := d.DB.Where("application_id = ?", tokenID).Order("messages.id desc").Find(&messages).Error
if err == gorm.ErrRecordNotFound { if err == gorm.ErrRecordNotFound {
err = nil err = nil
} }
@ -64,7 +64,7 @@ func (d *GormDatabase) GetMessagesByApplication(tokenID uint) ([]*model.Message,
// If since is 0 it will be ignored. // If since is 0 it will be ignored.
func (d *GormDatabase) GetMessagesByApplicationSince(appID uint, limit int, since uint) ([]*model.Message, error) { func (d *GormDatabase) GetMessagesByApplicationSince(appID uint, limit int, since uint) ([]*model.Message, error) {
var messages []*model.Message var messages []*model.Message
db := d.DB.Where("application_id = ?", appID).Order("id desc").Limit(limit) db := d.DB.Where("application_id = ?", appID).Order("messages.id desc").Limit(limit)
if since != 0 { if since != 0 {
db = db.Where("messages.id < ?", since) db = db.Where("messages.id < ?", since)
} }

View File

@ -5,9 +5,10 @@ import (
"github.com/gotify/server/v2/model" "github.com/gotify/server/v2/model"
"github.com/gotify/server/v2/test" "github.com/gotify/server/v2/test"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
) )
func TestMigration(t *testing.T) { func TestMigration(t *testing.T) {
@ -21,18 +22,20 @@ type MigrationSuite struct {
func (s *MigrationSuite) BeforeTest(suiteName, testName string) { func (s *MigrationSuite) BeforeTest(suiteName, testName string) {
s.tmpDir = test.NewTmpDir("gotify_migrationsuite") s.tmpDir = test.NewTmpDir("gotify_migrationsuite")
db, err := gorm.Open("sqlite3", s.tmpDir.Path("test_obsolete.db")) db, err := gorm.Open(sqlite.Open(s.tmpDir.Path("test_obsolete.db")), &gorm.Config{})
assert.Nil(s.T(), err) assert.NoError(s.T(), err)
defer db.Close() sqlDB, err := db.DB()
assert.NoError(s.T(), err)
defer sqlDB.Close()
assert.Nil(s.T(), db.CreateTable(new(model.User)).Error) assert.Nil(s.T(), db.Migrator().CreateTable(new(model.User)))
assert.Nil(s.T(), db.Create(&model.User{ assert.Nil(s.T(), db.Create(&model.User{
Name: "test_user", Name: "test_user",
Admin: true, Admin: true,
}).Error) }).Error)
// we should not be able to create applications by now // we should not be able to create applications by now
assert.False(s.T(), db.HasTable(new(model.Application))) assert.False(s.T(), db.Migrator().HasTable(new(model.Application)))
} }
func (s *MigrationSuite) AfterTest(suiteName, testName string) { func (s *MigrationSuite) AfterTest(suiteName, testName string) {
@ -44,7 +47,7 @@ func (s *MigrationSuite) TestMigration() {
assert.Nil(s.T(), err) assert.Nil(s.T(), err)
defer db.Close() defer db.Close()
assert.True(s.T(), db.DB.HasTable(new(model.Application))) assert.True(s.T(), db.DB.Migrator().HasTable(new(model.Application)))
// a user already exist, not adding a new user // a user already exist, not adding a new user
if user, err := db.GetUserByName("admin"); assert.NoError(s.T(), err) { if user, err := db.GetUserByName("admin"); assert.NoError(s.T(), err) {

View File

@ -2,5 +2,9 @@ package database
// Ping pings the database to verify the connection. // Ping pings the database to verify the connection.
func (d *GormDatabase) Ping() error { func (d *GormDatabase) Ping() error {
return d.DB.DB().Ping() sqldb, err := d.DB.DB()
if err != nil {
return err
}
return sqldb.Ping()
} }

View File

@ -2,7 +2,7 @@ package database
import ( import (
"github.com/gotify/server/v2/model" "github.com/gotify/server/v2/model"
"github.com/jinzhu/gorm" "gorm.io/gorm"
) )
// GetPluginConfByUser gets plugin configurations from a user. // GetPluginConfByUser gets plugin configurations from a user.

View File

@ -2,7 +2,7 @@ package database
import ( import (
"github.com/gotify/server/v2/model" "github.com/gotify/server/v2/model"
"github.com/jinzhu/gorm" "gorm.io/gorm"
) )
// GetUserByName returns the user by the given name or nil. // GetUserByName returns the user by the given name or nil.
@ -32,8 +32,8 @@ func (d *GormDatabase) GetUserByID(id uint) (*model.User, error) {
} }
// CountUser returns the user count which satisfies the given condition. // CountUser returns the user count which satisfies the given condition.
func (d *GormDatabase) CountUser(condition ...interface{}) (int, error) { func (d *GormDatabase) CountUser(condition ...interface{}) (int64, error) {
c := -1 c := int64(-1)
handle := d.DB.Model(new(model.User)) handle := d.DB.Model(new(model.User))
if len(condition) == 1 { if len(condition) == 1 {
handle = handle.Where(condition[0]) handle = handle.Where(condition[0])

View File

@ -21,7 +21,7 @@ func (s *DatabaseSuite) TestUser() {
adminCount, err := s.db.CountUser("admin = ?", true) adminCount, err := s.db.CountUser("admin = ?", true)
require.NoError(s.T(), err) require.NoError(s.T(), err)
assert.Equal(s.T(), 1, adminCount, 1, "there is initially one admin") assert.Equal(s.T(), int64(1), adminCount, "there is initially one admin")
users, err := s.db.GetUsers() users, err := s.db.GetUsers()
require.NoError(s.T(), err) require.NoError(s.T(), err)
@ -33,7 +33,7 @@ func (s *DatabaseSuite) TestUser() {
assert.NotEqual(s.T(), 0, nicories.ID, "on create user a new id should be assigned") assert.NotEqual(s.T(), 0, nicories.ID, "on create user a new id should be assigned")
userCount, err := s.db.CountUser() userCount, err := s.db.CountUser()
require.NoError(s.T(), err) require.NoError(s.T(), err)
assert.Equal(s.T(), 2, userCount, "two users should exist") assert.Equal(s.T(), int64(2), userCount, "two users should exist")
user, err = s.db.GetUserByName("nicories") user, err = s.db.GetUserByName("nicories")
require.NoError(s.T(), err) require.NoError(s.T(), err)
@ -60,7 +60,7 @@ func (s *DatabaseSuite) TestUser() {
adminCount, err = s.db.CountUser(&model.User{Admin: true}) adminCount, err = s.db.CountUser(&model.User{Admin: true})
require.NoError(s.T(), err) require.NoError(s.T(), err)
assert.Equal(s.T(), 2, adminCount, "two admins exist") assert.Equal(s.T(), int64(2), adminCount, "two admins exist")
require.NoError(s.T(), s.db.DeleteUserByID(tom.ID)) require.NoError(s.T(), s.db.DeleteUserByID(tom.ID))
users, err = s.db.GetUsers() users, err = s.db.GetUsers()

20
go.mod
View File

@ -11,15 +11,20 @@ require (
github.com/gotify/plugin-api v1.0.0 github.com/gotify/plugin-api v1.0.0
github.com/h2non/filetype v1.1.3 github.com/h2non/filetype v1.1.3
github.com/jinzhu/configor v1.2.2 github.com/jinzhu/configor v1.2.2
github.com/jinzhu/gorm v1.9.16 github.com/mattn/go-isatty v0.0.20
github.com/robfig/cron v1.2.0 github.com/robfig/cron v1.2.0
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
golang.org/x/crypto v0.43.0 golang.org/x/crypto v0.43.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/mysql v1.6.0
gorm.io/driver/postgres v1.6.0
gorm.io/driver/sqlite v1.6.0
gorm.io/gorm v1.31.0
) )
require ( require (
github.com/BurntSushi/toml v1.2.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic v1.14.1 // indirect github.com/bytedance/sonic v1.14.1 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect github.com/bytedance/sonic/loader v0.3.0 // indirect
@ -29,17 +34,20 @@ require (
github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-sql-driver/mysql v1.5.0 // indirect github.com/go-sql-driver/mysql v1.9.3 // indirect
github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect github.com/goccy/go-yaml v1.18.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.6 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/lib/pq v1.10.0 // indirect github.com/mattn/go-sqlite3 v1.14.32 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.7 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect

64
go.sum
View File

@ -1,7 +1,9 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0=
github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.14.1 h1:FBMC0zVz5XUmE4z9wF4Jey0An5FueFvOsTKKKtwIl7w= github.com/bytedance/sonic v1.14.1 h1:FBMC0zVz5XUmE4z9wF4Jey0An5FueFvOsTKKKtwIl7w=
@ -14,10 +16,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM=
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0= github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
@ -40,14 +38,14 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688= github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
@ -60,14 +58,22 @@ github.com/gotify/plugin-api v1.0.0 h1:kab40p2TEPLzjmcafOc7JOz75aTsYQyS2PXtElH8x
github.com/gotify/plugin-api v1.0.0/go.mod h1:xZfEyqVK/Zvu3RwA/CtpuiwFmzFDxifrrqMaH9BHnyU= github.com/gotify/plugin-api v1.0.0/go.mod h1:xZfEyqVK/Zvu3RwA/CtpuiwFmzFDxifrrqMaH9BHnyU=
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/configor v1.2.2 h1:sLgh6KMzpCmaQB4e+9Fu/29VErtBUqsS2t8C9BNIVsA= github.com/jinzhu/configor v1.2.2 h1:sLgh6KMzpCmaQB4e+9Fu/29VErtBUqsS2t8C9BNIVsA=
github.com/jinzhu/configor v1.2.2/go.mod h1:iFFSfOBKP3kC2Dku0ZGB3t3aulfQgTGJknodhFavsU8= github.com/jinzhu/configor v1.2.2/go.mod h1:iFFSfOBKP3kC2Dku0ZGB3t3aulfQgTGJknodhFavsU8=
github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o=
github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
@ -79,15 +85,13 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E=
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.7 h1:fxWBnXkxfM6sRiuH3bqJ4CfzZojMOLVc0UTsTglEghA= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -110,6 +114,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
@ -124,31 +129,20 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190110200230-915654e7eabc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190110200230-915654e7eabc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190109145017-48ac38b7c8cb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190109145017-48ac38b7c8cb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
@ -164,3 +158,11 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY=
gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=

View File

@ -13,13 +13,13 @@ type Application struct {
// read only: true // read only: true
// required: true // required: true
// example: 5 // example: 5
ID uint `gorm:"primary_key;unique_index;AUTO_INCREMENT" json:"id"` ID uint `gorm:"primaryKey;autoIncrement" json:"id"`
// The application token. Can be used as `appToken`. See Authentication. // The application token. Can be used as `appToken`. See Authentication.
// //
// read only: true // read only: true
// required: true // required: true
// example: AWH0wZ5r0Mbac.r // example: AWH0wZ5r0Mbac.r
Token string `gorm:"type:varchar(180);unique_index" json:"token"` Token string `gorm:"type:varchar(180);uniqueIndex:uix_applications_token" json:"token"`
UserID uint `gorm:"index" json:"-"` UserID uint `gorm:"index" json:"-"`
// The application name. This is how the application should be displayed to the user. // The application name. This is how the application should be displayed to the user.
// //
@ -43,7 +43,7 @@ type Application struct {
// required: true // required: true
// example: image/image.jpeg // example: image/image.jpeg
Image string `gorm:"type:text" json:"image"` Image string `gorm:"type:text" json:"image"`
Messages []MessageExternal `json:"-"` Messages []MessageExternal `gorm:"-" json:"-"`
// The default priority of messages sent by this application. Defaults to 0. // The default priority of messages sent by this application. Defaults to 0.
// //
// required: false // required: false

View File

@ -13,13 +13,13 @@ type Client struct {
// read only: true // read only: true
// required: true // required: true
// example: 5 // example: 5
ID uint `gorm:"primary_key;unique_index;AUTO_INCREMENT" json:"id"` ID uint `gorm:"primaryKey;autoIncrement" json:"id"`
// The client token. Can be used as `clientToken`. See Authentication. // The client token. Can be used as `clientToken`. See Authentication.
// //
// read only: true // read only: true
// required: true // required: true
// example: CWH0wZ5r0Mbac.r // example: CWH0wZ5r0Mbac.r
Token string `gorm:"type:varchar(180);unique_index" json:"token"` Token string `gorm:"type:varchar(180);uniqueIndex:uix_clients_token" json:"token"`
UserID uint `gorm:"index" json:"-"` UserID uint `gorm:"index" json:"-"`
// The client name. This is how the client should be displayed to the user. // The client name. This is how the client should be displayed to the user.
// //

View File

@ -6,7 +6,7 @@ import (
// Message holds information about a message. // Message holds information about a message.
type Message struct { type Message struct {
ID uint `gorm:"AUTO_INCREMENT;primary_key;index"` ID uint `gorm:"autoIncrement;primaryKey;index"`
ApplicationID uint ApplicationID uint
Message string `gorm:"type:text"` Message string `gorm:"type:text"`
Title string `gorm:"type:text"` Title string `gorm:"type:text"`

View File

@ -2,10 +2,10 @@ package model
// PluginConf holds information about the plugin. // PluginConf holds information about the plugin.
type PluginConf struct { type PluginConf struct {
ID uint `gorm:"primary_key;AUTO_INCREMENT;index"` ID uint `gorm:"primaryKey;autoIncrement"`
UserID uint UserID uint
ModulePath string `gorm:"type:text"` ModulePath string `gorm:"type:text"`
Token string `gorm:"type:varchar(180);unique_index"` Token string `gorm:"type:varchar(180);uniqueIndex:uix_plugin_confs_token"`
ApplicationID uint ApplicationID uint
Enabled bool Enabled bool
Config []byte Config []byte

View File

@ -2,8 +2,8 @@ package model
// The User holds information about the credentials of a user and its application and client tokens. // The User holds information about the credentials of a user and its application and client tokens.
type User struct { type User struct {
ID uint `gorm:"primary_key;unique_index;AUTO_INCREMENT"` ID uint `gorm:"primaryKey;autoIncrement"`
Name string `gorm:"type:varchar(180);unique_index"` Name string `gorm:"type:varchar(180);uniqueIndex:uix_users_name"`
Pass []byte Pass []byte
Admin bool Admin bool
Applications []Application Applications []Application

View File

@ -1,3 +1,4 @@
//go:build race
// +build race // +build race
package compat package compat

View File

@ -75,7 +75,6 @@ func (c *EchoPlugin) Disable() error {
func (c *EchoPlugin) RegisterWebhook(baseURL string, g *gin.RouterGroup) { func (c *EchoPlugin) RegisterWebhook(baseURL string, g *gin.RouterGroup) {
c.basePath = baseURL c.basePath = baseURL
g.GET("/echo", func(ctx *gin.Context) { g.GET("/echo", func(ctx *gin.Context) {
storage, _ := c.storageHandler.Load() storage, _ := c.storageHandler.Load()
conf := new(Storage) conf := new(Storage)
json.Unmarshal(storage, conf) json.Unmarshal(storage, conf)

View File

@ -20,7 +20,6 @@ import (
"github.com/gotify/server/v2/plugin/testing/mock" "github.com/gotify/server/v2/plugin/testing/mock"
"github.com/gotify/server/v2/test" "github.com/gotify/server/v2/test"
"github.com/gotify/server/v2/test/testdb" "github.com/gotify/server/v2/test/testdb"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
) )
@ -367,36 +366,6 @@ func TestNewManager_NonPluginFile_expectError(t *testing.T) {
assert.Error(t, err) assert.Error(t, err)
} }
func TestNewManager_FaultyDB_expectError(t *testing.T) {
tmpDir := test.NewTmpDir("gotify_testnewmanager_faultydb")
defer tmpDir.Clean()
for _, data := range []struct {
pkg string
faultyTable string
name string
}{{"plugin/example/minimal/", "plugin_confs", "minimal"}, {"plugin/example/clock/", "applications", "clock"}} {
test.WithWd(path.Join(test.GetProjectDir(), data.pkg), func(origWd string) {
exec.Command("go", "get", "-d").Run()
goBuildFlags := []string{"build", "-buildmode=plugin", "-o=" + tmpDir.Path(fmt.Sprintf("%s.so", data.name))}
goBuildFlags = append(goBuildFlags, extraGoBuildFlags...)
cmd := exec.Command("go", goBuildFlags...)
cmd.Stderr = os.Stderr
assert.Nil(t, cmd.Run())
})
db := testdb.NewDBWithDefaultUser(t)
db.GormDatabase.DB.Callback().Create().Register("no_create", func(s *gorm.Scope) {
if s.TableName() == data.faultyTable {
s.Err(errors.New("database failed"))
}
})
_, err := NewManager(db, tmpDir.Path(), nil, nil)
assert.Error(t, err)
os.Remove(tmpDir.Path(fmt.Sprintf("%s.so", data.name)))
}
}
func TestNewManager_InternalApplicationManagement(t *testing.T) { func TestNewManager_InternalApplicationManagement(t *testing.T) {
db := testdb.NewDBWithDefaultUser(t) db := testdb.NewDBWithDefaultUser(t)

View File

@ -1,3 +1,4 @@
//go:build race
// +build race // +build race
package plugin package plugin

View File

@ -58,8 +58,10 @@ type PluginConfig struct {
IsNotValid bool IsNotValid bool
} }
var disableFailUsers = make(map[uint]error) var (
var enableFailUsers = make(map[uint]error) disableFailUsers = make(map[uint]error)
enableFailUsers = make(map[uint]error)
)
// ReturnErrorOnEnableForUser registers a uid which will throw an error on enabling. // ReturnErrorOnEnableForUser registers a uid which will throw an error on enabling.
func ReturnErrorOnEnableForUser(uid uint, err error) { func ReturnErrorOnEnableForUser(uid uint, err error) {

View File

@ -111,7 +111,7 @@ func (l *LoggingRoundTripper) RoundTrip(r *http.Request) (resp *http.Response, e
} else if err != nil { } else if err != nil {
log.Printf("%s Request Failed: %s on %s %s\n", l.Name, err.Error(), r.Method, r.URL.String()) log.Printf("%s Request Failed: %s on %s %s\n", l.Name, err.Error(), r.Method, r.URL.String())
} }
return return resp, err
} }
func applyLetsEncrypt(s *http.Server, conf *config.Configuration) { func applyLetsEncrypt(s *http.Server, conf *config.Configuration) {