A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

151 lines
4.2 KiB

  1. /*
  2. * Copyright © 2019 Musing Studio LLC.
  3. *
  4. * This file is part of WriteFreely.
  5. *
  6. * WriteFreely is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License, included
  8. * in the LICENSE file in this source code package.
  9. */
  10. // Package migrations contains database migrations for WriteFreely
  11. package migrations
  12. import (
  13. "database/sql"
  14. "github.com/writeas/web-core/log"
  15. )
  16. // TODO: refactor to use the datastore struct from writefreely pkg
  17. type datastore struct {
  18. *sql.DB
  19. driverName string
  20. }
  21. func NewDatastore(db *sql.DB, dn string) *datastore {
  22. return &datastore{db, dn}
  23. }
  24. // TODO: use these consts from writefreely pkg
  25. const (
  26. driverMySQL = "mysql"
  27. driverSQLite = "sqlite3"
  28. )
  29. type Migration interface {
  30. Description() string
  31. Migrate(db *datastore) error
  32. }
  33. type migration struct {
  34. description string
  35. migrate func(db *datastore) error
  36. }
  37. func New(d string, fn func(db *datastore) error) Migration {
  38. return &migration{d, fn}
  39. }
  40. func (m *migration) Description() string {
  41. return m.description
  42. }
  43. func (m *migration) Migrate(db *datastore) error {
  44. return m.migrate(db)
  45. }
  46. var migrations = []Migration{
  47. New("support user invites", supportUserInvites), // -> V1 (v0.8.0)
  48. New("support dynamic instance pages", supportInstancePages), // V1 -> V2 (v0.9.0)
  49. New("support users suspension", supportUserStatus), // V2 -> V3 (v0.11.0)
  50. New("support oauth", oauth), // V3 -> V4
  51. New("support slack oauth", oauthSlack), // V4 -> v5
  52. New("support ActivityPub mentions", supportActivityPubMentions), // V5 -> V6
  53. New("support oauth attach", oauthAttach), // V6 -> V7
  54. New("support oauth via invite", oauthInvites), // V7 -> V8 (v0.12.0)
  55. New("optimize drafts retrieval", optimizeDrafts), // V8 -> V9
  56. New("support post signatures", supportPostSignatures), // V9 -> V10 (v0.13.0)
  57. New("Widen oauth_users.access_token", widenOauthAcceesToken), // V10 -> V11
  58. New("support verifying fedi profile", fediverseVerifyProfile), // V11 -> V12 (v0.14.0)
  59. New("support newsletters", supportLetters), // V12 -> V13
  60. New("support password resetting", supportPassReset), // V13 -> V14
  61. New("speed up blog post retrieval", addPostRetrievalIndex), // V14 -> V15
  62. }
  63. // CurrentVer returns the current migration version the application is on
  64. func CurrentVer() int {
  65. return len(migrations)
  66. }
  67. func SetInitialMigrations(db *datastore) error {
  68. // Included schema files represent changes up to V1, so note that in the database
  69. _, err := db.Exec("INSERT INTO appmigrations (version, migrated, result) VALUES (?, "+db.now()+", ?)", 1, "")
  70. if err != nil {
  71. return err
  72. }
  73. return nil
  74. }
  75. func Migrate(db *datastore) error {
  76. var version int
  77. var err error
  78. if db.tableExists("appmigrations") {
  79. err = db.QueryRow("SELECT MAX(version) FROM appmigrations").Scan(&version)
  80. if err != nil {
  81. return err
  82. }
  83. } else {
  84. log.Info("Initializing appmigrations table...")
  85. version = 0
  86. _, err = db.Exec(`CREATE TABLE appmigrations (
  87. version ` + db.typeInt() + ` NOT NULL,
  88. migrated ` + db.typeDateTime() + ` NOT NULL,
  89. result ` + db.typeText() + ` NOT NULL
  90. ) ` + db.engine() + `;`)
  91. if err != nil {
  92. return err
  93. }
  94. }
  95. if len(migrations[version:]) > 0 {
  96. for i, m := range migrations[version:] {
  97. curVer := version + i + 1
  98. log.Info("Migrating to V%d: %s", curVer, m.Description())
  99. err = m.Migrate(db)
  100. if err != nil {
  101. return err
  102. }
  103. // Update migrations table
  104. _, err = db.Exec("INSERT INTO appmigrations (version, migrated, result) VALUES (?, "+db.now()+", ?)", curVer, "")
  105. if err != nil {
  106. return err
  107. }
  108. }
  109. } else {
  110. log.Info("Database up-to-date. No migrations to run.")
  111. }
  112. return nil
  113. }
  114. func (db *datastore) tableExists(t string) bool {
  115. var dummy string
  116. var err error
  117. if db.driverName == driverSQLite {
  118. err = db.QueryRow("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?", t).Scan(&dummy)
  119. } else {
  120. err = db.QueryRow("SHOW TABLES LIKE '" + t + "'").Scan(&dummy)
  121. }
  122. switch {
  123. case err == sql.ErrNoRows:
  124. return false
  125. case err != nil:
  126. log.Error("Couldn't SHOW TABLES: %v", err)
  127. return false
  128. }
  129. return true
  130. }