import { type NextRequest, NextResponse } from "next/server"; import { AVATAR_FALLBACK_SRC } from "@/lib/imager"; import { fetchAvatarImage, ImagerUnavailableError, parseAvatarQuery, } from "@/lib/imager-upstream"; // The same neutral silhouette the client fallback uses, served as a real 200 // response when no upstream can render the figure. Images on server-rendered // pages therefore never show a broken-image glyph; browsers revalidate quickly // (`no-cache`) and pick up real renders as soon as an upstream recovers. const PLACEHOLDER_SVG_BYTES = new TextEncoder().encode( decodeURIComponent( AVATAR_FALLBACK_SRC.slice("data:image/svg+xml;utf8,".length), ), ); function cacheControlFor(source: string): string { switch (source) { case "fallback": case "cache-stale": // Reuse the render briefly but revalidate the upstream soon. return "public, max-age=300"; case "unavailable": return "no-cache, max-age=0"; default: return "public, max-age=3600"; } } export async function GET(request: NextRequest) { const { searchParams } = new URL(request.url); const parsed = parseAvatarQuery(searchParams); if (!parsed.ok) { return NextResponse.json({ error: parsed.message }, { status: 400 }); } try { const { body, contentType, source } = await fetchAvatarImage( new URL(request.url).origin, parsed.params, ); return new NextResponse(body as unknown as BodyInit, { status: 200, headers: { "Content-Type": contentType, "Content-Length": String(body.length), "Cache-Control": cacheControlFor(source), "X-Imager-Source": source, "Access-Control-Allow-Origin": "*", }, }); } catch (error) { if (error instanceof ImagerUnavailableError) { return new NextResponse(PLACEHOLDER_SVG_BYTES as unknown as BodyInit, { status: 200, headers: { "Content-Type": "image/svg+xml", "Content-Length": String(PLACEHOLDER_SVG_BYTES.length), "Cache-Control": cacheControlFor("unavailable"), "X-Imager-Source": "unavailable", "Access-Control-Allow-Origin": "*", }, }); } return NextResponse.json( { error: "Failed to fetch avatar from upstream" }, { status: 502 }, ); } } export async function OPTIONS() { return new NextResponse(null, { status: 204, headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, If-None-Match", }, }); }