Spaces:
Runtime error
Runtime error
File size: 3,574 Bytes
cd8bd0a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | import { NextResponse } from "next/server";
import { getDbInstance } from "@/lib/db/core";
import { backupDbFile } from "@/lib/db/backup";
import { isAuthRequired, isAuthenticated } from "@/shared/utils/apiAuth";
import { runJsonMigration, type LegacyJsonData } from "@/lib/db/jsonMigration";
import { getSettings } from "@/lib/db/settings";
import { setSystemPromptConfig } from "@omniroute/open-sse/services/systemPrompt.ts";
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error";
/**
* POST /api/settings/import-json
*
* Imports a legacy OmniRoute JSON backup into the current SQLite
* database. Accepts either multipart/form-data (file field) or a raw JSON body.
*
* π Auth-guarded.
* π Zero-Trust: password and requireLogin keys are stripped before insertion.
* π A pre-import backup is created automatically before any data is written.
*/
export async function POST(request: Request) {
if (await isAuthRequired(request)) {
if (!(await isAuthenticated(request))) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
}
try {
let rawText: string | null = null;
const contentType = request.headers.get("content-type") ?? "";
if (contentType.includes("multipart/form-data")) {
const formData = await request.formData();
const file = formData.get("file") as File | null;
if (!file) return NextResponse.json({ error: "No json file provided" }, { status: 400 });
rawText = await file.text();
} else {
rawText = await request.text();
}
if (!rawText?.trim()) {
return NextResponse.json({ error: "Empty request payload" }, { status: 400 });
}
// Parse with explicit 400 on malformed JSON (Gemini suggestion)
let data: LegacyJsonData;
try {
data = JSON.parse(rawText) as LegacyJsonData;
} catch {
return NextResponse.json(
{
error: "Invalid JSON: the file could not be parsed. Please upload a valid .json backup.",
},
{ status: 400 }
);
}
// π Zero-Trust: strip authentication config before migration
if (data.settings) {
const { password: _pw, requireLogin: _rl, ...safeSettings } = data.settings;
data = { ...data, settings: safeSettings };
}
const db = getDbInstance();
// Create a safety backup before writing anything
backupDbFile("pre-json-import");
// Delegate the actual migration to the shared helper (avoids duplication with core.ts)
const counts = runJsonMigration(db, data);
// Re-hydrate the in-memory Global System Prompt config β the migration writes it to
// the DB but the in-memory state would stay stale until a restart otherwise (#2470).
const importedSettings = await getSettings();
if (importedSettings.systemPrompt) {
setSystemPromptConfig(importedSettings.systemPrompt);
}
console.log(
`[JSON Import] Imported ${counts.connections} connections, ${counts.nodes} nodes, ` +
`${counts.combos} combos, ${counts.apiKeys} API keys, ` +
`${counts.usageHistory} usage rows, ${counts.domainCostHistory} cost rows, ` +
`${counts.domainBudgets} budgets`
);
return NextResponse.json({
success: true,
message: "Legacy JSON database imported successfully",
...counts,
});
} catch (err) {
console.error("[API] Error importing JSON backup:", err);
return NextResponse.json(
{ error: sanitizeErrorMessage(err instanceof Error ? err.message : String(err)) },
{ status: 500 }
);
}
}
|