d2l-ui / app /api /proxy /route.ts
Berkkirik's picture
Deploy: 2026-05-04T11:20:23Z
6db5454
/**
* Sibling route to `[...path]/route.ts` that handles the upstream root
* (`GET /` on the backend — health/summary).
*
* Why: Next 15's catch-all segment `[...path]` does not match an empty
* path, so `fetch("/api/proxy/")` 308-redirects to `/api/proxy` and 404s.
* `lib/api.ts` calls `health()` as `request("")`, which lands here.
*/
import { NextRequest, NextResponse } from "next/server";
const API_URL = process.env.D2L_API_URL || "https://etiya-d2l-api.hf.space";
const TOKEN = process.env.HF_TOKEN;
export const dynamic = "force-dynamic";
export const runtime = "nodejs";
async function forwardRoot(req: NextRequest) {
if (!TOKEN) {
return NextResponse.json(
{
error:
"HF_TOKEN env var not set on server. Copy .env.example to .env.local and add your token.",
},
{ status: 500 }
);
}
const upstreamUrl = `${API_URL}/${req.nextUrl.search}`;
const upstreamHeaders: Record<string, string> = {
Authorization: `Bearer ${TOKEN}`,
Accept: "application/json",
};
let body: BodyInit | null = null;
if (req.method !== "GET" && req.method !== "HEAD") {
const contentType = req.headers.get("content-type") || "application/json";
upstreamHeaders["Content-Type"] = contentType;
body = await req.text();
}
let resp: Response;
try {
resp = await fetch(upstreamUrl, {
method: req.method,
headers: upstreamHeaders,
body,
cache: "no-store",
});
} catch (e: unknown) {
return NextResponse.json(
{
error: "upstream_unreachable",
message: e instanceof Error ? e.message : String(e),
upstream: upstreamUrl,
},
{ status: 502 }
);
}
const contentType = resp.headers.get("content-type") || "application/json";
const text = await resp.text();
return new NextResponse(text, {
status: resp.status,
headers: { "content-type": contentType },
});
}
export const GET = forwardRoot;
export const POST = forwardRoot;
export const PUT = forwardRoot;
export const DELETE = forwardRoot;
export const PATCH = forwardRoot;