Files
wehub-resource-sync e30e75b5d4
Changesets / Create Version PR (push) Has been cancelled
Deploy Shadcn Registry / Deploy Production (push) Has been cancelled
Template Metrics / LOC + Bundle Size (push) Has been cancelled
Code Quality / Oxlint + Oxfmt (push) Has been cancelled
Code Quality / Template Sync (push) Has been cancelled
Code Quality / Build Changed Packages (push) Has been cancelled
Code Quality / Test Changed Packages (push) Has been cancelled
Deploy Expo Example / Deploy Production (push) Has been cancelled
Deploy Ink Example / Deploy Production (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.12) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:40:13 +08:00

151 lines
3.8 KiB
TypeScript

import { NextResponse } from "next/server";
import { isAiPlaygroundEnabled } from "@/lib/feature-flags";
import { fetchSandboxResource } from "@/lib/xulux/fetch-sandbox";
import { resolveSandboxDownloadUrl } from "@/lib/xulux/sandbox-download-url";
import { getXuluxHostedTemplatesCatalog } from "@/lib/xulux/templates-catalog";
export const runtime = "nodejs";
const MAX_ZIP_BYTES = 50 * 1024 * 1024; // 50 MB ceiling
async function readLimitedBody(
body: ReadableStream<Uint8Array>,
): Promise<ArrayBuffer | null> {
const reader = body.getReader();
const chunks: Uint8Array[] = [];
let total = 0;
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
total += value.byteLength;
if (total > MAX_ZIP_BYTES) {
await reader.cancel("Archive too large.");
return null;
}
chunks.push(value);
}
} finally {
reader.releaseLock();
}
const result = new ArrayBuffer(total);
const view = new Uint8Array(result);
let offset = 0;
for (const chunk of chunks) {
view.set(chunk, offset);
offset += chunk.byteLength;
}
return result;
}
export async function GET(req: Request) {
if (!isAiPlaygroundEnabled) {
return NextResponse.json({ error: "Not found." }, { status: 404 });
}
const { searchParams } = new URL(req.url);
const templateId = searchParams.get("templateId");
const versionId = searchParams.get("versionId") ?? undefined;
const downloadSearch = searchParams.get("downloadSearch") ?? undefined;
if (!templateId) {
return NextResponse.json(
{ error: "Missing `templateId` query parameter." },
{ status: 400 },
);
}
const upstreamUrl = resolveSandboxDownloadUrl({
templates: getXuluxHostedTemplatesCatalog().templates,
templateId,
versionId,
downloadSearch,
});
if (!upstreamUrl) {
return NextResponse.json(
{ error: "Template download URL not allowed." },
{ status: 403 },
);
}
try {
const upstream = await fetchSandboxResource(upstreamUrl, {
redirect: "manual",
});
if (upstream.status >= 300 && upstream.status < 400) {
return NextResponse.json(
{ error: "Redirects are not allowed." },
{ status: 400 },
);
}
if (!upstream.ok) {
const details = await upstream.text().catch(() => "");
return NextResponse.json(
{
error: `Upstream responded ${upstream.status}.`,
details: details.slice(0, 500) || undefined,
},
{ status: 502 },
);
}
const contentLengthHeader = upstream.headers.get("content-length");
const contentLength = contentLengthHeader
? Number(contentLengthHeader)
: undefined;
if (
contentLength !== undefined &&
(!Number.isFinite(contentLength) || contentLength > MAX_ZIP_BYTES)
) {
return NextResponse.json(
{ error: "Archive too large." },
{ status: 413 },
);
}
const body = upstream.body;
if (!body) {
return NextResponse.json(
{ error: "No body from upstream." },
{ status: 502 },
);
}
const responseBody = await readLimitedBody(body);
if (!responseBody) {
return NextResponse.json(
{ error: "Archive too large." },
{ status: 413 },
);
}
return new NextResponse(responseBody, {
status: 200,
headers: {
"Content-Type":
upstream.headers.get("content-type") ?? "application/octet-stream",
"Cache-Control": "public, max-age=3600",
},
});
} catch (err) {
const cause =
err instanceof Error && err.cause instanceof Error
? err.cause.message
: undefined;
return NextResponse.json(
{
error: "Proxy fetch failed.",
details: err instanceof Error ? err.message : String(err),
cause,
},
{ status: 502 },
);
}
}