mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-18 12:52:25 +03:00
Merged, with one adjustment.
Confirmed the bug end to end: `PricingTab.tsx:369` types the payload as `{ error?: string }` and feeds it to `new Error(errorPayload.error || ...)`, so the `{ message, details }` object landed in the toast as `[object Object]` — exactly what #12494 reported.
The one change I made before merging: `validation.error.message` is the fixed constant `"Invalid request"` (see `validateBody` in `src/shared/validation/helpers.ts:44`), so it would have swapped an unreadable toast for an uninformative one. The repo already has `formatValidationMessage()`, added in #10849 for precisely this case — it returns `"field: reason"` naming the first offending field. Merged with that instead, so a bad pricing value now says which field it was.
Validated on `release/v3.8.51`: `typecheck:core` clean, `check-file-size` OK. Rebased onto the release branch — the PR was cut from `main`, which is ~3695 commits behind the active branch.
Thank you for the report and the fix.
118 lines
3.5 KiB
TypeScript
118 lines
3.5 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
|
|
import {
|
|
getPricing,
|
|
getPricingWithSources,
|
|
updatePricing,
|
|
resetPricing,
|
|
resetAllPricing,
|
|
} from "@/lib/db/settings";
|
|
import { updatePricingSchema } from "@/shared/validation/schemas";
|
|
import {
|
|
formatValidationMessage,
|
|
isValidationFailure,
|
|
validateBody,
|
|
} from "@/shared/validation/helpers";
|
|
|
|
/**
|
|
* GET /api/pricing
|
|
* Get current pricing configuration (merged user + defaults)
|
|
*/
|
|
export async function GET(request: Request) {
|
|
const authError = await requireManagementAuth(request);
|
|
if (authError) return authError;
|
|
|
|
try {
|
|
const includeSources = new URL(request.url).searchParams.get("includeSources") === "1";
|
|
if (includeSources) {
|
|
return NextResponse.json(await getPricingWithSources());
|
|
}
|
|
|
|
const pricing = await getPricing();
|
|
return NextResponse.json(pricing);
|
|
} catch (error) {
|
|
console.error("Error fetching pricing:", error);
|
|
return NextResponse.json({ error: "Failed to fetch pricing" }, { status: 500 });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* PATCH /api/pricing
|
|
* Update pricing configuration
|
|
* Body: { provider: { model: { input: number, output: number, cached: number, ... } } }
|
|
*/
|
|
export async function PATCH(request) {
|
|
const authError = await requireManagementAuth(request);
|
|
if (authError) return authError;
|
|
|
|
let rawBody;
|
|
try {
|
|
rawBody = await request.json();
|
|
} catch {
|
|
return NextResponse.json(
|
|
{
|
|
error: {
|
|
message: "Invalid request",
|
|
details: [{ field: "body", message: "Invalid JSON body" }],
|
|
},
|
|
},
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
try {
|
|
const validation = validateBody(updatePricingSchema, rawBody);
|
|
if (isValidationFailure(validation)) {
|
|
// #12494: PricingTab reads this payload as `{ error?: string }` and feeds it
|
|
// straight to `new Error(...)`, so handing back the `{ message, details }`
|
|
// object rendered as "Falha ao salvar preços: [object Object]". Send a string.
|
|
// `formatValidationMessage` names the offending field ("field: reason") instead
|
|
// of the bare "Invalid request" constant, so the toast stays actionable (#10849).
|
|
return NextResponse.json(
|
|
{ error: formatValidationMessage(validation.error) },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
const body = validation.data;
|
|
|
|
const updatedPricing = await updatePricing(body);
|
|
return NextResponse.json(updatedPricing);
|
|
} catch (error) {
|
|
console.error("Error updating pricing:", error);
|
|
return NextResponse.json({ error: "Failed to update pricing" }, { status: 500 });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* DELETE /api/pricing
|
|
* Reset pricing to defaults
|
|
* Query params: ?provider=xxx&model=yyy (optional)
|
|
*/
|
|
export async function DELETE(request) {
|
|
const authError = await requireManagementAuth(request);
|
|
if (authError) return authError;
|
|
|
|
try {
|
|
const { searchParams } = new URL(request.url);
|
|
const provider = searchParams.get("provider");
|
|
const model = searchParams.get("model");
|
|
|
|
if (provider && model) {
|
|
// Reset specific model
|
|
await resetPricing(provider, model);
|
|
} else if (provider) {
|
|
// Reset entire provider
|
|
await resetPricing(provider);
|
|
} else {
|
|
// Reset all pricing
|
|
await resetAllPricing();
|
|
}
|
|
|
|
const pricing = await getPricing();
|
|
return NextResponse.json(pricing);
|
|
} catch (error) {
|
|
console.error("Error resetting pricing:", error);
|
|
return NextResponse.json({ error: "Failed to reset pricing" }, { status: 500 });
|
|
}
|
|
}
|