feat(web): hash-encoded API paths + catch-all Route Handler proxy
This commit is contained in:
parent
17e60db935
commit
addc40e851
55
web/app/api/[...path]/route.ts
Normal file
55
web/app/api/[...path]/route.ts
Normal file
@ -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<NextResponse> {
|
||||||
|
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<NextResponse> {
|
||||||
|
return forward(request, (await ctx.params).path);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST(
|
||||||
|
request: NextRequest,
|
||||||
|
ctx: { params: Promise<{ path: string[] }> },
|
||||||
|
): Promise<NextResponse> {
|
||||||
|
return forward(request, (await ctx.params).path);
|
||||||
|
}
|
||||||
23
web/lib/api-paths.ts
Normal file
23
web/lib/api-paths.ts
Normal file
@ -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 },
|
||||||
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user