mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-06 15:22:12 +03:00
* feat(proxy): relay repair + free-pool UX + relay awareness * fix(proxy): preserve existing notes fields on relay repair; fix cpu limit /1000 across all 5 container providers * feat(proxy): extract bulk-import and pool-modal hooks (#6625) * fix: prevent relay type normalization to http on PATCH Bug: updateProxyRegistrySchema inherited .default("http") from the base schema, causing PATCH to silently overwrite relay types (vercel/deno/cloudflare) with "http" when the client didn't send a type field. - Move .default('http') from proxyRegistryFieldsSchema to createProxyRegistrySchema (only applies to new proxies) - Strip undefined keys from validated changes before passing to updateProxy — .partial() leaves absent fields as undefined, which the spread merge in updateProxyRow would propagate to the DB - Add console.warn in extractRelayAuth when decrypt fails on a known-encrypted relayAuthEnc blob Closes #6905 * feat: replace Load More with page-number pagination in FreePoolTab - Adds page-number pagination controls with prev/next buttons - Shows per-page summary with total counts - Resets to page 1 on filter change via wrapper setters * fix(proxy): restore free-proxy sync-error tracking reverted by pagination commit commit2c8e79f13(page-number pagination for FreePoolTab) accidentally reverted the recordFreeProxySyncErrors/clearFreeProxySyncErrors/ getFreeProxySyncErrors functions and the search/sortBy list options that an earlier commit (6f9ce75f3) in this same PR had added to src/lib/db/freeProxies.ts and src/app/api/settings/free-proxies/route.ts. localDb.ts still re-exported the three sync-error functions, so every module that transitively imports it (which is most of the unit test suite, plus the Next.js build used by dast-smoke) failed at load time with "The requested module './db/freeProxies' does not provide an export named 'clearFreeProxySyncErrors'". Restores the reverted implementation (verbatim) and fixes the two new "requires management auth" tests, which asserted 401 for an unauthenticated request without configuring INITIAL_PASSWORD + requireLogin first — on a fresh DB with no password configured, isAuthRequired() treats a loopback request as the pre-setup bootstrap path and allows it through, so the assertion needs the same password/requireLogin setup already used by tests/unit/api/settings-audit.test.ts. Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> * fix(proxy): trim unrelated scope from relay-repair/free-pool PR Remove two subsystems that are unrelated to relay repair and the free-pool UX and were never wired into the app: - open-sse/services/combo/capabilityRequirements.ts and CapabilityRequirementsEditor.tsx: a combo capability-filtering feature never imported by combo.ts or combos/page.tsx, with no test coverage. - src/lib/skills/containerProvider.ts CPU-limit rescale: an untested change to sandbox resource limits, unrelated to the proxy/relay subsystem this PR targets. Restored to the release baseline. Also renumber the free_proxy_sync_errors migration 121 -> 122 to avoid colliding with #6855, which independently claims 121 on the same release branch. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: oyi77 <oyi77@users.noreply.github.com> Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
149 lines
4.6 KiB
TypeScript
149 lines
4.6 KiB
TypeScript
import {
|
|
createProxy,
|
|
createProxyAndAssign,
|
|
deleteProxyById,
|
|
getProxyById,
|
|
getProxyWhereUsed,
|
|
updateProxy,
|
|
updateProxyAndAssign,
|
|
} from "@/lib/localDb";
|
|
import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse";
|
|
import { createProxyRegistrySchema, updateProxyRegistrySchema } from "@/shared/validation/schemas";
|
|
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
|
|
import { clearDispatcherCache } from "@omniroute/open-sse/utils/proxyDispatcher";
|
|
|
|
async function readJsonBody(request: Request) {
|
|
try {
|
|
return { ok: true as const, body: await request.json() };
|
|
} catch {
|
|
return {
|
|
ok: false as const,
|
|
response: createErrorResponse({
|
|
status: 400,
|
|
message: "Invalid JSON body",
|
|
type: "invalid_request",
|
|
}),
|
|
};
|
|
}
|
|
}
|
|
|
|
export async function resolveProxyLookupResponse(
|
|
searchParams: URLSearchParams,
|
|
whereUsedParam: string
|
|
): Promise<Response | null> {
|
|
const id = searchParams.get("id");
|
|
const whereUsed = searchParams.get(whereUsedParam) === "1";
|
|
|
|
if (id && whereUsed) {
|
|
const usage = await getProxyWhereUsed(id);
|
|
return Response.json(usage);
|
|
}
|
|
|
|
if (!id) {
|
|
return null;
|
|
}
|
|
|
|
const proxy = await getProxyById(id, { includeSecrets: false });
|
|
if (!proxy) {
|
|
return createErrorResponse({ status: 404, message: "Proxy not found", type: "not_found" });
|
|
}
|
|
return Response.json(proxy);
|
|
}
|
|
|
|
export async function handleProxyCreate(request: Request) {
|
|
const parsed = await readJsonBody(request);
|
|
if (!parsed.ok) return parsed.response;
|
|
|
|
try {
|
|
const validation = validateBody(createProxyRegistrySchema, parsed.body);
|
|
if (isValidationFailure(validation)) {
|
|
return createErrorResponse({
|
|
status: 400,
|
|
message: validation.error.message,
|
|
details: validation.error.details,
|
|
type: "invalid_request",
|
|
});
|
|
}
|
|
|
|
const { assignment, ...proxyFields } = validation.data;
|
|
if (assignment) {
|
|
const result = await createProxyAndAssign(proxyFields, assignment);
|
|
clearDispatcherCache();
|
|
return Response.json({ ...result.proxy, assignment: result.assignment }, { status: 201 });
|
|
}
|
|
|
|
const created = await createProxy(proxyFields);
|
|
return Response.json(created, { status: 201 });
|
|
} catch (error) {
|
|
return createErrorResponseFromUnknown(error, "Failed to create proxy");
|
|
}
|
|
}
|
|
|
|
export async function handleProxyUpdate(request: Request) {
|
|
const parsed = await readJsonBody(request);
|
|
if (!parsed.ok) return parsed.response;
|
|
|
|
try {
|
|
const validation = validateBody(updateProxyRegistrySchema, parsed.body);
|
|
if (isValidationFailure(validation)) {
|
|
return createErrorResponse({
|
|
status: 400,
|
|
message: validation.error.message,
|
|
details: validation.error.details,
|
|
type: "invalid_request",
|
|
});
|
|
}
|
|
|
|
const { id, assignment, ...rawChanges } = validation.data;
|
|
// Strip keys the client didn't send — .partial() resolves absent fields to
|
|
// undefined, which would silently overwrite DB values via the spread merge
|
|
// in updateProxyRow. Only include keys the client explicitly provided.
|
|
const changes = Object.fromEntries(
|
|
Object.entries(rawChanges).filter(([_, v]) => v !== undefined)
|
|
);
|
|
if (assignment) {
|
|
const result = await updateProxyAndAssign(id, changes, assignment);
|
|
if (!result?.proxy) {
|
|
return createErrorResponse({ status: 404, message: "Proxy not found", type: "not_found" });
|
|
}
|
|
|
|
clearDispatcherCache();
|
|
return Response.json({ ...result.proxy, assignment: result.assignment });
|
|
}
|
|
|
|
const updated = await updateProxy(id, changes);
|
|
if (!updated) {
|
|
return createErrorResponse({ status: 404, message: "Proxy not found", type: "not_found" });
|
|
}
|
|
|
|
return Response.json(updated);
|
|
} catch (error) {
|
|
return createErrorResponseFromUnknown(error, "Failed to update proxy");
|
|
}
|
|
}
|
|
|
|
export async function handleProxyDelete(request: Request) {
|
|
try {
|
|
const { searchParams } = new URL(request.url);
|
|
const id = searchParams.get("id");
|
|
const force = searchParams.get("force") === "1";
|
|
|
|
if (!id) {
|
|
return createErrorResponse({
|
|
status: 400,
|
|
message: "id is required",
|
|
type: "invalid_request",
|
|
});
|
|
}
|
|
|
|
const deleted = await deleteProxyById(id, { force });
|
|
if (!deleted) {
|
|
return createErrorResponse({ status: 404, message: "Proxy not found", type: "not_found" });
|
|
}
|
|
|
|
return Response.json({ success: true });
|
|
} catch (error) {
|
|
return createErrorResponseFromUnknown(error, "Failed to delete proxy");
|
|
}
|
|
}
|