fosrl.pangolin/server/setup/migrations.ts

164 lines
5.3 KiB
TypeScript
Raw Normal View History

2024-12-25 15:49:35 -05:00
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
2024-12-25 16:40:39 -05:00
import db, { exists } from "@server/db";
2024-12-25 15:49:35 -05:00
import path from "path";
import semver from "semver";
import { versionMigrations } from "@server/db/schema";
import { __DIRNAME, APP_PATH, APP_VERSION } from "@server/lib/consts";
2025-01-30 23:30:33 -05:00
import { SqliteError } from "better-sqlite3";
import fs from "fs";
import m1 from "./scripts/1.0.0-beta1";
import m2 from "./scripts/1.0.0-beta2";
import m3 from "./scripts/1.0.0-beta3";
import m4 from "./scripts/1.0.0-beta5";
import m5 from "./scripts/1.0.0-beta6";
2025-01-21 18:36:50 -05:00
import m6 from "./scripts/1.0.0-beta9";
2025-01-30 21:10:24 -05:00
import m7 from "./scripts/1.0.0-beta10";
import m8 from "./scripts/1.0.0-beta12";
2025-02-09 11:10:19 -05:00
import m13 from "./scripts/1.0.0-beta13";
2025-02-25 22:58:52 -05:00
import m15 from "./scripts/1.0.0-beta15";
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
// EXCEPT FOR THE DATABASE AND THE SCHEMA
2024-12-25 16:40:39 -05:00
// Define the migration list with versions and their corresponding functions
const migrations = [
{ version: "1.0.0-beta.1", run: m1 },
{ version: "1.0.0-beta.2", run: m2 },
{ version: "1.0.0-beta.3", run: m3 },
{ version: "1.0.0-beta.5", run: m4 },
2025-01-21 18:36:50 -05:00
{ version: "1.0.0-beta.6", run: m5 },
2025-01-30 21:10:24 -05:00
{ version: "1.0.0-beta.9", run: m6 },
{ version: "1.0.0-beta.10", run: m7 },
2025-02-09 11:10:19 -05:00
{ version: "1.0.0-beta.12", run: m8 },
2025-02-25 22:58:52 -05:00
{ version: "1.0.0-beta.13", run: m13 },
{ version: "1.0.0-beta.15", run: m15 }
2024-12-25 16:40:39 -05:00
// Add new migrations here as they are created
] as const;
2024-12-25 15:49:35 -05:00
await run();
async function run() {
// backup the database
backupDb();
// run the migrations
await runMigrations();
}
function backupDb() {
// make dir config/db/backups
const appPath = APP_PATH;
const dbDir = path.join(appPath, "db");
const backupsDir = path.join(dbDir, "backups");
// check if the backups directory exists and create it if it doesn't
if (!fs.existsSync(backupsDir)) {
fs.mkdirSync(backupsDir, { recursive: true });
}
// copy the db.sqlite file to backups
// add the date to the filename
const date = new Date();
const dateString = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}_${date.getHours()}-${date.getMinutes()}-${date.getSeconds()}`;
const dbPath = path.join(dbDir, "db.sqlite");
const backupPath = path.join(backupsDir, `db_${dateString}.sqlite`);
fs.copyFileSync(dbPath, backupPath);
}
2024-12-25 15:49:35 -05:00
export async function runMigrations() {
2025-01-30 00:03:11 -05:00
try {
const appVersion = APP_VERSION;
2024-12-25 15:49:35 -05:00
2025-01-30 00:03:11 -05:00
if (exists) {
await executeScripts();
} else {
console.log("Running migrations...");
try {
migrate(db, {
migrationsFolder: path.join(__DIRNAME, "init") // put here during the docker build
});
console.log("Migrations completed successfully.");
} catch (error) {
console.error("Error running migrations:", error);
}
await db
2025-01-30 23:30:33 -05:00
.insert(versionMigrations)
.values({
version: appVersion,
executedAt: Date.now()
})
.execute();
2025-01-30 00:03:11 -05:00
}
} catch (e) {
console.error("Error running migrations:", e);
2025-01-30 23:30:33 -05:00
await new Promise((resolve) =>
setTimeout(resolve, 1000 * 60 * 60 * 24 * 1)
);
2024-12-25 15:49:35 -05:00
}
}
2024-12-25 16:40:39 -05:00
async function executeScripts() {
2024-12-25 15:49:35 -05:00
try {
2024-12-25 16:40:39 -05:00
// Get the last executed version from the database
2025-01-30 23:30:33 -05:00
const lastExecuted = await db.select().from(versionMigrations);
2024-12-25 16:40:39 -05:00
2025-01-30 23:30:33 -05:00
// Filter and sort migrations
const pendingMigrations = lastExecuted
.map((m) => m)
.sort((a, b) => semver.compare(b.version, a.version));
const startVersion = pendingMigrations[0]?.version ?? "0.0.0";
console.log(`Starting migrations from version ${startVersion}`);
2024-12-25 16:40:39 -05:00
2025-01-30 23:30:33 -05:00
const migrationsToRun = migrations.filter((migration) =>
semver.gt(migration.version, startVersion)
);
console.log(
"Migrations to run:",
migrationsToRun.map((m) => m.version).join(", ")
);
2024-12-25 16:40:39 -05:00
// Run migrations in order
2025-01-30 23:30:33 -05:00
for (const migration of migrationsToRun) {
console.log(`Running migration ${migration.version}`);
2024-12-25 16:40:39 -05:00
try {
await migration.run();
// Update version in database
await db
.insert(versionMigrations)
.values({
version: migration.version,
executedAt: Date.now()
})
.execute();
console.log(
2024-12-25 16:40:39 -05:00
`Successfully completed migration ${migration.version}`
);
2025-01-30 23:30:33 -05:00
} catch (e) {
if (
e instanceof SqliteError &&
e.code === "SQLITE_CONSTRAINT_UNIQUE"
) {
2025-01-30 23:30:33 -05:00
console.error("Migration has already run! Skipping...");
continue;
}
console.error(
2024-12-25 16:40:39 -05:00
`Failed to run migration ${migration.version}:`,
2025-01-30 23:30:33 -05:00
e
2024-12-25 16:40:39 -05:00
);
2025-01-30 23:30:33 -05:00
throw e; // Re-throw to stop migration process
2024-12-25 16:40:39 -05:00
}
}
2024-12-25 15:49:35 -05:00
console.log("All migrations completed successfully");
2024-12-25 16:40:39 -05:00
} catch (error) {
console.error("Migration process failed:", error);
2024-12-25 16:40:39 -05:00
throw error;
}
2024-12-25 15:49:35 -05:00
}