Add hosted synthetic control plane
This commit is contained in:
parent
af04875107
commit
8dcbcd2201
21 changed files with 3695 additions and 772 deletions
19
apps/web/app/api/admin/synthetic/control/route.ts
Normal file
19
apps/web/app/api/admin/synthetic/control/route.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { proxySyntheticAdminRequest } from "../shared";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function GET(): Promise<Response> {
|
||||
return proxySyntheticAdminRequest("/admin/synthetic/control", {
|
||||
method: "GET"
|
||||
});
|
||||
}
|
||||
|
||||
export async function PUT(req: Request): Promise<Response> {
|
||||
return proxySyntheticAdminRequest(
|
||||
"/admin/synthetic/control",
|
||||
{
|
||||
method: "PUT",
|
||||
body: await req.text()
|
||||
}
|
||||
);
|
||||
}
|
||||
61
apps/web/app/api/admin/synthetic/routes.test.ts
Normal file
61
apps/web/app/api/admin/synthetic/routes.test.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test";
|
||||
import {
|
||||
getSyntheticAdminProxyConfig,
|
||||
isSyntheticAdminFeatureEnabled
|
||||
} from "./shared";
|
||||
|
||||
const originalFetch = globalThis.fetch;
|
||||
|
||||
describe("synthetic admin proxy helpers", () => {
|
||||
beforeEach(() => {
|
||||
process.env.NEXT_PUBLIC_SYNTHETIC_ADMIN = "1";
|
||||
process.env.NEXT_PUBLIC_API_URL = "http://127.0.0.1:4000";
|
||||
process.env.SYNTHETIC_ADMIN_TOKEN = "secret-token";
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.fetch = originalFetch;
|
||||
});
|
||||
|
||||
it("gates visibility on the public env flag", () => {
|
||||
expect(isSyntheticAdminFeatureEnabled("1")).toBe(true);
|
||||
expect(isSyntheticAdminFeatureEnabled("0")).toBe(false);
|
||||
});
|
||||
|
||||
it("reads the proxy config from server env only", () => {
|
||||
expect(getSyntheticAdminProxyConfig()).toEqual({
|
||||
apiBaseUrl: "http://127.0.0.1:4000",
|
||||
token: "secret-token"
|
||||
});
|
||||
});
|
||||
|
||||
it("proxies status requests with the backend admin token", async () => {
|
||||
const fetchMock = mock(async (input: string | URL, init?: RequestInit) => {
|
||||
expect(String(input)).toBe("http://127.0.0.1:4000/admin/synthetic/status");
|
||||
expect(new Headers(init?.headers).get("authorization")).toBe("Bearer secret-token");
|
||||
return new Response(JSON.stringify({ enabled: true }), {
|
||||
status: 200,
|
||||
headers: {
|
||||
"content-type": "application/json"
|
||||
}
|
||||
});
|
||||
});
|
||||
globalThis.fetch = fetchMock as typeof fetch;
|
||||
const route = await import("./status/route");
|
||||
|
||||
const response = await route.GET();
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(await response.json()).toEqual({ enabled: true });
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("returns 404 from proxy routes when the internal UI flag is off", async () => {
|
||||
process.env.NEXT_PUBLIC_SYNTHETIC_ADMIN = "0";
|
||||
const route = await import("./control/route");
|
||||
|
||||
const response = await route.GET();
|
||||
|
||||
expect(response.status).toBe(404);
|
||||
});
|
||||
});
|
||||
63
apps/web/app/api/admin/synthetic/shared.ts
Normal file
63
apps/web/app/api/admin/synthetic/shared.ts
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
const jsonResponse = (body: unknown, status = 200): Response => {
|
||||
return new Response(JSON.stringify(body), {
|
||||
status,
|
||||
headers: {
|
||||
"content-type": "application/json"
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const isSyntheticAdminFeatureEnabled = (
|
||||
value = process.env.NEXT_PUBLIC_SYNTHETIC_ADMIN
|
||||
): boolean => value === "1";
|
||||
|
||||
export const getSyntheticAdminProxyConfig = (
|
||||
env: Record<string, string | undefined> = process.env
|
||||
): { apiBaseUrl: string; token: string } | null => {
|
||||
const apiBaseUrl = env.NEXT_PUBLIC_API_URL?.trim();
|
||||
const token = env.SYNTHETIC_ADMIN_TOKEN?.trim();
|
||||
if (!apiBaseUrl || !token) {
|
||||
return null;
|
||||
}
|
||||
return { apiBaseUrl, token };
|
||||
};
|
||||
|
||||
export const proxySyntheticAdminRequest = async (
|
||||
path: string,
|
||||
init: RequestInit = {},
|
||||
env: Record<string, string | undefined> = process.env
|
||||
): Promise<Response> => {
|
||||
if (!isSyntheticAdminFeatureEnabled(env.NEXT_PUBLIC_SYNTHETIC_ADMIN)) {
|
||||
return jsonResponse({ error: "not found" }, 404);
|
||||
}
|
||||
|
||||
const config = getSyntheticAdminProxyConfig(env);
|
||||
if (!config) {
|
||||
return jsonResponse(
|
||||
{
|
||||
error: "synthetic admin proxy misconfigured"
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
|
||||
const url = new URL(path, config.apiBaseUrl);
|
||||
const headers = new Headers(init.headers);
|
||||
headers.set("authorization", `Bearer ${config.token}`);
|
||||
if (!headers.has("content-type") && init.body) {
|
||||
headers.set("content-type", "application/json");
|
||||
}
|
||||
|
||||
const response = await fetch(url.toString(), {
|
||||
...init,
|
||||
cache: "no-store",
|
||||
headers
|
||||
});
|
||||
|
||||
return new Response(response.body, {
|
||||
status: response.status,
|
||||
headers: {
|
||||
"content-type": response.headers.get("content-type") ?? "application/json"
|
||||
}
|
||||
});
|
||||
};
|
||||
9
apps/web/app/api/admin/synthetic/status/route.ts
Normal file
9
apps/web/app/api/admin/synthetic/status/route.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { proxySyntheticAdminRequest } from "../shared";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function GET(): Promise<Response> {
|
||||
return proxySyntheticAdminRequest("/admin/synthetic/status", {
|
||||
method: "GET"
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue