import { NextResponse } from "next/server"; import { VIEW_NAMES, type ViewName, } from "yt-dlp-transcript-common/views/names"; import { VIEWS } from "../views"; export const dynamic = "force-dynamic"; // ONE POLLING ROUTE. Eight route files that each called one builder and // serialized it are this file plus a table (`../views.ts`); the old paths are // REWRITES in next.config.ts, so `/api/pulse?rev=…` and `/api/widget/actionable` // still answer, byte for byte, for every pinned widget and open tab out there. // // NO AUTH AND NO ENV GUARD HERE, and that is today's behaviour preserved rather // than a gap: all eight of these were unauthenticated polls, they are read-only, // and the editor is a loopback-only admin surface (docker/guard-exposure.sh // refuses to start it otherwise). Contrast `api/worker/health`, which takes the // worker token, and `api/test/*`, which is gated on E2E_TEST_ROUTES because // it MUTATES. A `/api/test/*` name may never join `VIEW_NAMES`: this dispatcher // would serve it with no guard at all. // // THE NAME IS CHECKED BEFORE ANY INPUT IS CONSTRUCTED. An unknown name costs a // tuple lookup and a 404 — no corpus read, no singleton, nothing to wedge. The // handlers in the table are lazy closures for exactly that reason. function isViewName(name: string): name is ViewName { return (VIEW_NAMES as readonly string[]).includes(name); } export async function GET( request: Request, ctx: { params: Promise<{ name: string }> }, ) { const { name } = await ctx.params; // 404 and not 500: a name nobody serves is a path that does not exist, which // is what the client's `res.ok` check already knows how to handle. if (!isViewName(name)) { return NextResponse.json({ error: "Not Found" }, { status: 404 }); } return VIEWS[name](request); }