mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
The db-backups import route statically imported better-sqlite3, which is stripped from the Next standalone server's node_modules in the packaged Electron app. Loading the route then crashed with "Cannot find module 'better-sqlite3'" on the Windows installer, even though node:sqlite was available. Route the upload integrity-check through openDatabaseAsync (better-sqlite3 -> node:sqlite -> sql.js), matching every other DB path. Adds a guard test so no API route can reintroduce a direct native import.
This commit is contained in:
committed by
GitHub
parent
5afb984425
commit
c116bfbc7f
@@ -1,9 +1,10 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import Database from "better-sqlite3";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import os from "os";
|
||||
import { getDbInstance, resetDbInstance, SQLITE_FILE } from "@/lib/db/core";
|
||||
import { openDatabaseAsync } from "@/lib/db/adapters/driverFactory";
|
||||
import type { SqliteAdapter } from "@/lib/db/adapters/types";
|
||||
import { backupDbFile } from "@/lib/db/backup";
|
||||
import { isAuthRequired, isAuthenticated } from "@/shared/utils/apiAuth";
|
||||
import { getSettings } from "@/lib/db/settings";
|
||||
@@ -86,10 +87,14 @@ export async function POST(request: Request) {
|
||||
tmpPath = path.join(os.tmpdir(), `omniroute-import-${Date.now()}.sqlite`);
|
||||
fs.writeFileSync(tmpPath, fileBuffer!);
|
||||
|
||||
// Validate SQLite integrity
|
||||
let testDb: InstanceType<typeof Database> | null = null;
|
||||
// Validate SQLite integrity.
|
||||
// Use the resilient driver factory (better-sqlite3 → node:sqlite → sql.js) rather than
|
||||
// a direct `better-sqlite3` import: in the packaged Electron app that native module is
|
||||
// absent from the standalone server's node_modules, so a hard import crashes the route
|
||||
// with "Cannot find module 'better-sqlite3'" even though node:sqlite is available (#3025).
|
||||
let testDb: SqliteAdapter | null = null;
|
||||
try {
|
||||
testDb = new Database(tmpPath, { readonly: true });
|
||||
testDb = await openDatabaseAsync(tmpPath, { readonly: true });
|
||||
const result = testDb.pragma("integrity_check") as any[];
|
||||
if (result[0]?.integrity_check !== "ok") {
|
||||
return NextResponse.json(
|
||||
|
||||
73
tests/unit/db-import-resilient-driver-3025.test.ts
Normal file
73
tests/unit/db-import-resilient-driver-3025.test.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* #3025 — DB import route must not hard-depend on a static `better-sqlite3` import.
|
||||
*
|
||||
* In the packaged Electron app, `better-sqlite3` is stripped from the Next standalone
|
||||
* server's `node_modules` (it is rebuilt for the Electron ABI elsewhere). Every other DB
|
||||
* code path survives because it loads the driver through the resilient driver factory
|
||||
* (`tryOpenSync` → better-sqlite3 → node:sqlite → sql.js). The db-backups *import* route
|
||||
* was the lone exception: it did `import Database from "better-sqlite3"` at module scope,
|
||||
* so loading the route crashed with `Cannot find module 'better-sqlite3'` (reported on
|
||||
* Windows installer v3.8.10), even though node:sqlite was available.
|
||||
*
|
||||
* Guard: no API route under src/app/api may statically import/require better-sqlite3 —
|
||||
* routes run inside the standalone server where the native module is not guaranteed.
|
||||
* Behaviour: the resilient opener validates a real sqlite file via PRAGMA integrity_check
|
||||
* and surfaces the same `[{ integrity_check: "ok" }]` shape the route relies on.
|
||||
*/
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { readFileSync, readdirSync, statSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { openDatabaseAsync } from "../../src/lib/db/adapters/driverFactory.ts";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const API_DIR = path.join(__dirname, "..", "..", "src", "app", "api");
|
||||
|
||||
function walk(dir: string): string[] {
|
||||
const out: string[] = [];
|
||||
for (const entry of readdirSync(dir)) {
|
||||
const full = path.join(dir, entry);
|
||||
if (statSync(full).isDirectory()) out.push(...walk(full));
|
||||
else if (/\.(ts|tsx|js|mjs)$/.test(entry)) out.push(full);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
const DIRECT_IMPORT = /(?:from\s+|require\(\s*)["']better-sqlite3["']/;
|
||||
|
||||
test("no API route statically imports better-sqlite3 (must use the resilient driver factory)", () => {
|
||||
const offenders = walk(API_DIR).filter((file) => DIRECT_IMPORT.test(readFileSync(file, "utf8")));
|
||||
assert.deepEqual(
|
||||
offenders.map((f) => path.relative(API_DIR, f)),
|
||||
[],
|
||||
"API routes run in the standalone server where better-sqlite3 may be absent; open " +
|
||||
"databases via src/lib/db (openDatabaseAsync / getDbInstance), never a direct import."
|
||||
);
|
||||
});
|
||||
|
||||
test("openDatabaseAsync validates a real sqlite file with the integrity_check shape the route expects", async () => {
|
||||
const tmp = path.join(os.tmpdir(), `omniroute-3025-${process.pid}-${process.hrtime.bigint()}.sqlite`);
|
||||
// Seed a valid sqlite file through the same resilient adapter the route will now use.
|
||||
const seed = await openDatabaseAsync(tmp);
|
||||
seed.exec("CREATE TABLE api_keys (id INTEGER PRIMARY KEY)");
|
||||
seed.close();
|
||||
|
||||
const db = await openDatabaseAsync(tmp, { readonly: true });
|
||||
try {
|
||||
const result = db.pragma("integrity_check") as Array<{ integrity_check?: string }>;
|
||||
assert.equal(result[0]?.integrity_check, "ok");
|
||||
const tables = (db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all() as Array<{
|
||||
name: string;
|
||||
}>).map((r) => r.name);
|
||||
assert.ok(tables.includes("api_keys"));
|
||||
} finally {
|
||||
db.close();
|
||||
for (const f of [tmp, `${tmp}-wal`, `${tmp}-shm`, `${tmp}-journal`]) {
|
||||
if (fs.existsSync(f)) fs.unlinkSync(f);
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user