diff --git a/web/app/api/[...path]/route.ts b/web/app/api/[...path]/route.ts new file mode 100644 index 0000000..176eb2d --- /dev/null +++ b/web/app/api/[...path]/route.ts @@ -0,0 +1,55 @@ +import { NextRequest, NextResponse } from "next/server"; +import { PUBLIC_TO_UPSTREAM } from "@/lib/api-paths"; + +const API_BASE_URL = process.env.API_BASE_URL ?? "http://api-server:3000"; + +async function forward( + request: NextRequest, + path: string[], +): Promise { + const hash = path[0]; + const route = PUBLIC_TO_UPSTREAM[hash]; + if (!route) { + return NextResponse.json({ error: "Not Found" }, { status: 404 }); + } + const target = `${API_BASE_URL}/${route.upstream}${route.trailingSlash ? "/" : ""}`; + + const init: RequestInit = { + method: request.method, + headers: { + "content-type": + request.headers.get("content-type") ?? "application/json", + }, + }; + if (request.method !== "GET" && request.method !== "HEAD") { + init.body = await request.text(); + } + + try { + const upstream = await fetch(target, init); + const body = await upstream.text(); + return new NextResponse(body, { + status: upstream.status, + headers: { + "content-type": + upstream.headers.get("content-type") ?? "application/json", + }, + }); + } catch (err) { + return NextResponse.json({ error: String(err) }, { status: 500 }); + } +} + +export async function GET( + request: NextRequest, + ctx: { params: Promise<{ path: string[] }> }, +): Promise { + return forward(request, (await ctx.params).path); +} + +export async function POST( + request: NextRequest, + ctx: { params: Promise<{ path: string[] }> }, +): Promise { + return forward(request, (await ctx.params).path); +} diff --git a/web/lib/api-paths.ts b/web/lib/api-paths.ts new file mode 100644 index 0000000..b346433 --- /dev/null +++ b/web/lib/api-paths.ts @@ -0,0 +1,23 @@ +// SHA-256(endpoint).hex[:16]. Deterministic; no salt. Public-boundary only: +// the upstream api-server still uses the readable names. See B1 spec. +// +// Verify with: +// python3 -c "import hashlib; print(hashlib.sha256(b'acc').hexdigest()[:16])" +export const API_PATHS = { + acc: "414322309db5c06d", // upstream: /acc/ + user: "04f8996da763b7a9", // upstream: /user/ + updateAcc: "982830e2982d95de", // upstream: /update-acc-data + updateUser: "f1a25b37d8db494c", // upstream: /update-user-data +} as const; + +// Reverse map for the Route Handler. Keys are the public path segment, +// values are { upstream: string; trailingSlash: boolean }. +export const PUBLIC_TO_UPSTREAM: Record< + string, + { upstream: string; trailingSlash: boolean } +> = { + [API_PATHS.acc]: { upstream: "acc", trailingSlash: true }, + [API_PATHS.user]: { upstream: "user", trailingSlash: true }, + [API_PATHS.updateAcc]: { upstream: "update-acc-data", trailingSlash: false }, + [API_PATHS.updateUser]: { upstream: "update-user-data", trailingSlash: false }, +};