mirror of
https://github.com/ReCloudStudio/WebHooker.git
synced 2026-09-22 16:11:29 +00:00
fix: skip validation for unchanged admin routes
This commit is contained in:
parent
9e0f21c538
commit
d40f34cc4d
1 changed files with 36 additions and 4 deletions
|
|
@ -27,8 +27,35 @@ function isValidMatch(match: unknown): match is string | string[] {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deepEqual(a: unknown, b: unknown): boolean {
|
||||||
|
if (a === b) return true;
|
||||||
|
if (typeof a !== typeof b || a === null || b === null) return false;
|
||||||
|
if (Array.isArray(a) || Array.isArray(b)) {
|
||||||
|
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
||||||
|
return a.every((x, i) => deepEqual(x, b[i]));
|
||||||
|
}
|
||||||
|
if (typeof a === "object" && typeof b === "object") {
|
||||||
|
const ao = a as Record<string, unknown>;
|
||||||
|
const bo = b as Record<string, unknown>;
|
||||||
|
const ak = Object.keys(ao);
|
||||||
|
const bk = Object.keys(bo);
|
||||||
|
if (ak.length !== bk.length) return false;
|
||||||
|
return ak.every(
|
||||||
|
(k) => Object.prototype.hasOwnProperty.call(bo, k) && deepEqual(ao[k], bo[k]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the submitted routes. Routes that are byte-for-byte identical to an
|
||||||
|
* entry in `unchanged` (keyed by id) skip the full content check, so a pre-existing
|
||||||
|
* incomplete route can never block edits to a different route. Only new or modified
|
||||||
|
* routes are fully validated. Structural checks (id shape, uniqueness) still run for all.
|
||||||
|
*/
|
||||||
function validateRoutes(
|
function validateRoutes(
|
||||||
routes: unknown,
|
routes: unknown,
|
||||||
|
unchanged?: Map<string, Route>,
|
||||||
): { ok: true; routes: Route[] } | { ok: false; error: string } {
|
): { ok: true; routes: Route[] } | { ok: false; error: string } {
|
||||||
if (!Array.isArray(routes)) return { ok: false, error: "routes must be an array" };
|
if (!Array.isArray(routes)) return { ok: false, error: "routes must be an array" };
|
||||||
if (routes.length > 200) return { ok: false, error: "too many routes" };
|
if (routes.length > 200) return { ok: false, error: "too many routes" };
|
||||||
|
|
@ -42,6 +69,9 @@ function validateRoutes(
|
||||||
}
|
}
|
||||||
if (seen.has(r.id)) return { ok: false, error: `duplicate route id "${r.id}"` };
|
if (seen.has(r.id)) return { ok: false, error: `duplicate route id "${r.id}"` };
|
||||||
seen.add(r.id);
|
seen.add(r.id);
|
||||||
|
// Skip full validation for routes that are unchanged from what is stored.
|
||||||
|
const prev = unchanged?.get(r.id);
|
||||||
|
if (prev && deepEqual(r, prev)) continue;
|
||||||
if (typeof r.name !== "string" || r.name.trim().length === 0) {
|
if (typeof r.name !== "string" || r.name.trim().length === 0) {
|
||||||
return { ok: false, error: `route "${r.id}" needs a name` };
|
return { ok: false, error: `route "${r.id}" needs a name` };
|
||||||
}
|
}
|
||||||
|
|
@ -217,10 +247,11 @@ export function createAdminRoutes(): Hono<{ Bindings: Env }> {
|
||||||
} catch {
|
} catch {
|
||||||
return c.json({ error: "Invalid JSON body" }, 400);
|
return c.json({ error: "Invalid JSON body" }, 400);
|
||||||
}
|
}
|
||||||
const result = validateRoutes((body as { routes?: unknown })?.routes);
|
const existing = await loadRoutes(c.env.KV);
|
||||||
|
const unchanged = new Map(existing.map((r) => [r.id, r]));
|
||||||
|
const result = validateRoutes((body as { routes?: unknown })?.routes, unchanged);
|
||||||
if (!result.ok) return c.json({ error: result.error }, 400);
|
if (!result.ok) return c.json({ error: result.error }, 400);
|
||||||
|
|
||||||
const existing = await loadRoutes(c.env.KV);
|
|
||||||
let nextAll: Route[];
|
let nextAll: Route[];
|
||||||
|
|
||||||
if (s.scope.isSuper) {
|
if (s.scope.isSuper) {
|
||||||
|
|
@ -305,10 +336,11 @@ export function createAdminRoutes(): Hono<{ Bindings: Env }> {
|
||||||
const scoped = Array.isArray(submitted)
|
const scoped = Array.isArray(submitted)
|
||||||
? submitted.map((r) => ({ ...(r as Record<string, unknown>), groupId }))
|
? submitted.map((r) => ({ ...(r as Record<string, unknown>), groupId }))
|
||||||
: submitted;
|
: submitted;
|
||||||
const result = validateRoutes(scoped);
|
const existing = await loadRoutes(c.env.KV);
|
||||||
|
const unchanged = new Map(existing.map((r) => [r.id, r]));
|
||||||
|
const result = validateRoutes(scoped, unchanged);
|
||||||
if (!result.ok) return c.json({ error: result.error }, 400);
|
if (!result.ok) return c.json({ error: result.error }, 400);
|
||||||
|
|
||||||
const existing = await loadRoutes(c.env.KV);
|
|
||||||
// Replace only this group's routes; every other group is preserved untouched.
|
// Replace only this group's routes; every other group is preserved untouched.
|
||||||
const others = existing.filter((r) => r.groupId !== groupId);
|
const others = existing.filter((r) => r.groupId !== groupId);
|
||||||
const nextAll = [...others, ...result.routes];
|
const nextAll = [...others, ...result.routes];
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue