commit c0894548dac5133bd89da5b68684443748fa2559 Author: fuomag9 <1580624+fuomag9@users.noreply.github.com> Date: Fri Nov 7 18:38:30 2025 +0100 Update config.ts commit 5a4f1159d2123ada0f698a10011c24720bf6ea6f Author: fuomag9 <1580624+fuomag9@users.noreply.github.com> Date: Fri Nov 7 15:58:13 2025 +0100 first drizzle rewrite
129 lines
3.7 KiB
TypeScript
129 lines
3.7 KiB
TypeScript
import Database from "better-sqlite3";
|
|
import { drizzle } from "drizzle-orm/better-sqlite3";
|
|
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
|
|
import { mkdirSync } from "node:fs";
|
|
import { dirname, isAbsolute, resolve as resolvePath } from "node:path";
|
|
import * as schema from "./db/schema";
|
|
|
|
const DEFAULT_SQLITE_URL = "file:./data/caddy-proxy-manager.db";
|
|
|
|
type GlobalForDrizzle = typeof globalThis & {
|
|
__DRIZZLE_DB__?: ReturnType<typeof drizzle<typeof schema>>;
|
|
__SQLITE_CLIENT__?: Database.Database;
|
|
__MIGRATIONS_RAN__?: boolean;
|
|
};
|
|
|
|
function resolveSqlitePath(rawUrl: string): string {
|
|
if (!rawUrl) {
|
|
return ":memory:";
|
|
}
|
|
if (rawUrl === ":memory:" || rawUrl === "file::memory:") {
|
|
return ":memory:";
|
|
}
|
|
|
|
if (rawUrl.startsWith("file:./") || rawUrl.startsWith("file:../")) {
|
|
const relative = rawUrl.slice("file:".length);
|
|
return resolvePath(process.cwd(), relative);
|
|
}
|
|
|
|
if (rawUrl.startsWith("file:")) {
|
|
try {
|
|
const fileUrl = new URL(rawUrl);
|
|
if (fileUrl.host && fileUrl.host !== "localhost") {
|
|
throw new Error("Remote SQLite hosts are not supported.");
|
|
}
|
|
return decodeURIComponent(fileUrl.pathname);
|
|
} catch {
|
|
const remainder = rawUrl.slice("file:".length);
|
|
if (!remainder) {
|
|
return ":memory:";
|
|
}
|
|
return isAbsolute(remainder) ? remainder : resolvePath(process.cwd(), remainder);
|
|
}
|
|
}
|
|
|
|
return isAbsolute(rawUrl) ? rawUrl : resolvePath(process.cwd(), rawUrl);
|
|
}
|
|
|
|
const databaseUrl = process.env.DATABASE_URL ?? DEFAULT_SQLITE_URL;
|
|
const sqlitePath = resolveSqlitePath(databaseUrl);
|
|
|
|
function ensureDirectoryFor(pathname: string) {
|
|
if (pathname === ":memory:") {
|
|
return;
|
|
}
|
|
const dir = dirname(pathname);
|
|
mkdirSync(dir, { recursive: true });
|
|
}
|
|
|
|
const globalForDrizzle = globalThis as GlobalForDrizzle;
|
|
|
|
const sqlite =
|
|
globalForDrizzle.__SQLITE_CLIENT__ ??
|
|
(() => {
|
|
ensureDirectoryFor(sqlitePath);
|
|
return new Database(sqlitePath);
|
|
})();
|
|
|
|
if (process.env.NODE_ENV !== "production") {
|
|
globalForDrizzle.__SQLITE_CLIENT__ = sqlite;
|
|
}
|
|
|
|
export const db =
|
|
globalForDrizzle.__DRIZZLE_DB__ ?? drizzle(sqlite, { schema, casing: "snake_case" });
|
|
|
|
if (process.env.NODE_ENV !== "production") {
|
|
globalForDrizzle.__DRIZZLE_DB__ = db;
|
|
}
|
|
|
|
const migrationsFolder = resolvePath(process.cwd(), "drizzle");
|
|
|
|
function runMigrations() {
|
|
if (sqlitePath === ":memory:") {
|
|
return;
|
|
}
|
|
if (globalForDrizzle.__MIGRATIONS_RAN__) {
|
|
return;
|
|
}
|
|
try {
|
|
migrate(db, { migrationsFolder });
|
|
globalForDrizzle.__MIGRATIONS_RAN__ = true;
|
|
} catch (error: any) {
|
|
// During build, pages may be pre-rendered in parallel, causing race conditions
|
|
// with migrations. If tables already exist, just continue.
|
|
if (error?.code === 'SQLITE_ERROR' && error?.message?.includes('already exists')) {
|
|
console.log('Database tables already exist, skipping migrations');
|
|
globalForDrizzle.__MIGRATIONS_RAN__ = true;
|
|
return;
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
try {
|
|
runMigrations();
|
|
} catch (error) {
|
|
console.error("Failed to run database migrations:", error);
|
|
// In build mode, allow the build to continue even if migrations fail
|
|
// The runtime initialization will handle migrations properly
|
|
if (process.env.NODE_ENV !== 'production' || process.env.NEXT_PHASE === 'phase-production-build') {
|
|
console.warn('Continuing despite migration error during build phase');
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export { schema };
|
|
export default db;
|
|
|
|
export function nowIso(): string {
|
|
return new Date().toISOString();
|
|
}
|
|
|
|
export function toIso(value: string | Date | null | undefined): string | null {
|
|
if (!value) {
|
|
return null;
|
|
}
|
|
return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
|
|
}
|