This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
// Keep managed artifacts within the host agent's MAX_FILE limit.
|
||||
export const MAX_ARTIFACT_BYTES = 32 * 1024 * 1024;
|
||||
@@ -2,6 +2,7 @@ import { putFileBlob, blobContent } from '../db/file-blobs.js';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { db } from '../db/index.js';
|
||||
import { configFileForGame, type ArtifactKind } from '../config.js';
|
||||
import { MAX_ARTIFACT_BYTES } from '../../artifact-limits.js';
|
||||
|
||||
export type { ArtifactKind };
|
||||
|
||||
@@ -72,6 +73,7 @@ export class ScriptError extends Error {
|
||||
| 'baseline-exists'
|
||||
| 'empty-overlay'
|
||||
| 'invalid-release-version'
|
||||
| 'too-large'
|
||||
) {
|
||||
super(message);
|
||||
}
|
||||
@@ -218,6 +220,8 @@ export function createScript(input: CreateScriptInput): {
|
||||
script: ScriptRow;
|
||||
version: ScriptVersionRow;
|
||||
} {
|
||||
if (input.content.byteLength > MAX_ARTIFACT_BYTES)
|
||||
throw new ScriptError('Artifacts must not exceed 32 MiB.', 'too-large');
|
||||
if (!input.name.trim()) throw new ScriptError('script name is required', 'name-empty');
|
||||
const kind: ArtifactKind = input.kind ?? 'script';
|
||||
if (!isArtifactKind(kind)) throw new ScriptError(`invalid kind: ${input.kind}`, 'invalid-kind');
|
||||
@@ -316,6 +320,8 @@ export function uploadNewVersion(
|
||||
notes?: string,
|
||||
releaseVersion?: string
|
||||
): ScriptVersionRow {
|
||||
if (content.byteLength > MAX_ARTIFACT_BYTES)
|
||||
throw new ScriptError('Artifacts must not exceed 32 MiB.', 'too-large');
|
||||
const script = getScript(scriptId);
|
||||
if (!script) throw new ScriptError(`no such script (id=${scriptId})`, 'no-such-script');
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createHash } from 'node:crypto';
|
||||
import type { GithubPluginAsset, GithubPluginRelease } from '$lib/plugin-github.js';
|
||||
import { MAX_ARTIFACT_BYTES } from '../../artifact-limits.js';
|
||||
|
||||
const MAX_DLL_BYTES = 64 * 1024 * 1024;
|
||||
const CACHE_MS = 5 * 60 * 1000;
|
||||
const cache = new Map<string, { expires: number; release: Promise<GithubPluginRelease> }>();
|
||||
|
||||
@@ -157,7 +157,7 @@ async function fetchRelease(repository: string, tag: string | null): Promise<Git
|
||||
a.state === 'uploaded' &&
|
||||
Number.isSafeInteger(a.size) &&
|
||||
a.size > 0 &&
|
||||
a.size <= MAX_DLL_BYTES
|
||||
a.size <= MAX_ARTIFACT_BYTES
|
||||
)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- The preceding filter validates the decoded asset fields.
|
||||
.map((a: any) => ({
|
||||
@@ -238,7 +238,7 @@ export async function downloadGithubAsset(
|
||||
502
|
||||
);
|
||||
}
|
||||
const content = await limitedBody(response, MAX_DLL_BYTES);
|
||||
const content = await limitedBody(response, MAX_ARTIFACT_BYTES);
|
||||
if (content.length !== asset.size)
|
||||
throw new GithubReleaseError(
|
||||
'Downloaded size differs from the release asset. Preview the release again.',
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
import { MAX_ARTIFACT_BYTES } from '../artifact-limits.js';
|
||||
|
||||
export const JSON_BODY_LIMIT = 1024 * 1024;
|
||||
// Leave room for form fields and multipart framing around a maximum-size file.
|
||||
export const ARTIFACT_FORM_LIMIT = MAX_ARTIFACT_BYTES + 1024 * 1024;
|
||||
export const BETA_REVISION_FORM_LIMIT = 2 * MAX_ARTIFACT_BYTES + 1024 * 1024;
|
||||
|
||||
/** Enforce the limit before parsing, including absent or understated lengths. */
|
||||
export async function boundedBody(request: Request, maxBytes: number) {
|
||||
if (Number(request.headers.get('content-length')) > maxBytes) {
|
||||
void request.body?.cancel().catch(() => {});
|
||||
error(413, 'Request is too large.');
|
||||
}
|
||||
const reader = request.body?.getReader();
|
||||
if (!reader) return Buffer.alloc(0);
|
||||
const chunks: Buffer[] = [];
|
||||
let block = Buffer.alloc(0);
|
||||
let used = 0;
|
||||
let received = 0;
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
received += value.byteLength;
|
||||
if (received > maxBytes) {
|
||||
// Cancellation may wait for another branch of a cloned request.
|
||||
void reader.cancel().catch(() => {});
|
||||
error(413, 'Request is too large.');
|
||||
}
|
||||
// Coalesce tiny chunks so bookkeeping cannot outgrow the byte limit.
|
||||
let offset = 0;
|
||||
while (offset < value.byteLength) {
|
||||
if (used === block.length) {
|
||||
block = Buffer.allocUnsafe(Math.min(maxBytes, 64 * 1024));
|
||||
chunks.push(block);
|
||||
used = 0;
|
||||
}
|
||||
const length = Math.min(value.byteLength - offset, block.length - used);
|
||||
block.set(value.subarray(offset, offset + length), used);
|
||||
used += length;
|
||||
offset += length;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
return Buffer.concat(chunks, received);
|
||||
}
|
||||
|
||||
// Match Request.json()'s result type; each route retains its input validation.
|
||||
export async function jsonBody(
|
||||
request: Request,
|
||||
maxBytes = JSON_BODY_LIMIT
|
||||
): ReturnType<Request['json']> {
|
||||
return JSON.parse((await boundedBody(request, maxBytes)).toString('utf8'));
|
||||
}
|
||||
|
||||
export async function artifactFormBody(
|
||||
request: Request,
|
||||
maxBytes = ARTIFACT_FORM_LIMIT
|
||||
): Promise<FormData> {
|
||||
const body = await boundedBody(request, maxBytes);
|
||||
let form: FormData;
|
||||
try {
|
||||
form = await new Response(body, {
|
||||
headers: { 'Content-Type': request.headers.get('content-type') ?? '' }
|
||||
}).formData();
|
||||
} catch {
|
||||
error(400, 'Request body must be valid form data.');
|
||||
}
|
||||
// Validate every part before any handler copies file bytes or writes versions.
|
||||
for (const value of form.values()) {
|
||||
const size = typeof value === 'string' ? Buffer.byteLength(value, 'utf8') : value.size;
|
||||
if (size > MAX_ARTIFACT_BYTES) error(413, 'Artifacts must not exceed 32 MiB.');
|
||||
}
|
||||
return form;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import {
|
||||
saveScaling,
|
||||
deleteScaling,
|
||||
@@ -12,7 +13,7 @@ export function GET() {
|
||||
}
|
||||
export async function POST({ request }: { request: Request }) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const body = await jsonBody(request);
|
||||
if (!body || typeof body !== 'object' || Array.isArray(body))
|
||||
throw new ScalingError('Provide a policy action.');
|
||||
if (body.id !== undefined && typeof body.id !== 'string')
|
||||
@@ -36,6 +37,7 @@ export async function POST({ request }: { request: Request }) {
|
||||
}
|
||||
throw new ScalingError('Choose a supported policy action.');
|
||||
} catch (error) {
|
||||
if (isHttpError(error, 413)) throw error;
|
||||
return json(
|
||||
{ error: (error as Error).message },
|
||||
{ status: error instanceof ScalingError ? error.status : 400 }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import { openBeta, BetaError } from '$lib/server/fleet/beta.js';
|
||||
import { betaScriptMetadata } from '$lib/server/fleet/library-metadata.js';
|
||||
import { db } from '$lib/server/db/index.js';
|
||||
@@ -56,7 +57,10 @@ export async function GET({ url }: { url: URL }) {
|
||||
// or the legacy form:
|
||||
// { scripts: [{ scriptId, scriptVersionId }], ... }
|
||||
export async function POST({ request }: { request: Request }) {
|
||||
const body = (await request.json().catch(() => ({}))) as {
|
||||
const body = (await jsonBody(request).catch((cause) => {
|
||||
if (isHttpError(cause, 413)) throw cause;
|
||||
return {};
|
||||
})) as {
|
||||
artifacts?: Array<{ id?: number; versionId?: number }>;
|
||||
scripts?: Array<{ scriptId?: number; scriptVersionId?: number }>;
|
||||
servers?: string[];
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import { promoteBeta, previewBetaPromotion, BetaError } from '$lib/server/fleet/beta.js';
|
||||
|
||||
function parseId(raw: string): number | null {
|
||||
@@ -23,7 +24,10 @@ export async function GET({ params }: { params: { id: string } }) {
|
||||
export async function POST({ params, request }: { params: { id: string }; request: Request }) {
|
||||
const id = parseId(params.id);
|
||||
if (id === null) return json({ ok: false, error: 'bad id' }, { status: 400 });
|
||||
const body = await request.json().catch(() => null);
|
||||
const body = await jsonBody(request).catch((cause) => {
|
||||
if (isHttpError(cause, 413)) throw cause;
|
||||
return null;
|
||||
});
|
||||
if (typeof body?.previewToken !== 'string')
|
||||
return json(
|
||||
{ ok: false, error: 'Review promotion targets before confirming.' },
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import { db } from '$lib/server/db/index.js';
|
||||
import { emit, type BetaStage } from '$lib/server/pipeline/bus.js';
|
||||
import { storedPlan } from '$lib/server/fleet/beta-tests.js';
|
||||
@@ -24,7 +25,10 @@ export async function POST({ params, request }: { params: { id: string }; reques
|
||||
{ ok: false, error: 'Use the test plan retry action to collect fresh evidence' },
|
||||
{ status: 409 }
|
||||
);
|
||||
const body = (await request.json().catch(() => ({}))) as { server?: string };
|
||||
const body = (await jsonBody(request).catch((cause) => {
|
||||
if (isHttpError(cause, 413)) throw cause;
|
||||
return {};
|
||||
})) as { server?: string };
|
||||
if (!body.server) return json({ ok: false, error: 'server is required' }, { status: 400 });
|
||||
|
||||
const row = db()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { artifactFormBody, BETA_REVISION_FORM_LIMIT } from '$lib/server/request-body.js';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import { BetaError, reviseBeta, type BetaRevisionUpload } from '$lib/server/fleet/beta.js';
|
||||
import { ScriptError } from '$lib/server/fleet/scripts.js';
|
||||
import { kick } from '$lib/server/pipeline/worker.js';
|
||||
@@ -8,7 +9,7 @@ export async function POST({ params, request }: { params: { id: string }; reques
|
||||
if (!Number.isSafeInteger(id) || id < 1)
|
||||
return json({ ok: false, error: 'Invalid beta ID' }, { status: 400 });
|
||||
try {
|
||||
const form = await request.formData();
|
||||
const form = await artifactFormBody(request, BETA_REVISION_FORM_LIMIT);
|
||||
const expectedRevision = Number(form.get('expectedRevision'));
|
||||
if (!Number.isSafeInteger(expectedRevision) || expectedRevision < 1)
|
||||
throw new Error('A reviewed revision is required.');
|
||||
@@ -35,6 +36,7 @@ export async function POST({ params, request }: { params: { id: string }; reques
|
||||
void kick();
|
||||
return json({ ok: true, result });
|
||||
} catch (error) {
|
||||
if (isHttpError(error)) throw error;
|
||||
return json(
|
||||
{ ok: false, error: (error as Error).message },
|
||||
{ status: error instanceof BetaError || error instanceof ScriptError ? 409 : 400 }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import { releaseRestartRequest } from '$lib/server/provisioning/index.js';
|
||||
import { db } from '$lib/server/db/index.js';
|
||||
import { deployBetaServers, getBeta } from '$lib/server/fleet/beta.js';
|
||||
@@ -27,7 +28,7 @@ export async function POST({ params, request }: { params: { id: string }; reques
|
||||
if (!['running', 'ready-promote'].includes(beta.state))
|
||||
return json({ error: 'Beta is closed' }, { status: 409 });
|
||||
try {
|
||||
const body = await request.json();
|
||||
const body = await jsonBody(request);
|
||||
if (
|
||||
db()
|
||||
.prepare(
|
||||
@@ -148,6 +149,7 @@ export async function POST({ params, request }: { params: { id: string }; reques
|
||||
void kick();
|
||||
return json({ ok: true });
|
||||
} catch (error) {
|
||||
if (isHttpError(error, 413)) throw error;
|
||||
return json({ ok: false, error: (error as Error).message }, { status: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import { db } from '$lib/server/db/index.js';
|
||||
import { builtInPlans, parseBetaPlan } from '$lib/beta-plan.js';
|
||||
export function GET() {
|
||||
@@ -14,7 +15,7 @@ export function GET() {
|
||||
}
|
||||
export async function POST({ request }: { request: Request }) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const body = await jsonBody(request);
|
||||
if (typeof body.name !== 'string' || !body.name.trim() || body.name.length > 80)
|
||||
throw new Error('Preset name must be 1–80 characters');
|
||||
if (builtInPlans.some((p) => p.name === body.name.trim()))
|
||||
@@ -27,6 +28,7 @@ export async function POST({ request }: { request: Request }) {
|
||||
.run(body.name.trim(), JSON.stringify(plan));
|
||||
return json({ ok: true });
|
||||
} catch (error) {
|
||||
if (isHttpError(error, 413)) throw error;
|
||||
return json({ ok: false, error: (error as Error).message }, { status: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import { listBlueprints, saveBlueprint } from '$lib/server/fleet/launch-recipes.js';
|
||||
|
||||
export function GET() {
|
||||
@@ -7,11 +8,12 @@ export function GET() {
|
||||
|
||||
export async function POST({ request }: { request: Request }) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const body = await jsonBody(request);
|
||||
if (!body || typeof body !== 'object' || Array.isArray(body))
|
||||
throw new Error('Provide a collection name and a launch recipe.');
|
||||
return json({ blueprint: saveBlueprint(body) }, { status: 201 });
|
||||
} catch (err) {
|
||||
if (isHttpError(err, 413)) throw err;
|
||||
return json({ error: (err as Error).message }, { status: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import { deleteBlueprint, updateBlueprint } from '$lib/server/fleet/launch-recipes.js';
|
||||
|
||||
export function DELETE({ params }: { params: { id: string } }) {
|
||||
@@ -9,11 +10,12 @@ export function DELETE({ params }: { params: { id: string } }) {
|
||||
|
||||
export async function PUT({ params, request }: { params: { id: string }; request: Request }) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const body = await jsonBody(request);
|
||||
if (!body || typeof body !== 'object' || Array.isArray(body))
|
||||
throw new Error('Provide a collection name and map selections.');
|
||||
return json({ blueprint: updateBlueprint(params.id, body) });
|
||||
} catch (err) {
|
||||
if (isHttpError(err, 413)) throw err;
|
||||
return json({ error: (err as Error).message }, { status: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { getVersionContent } from '$lib/server/fleet/scripts.js';
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import { getServer, listServers } from '$lib/server/fleet/servers.js';
|
||||
import {
|
||||
getBaselineForScope,
|
||||
@@ -90,8 +91,9 @@ export async function GET({ url }: { url: URL }) {
|
||||
export async function POST({ request }: { request: Request }) {
|
||||
let body;
|
||||
try {
|
||||
body = await request.json();
|
||||
} catch {
|
||||
body = await jsonBody(request);
|
||||
} catch (cause) {
|
||||
if (isHttpError(cause, 413)) throw cause;
|
||||
return json({ error: 'Invalid preview request.' }, { status: 400 });
|
||||
}
|
||||
if (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import { listCollections, queueCollection } from '$lib/server/fleet/collections.js';
|
||||
import { kickProvisioning } from '$lib/server/pipeline/worker.js';
|
||||
|
||||
@@ -8,7 +9,7 @@ export function GET() {
|
||||
|
||||
export async function POST({ request }: { request: Request }) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const body = await jsonBody(request);
|
||||
if (!body || typeof body !== 'object' || Array.isArray(body))
|
||||
throw new Error('Provide a collection preview and its server keys.');
|
||||
const result = queueCollection(body);
|
||||
@@ -22,6 +23,7 @@ export async function POST({ request }: { request: Request }) {
|
||||
});
|
||||
return json(result, { status: result.collectionId ? 202 : 200 });
|
||||
} catch (err) {
|
||||
if (isHttpError(err, 413)) throw err;
|
||||
return json({ error: (err as Error).message }, { status: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import { previewCollection } from '$lib/server/fleet/collections.js';
|
||||
|
||||
export async function POST({ request }: { request: Request }) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const body = await jsonBody(request);
|
||||
return json(previewCollection(body?.blueprintId, body?.mode, body));
|
||||
} catch (err) {
|
||||
if (isHttpError(err, 413)) throw err;
|
||||
return json({ error: (err as Error).message }, { status: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import { housePartyTargets, queueHouseParty } from '$lib/server/fleet/house-party.js';
|
||||
import { kick } from '$lib/server/pipeline/worker.js';
|
||||
|
||||
@@ -7,7 +8,10 @@ export function GET() {
|
||||
}
|
||||
|
||||
export async function POST({ request }: { request: Request }) {
|
||||
const body = await request.json().catch(() => null);
|
||||
const body = await jsonBody(request).catch((cause) => {
|
||||
if (isHttpError(cause, 413)) throw cause;
|
||||
return null;
|
||||
});
|
||||
if (
|
||||
!Array.isArray(body?.servers) ||
|
||||
!body.servers.every((name: unknown) => typeof name === 'string')
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import { previewCreation } from '$lib/server/provisioning/index.js';
|
||||
|
||||
export async function POST({ request }: { request: Request }) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const body = await jsonBody(request);
|
||||
if (!body || typeof body !== 'object' || Array.isArray(body))
|
||||
throw new Error('Provide a launch selection.');
|
||||
if (body.blueprintId !== undefined)
|
||||
@@ -12,6 +13,7 @@ export async function POST({ request }: { request: Request }) {
|
||||
);
|
||||
return json(previewCreation(body));
|
||||
} catch (err) {
|
||||
if (isHttpError(err, 413)) throw err;
|
||||
return json({ error: (err as Error).message }, { status: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import { previewDrift } from '$lib/server/fleet/drift.js';
|
||||
|
||||
// GET returns the current drift plan for the whole fleet without touching disk.
|
||||
@@ -33,7 +34,10 @@ export async function GET() {
|
||||
|
||||
// Legacy previews remain readable. Watchdog owns all new reconciliation requests.
|
||||
export async function POST({ request }: { request: Request }) {
|
||||
const body = await request.json().catch(() => null);
|
||||
const body = await jsonBody(request).catch((cause) => {
|
||||
if (isHttpError(cause, 413)) throw cause;
|
||||
return null;
|
||||
});
|
||||
if (
|
||||
!body ||
|
||||
(body.servers !== undefined &&
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import { maintenanceOverview } from '$lib/server/maintenance/index.js';
|
||||
import { pauseMaintenance, setMaintenancePolicy } from '$lib/server/maintenance/policies.js';
|
||||
import { watchdogTick } from '$lib/server/maintenance/recovery.js';
|
||||
@@ -23,7 +24,10 @@ export function GET(event?: { url: URL }) {
|
||||
return json(maintenanceOverview(job ? Number(job) : null));
|
||||
}
|
||||
export async function POST({ request }: { request: Request }) {
|
||||
const body = await request.json().catch(() => null);
|
||||
const body = await jsonBody(request).catch((cause) => {
|
||||
if (isHttpError(cause, 413)) throw cause;
|
||||
return null;
|
||||
});
|
||||
if (!body || typeof body !== 'object' || Array.isArray(body))
|
||||
return json({ error: 'Invalid request.' }, { status: 400 });
|
||||
let notice: string;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import { watchdogDetail, updateWatchdogIncident } from '$lib/server/maintenance/recovery.js';
|
||||
import { kick } from '$lib/server/pipeline/worker.js';
|
||||
|
||||
@@ -9,7 +10,10 @@ export function GET({ params }: { params: { id: string } }) {
|
||||
: json({ error: 'Maintenance finding not found.' }, { status: 404 });
|
||||
}
|
||||
export async function POST({ params, request }: { params: { id: string }; request: Request }) {
|
||||
const body = await request.json().catch(() => null);
|
||||
const body = await jsonBody(request).catch((cause) => {
|
||||
if (isHttpError(cause, 413)) throw cause;
|
||||
return null;
|
||||
});
|
||||
if (!body || !['retry', 'dismiss'].includes(body.action))
|
||||
return json({ error: 'Choose retry or dismiss.' }, { status: 400 });
|
||||
const result = updateWatchdogIncident(params.id, body.action);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import { importPluginRelease } from '$lib/server/fleet/plugin-releases.js';
|
||||
import { GithubReleaseError } from '$lib/server/integrations/github-releases.js';
|
||||
import { ScriptError } from '$lib/server/fleet/scripts.js';
|
||||
@@ -6,8 +7,9 @@ import { ScriptError } from '$lib/server/fleet/scripts.js';
|
||||
export async function POST({ request }: { request: Request }) {
|
||||
let input;
|
||||
try {
|
||||
input = await request.json();
|
||||
} catch {
|
||||
input = await jsonBody(request);
|
||||
} catch (cause) {
|
||||
if (isHttpError(cause, 413)) throw cause;
|
||||
return json({ error: 'Invalid JSON body.' }, { status: 400 });
|
||||
}
|
||||
if (!input || typeof input !== 'object' || Array.isArray(input))
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import { validTimeZone } from '$lib/time-zone.js';
|
||||
import {
|
||||
cancelMaintenance,
|
||||
@@ -15,8 +16,9 @@ export function GET() {
|
||||
export async function POST({ request }: { request: Request }) {
|
||||
let body;
|
||||
try {
|
||||
body = await request.json();
|
||||
} catch {
|
||||
body = await jsonBody(request);
|
||||
} catch (cause) {
|
||||
if (isHttpError(cause, 413)) throw cause;
|
||||
return json({ error: 'Invalid JSON.' }, { status: 400 });
|
||||
}
|
||||
if (!body || typeof body !== 'object')
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import { deploymentListPage } from '$lib/server/provisioning/history.js';
|
||||
import { requestLifecycle } from '$lib/server/provisioning/index.js';
|
||||
import {
|
||||
@@ -32,7 +33,10 @@ export function GET({ url }: { url: URL }) {
|
||||
}
|
||||
|
||||
export async function POST({ request }: { request: Request }) {
|
||||
const body = await request.json().catch(() => null);
|
||||
const body = await jsonBody(request).catch((cause) => {
|
||||
if (isHttpError(cause, 413)) throw cause;
|
||||
return null;
|
||||
});
|
||||
if (
|
||||
!body ||
|
||||
typeof body.server !== 'string' ||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import {
|
||||
RebalanceError,
|
||||
previewRebalance,
|
||||
@@ -12,7 +13,7 @@ export function GET() {
|
||||
}
|
||||
export async function POST({ request }: { request: Request }) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const body = await jsonBody(request);
|
||||
if (!body || typeof body !== 'object' || Array.isArray(body))
|
||||
throw new RebalanceError('Provide a rebalance action.');
|
||||
if (body.action === 'preview') return json({ preview: previewRebalance(body.settings) });
|
||||
@@ -26,6 +27,7 @@ export async function POST({ request }: { request: Request }) {
|
||||
return json({ run: stopRebalance(body.id) });
|
||||
throw new RebalanceError('Choose a supported rebalance action.');
|
||||
} catch (error) {
|
||||
if (isHttpError(error, 413)) throw error;
|
||||
return json(
|
||||
{ error: (error as Error).message },
|
||||
{ status: error instanceof RebalanceError ? error.status : 400 }
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { artifactFormBody } from '$lib/server/request-body.js';
|
||||
import { json } from '@sveltejs/kit';
|
||||
import {
|
||||
createScript,
|
||||
@@ -38,7 +39,7 @@ export async function GET({ url }: { url: URL }) {
|
||||
// name, scopeType, scopeValue, notes, releaseVersion, kind (defaults to 'script'),
|
||||
// content (file). Configs may alternatively send `text` as a form field.
|
||||
export async function POST({ request, url }: { request: Request; url: URL }) {
|
||||
const form = await request.formData();
|
||||
const form = await artifactFormBody(request);
|
||||
const name = String(form.get('name') ?? '').trim();
|
||||
const notes = form.get('notes') ? String(form.get('notes')) : undefined;
|
||||
let scopeType: ScopeType | null;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import {
|
||||
getScript,
|
||||
setScope,
|
||||
@@ -65,7 +66,10 @@ export async function POST({ params, request }: { params: { id: string }; reques
|
||||
const script = getScript(scriptId);
|
||||
if (!script) return json({ ok: false, error: 'not found' }, { status: 404 });
|
||||
|
||||
const body = (await request.json().catch(() => ({}))) as {
|
||||
const body = (await jsonBody(request).catch((cause) => {
|
||||
if (isHttpError(cause, 413)) throw cause;
|
||||
return {};
|
||||
})) as {
|
||||
scopeType?: ScopeType | null;
|
||||
scopeValue?: string | null;
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { artifactFormBody } from '$lib/server/request-body.js';
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { uploadNewVersion, getScript, ScriptError } from '$lib/server/fleet/scripts.js';
|
||||
import { createHash } from 'node:crypto';
|
||||
@@ -28,7 +29,7 @@ export async function POST({ params, request }: { params: { id: string }; reques
|
||||
);
|
||||
}
|
||||
|
||||
const form = await request.formData();
|
||||
const form = await artifactFormBody(request);
|
||||
const notes = form.get('notes') ? String(form.get('notes')) : undefined;
|
||||
const file = form.get('content');
|
||||
if (!(file instanceof File))
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { artifactFormBody } from '$lib/server/request-body.js';
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { db } from '$lib/server/db/index.js';
|
||||
|
||||
// Read-only inspection works on HTTP fleet consoles as well as secure origins.
|
||||
export async function POST({ request }: { request: Request }) {
|
||||
const form = await request.formData();
|
||||
const form = await artifactFormBody(request);
|
||||
const file = form.get('content');
|
||||
if (!(file instanceof File) || file.size === 0) {
|
||||
return json({ error: 'Choose a non-empty script file.' }, { status: 400 });
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import { listServerKeys, saveServerKey, removeServerKey } from '$lib/server/fleet/server-keys.js';
|
||||
|
||||
export function GET() {
|
||||
@@ -6,7 +7,10 @@ export function GET() {
|
||||
}
|
||||
|
||||
export async function PUT({ request }: { request: Request }) {
|
||||
const body = await request.json().catch(() => null);
|
||||
const body = await jsonBody(request).catch((cause) => {
|
||||
if (isHttpError(cause, 413)) throw cause;
|
||||
return null;
|
||||
});
|
||||
try {
|
||||
const id = saveServerKey(body?.game, body?.plutoKey, body?.label);
|
||||
return json({ ok: true, id });
|
||||
@@ -16,7 +20,10 @@ export async function PUT({ request }: { request: Request }) {
|
||||
}
|
||||
|
||||
export async function DELETE({ request }: { request: Request }) {
|
||||
const body = await request.json().catch(() => null);
|
||||
const body = await jsonBody(request).catch((cause) => {
|
||||
if (isHttpError(cause, 413)) throw cause;
|
||||
return null;
|
||||
});
|
||||
if (typeof body?.id !== 'string' || !body.id.trim() || body.id.length > 128)
|
||||
return json({ error: 'Invalid key ID' }, { status: 400 });
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { fleetSnapshot } from '$lib/server/fleet/snapshot.js';
|
||||
import { kickProvisioning } from '$lib/server/pipeline/worker.js';
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import { queueCreateServer } from '$lib/server/provisioning/index.js';
|
||||
import type { GameMode, ZombieGame } from '$lib/server/fleet/map-aliases.js';
|
||||
|
||||
@@ -9,7 +10,10 @@ export function GET() {
|
||||
}
|
||||
|
||||
export async function POST({ request }: { request: Request }) {
|
||||
const body = (await request.json().catch(() => ({}))) as {
|
||||
const body = (await jsonBody(request).catch((cause) => {
|
||||
if (isHttpError(cause, 413)) throw cause;
|
||||
return {};
|
||||
})) as {
|
||||
hostId?: string;
|
||||
libraryId?: string;
|
||||
image?: string;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import { validateFleetSettings, concurrencyKeys, retentionKeys } from '$lib/fleet-settings.js';
|
||||
import { fleetSettings, saveFleetSettings } from '$lib/server/settings.js';
|
||||
|
||||
@@ -9,8 +10,9 @@ export function GET() {
|
||||
export async function PUT({ request }: { request: Request }) {
|
||||
let settings;
|
||||
try {
|
||||
settings = validateFleetSettings(await request.json());
|
||||
settings = validateFleetSettings(await jsonBody(request));
|
||||
} catch (error) {
|
||||
if (isHttpError(error, 413)) throw error;
|
||||
return json({ error: (error as Error).message }, { status: 400 });
|
||||
}
|
||||
return json(saveFleetSettings(settings));
|
||||
@@ -18,7 +20,7 @@ export async function PUT({ request }: { request: Request }) {
|
||||
|
||||
export async function PATCH({ request }: { request: Request }) {
|
||||
try {
|
||||
const { section, values } = await request.json();
|
||||
const { section, values } = await jsonBody(request);
|
||||
const keys =
|
||||
section === 'Concurrency'
|
||||
? concurrencyKeys
|
||||
@@ -37,6 +39,7 @@ export async function PATCH({ request }: { request: Request }) {
|
||||
throw new Error('Provide all settings for one section.');
|
||||
return json(saveFleetSettings({ ...fleetSettings(), ...values }));
|
||||
} catch (error) {
|
||||
if (isHttpError(error, 413)) throw error;
|
||||
return json({ error: (error as Error).message }, { status: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import {
|
||||
retentionPreview,
|
||||
retentionControls,
|
||||
@@ -22,9 +23,10 @@ export function GET() {
|
||||
}
|
||||
export async function POST({ request }: { request: Request }) {
|
||||
try {
|
||||
setRetentionControl(await request.json());
|
||||
setRetentionControl(await jsonBody(request));
|
||||
return json({ ok: true });
|
||||
} catch (error) {
|
||||
if (isHttpError(error, 413)) throw error;
|
||||
return json({ error: (error as Error).message }, { status: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import { validateFleetSettings } from '$lib/fleet-settings.js';
|
||||
import {
|
||||
retentionPreview,
|
||||
@@ -8,7 +9,7 @@ import {
|
||||
import { gameFileRetentionPreview } from '$lib/server/game-files/index.js';
|
||||
export async function POST({ request }: { request: Request }) {
|
||||
try {
|
||||
const settings = validateFleetSettings(await request.json());
|
||||
const settings = validateFleetSettings(await jsonBody(request));
|
||||
return json(
|
||||
{
|
||||
policies: [
|
||||
@@ -21,6 +22,7 @@ export async function POST({ request }: { request: Request }) {
|
||||
{ headers: { 'Cache-Control': 'no-store' } }
|
||||
);
|
||||
} catch (error) {
|
||||
if (isHttpError(error, 413)) throw error;
|
||||
return json({ error: (error as Error).message }, { status: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import { parseGameEvent, type GameTelemetry } from '$lib/telemetry.js';
|
||||
import { getServer } from '$lib/server/fleet/servers.js';
|
||||
import { saveTelemetry } from '$lib/server/fleet/telemetry.js';
|
||||
@@ -26,8 +27,9 @@ export async function POST({ request }: { request: Request }) {
|
||||
let value: unknown;
|
||||
let malformed = false;
|
||||
try {
|
||||
value = await request.json();
|
||||
} catch {
|
||||
value = await jsonBody(request);
|
||||
} catch (cause) {
|
||||
if (isHttpError(cause, 413)) throw cause;
|
||||
malformed = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import { watchdogOverview, watchdogTick, setWatchdogEnabled } from '$lib/server/watchdog/index.js';
|
||||
import { kick } from '$lib/server/pipeline/worker.js';
|
||||
|
||||
@@ -6,7 +7,10 @@ export function GET() {
|
||||
return json(watchdogOverview());
|
||||
}
|
||||
export async function POST({ request }: { request: Request }) {
|
||||
const body = await request.json().catch(() => null);
|
||||
const body = await jsonBody(request).catch((cause) => {
|
||||
if (isHttpError(cause, 413)) throw cause;
|
||||
return null;
|
||||
});
|
||||
if (
|
||||
!body ||
|
||||
typeof body !== 'object' ||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { isHttpError, json } from '@sveltejs/kit';
|
||||
import { watchdogDetail, updateWatchdogIncident } from '$lib/server/watchdog/index.js';
|
||||
import { kick } from '$lib/server/pipeline/worker.js';
|
||||
|
||||
@@ -7,7 +8,10 @@ export function GET({ params }: { params: { id: string } }) {
|
||||
return detail ? json(detail) : json({ error: 'Watchdog incident not found.' }, { status: 404 });
|
||||
}
|
||||
export async function POST({ params, request }: { params: { id: string }; request: Request }) {
|
||||
const body = await request.json().catch(() => null);
|
||||
const body = await jsonBody(request).catch((cause) => {
|
||||
if (isHttpError(cause, 413)) throw cause;
|
||||
return null;
|
||||
});
|
||||
if (!body || !['retry', 'dismiss'].includes(body.action))
|
||||
return json({ error: 'Choose retry or dismiss.' }, { status: 400 });
|
||||
const result = updateWatchdogIncident(params.id, body.action);
|
||||
|
||||
Reference in New Issue
Block a user