migrations.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. import { migrate } from "drizzle-orm/better-sqlite3/migrator";
  2. import db, { exists } from "@server/db";
  3. import path from "path";
  4. import semver from "semver";
  5. import { versionMigrations } from "@server/db/schema";
  6. import { __DIRNAME, APP_PATH, APP_VERSION } from "@server/lib/consts";
  7. import { SqliteError } from "better-sqlite3";
  8. import fs from "fs";
  9. import m1 from "./scripts/1.0.0-beta1";
  10. import m2 from "./scripts/1.0.0-beta2";
  11. import m3 from "./scripts/1.0.0-beta3";
  12. import m4 from "./scripts/1.0.0-beta5";
  13. import m5 from "./scripts/1.0.0-beta6";
  14. import m6 from "./scripts/1.0.0-beta9";
  15. import m7 from "./scripts/1.0.0-beta10";
  16. import m8 from "./scripts/1.0.0-beta12";
  17. import m13 from "./scripts/1.0.0-beta13";
  18. // THIS CANNOT IMPORT ANYTHING FROM THE SERVER
  19. // EXCEPT FOR THE DATABASE AND THE SCHEMA
  20. // Define the migration list with versions and their corresponding functions
  21. const migrations = [
  22. { version: "1.0.0-beta.1", run: m1 },
  23. { version: "1.0.0-beta.2", run: m2 },
  24. { version: "1.0.0-beta.3", run: m3 },
  25. { version: "1.0.0-beta.5", run: m4 },
  26. { version: "1.0.0-beta.6", run: m5 },
  27. { version: "1.0.0-beta.9", run: m6 },
  28. { version: "1.0.0-beta.10", run: m7 },
  29. { version: "1.0.0-beta.12", run: m8 },
  30. { version: "1.0.0-beta.13", run: m13 }
  31. // Add new migrations here as they are created
  32. ] as const;
  33. await run();
  34. async function run() {
  35. // backup the database
  36. backupDb();
  37. // run the migrations
  38. await runMigrations();
  39. }
  40. function backupDb() {
  41. // make dir config/db/backups
  42. const appPath = APP_PATH;
  43. const dbDir = path.join(appPath, "db");
  44. const backupsDir = path.join(dbDir, "backups");
  45. // check if the backups directory exists and create it if it doesn't
  46. if (!fs.existsSync(backupsDir)) {
  47. fs.mkdirSync(backupsDir, { recursive: true });
  48. }
  49. // copy the db.sqlite file to backups
  50. // add the date to the filename
  51. const date = new Date();
  52. const dateString = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}_${date.getHours()}-${date.getMinutes()}-${date.getSeconds()}`;
  53. const dbPath = path.join(dbDir, "db.sqlite");
  54. const backupPath = path.join(backupsDir, `db_${dateString}.sqlite`);
  55. fs.copyFileSync(dbPath, backupPath);
  56. }
  57. export async function runMigrations() {
  58. try {
  59. const appVersion = APP_VERSION;
  60. if (exists) {
  61. await executeScripts();
  62. } else {
  63. console.log("Running migrations...");
  64. try {
  65. migrate(db, {
  66. migrationsFolder: path.join(__DIRNAME, "init") // put here during the docker build
  67. });
  68. console.log("Migrations completed successfully.");
  69. } catch (error) {
  70. console.error("Error running migrations:", error);
  71. }
  72. await db
  73. .insert(versionMigrations)
  74. .values({
  75. version: appVersion,
  76. executedAt: Date.now()
  77. })
  78. .execute();
  79. }
  80. } catch (e) {
  81. console.error("Error running migrations:", e);
  82. await new Promise((resolve) =>
  83. setTimeout(resolve, 1000 * 60 * 60 * 24 * 1)
  84. );
  85. }
  86. }
  87. async function executeScripts() {
  88. try {
  89. // Get the last executed version from the database
  90. const lastExecuted = await db.select().from(versionMigrations);
  91. // Filter and sort migrations
  92. const pendingMigrations = lastExecuted
  93. .map((m) => m)
  94. .sort((a, b) => semver.compare(b.version, a.version));
  95. const startVersion = pendingMigrations[0]?.version ?? "0.0.0";
  96. console.log(`Starting migrations from version ${startVersion}`);
  97. const migrationsToRun = migrations.filter((migration) =>
  98. semver.gt(migration.version, startVersion)
  99. );
  100. console.log(
  101. "Migrations to run:",
  102. migrationsToRun.map((m) => m.version).join(", ")
  103. );
  104. // Run migrations in order
  105. for (const migration of migrationsToRun) {
  106. console.log(`Running migration ${migration.version}`);
  107. try {
  108. await migration.run();
  109. // Update version in database
  110. await db
  111. .insert(versionMigrations)
  112. .values({
  113. version: migration.version,
  114. executedAt: Date.now()
  115. })
  116. .execute();
  117. console.log(
  118. `Successfully completed migration ${migration.version}`
  119. );
  120. } catch (e) {
  121. if (
  122. e instanceof SqliteError &&
  123. e.code === "SQLITE_CONSTRAINT_UNIQUE"
  124. ) {
  125. console.error("Migration has already run! Skipping...");
  126. continue;
  127. }
  128. console.error(
  129. `Failed to run migration ${migration.version}:`,
  130. e
  131. );
  132. throw e; // Re-throw to stop migration process
  133. }
  134. }
  135. console.log("All migrations completed successfully");
  136. } catch (error) {
  137. console.error("Migration process failed:", error);
  138. throw error;
  139. }
  140. }