/database/database.go

https://github.com/gotify/server · Go · 107 lines · 78 code · 16 blank · 13 comment · 24 complexity · c1debae46d6e86fef5f2a47772ead75d MD5 · raw file

  1. package database
  2. import (
  3. "os"
  4. "path/filepath"
  5. "time"
  6. "github.com/gotify/server/v2/auth/password"
  7. "github.com/gotify/server/v2/model"
  8. "github.com/jinzhu/gorm"
  9. _ "github.com/jinzhu/gorm/dialects/mysql" // enable the mysql dialect
  10. _ "github.com/jinzhu/gorm/dialects/postgres" // enable the postgres dialect
  11. _ "github.com/jinzhu/gorm/dialects/sqlite" // enable the sqlite3 dialect
  12. )
  13. var mkdirAll = os.MkdirAll
  14. // New creates a new wrapper for the gorm database framework.
  15. func New(dialect, connection, defaultUser, defaultPass string, strength int, createDefaultUserIfNotExist bool) (*GormDatabase, error) {
  16. createDirectoryIfSqlite(dialect, connection)
  17. db, err := gorm.Open(dialect, connection)
  18. if err != nil {
  19. return nil, err
  20. }
  21. // We normally don't need that much connections, so we limit them. F.ex. mysql complains about
  22. // "too many connections", while load testing Gotify.
  23. db.DB().SetMaxOpenConns(10)
  24. if dialect == "sqlite3" {
  25. // We use the database connection inside the handlers from the http
  26. // framework, therefore concurrent access occurs. Sqlite cannot handle
  27. // concurrent writes, so we limit sqlite to one connection.
  28. // see https://github.com/mattn/go-sqlite3/issues/274
  29. db.DB().SetMaxOpenConns(1)
  30. }
  31. if dialect == "mysql" {
  32. // Mysql has a setting called wait_timeout, which defines the duration
  33. // after which a connection may not be used anymore.
  34. // The default for this setting on mariadb is 10 minutes.
  35. // See https://github.com/docker-library/mariadb/issues/113
  36. db.DB().SetConnMaxLifetime(9 * time.Minute)
  37. }
  38. if err := db.AutoMigrate(new(model.User), new(model.Application), new(model.Message), new(model.Client), new(model.PluginConf)).Error; err != nil {
  39. return nil, err
  40. }
  41. if err := prepareBlobColumn(dialect, db); err != nil {
  42. return nil, err
  43. }
  44. userCount := 0
  45. db.Find(new(model.User)).Count(&userCount)
  46. if createDefaultUserIfNotExist && userCount == 0 {
  47. db.Create(&model.User{Name: defaultUser, Pass: password.CreatePassword(defaultPass, strength), Admin: true})
  48. }
  49. return &GormDatabase{DB: db}, nil
  50. }
  51. func prepareBlobColumn(dialect string, db *gorm.DB) error {
  52. blobType := ""
  53. switch dialect {
  54. case "mysql":
  55. blobType = "longblob"
  56. case "postgres":
  57. blobType = "bytea"
  58. }
  59. if blobType != "" {
  60. for _, target := range []struct {
  61. Table interface{}
  62. Column string
  63. }{
  64. {model.Message{}, "extras"},
  65. {model.PluginConf{}, "config"},
  66. {model.PluginConf{}, "storage"},
  67. } {
  68. if err := db.Model(target.Table).ModifyColumn(target.Column, blobType).Error; err != nil {
  69. return err
  70. }
  71. }
  72. }
  73. return nil
  74. }
  75. func createDirectoryIfSqlite(dialect string, connection string) {
  76. if dialect == "sqlite3" {
  77. if _, err := os.Stat(filepath.Dir(connection)); os.IsNotExist(err) {
  78. if err := mkdirAll(filepath.Dir(connection), 0777); err != nil {
  79. panic(err)
  80. }
  81. }
  82. }
  83. }
  84. // GormDatabase is a wrapper for the gorm framework.
  85. type GormDatabase struct {
  86. DB *gorm.DB
  87. }
  88. // Close closes the gorm database connection.
  89. func (d *GormDatabase) Close() {
  90. d.DB.Close()
  91. }