migrations.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. package data
  2. import (
  3. "database/sql"
  4. "fmt"
  5. "path/filepath"
  6. "time"
  7. "github.com/owncast/owncast/config"
  8. "github.com/owncast/owncast/utils"
  9. log "github.com/sirupsen/logrus"
  10. "github.com/teris-io/shortid"
  11. )
  12. func migrateDatabaseSchema(db *sql.DB, from, to int) error {
  13. log.Printf("Migrating database from version %d to %d", from, to)
  14. dbBackupFile := filepath.Join(config.BackupDirectory, fmt.Sprintf("owncast-v%d.bak", from))
  15. utils.Backup(db, dbBackupFile)
  16. for v := from; v < to; v++ {
  17. log.Tracef("Migration step from %d to %d\n", v, v+1)
  18. switch v {
  19. case 0:
  20. migrateToSchema1(db)
  21. case 1:
  22. migrateToSchema2(db)
  23. case 2:
  24. migrateToSchema3(db)
  25. case 3:
  26. migrateToSchema4(db)
  27. case 4:
  28. migrateToSchema5(db)
  29. case 5:
  30. migrateToSchema6(db)
  31. case 6:
  32. migrateToSchema7(db)
  33. default:
  34. log.Fatalln("missing database migration step")
  35. }
  36. }
  37. _, err := db.Exec("UPDATE config SET value = ? WHERE key = ?", to, "version")
  38. if err != nil {
  39. return err
  40. }
  41. return nil
  42. }
  43. func migrateToSchema7(db *sql.DB) {
  44. log.Println("Migrating users. This may take time if you have lots of users...")
  45. var ids []string
  46. rows, err := db.Query(`SELECT id FROM users`)
  47. if err != nil {
  48. log.Errorln("error migrating access tokens to schema v5", err)
  49. return
  50. }
  51. if rows.Err() != nil {
  52. log.Errorln("error migrating users to schema v7", rows.Err())
  53. return
  54. }
  55. for rows.Next() {
  56. var id string
  57. if err := rows.Scan(&id); err != nil {
  58. log.Error("There is a problem reading the database when migrating users.", err)
  59. return
  60. }
  61. ids = append(ids, id)
  62. }
  63. defer rows.Close()
  64. tx, _ := db.Begin()
  65. stmt, _ := tx.Prepare("update users set display_color=? WHERE id=?")
  66. defer stmt.Close()
  67. for _, id := range ids {
  68. displayColor := utils.GenerateRandomDisplayColor(config.MaxUserColor)
  69. if _, err := stmt.Exec(displayColor, id); err != nil {
  70. log.Panic(err)
  71. return
  72. }
  73. }
  74. if err := tx.Commit(); err != nil {
  75. log.Panicln(err)
  76. }
  77. }
  78. func migrateToSchema6(db *sql.DB) {
  79. // Fix chat messages table schema. Since chat is ephemeral we can drop
  80. // the table and recreate it.
  81. // Drop the old messages table
  82. MustExec(`DROP TABLE messages`, db)
  83. // Recreate it
  84. CreateMessagesTable(db)
  85. }
  86. // nolint:cyclop
  87. func migrateToSchema5(db *sql.DB) {
  88. // Create the access tokens table.
  89. createAccessTokenTable(db)
  90. // 1. Authenticated bool added to the users table.
  91. // 2. Access tokens are now stored in their own table.
  92. //
  93. // Long story short, the access_token used to be the primary key of the users
  94. // table. However, now it's going to live in its own table. However, you
  95. // cannot change the primary key. So we need to create a copy table, then
  96. // migrate the access tokens, and then move the copy into place.
  97. createTempTable := `CREATE TABLE IF NOT EXISTS users_copy (
  98. "id" TEXT,
  99. "display_name" TEXT NOT NULL,
  100. "display_color" NUMBER NOT NULL,
  101. "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  102. "disabled_at" TIMESTAMP,
  103. "previous_names" TEXT DEFAULT '',
  104. "namechanged_at" TIMESTAMP,
  105. "authenticated_at" TIMESTAMP,
  106. "scopes" TEXT,
  107. "type" TEXT DEFAULT 'STANDARD',
  108. "last_used" DATETIME DEFAULT CURRENT_TIMESTAMP,
  109. PRIMARY KEY (id)
  110. );CREATE INDEX user_id_disabled_at_index ON users (id, disabled_at);
  111. CREATE INDEX user_id_index ON users (id);
  112. CREATE INDEX user_id_disabled_index ON users (id, disabled_at);
  113. CREATE INDEX user_disabled_at_index ON USERS (disabled_at);`
  114. _, err := db.Exec(createTempTable)
  115. if err != nil {
  116. log.Errorln("error running migration, you may experience issues: ", err)
  117. }
  118. // Start insert transaction
  119. tx, err := db.Begin()
  120. if err != nil {
  121. log.Errorln(err)
  122. return
  123. }
  124. // Migrate the users table to the new users_copy table.
  125. rows, err := tx.Query(`SELECT id, access_token, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at, scopes, type, last_used FROM users`)
  126. if err != nil {
  127. log.Errorln("error migrating access tokens to schema v5", err)
  128. return
  129. }
  130. if rows.Err() != nil {
  131. log.Errorln("error migrating access tokens to schema v5", rows.Err())
  132. return
  133. }
  134. defer rows.Close()
  135. defer tx.Rollback() //nolint:errcheck
  136. log.Println("Migrating users. This may take time if you have lots of users...")
  137. for rows.Next() {
  138. var id string
  139. var accessToken string
  140. var displayName string
  141. var displayColor int
  142. var createdAt time.Time
  143. var disabledAt *time.Time
  144. var previousNames string
  145. var namechangedAt *time.Time
  146. var scopes *string
  147. var userType string
  148. var lastUsed *time.Time
  149. if err := rows.Scan(&id, &accessToken, &displayName, &displayColor, &createdAt, &disabledAt, &previousNames, &namechangedAt, &scopes, &userType, &lastUsed); err != nil {
  150. log.Error("There is a problem reading the database when migrating users.", err)
  151. return
  152. }
  153. stmt, err := tx.Prepare(`INSERT INTO users_copy (id, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at, scopes, type, last_used) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
  154. if err != nil {
  155. log.Errorln(err)
  156. return
  157. }
  158. defer stmt.Close()
  159. if _, err := stmt.Exec(id, displayName, displayColor, createdAt, disabledAt, previousNames, namechangedAt, scopes, userType, lastUsed); err != nil {
  160. log.Errorln(err)
  161. return
  162. }
  163. stmt, err = tx.Prepare(`INSERT INTO user_access_tokens(token, user_id, timestamp) VALUES (?, ?, ?) ON CONFLICT DO NOTHING`)
  164. if err != nil {
  165. log.Errorln(err)
  166. return
  167. }
  168. defer stmt.Close()
  169. if _, err := stmt.Exec(accessToken, id, createdAt); err != nil {
  170. log.Errorln(err)
  171. return
  172. }
  173. }
  174. if err := tx.Commit(); err != nil {
  175. log.Errorln(err)
  176. }
  177. _, err = db.Exec(`PRAGMA foreign_keys = OFF;DROP TABLE "users";ALTER TABLE "users_copy" RENAME TO users;PRAGMA foreign_keys = ON;`)
  178. if err != nil {
  179. log.Errorln("error running migration, you may experience issues: ", err)
  180. }
  181. }
  182. func migrateToSchema4(db *sql.DB) {
  183. // We now save the follow request object.
  184. stmt, err := db.Prepare("ALTER TABLE ap_followers ADD COLUMN request_object BLOB")
  185. if err != nil {
  186. log.Errorln("Error running migration. This may be because you have already been running a dev version.", err)
  187. return
  188. }
  189. defer stmt.Close()
  190. _, err = stmt.Exec()
  191. if err != nil {
  192. log.Warnln(err)
  193. }
  194. }
  195. func migrateToSchema3(db *sql.DB) {
  196. // Since it's just a backlog of chat messages let's wipe the old messages
  197. // and recreate the table.
  198. // Drop the old messages table
  199. stmt, err := db.Prepare("DROP TABLE messages")
  200. if err != nil {
  201. log.Fatal(err)
  202. }
  203. defer stmt.Close()
  204. _, err = stmt.Exec()
  205. if err != nil {
  206. log.Warnln(err)
  207. }
  208. // Recreate it
  209. CreateMessagesTable(db)
  210. }
  211. func migrateToSchema2(db *sql.DB) {
  212. // Since it's just a backlog of chat messages let's wipe the old messages
  213. // and recreate the table.
  214. // Drop the old messages table
  215. stmt, err := db.Prepare("DROP TABLE messages")
  216. if err != nil {
  217. log.Fatal(err)
  218. }
  219. defer stmt.Close()
  220. _, err = stmt.Exec()
  221. if err != nil {
  222. log.Warnln(err)
  223. }
  224. // Recreate it
  225. CreateMessagesTable(db)
  226. }
  227. func migrateToSchema1(db *sql.DB) {
  228. // Since it's just a backlog of chat messages let's wipe the old messages
  229. // and recreate the table.
  230. // Drop the old messages table
  231. stmt, err := db.Prepare("DROP TABLE messages")
  232. if err != nil {
  233. log.Fatal(err)
  234. }
  235. defer stmt.Close()
  236. _, err = stmt.Exec()
  237. if err != nil {
  238. log.Warnln(err)
  239. }
  240. // Recreate it
  241. CreateMessagesTable(db)
  242. // Migrate access tokens to become chat users
  243. type oldAccessToken struct {
  244. accessToken string
  245. displayName string
  246. scopes string
  247. createdAt time.Time
  248. lastUsedAt *time.Time
  249. }
  250. oldAccessTokens := make([]oldAccessToken, 0)
  251. query := `SELECT * FROM access_tokens`
  252. rows, err := db.Query(query)
  253. if err != nil || rows.Err() != nil {
  254. log.Errorln("error migrating access tokens to schema v1", err, rows.Err())
  255. return
  256. }
  257. defer rows.Close()
  258. for rows.Next() {
  259. var token string
  260. var name string
  261. var scopes string
  262. var timestampString string
  263. var lastUsedString *string
  264. if err := rows.Scan(&token, &name, &scopes, &timestampString, &lastUsedString); err != nil {
  265. log.Error("There is a problem reading the database.", err)
  266. return
  267. }
  268. timestamp, err := time.Parse(time.RFC3339, timestampString)
  269. if err != nil {
  270. return
  271. }
  272. var lastUsed *time.Time
  273. if lastUsedString != nil {
  274. lastUsedTime, _ := time.Parse(time.RFC3339, *lastUsedString)
  275. lastUsed = &lastUsedTime
  276. }
  277. oldToken := oldAccessToken{
  278. accessToken: token,
  279. displayName: name,
  280. scopes: scopes,
  281. createdAt: timestamp,
  282. lastUsedAt: lastUsed,
  283. }
  284. oldAccessTokens = append(oldAccessTokens, oldToken)
  285. }
  286. // Recreate them as users
  287. for _, token := range oldAccessTokens {
  288. color := utils.GenerateRandomDisplayColor(config.MaxUserColor)
  289. if err := insertAPIToken(db, token.accessToken, token.displayName, color, token.scopes); err != nil {
  290. log.Errorln("Error migrating access token", err)
  291. }
  292. }
  293. }
  294. func insertAPIToken(db *sql.DB, token string, name string, color int, scopes string) error {
  295. log.Debugln("Adding new access token:", name)
  296. id := shortid.MustGenerate()
  297. tx, err := db.Begin()
  298. if err != nil {
  299. return err
  300. }
  301. stmt, err := tx.Prepare("INSERT INTO users(id, access_token, display_name, display_color, scopes, type) values(?, ?, ?, ?, ?, ?)")
  302. if err != nil {
  303. return err
  304. }
  305. defer stmt.Close()
  306. if _, err = stmt.Exec(id, token, name, color, scopes, "API"); err != nil {
  307. return err
  308. }
  309. if err = tx.Commit(); err != nil {
  310. return err
  311. }
  312. return nil
  313. }