mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
fix(backup): exclude paid models from JSON export/backup when hidePaidModels=true (#6328) Integrated into release/v3.8.47. (thanks @chirag127)
This commit is contained in:
@@ -12,6 +12,37 @@ import {
|
||||
getAllDomainCostHistory,
|
||||
getAllDomainBudgets,
|
||||
} from "@/lib/db/usageAnalytics";
|
||||
import { isFreeModel, providerHasFreeModels } from "@/shared/utils/freeModels";
|
||||
|
||||
/**
|
||||
* When `settings.hidePaidModels === true`, exports must not leak paid model ids
|
||||
* via combos — otherwise a round-trip (export → share/store → import) silently
|
||||
* re-materialises the paid targets the operator asked to REMOVE (#6328).
|
||||
* Filter combo model steps in place; keep combo-ref steps untouched.
|
||||
*/
|
||||
export function filterPaidComboSteps<T extends { models?: unknown }>(combos: T[]): T[] {
|
||||
return combos.map((combo) => {
|
||||
if (!Array.isArray(combo.models)) return combo;
|
||||
const filtered = combo.models.filter((step) => {
|
||||
if (!step || typeof step !== "object") return true;
|
||||
const s = step as Record<string, unknown>;
|
||||
if (s.kind === "combo-ref") return true;
|
||||
const rawModel = typeof s.model === "string" ? s.model.trim() : "";
|
||||
if (!rawModel) return true;
|
||||
const provider =
|
||||
(typeof s.providerId === "string" && s.providerId.trim()) ||
|
||||
(typeof s.provider === "string" && s.provider.trim()) ||
|
||||
(rawModel.includes("/") ? rawModel.split("/")[0] : "");
|
||||
if (!provider) return true;
|
||||
if (!providerHasFreeModels(provider)) return false;
|
||||
const modelId = rawModel.startsWith(`${provider}/`)
|
||||
? rawModel.slice(provider.length + 1)
|
||||
: rawModel;
|
||||
return isFreeModel(provider, { id: modelId });
|
||||
});
|
||||
return { ...combo, models: filtered };
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/settings/export-json
|
||||
@@ -39,9 +70,15 @@ export async function GET(request: Request) {
|
||||
|
||||
const providerConnections = await getProviderConnections();
|
||||
const providerNodes = await getProviderNodes();
|
||||
const combos = await getCombos();
|
||||
const combosRaw = await getCombos();
|
||||
const apiKeys = await getApiKeys();
|
||||
|
||||
// #6328: honor hidePaidModels at the export boundary so backup files
|
||||
// cannot silently smuggle paid model ids back in on import.
|
||||
const combos = rawSettings.hidePaidModels === true
|
||||
? filterPaidComboSteps(combosRaw as Array<{ models?: unknown }>)
|
||||
: combosRaw;
|
||||
|
||||
const exportData: Record<string, unknown> = {
|
||||
settings: safeSettings,
|
||||
providerConnections,
|
||||
|
||||
34
tests/unit/export-json-hide-paid-combos-6328.test.ts
Normal file
34
tests/unit/export-json-hide-paid-combos-6328.test.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* #6328 (export/backup boundary) — when `hidePaidModels` is on, the settings
|
||||
* JSON export must strip paid combo model steps so a round-trip (export →
|
||||
* import) does not silently re-materialise the paid targets the operator asked
|
||||
* to REMOVE. `filterPaidComboSteps` is the pure filter wired into the export
|
||||
* route; `combo-ref` steps and pricing-less combos are left untouched.
|
||||
* Rule #18 regression guard.
|
||||
*/
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { filterPaidComboSteps } from "../../src/app/api/settings/export-json/route.ts";
|
||||
|
||||
test("#6328 export filter drops paid model steps but keeps combo-ref steps", () => {
|
||||
// `openai` has no curated free roster, so `openai/gpt-4o` is paid-tier.
|
||||
const combos = [
|
||||
{
|
||||
id: "c1",
|
||||
models: [
|
||||
{ kind: "combo-ref", ref: "other-combo" },
|
||||
{ model: "openai/gpt-4o" },
|
||||
],
|
||||
},
|
||||
];
|
||||
const [out] = filterPaidComboSteps(combos);
|
||||
const kinds = out.models.map((m) => (m as { kind?: string; model?: string }).kind ?? (m as { model?: string }).model);
|
||||
assert.deepEqual(kinds, ["combo-ref"], "paid model dropped, combo-ref kept");
|
||||
});
|
||||
|
||||
test("#6328 export filter leaves a combo without a models array untouched", () => {
|
||||
const combos = [{ id: "c2", name: "no-models" }];
|
||||
const out = filterPaidComboSteps(combos);
|
||||
assert.deepEqual(out, combos);
|
||||
});
|
||||
Reference in New Issue
Block a user