From 6a2df0126c614b799070f694b03c13c8b6a8a3c1 Mon Sep 17 00:00:00 2001 From: eternal-flame-AD Date: Thu, 16 Oct 2025 01:09:45 -0500 Subject: [PATCH 01/11] chore: Migrate github.com/jinzhu/gorm to gorm.io/gorm Signed-off-by: eternal-flame-AD --- api/application_test.go | 6 +-- api/user.go | 2 +- auth/authentication_test.go | 4 +- database/application.go | 2 +- database/client.go | 2 +- database/database.go | 72 +++++++++++++---------------- database/message.go | 10 ++--- database/migration_test.go | 13 +++--- database/ping.go | 6 ++- database/plugin.go | 2 +- database/user.go | 6 +-- database/user_test.go | 6 +-- go.mod | 28 +++++++----- go.sum | 80 +++++++++++++++------------------ model/application.go | 2 +- plugin/compat/wrap_test_race.go | 1 + plugin/example/echo/echo.go | 1 - plugin/manager_test.go | 31 ------------- plugin/manager_test_race.go | 1 + plugin/testing/mock/mock.go | 6 ++- runner/runner.go | 2 +- 21 files changed, 127 insertions(+), 156 deletions(-) diff --git a/api/application_test.go b/api/application_test.go index 9e6efe74..03f81571 100644 --- a/api/application_test.go +++ b/api/application_test.go @@ -611,16 +611,16 @@ func upload(values map[string]*os.File) (contentType string, buffer bytes.Buffer for key, r := range values { var fw io.Writer if fw, err = w.CreateFormFile(key, r.Name()); err != nil { - return + return contentType, buffer, err } if _, err = io.Copy(fw, r); err != nil { - return + return contentType, buffer, err } } contentType = w.FormDataContentType() w.Close() - return + return contentType, buffer, err } func mustOpen(f string) *os.File { diff --git a/api/user.go b/api/user.go index bfaf88be..02d9aab3 100644 --- a/api/user.go +++ b/api/user.go @@ -19,7 +19,7 @@ type UserDatabase interface { DeleteUserByID(id uint) error UpdateUser(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. diff --git a/auth/authentication_test.go b/auth/authentication_test.go index 9d3a626b..92efff43 100644 --- a/auth/authentication_test.go +++ b/auth/authentication_test.go @@ -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) f()(ctx) assert.Equal(s.T(), code, recorder.Code) - return + return ctx } func (s *AuthenticationSuite) TestNothingProvided() { @@ -217,7 +217,7 @@ func (s *AuthenticationSuite) assertHeaderRequest(key, value string, f fMiddlewa ctx.Request.Header.Set(key, value) f()(ctx) assert.Equal(s.T(), code, recorder.Code) - return + return ctx } type fMiddleware func() gin.HandlerFunc diff --git a/database/application.go b/database/application.go index f62f4688..7f5ace59 100644 --- a/database/application.go +++ b/database/application.go @@ -4,7 +4,7 @@ import ( "time" "github.com/gotify/server/v2/model" - "github.com/jinzhu/gorm" + "gorm.io/gorm" ) // GetApplicationByToken returns the application for the given token or nil. diff --git a/database/client.go b/database/client.go index 8ab5b3f8..f85c4948 100644 --- a/database/client.go +++ b/database/client.go @@ -4,7 +4,7 @@ import ( "time" "github.com/gotify/server/v2/model" - "github.com/jinzhu/gorm" + "gorm.io/gorm" ) // GetClientByID returns the client for the given id or nil. diff --git a/database/database.go b/database/database.go index 7920c8c6..90faff42 100644 --- a/database/database.go +++ b/database/database.go @@ -1,16 +1,17 @@ package database import ( + "errors" "os" "path/filepath" "time" "github.com/gotify/server/v2/auth/password" "github.com/gotify/server/v2/model" - "github.com/jinzhu/gorm" - _ "github.com/jinzhu/gorm/dialects/mysql" // enable the mysql dialect. - _ "github.com/jinzhu/gorm/dialects/postgres" // enable the postgres dialect. - _ "github.com/jinzhu/gorm/dialects/sqlite" // enable the sqlite3 dialect. + "gorm.io/driver/mysql" + "gorm.io/driver/postgres" + "gorm.io/driver/sqlite" + "gorm.io/gorm" ) var mkdirAll = os.MkdirAll @@ -19,21 +20,37 @@ var mkdirAll = os.MkdirAll func New(dialect, connection, defaultUser, defaultPass string, strength int, createDefaultUserIfNotExist bool) (*GormDatabase, error) { createDirectoryIfSqlite(dialect, connection) - db, err := gorm.Open(dialect, connection) + var db *gorm.DB + err := errors.New("unsupported dialect: " + dialect) + + switch dialect { + case "mysql": + db, err = gorm.Open(mysql.Open(connection), &gorm.Config{}) + case "postgres": + db, err = gorm.Open(postgres.Open(connection), &gorm.Config{}) + case "sqlite3": + db, err = gorm.Open(sqlite.Open(connection), &gorm.Config{}) + } + + if err != nil { + return nil, err + } + + sqldb, err := db.DB() if err != nil { return nil, err } // We normally don't need that much connections, so we limit them. F.ex. mysql complains about // "too many connections", while load testing Gotify. - db.DB().SetMaxOpenConns(10) + sqldb.SetMaxOpenConns(10) if dialect == "sqlite3" { // We use the database connection inside the handlers from the http // framework, therefore concurrent access occurs. Sqlite cannot handle // concurrent writes, so we limit sqlite to one connection. // see https://github.com/mattn/go-sqlite3/issues/274 - db.DB().SetMaxOpenConns(1) + sqldb.SetMaxOpenConns(1) } if dialect == "mysql" { @@ -41,18 +58,14 @@ func New(dialect, connection, defaultUser, defaultPass string, strength int, cre // after which a connection may not be used anymore. // The default for this setting on mariadb is 10 minutes. // See https://github.com/docker-library/mariadb/issues/113 - db.DB().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 { - return nil, err + sqldb.SetConnMaxLifetime(9 * time.Minute) } - if err := prepareBlobColumn(dialect, db); 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 } - userCount := 0 + userCount := int64(0) db.Find(new(model.User)).Count(&userCount) if createDefaultUserIfNotExist && userCount == 0 { db.Create(&model.User{Name: defaultUser, Pass: password.CreatePassword(defaultPass, strength), Admin: true}) @@ -61,31 +74,6 @@ func New(dialect, connection, defaultUser, defaultPass string, strength int, cre 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) { if dialect == "sqlite3" { if _, err := os.Stat(filepath.Dir(connection)); os.IsNotExist(err) { @@ -103,5 +91,9 @@ type GormDatabase struct { // Close closes the gorm database connection. func (d *GormDatabase) Close() { - d.DB.Close() + sqldb, err := d.DB.DB() + if err != nil { + return + } + sqldb.Close() } diff --git a/database/message.go b/database/message.go index be351440..b8b23175 100644 --- a/database/message.go +++ b/database/message.go @@ -2,7 +2,7 @@ package database import ( "github.com/gotify/server/v2/model" - "github.com/jinzhu/gorm" + "gorm.io/gorm" ) // 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) { var messages []*model.Message 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 { 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) { var messages []*model.Message 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 { 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. func (d *GormDatabase) GetMessagesByApplication(tokenID uint) ([]*model.Message, error) { 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 { err = nil } @@ -64,7 +64,7 @@ func (d *GormDatabase) GetMessagesByApplication(tokenID uint) ([]*model.Message, // If since is 0 it will be ignored. func (d *GormDatabase) GetMessagesByApplicationSince(appID uint, limit int, since uint) ([]*model.Message, error) { 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 { db = db.Where("messages.id < ?", since) } diff --git a/database/migration_test.go b/database/migration_test.go index 4feb3752..5ba4884b 100644 --- a/database/migration_test.go +++ b/database/migration_test.go @@ -5,9 +5,10 @@ import ( "github.com/gotify/server/v2/model" "github.com/gotify/server/v2/test" - "github.com/jinzhu/gorm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" + "gorm.io/driver/sqlite" + "gorm.io/gorm" ) func TestMigration(t *testing.T) { @@ -21,18 +22,18 @@ type MigrationSuite struct { func (s *MigrationSuite) BeforeTest(suiteName, testName string) { 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) - defer db.Close() + defer db.DB() - 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{ Name: "test_user", Admin: true, }).Error) // 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) { @@ -44,7 +45,7 @@ func (s *MigrationSuite) TestMigration() { assert.Nil(s.T(), err) 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 if user, err := db.GetUserByName("admin"); assert.NoError(s.T(), err) { diff --git a/database/ping.go b/database/ping.go index 181bc4ad..4c46587e 100644 --- a/database/ping.go +++ b/database/ping.go @@ -2,5 +2,9 @@ package database // Ping pings the database to verify the connection. func (d *GormDatabase) Ping() error { - return d.DB.DB().Ping() + sqldb, err := d.DB.DB() + if err != nil { + return err + } + return sqldb.Ping() } diff --git a/database/plugin.go b/database/plugin.go index 2287fc19..98a0301e 100644 --- a/database/plugin.go +++ b/database/plugin.go @@ -2,7 +2,7 @@ package database import ( "github.com/gotify/server/v2/model" - "github.com/jinzhu/gorm" + "gorm.io/gorm" ) // GetPluginConfByUser gets plugin configurations from a user. diff --git a/database/user.go b/database/user.go index 01f08ce5..8e6bab35 100644 --- a/database/user.go +++ b/database/user.go @@ -2,7 +2,7 @@ package database import ( "github.com/gotify/server/v2/model" - "github.com/jinzhu/gorm" + "gorm.io/gorm" ) // 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. -func (d *GormDatabase) CountUser(condition ...interface{}) (int, error) { - c := -1 +func (d *GormDatabase) CountUser(condition ...interface{}) (int64, error) { + c := int64(-1) handle := d.DB.Model(new(model.User)) if len(condition) == 1 { handle = handle.Where(condition[0]) diff --git a/database/user_test.go b/database/user_test.go index af1cf245..78a5f737 100644 --- a/database/user_test.go +++ b/database/user_test.go @@ -21,7 +21,7 @@ func (s *DatabaseSuite) TestUser() { adminCount, err := s.db.CountUser("admin = ?", true) 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() 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") userCount, err := s.db.CountUser() 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") require.NoError(s.T(), err) @@ -60,7 +60,7 @@ func (s *DatabaseSuite) TestUser() { adminCount, err = s.db.CountUser(&model.User{Admin: true}) 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)) users, err = s.db.GetUsers() diff --git a/go.mod b/go.mod index 7ccd5bc8..66c8852b 100644 --- a/go.mod +++ b/go.mod @@ -11,14 +11,18 @@ require ( github.com/gotify/plugin-api v1.0.0 github.com/h2non/filetype v1.1.3 github.com/jinzhu/configor v1.2.2 - github.com/jinzhu/gorm v1.9.16 github.com/robfig/cron v1.2.0 github.com/stretchr/testify v1.11.1 - golang.org/x/crypto v0.42.0 + golang.org/x/crypto v0.43.0 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 ( + filippo.io/edwards25519 v1.1.0 // indirect github.com/BurntSushi/toml v1.2.0 // indirect github.com/bytedance/sonic v1.14.0 // indirect github.com/bytedance/sonic/loader v0.3.0 // indirect @@ -28,17 +32,21 @@ require ( github.com/gin-contrib/sse v1.1.0 // indirect github.com/go-playground/locales v0.14.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.8.1 // indirect github.com/goccy/go-json v0.10.5 // 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.6.0 // indirect + github.com/jackc/puddle/v2 v2.2.2 // 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/klauspost/cpuid/v2 v2.3.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/lib/pq v1.10.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-sqlite3 v1.14.7 // indirect + github.com/mattn/go-sqlite3 v1.14.22 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect @@ -49,12 +57,12 @@ require ( github.com/ugorji/go/codec v1.3.0 // indirect go.uber.org/mock v0.5.0 // indirect golang.org/x/arch v0.20.0 // indirect - golang.org/x/mod v0.27.0 // indirect - golang.org/x/net v0.43.0 // indirect + golang.org/x/mod v0.29.0 // indirect + golang.org/x/net v0.46.0 // indirect golang.org/x/sync v0.17.0 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/text v0.29.0 // indirect - golang.org/x/tools v0.36.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/text v0.30.0 // indirect + golang.org/x/tools v0.38.0 // indirect google.golang.org/protobuf v1.36.9 // indirect ) diff --git a/go.sum b/go.sum index 37b527a2..1583d2c1 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ +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/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= -github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= @@ -12,10 +12,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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= @@ -38,14 +34,12 @@ 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/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= -github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= 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-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= 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/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= @@ -58,14 +52,20 @@ 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/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= 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/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/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/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.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +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.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= @@ -77,15 +77,11 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 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/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.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 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.7 h1:fxWBnXkxfM6sRiuH3bqJ4CfzZojMOLVc0UTsTglEghA= -github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/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-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -108,6 +104,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.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 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.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -122,35 +119,24 @@ go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= -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.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= -golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= -golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +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/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= 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.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +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.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= 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-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.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +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/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -162,3 +148,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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 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= diff --git a/model/application.go b/model/application.go index 0a45f2e4..cc60852d 100644 --- a/model/application.go +++ b/model/application.go @@ -43,7 +43,7 @@ type Application struct { // required: true // example: image/image.jpeg 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. // // required: false diff --git a/plugin/compat/wrap_test_race.go b/plugin/compat/wrap_test_race.go index 0bf9c39a..b5668b90 100644 --- a/plugin/compat/wrap_test_race.go +++ b/plugin/compat/wrap_test_race.go @@ -1,3 +1,4 @@ +//go:build race // +build race package compat diff --git a/plugin/example/echo/echo.go b/plugin/example/echo/echo.go index 3def0602..b8b8407d 100644 --- a/plugin/example/echo/echo.go +++ b/plugin/example/echo/echo.go @@ -75,7 +75,6 @@ func (c *EchoPlugin) Disable() error { func (c *EchoPlugin) RegisterWebhook(baseURL string, g *gin.RouterGroup) { c.basePath = baseURL g.GET("/echo", func(ctx *gin.Context) { - storage, _ := c.storageHandler.Load() conf := new(Storage) json.Unmarshal(storage, conf) diff --git a/plugin/manager_test.go b/plugin/manager_test.go index 0a03cc68..8d698966 100644 --- a/plugin/manager_test.go +++ b/plugin/manager_test.go @@ -20,7 +20,6 @@ import ( "github.com/gotify/server/v2/plugin/testing/mock" "github.com/gotify/server/v2/test" "github.com/gotify/server/v2/test/testdb" - "github.com/jinzhu/gorm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) @@ -367,36 +366,6 @@ func TestNewManager_NonPluginFile_expectError(t *testing.T) { 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) { db := testdb.NewDBWithDefaultUser(t) diff --git a/plugin/manager_test_race.go b/plugin/manager_test_race.go index 4715167d..ff558d76 100644 --- a/plugin/manager_test_race.go +++ b/plugin/manager_test_race.go @@ -1,3 +1,4 @@ +//go:build race // +build race package plugin diff --git a/plugin/testing/mock/mock.go b/plugin/testing/mock/mock.go index c4165215..10c74849 100644 --- a/plugin/testing/mock/mock.go +++ b/plugin/testing/mock/mock.go @@ -58,8 +58,10 @@ type PluginConfig struct { IsNotValid bool } -var disableFailUsers = make(map[uint]error) -var enableFailUsers = make(map[uint]error) +var ( + disableFailUsers = make(map[uint]error) + enableFailUsers = make(map[uint]error) +) // ReturnErrorOnEnableForUser registers a uid which will throw an error on enabling. func ReturnErrorOnEnableForUser(uid uint, err error) { diff --git a/runner/runner.go b/runner/runner.go index f985a6c8..c1ce239c 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -111,7 +111,7 @@ func (l *LoggingRoundTripper) RoundTrip(r *http.Request) (resp *http.Response, e } else if err != nil { 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) { From b494a3bd1f2e18dc9b5e57bbb87e755a6247452f Mon Sep 17 00:00:00 2001 From: eternal-flame-AD Date: Thu, 16 Oct 2025 01:21:11 -0500 Subject: [PATCH 02/11] chore: drop singleton connection limit on sqlite3 backend Signed-off-by: eternal-flame-AD --- database/database.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/database/database.go b/database/database.go index 90faff42..2dd62244 100644 --- a/database/database.go +++ b/database/database.go @@ -45,14 +45,6 @@ func New(dialect, connection, defaultUser, defaultPass string, strength int, cre // "too many connections", while load testing Gotify. sqldb.SetMaxOpenConns(10) - if dialect == "sqlite3" { - // We use the database connection inside the handlers from the http - // framework, therefore concurrent access occurs. Sqlite cannot handle - // concurrent writes, so we limit sqlite to one connection. - // see https://github.com/mattn/go-sqlite3/issues/274 - sqldb.SetMaxOpenConns(1) - } - if dialect == "mysql" { // Mysql has a setting called wait_timeout, which defines the duration // after which a connection may not be used anymore. From 6668c99f54af4c04c23d63c5c48cdbadb66c50c5 Mon Sep 17 00:00:00 2001 From: eternal-flame-AD Date: Thu, 16 Oct 2025 01:35:12 -0500 Subject: [PATCH 03/11] enhance: database logging Signed-off-by: eternal-flame-AD --- database/database.go | 25 ++++++++++++++++++++++--- go.mod | 2 +- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/database/database.go b/database/database.go index 2dd62244..da418139 100644 --- a/database/database.go +++ b/database/database.go @@ -2,16 +2,20 @@ package database import ( "errors" + "log" "os" "path/filepath" "time" "github.com/gotify/server/v2/auth/password" + "github.com/gotify/server/v2/mode" "github.com/gotify/server/v2/model" + "github.com/mattn/go-isatty" "gorm.io/driver/mysql" "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" + "gorm.io/gorm/logger" ) var mkdirAll = os.MkdirAll @@ -20,16 +24,31 @@ var mkdirAll = os.MkdirAll func New(dialect, connection, defaultUser, defaultPass string, strength int, createDefaultUserIfNotExist bool) (*GormDatabase, error) { createDirectoryIfSqlite(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, + } + var db *gorm.DB err := errors.New("unsupported dialect: " + dialect) switch dialect { case "mysql": - db, err = gorm.Open(mysql.Open(connection), &gorm.Config{}) + db, err = gorm.Open(mysql.Open(connection), gormConfig) case "postgres": - db, err = gorm.Open(postgres.Open(connection), &gorm.Config{}) + db, err = gorm.Open(postgres.Open(connection), gormConfig) case "sqlite3": - db, err = gorm.Open(sqlite.Open(connection), &gorm.Config{}) + db, err = gorm.Open(sqlite.Open(connection), gormConfig) } if err != nil { diff --git a/go.mod b/go.mod index 66c8852b..2252f130 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/gotify/plugin-api v1.0.0 github.com/h2non/filetype v1.1.3 github.com/jinzhu/configor v1.2.2 + github.com/mattn/go-isatty v0.0.20 github.com/robfig/cron v1.2.0 github.com/stretchr/testify v1.11.1 golang.org/x/crypto v0.43.0 @@ -45,7 +46,6 @@ require ( github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-sqlite3 v1.14.22 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect From 750fee065575cc67d3a97db0e259d44096b0cad3 Mon Sep 17 00:00:00 2001 From: eternal-flame-AD Date: Thu, 16 Oct 2025 01:44:11 -0500 Subject: [PATCH 04/11] Revert "chore: drop singleton connection limit on sqlite3 backend" This reverts commit b494a3bd1f2e18dc9b5e57bbb87e755a6247452f. Signed-off-by: eternal-flame-AD --- database/database.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/database/database.go b/database/database.go index da418139..d49c517b 100644 --- a/database/database.go +++ b/database/database.go @@ -64,6 +64,14 @@ func New(dialect, connection, defaultUser, defaultPass string, strength int, cre // "too many connections", while load testing Gotify. sqldb.SetMaxOpenConns(10) + if dialect == "sqlite3" { + // We use the database connection inside the handlers from the http + // framework, therefore concurrent access occurs. Sqlite cannot handle + // concurrent writes, so we limit sqlite to one connection. + // see https://github.com/mattn/go-sqlite3/issues/274 + sqldb.SetMaxOpenConns(1) + } + if dialect == "mysql" { // Mysql has a setting called wait_timeout, which defines the duration // after which a connection may not be used anymore. From a6da146dfe53657fdfac92409c830f6a26cb66c9 Mon Sep 17 00:00:00 2001 From: eternal-flame-AD Date: Thu, 16 Oct 2025 04:59:02 -0500 Subject: [PATCH 05/11] typo Signed-off-by: eternal-flame-AD --- database/migration_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/database/migration_test.go b/database/migration_test.go index 5ba4884b..dc3d10e4 100644 --- a/database/migration_test.go +++ b/database/migration_test.go @@ -23,8 +23,10 @@ type MigrationSuite struct { func (s *MigrationSuite) BeforeTest(suiteName, testName string) { s.tmpDir = test.NewTmpDir("gotify_migrationsuite") db, err := gorm.Open(sqlite.Open(s.tmpDir.Path("test_obsolete.db")), &gorm.Config{}) - assert.Nil(s.T(), err) - defer db.DB() + assert.NoError(s.T(), err) + sqlDB, err := db.DB() + assert.NoError(s.T(), err) + defer sqlDB.Close() assert.Nil(s.T(), db.Migrator().CreateTable(new(model.User))) assert.Nil(s.T(), db.Create(&model.User{ From 26f12c0fe845917472b8044a55ee9bce4a38294a Mon Sep 17 00:00:00 2001 From: eternal-flame-AD Date: Sun, 19 Oct 2025 07:39:30 -0500 Subject: [PATCH 06/11] rename unique_index -> uniqueIndex Signed-off-by: eternal-flame-AD --- model/application.go | 4 ++-- model/client.go | 4 ++-- model/pluginconf.go | 2 +- model/user.go | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/model/application.go b/model/application.go index cc60852d..b13bcf07 100644 --- a/model/application.go +++ b/model/application.go @@ -13,13 +13,13 @@ type Application struct { // read only: true // required: true // example: 5 - ID uint `gorm:"primary_key;unique_index;AUTO_INCREMENT" json:"id"` + ID uint `gorm:"primary_key;uniqueIndex;AUTO_INCREMENT" json:"id"` // The application token. Can be used as `appToken`. See Authentication. // // read only: true // required: true // example: AWH0wZ5r0Mbac.r - Token string `gorm:"type:varchar(180);unique_index" json:"token"` + Token string `gorm:"type:varchar(180);uniqueIndex" json:"token"` UserID uint `gorm:"index" json:"-"` // The application name. This is how the application should be displayed to the user. // diff --git a/model/client.go b/model/client.go index c858165e..4ad4ba27 100644 --- a/model/client.go +++ b/model/client.go @@ -13,13 +13,13 @@ type Client struct { // read only: true // required: true // example: 5 - ID uint `gorm:"primary_key;unique_index;AUTO_INCREMENT" json:"id"` + ID uint `gorm:"primary_key;uniqueIndex;AUTO_INCREMENT" json:"id"` // The client token. Can be used as `clientToken`. See Authentication. // // read only: true // required: true // example: CWH0wZ5r0Mbac.r - Token string `gorm:"type:varchar(180);unique_index" json:"token"` + Token string `gorm:"type:varchar(180);uniqueIndex" json:"token"` UserID uint `gorm:"index" json:"-"` // The client name. This is how the client should be displayed to the user. // diff --git a/model/pluginconf.go b/model/pluginconf.go index d7aed8f7..18ce097e 100644 --- a/model/pluginconf.go +++ b/model/pluginconf.go @@ -5,7 +5,7 @@ type PluginConf struct { ID uint `gorm:"primary_key;AUTO_INCREMENT;index"` UserID uint ModulePath string `gorm:"type:text"` - Token string `gorm:"type:varchar(180);unique_index"` + Token string `gorm:"type:varchar(180);uniqueIndex"` ApplicationID uint Enabled bool Config []byte diff --git a/model/user.go b/model/user.go index bde1056b..0c0e4fe4 100644 --- a/model/user.go +++ b/model/user.go @@ -2,8 +2,8 @@ package model // The User holds information about the credentials of a user and its application and client tokens. type User struct { - ID uint `gorm:"primary_key;unique_index;AUTO_INCREMENT"` - Name string `gorm:"type:varchar(180);unique_index"` + ID uint `gorm:"primary_key;uniqueIndex;AUTO_INCREMENT"` + Name string `gorm:"type:varchar(180);uniqueIndex"` Pass []byte Admin bool Applications []Application From 4937aed20ff99b889a0888278aed3419e768c947 Mon Sep 17 00:00:00 2001 From: eternal-flame-AD Date: Sat, 25 Oct 2025 11:23:26 -0500 Subject: [PATCH 07/11] drop uniqueIndex on primary key Signed-off-by: eternal-flame-AD --- model/application.go | 2 +- model/client.go | 2 +- model/user.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/model/application.go b/model/application.go index b13bcf07..7bc509cb 100644 --- a/model/application.go +++ b/model/application.go @@ -13,7 +13,7 @@ type Application struct { // read only: true // required: true // example: 5 - ID uint `gorm:"primary_key;uniqueIndex;AUTO_INCREMENT" json:"id"` + ID uint `gorm:"primary_key;AUTO_INCREMENT" json:"id"` // The application token. Can be used as `appToken`. See Authentication. // // read only: true diff --git a/model/client.go b/model/client.go index 4ad4ba27..84cfccac 100644 --- a/model/client.go +++ b/model/client.go @@ -13,7 +13,7 @@ type Client struct { // read only: true // required: true // example: 5 - ID uint `gorm:"primary_key;uniqueIndex;AUTO_INCREMENT" json:"id"` + ID uint `gorm:"primary_key;AUTO_INCREMENT" json:"id"` // The client token. Can be used as `clientToken`. See Authentication. // // read only: true diff --git a/model/user.go b/model/user.go index 0c0e4fe4..4d46b29a 100644 --- a/model/user.go +++ b/model/user.go @@ -2,7 +2,7 @@ package model // The User holds information about the credentials of a user and its application and client tokens. type User struct { - ID uint `gorm:"primary_key;uniqueIndex;AUTO_INCREMENT"` + ID uint `gorm:"primary_key;AUTO_INCREMENT"` Name string `gorm:"type:varchar(180);uniqueIndex"` Pass []byte Admin bool From f1da45d2d42065f24716d4f6fb807353d379720d Mon Sep 17 00:00:00 2001 From: eternal-flame-AD Date: Sat, 25 Oct 2025 11:28:04 -0500 Subject: [PATCH 08/11] migrate fully to new gorm tag format Signed-off-by: eternal-flame-AD --- model/application.go | 2 +- model/client.go | 2 +- model/message.go | 2 +- model/pluginconf.go | 2 +- model/user.go | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/model/application.go b/model/application.go index 7bc509cb..7f61282e 100644 --- a/model/application.go +++ b/model/application.go @@ -13,7 +13,7 @@ type Application struct { // read only: true // required: true // example: 5 - ID uint `gorm:"primary_key;AUTO_INCREMENT" json:"id"` + ID uint `gorm:"primaryKey;autoIncrement" json:"id"` // The application token. Can be used as `appToken`. See Authentication. // // read only: true diff --git a/model/client.go b/model/client.go index 84cfccac..76320049 100644 --- a/model/client.go +++ b/model/client.go @@ -13,7 +13,7 @@ type Client struct { // read only: true // required: true // example: 5 - ID uint `gorm:"primary_key;AUTO_INCREMENT" json:"id"` + ID uint `gorm:"primaryKey;autoIncrement" json:"id"` // The client token. Can be used as `clientToken`. See Authentication. // // read only: true diff --git a/model/message.go b/model/message.go index dbee2ec6..e00545a2 100644 --- a/model/message.go +++ b/model/message.go @@ -6,7 +6,7 @@ import ( // Message holds information about a message. type Message struct { - ID uint `gorm:"AUTO_INCREMENT;primary_key;index"` + ID uint `gorm:"autoIncrement;primaryKey;index"` ApplicationID uint Message string `gorm:"type:text"` Title string `gorm:"type:text"` diff --git a/model/pluginconf.go b/model/pluginconf.go index 18ce097e..31f0fa68 100644 --- a/model/pluginconf.go +++ b/model/pluginconf.go @@ -2,7 +2,7 @@ package model // PluginConf holds information about the plugin. type PluginConf struct { - ID uint `gorm:"primary_key;AUTO_INCREMENT;index"` + ID uint `gorm:"primaryKey;autoIncrement;index"` UserID uint ModulePath string `gorm:"type:text"` Token string `gorm:"type:varchar(180);uniqueIndex"` diff --git a/model/user.go b/model/user.go index 4d46b29a..c0ecd0d6 100644 --- a/model/user.go +++ b/model/user.go @@ -2,7 +2,7 @@ package model // The User holds information about the credentials of a user and its application and client tokens. type User struct { - ID uint `gorm:"primary_key;AUTO_INCREMENT"` + ID uint `gorm:"primaryKey;autoIncrement"` Name string `gorm:"type:varchar(180);uniqueIndex"` Pass []byte Admin bool From 1c9a84170c5d4bca8863c8a5e178a1cd5ef3be43 Mon Sep 17 00:00:00 2001 From: eternal-flame-AD Date: Sat, 25 Oct 2025 11:30:52 -0500 Subject: [PATCH 09/11] specify unique index name Signed-off-by: eternal-flame-AD --- model/application.go | 2 +- model/client.go | 2 +- model/pluginconf.go | 2 +- model/user.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/model/application.go b/model/application.go index 7f61282e..b08a56ca 100644 --- a/model/application.go +++ b/model/application.go @@ -19,7 +19,7 @@ type Application struct { // read only: true // required: true // example: AWH0wZ5r0Mbac.r - Token string `gorm:"type:varchar(180);uniqueIndex" json:"token"` + Token string `gorm:"type:varchar(180);uniqueIndex:uix_applications_token" json:"token"` UserID uint `gorm:"index" json:"-"` // The application name. This is how the application should be displayed to the user. // diff --git a/model/client.go b/model/client.go index 76320049..9b96c82a 100644 --- a/model/client.go +++ b/model/client.go @@ -19,7 +19,7 @@ type Client struct { // read only: true // required: true // example: CWH0wZ5r0Mbac.r - Token string `gorm:"type:varchar(180);uniqueIndex" json:"token"` + Token string `gorm:"type:varchar(180);uniqueIndex:uix_clients_token" json:"token"` UserID uint `gorm:"index" json:"-"` // The client name. This is how the client should be displayed to the user. // diff --git a/model/pluginconf.go b/model/pluginconf.go index 31f0fa68..1dcad240 100644 --- a/model/pluginconf.go +++ b/model/pluginconf.go @@ -5,7 +5,7 @@ type PluginConf struct { ID uint `gorm:"primaryKey;autoIncrement;index"` UserID uint ModulePath string `gorm:"type:text"` - Token string `gorm:"type:varchar(180);uniqueIndex"` + Token string `gorm:"type:varchar(180);uniqueIndex:uix_plugin_confs_token"` ApplicationID uint Enabled bool Config []byte diff --git a/model/user.go b/model/user.go index c0ecd0d6..7593851e 100644 --- a/model/user.go +++ b/model/user.go @@ -3,7 +3,7 @@ package model // The User holds information about the credentials of a user and its application and client tokens. type User struct { ID uint `gorm:"primaryKey;autoIncrement"` - Name string `gorm:"type:varchar(180);uniqueIndex"` + Name string `gorm:"type:varchar(180);uniqueIndex:uix_users_name"` Pass []byte Admin bool Applications []Application From c7777fd5e2007fe4758333cf91756fd7e34978f3 Mon Sep 17 00:00:00 2001 From: eternal-flame-AD Date: Wed, 29 Oct 2025 03:09:12 -0500 Subject: [PATCH 10/11] remove pluginConf duplicate index Signed-off-by: eternal-flame-AD --- model/pluginconf.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/pluginconf.go b/model/pluginconf.go index 1dcad240..9f798817 100644 --- a/model/pluginconf.go +++ b/model/pluginconf.go @@ -2,7 +2,7 @@ package model // PluginConf holds information about the plugin. type PluginConf struct { - ID uint `gorm:"primaryKey;autoIncrement;index"` + ID uint `gorm:"primaryKey;autoIncrement"` UserID uint ModulePath string `gorm:"type:text"` Token string `gorm:"type:varchar(180);uniqueIndex:uix_plugin_confs_token"` From d9509a29e84c62b95fb73dd6e5ebc4b8388d48b8 Mon Sep 17 00:00:00 2001 From: eternal-flame-AD Date: Wed, 29 Oct 2025 03:11:48 -0500 Subject: [PATCH 11/11] disable auto migrate FK Signed-off-by: eternal-flame-AD --- database/database.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/database/database.go b/database/database.go index d49c517b..fd4bd2f7 100644 --- a/database/database.go +++ b/database/database.go @@ -36,7 +36,8 @@ func New(dialect, connection, defaultUser, defaultPass string, strength int, cre Colorful: isatty.IsTerminal(os.Stderr.Fd()), }) gormConfig := &gorm.Config{ - Logger: dbLogger, + Logger: dbLogger, + DisableForeignKeyConstraintWhenMigrating: true, } var db *gorm.DB