migrations.ts 5.1 KB

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