From 52484c774e96d7f3a306042ac789829364967d9d Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 12 Feb 2026 12:05:15 -0800 Subject: [PATCH] Setting up drizzle and fix site not showing in private resource --- .gitignore | 3 +- drizzle.config.ts | 7 +- server/setup/.gitignore | 1 + server/setup/migrations.ts | 150 ++++++++++++------ .../CreateInternalResourceDialog.tsx | 2 +- src/components/EditInternalResourceDialog.tsx | 2 +- 6 files changed, 107 insertions(+), 58 deletions(-) create mode 100644 server/setup/.gitignore diff --git a/.gitignore b/.gitignore index df9179a4..1067dc2c 100644 --- a/.gitignore +++ b/.gitignore @@ -51,4 +51,5 @@ dynamic/ scratch/ tsconfig.json hydrateSaas.ts -CLAUDE.md \ No newline at end of file +CLAUDE.md +drizzle.config.ts diff --git a/drizzle.config.ts b/drizzle.config.ts index ba4ca8fe..d8344f94 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,14 +1,15 @@ +import { APP_PATH } from "@server/lib/consts"; import { defineConfig } from "drizzle-kit"; import path from "path"; -const schema = [path.join("server", "db", "pg", "schema")]; +const schema = [path.join("server", "db", "sqlite", "schema")]; export default defineConfig({ - dialect: "postgresql", + dialect: "sqlite", schema: schema, out: path.join("server", "migrations"), verbose: true, dbCredentials: { - url: process.env.DATABASE_URL as string + url: path.join(APP_PATH, "db", "db.sqlite") } }); diff --git a/server/setup/.gitignore b/server/setup/.gitignore new file mode 100644 index 00000000..a61cfd64 --- /dev/null +++ b/server/setup/.gitignore @@ -0,0 +1 @@ +migrations.ts diff --git a/server/setup/migrations.ts b/server/setup/migrations.ts index 7ae21836..170cf93d 100644 --- a/server/setup/migrations.ts +++ b/server/setup/migrations.ts @@ -1,45 +1,78 @@ #! /usr/bin/env node -import { migrate } from "drizzle-orm/node-postgres/migrator"; -import { db } from "../db/pg"; -import semver from "semver"; -import { versionMigrations } from "../db/pg"; -import { __DIRNAME, APP_VERSION } from "@server/lib/consts"; +import { migrate } from "drizzle-orm/better-sqlite3/migrator"; +import { db, exists } from "../db/sqlite"; import path from "path"; -import m1 from "./scriptsPg/1.6.0"; -import m2 from "./scriptsPg/1.7.0"; -import m3 from "./scriptsPg/1.8.0"; -import m4 from "./scriptsPg/1.9.0"; -import m5 from "./scriptsPg/1.10.0"; -import m6 from "./scriptsPg/1.10.2"; -import m7 from "./scriptsPg/1.11.0"; -import m8 from "./scriptsPg/1.11.1"; -import m9 from "./scriptsPg/1.12.0"; -import m10 from "./scriptsPg/1.13.0"; -import m11 from "./scriptsPg/1.14.0"; -import m12 from "./scriptsPg/1.15.0"; +import semver from "semver"; +import { versionMigrations } from "../db/sqlite"; +import { __DIRNAME, APP_PATH, APP_VERSION } from "@server/lib/consts"; +import { SqliteError } from "better-sqlite3"; +import fs from "fs"; +import m1 from "./scriptsSqlite/1.0.0-beta1"; +import m2 from "./scriptsSqlite/1.0.0-beta2"; +import m3 from "./scriptsSqlite/1.0.0-beta3"; +import m4 from "./scriptsSqlite/1.0.0-beta5"; +import m5 from "./scriptsSqlite/1.0.0-beta6"; +import m6 from "./scriptsSqlite/1.0.0-beta9"; +import m7 from "./scriptsSqlite/1.0.0-beta10"; +import m8 from "./scriptsSqlite/1.0.0-beta12"; +import m13 from "./scriptsSqlite/1.0.0-beta13"; +import m15 from "./scriptsSqlite/1.0.0-beta15"; +import m16 from "./scriptsSqlite/1.0.0"; +import m17 from "./scriptsSqlite/1.1.0"; +import m18 from "./scriptsSqlite/1.2.0"; +import m19 from "./scriptsSqlite/1.3.0"; +import m20 from "./scriptsSqlite/1.5.0"; +import m21 from "./scriptsSqlite/1.6.0"; +import m22 from "./scriptsSqlite/1.7.0"; +import m23 from "./scriptsSqlite/1.8.0"; +import m24 from "./scriptsSqlite/1.9.0"; +import m25 from "./scriptsSqlite/1.10.0"; +import m26 from "./scriptsSqlite/1.10.1"; +import m27 from "./scriptsSqlite/1.10.2"; +import m28 from "./scriptsSqlite/1.11.0"; +import m29 from "./scriptsSqlite/1.11.1"; +import m30 from "./scriptsSqlite/1.12.0"; +import m31 from "./scriptsSqlite/1.13.0"; +import m32 from "./scriptsSqlite/1.14.0"; +import m33 from "./scriptsSqlite/1.15.0"; +import m34 from "./scriptsSqlite/1.15.3"; // THIS CANNOT IMPORT ANYTHING FROM THE SERVER // EXCEPT FOR THE DATABASE AND THE SCHEMA // Define the migration list with versions and their corresponding functions const migrations = [ - { version: "1.6.0", run: m1 }, - { version: "1.7.0", run: m2 }, - { version: "1.8.0", run: m3 }, - { version: "1.9.0", run: m4 }, - { version: "1.10.0", run: m5 }, - { version: "1.10.2", run: m6 }, - { version: "1.11.0", run: m7 }, - { version: "1.11.1", run: m8 }, - { version: "1.12.0", run: m9 }, - { version: "1.13.0", run: m10 }, - { version: "1.14.0", run: m11 }, - { version: "1.15.0", run: m12 } + { 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 }, + { version: "1.0.0-beta.6", run: m5 }, + { version: "1.0.0-beta.9", run: m6 }, + { version: "1.0.0-beta.10", run: m7 }, + { version: "1.0.0-beta.12", run: m8 }, + { version: "1.0.0-beta.13", run: m13 }, + { version: "1.0.0-beta.15", run: m15 }, + { version: "1.0.0", run: m16 }, + { version: "1.1.0", run: m17 }, + { version: "1.2.0", run: m18 }, + { version: "1.3.0", run: m19 }, + { version: "1.5.0", run: m20 }, + { version: "1.6.0", run: m21 }, + { version: "1.7.0", run: m22 }, + { version: "1.8.0", run: m23 }, + { version: "1.9.0", run: m24 }, + { version: "1.10.0", run: m25 }, + { version: "1.10.1", run: m26 }, + { version: "1.10.2", run: m27 }, + { version: "1.11.0", run: m28 }, + { version: "1.11.1", run: m29 }, + { version: "1.12.0", run: m30 }, + { version: "1.13.0", run: m31 }, + { version: "1.14.0", run: m32 }, + { version: "1.15.0", run: m33 }, + { version: "1.15.3", run: m34 } // Add new migrations here as they are created -] as { - version: string; - run: () => Promise; -}[]; +] as const; await run(); @@ -48,6 +81,27 @@ async function run() { 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); +} + export async function runMigrations() { if (process.env.DISABLE_MIGRATIONS) { console.log("Migrations are disabled. Skipping..."); @@ -56,23 +110,12 @@ export async function runMigrations() { try { const appVersion = APP_VERSION; - // determine if the migrations table exists - const exists = await db - .select() - .from(versionMigrations) - .limit(1) - .execute() - .then((res) => res.length > 0) - .catch(() => false); - if (exists) { - console.log("Migrations table exists, running scripts..."); await executeScripts(); } else { - console.log("Migrations table does not exist, creating it..."); console.log("Running migrations..."); try { - await migrate(db, { + migrate(db, { migrationsFolder: path.join(__DIRNAME, "init") // put here during the docker build }); console.log("Migrations completed successfully."); @@ -105,7 +148,7 @@ async function executeScripts() { const pendingMigrations = lastExecuted .map((m) => m) .sort((a, b) => semver.compare(b.version, a.version)); - const startVersion = pendingMigrations[0]?.version ?? "0.0.0"; + const startVersion = pendingMigrations[0]?.version ?? APP_VERSION; console.log(`Starting migrations from version ${startVersion}`); const migrationsToRun = migrations.filter((migration) => @@ -122,6 +165,11 @@ async function executeScripts() { console.log(`Running migration ${migration.version}`); try { + if (!process.env.DISABLE_BACKUP_ON_MIGRATION) { + // Backup the database before running the migration + backupDb(); + } + await migration.run(); // Update version in database @@ -138,19 +186,17 @@ async function executeScripts() { ); } catch (e) { if ( - e instanceof Error && - typeof (e as any).code === "string" && - (e as any).code === "23505" + e instanceof SqliteError && + e.code === "SQLITE_CONSTRAINT_UNIQUE" ) { console.error("Migration has already run! Skipping..."); - continue; // or return, depending on context + continue; } - console.error( `Failed to run migration ${migration.version}:`, e ); - throw e; + throw e; // Re-throw to stop migration process } } diff --git a/src/components/CreateInternalResourceDialog.tsx b/src/components/CreateInternalResourceDialog.tsx index 4c8d6325..34e8f55e 100644 --- a/src/components/CreateInternalResourceDialog.tsx +++ b/src/components/CreateInternalResourceDialog.tsx @@ -303,7 +303,7 @@ export default function CreateInternalResourceDialog({ const [udpCustomPorts, setUdpCustomPorts] = useState(""); const availableSites = sites.filter( - (site) => site.type === "newt" && site.subnet + (site) => site.type === "newt" ); const form = useForm({ diff --git a/src/components/EditInternalResourceDialog.tsx b/src/components/EditInternalResourceDialog.tsx index 4b5030be..4c1176e5 100644 --- a/src/components/EditInternalResourceDialog.tsx +++ b/src/components/EditInternalResourceDialog.tsx @@ -397,7 +397,7 @@ export default function EditInternalResourceDialog({ ); const availableSites = sites.filter( - (site) => site.type === "newt" && site.subnet + (site) => site.type === "newt" ); const form = useForm({