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 } ); } }