/** * 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 = { 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;