43 lines
1.7 KiB
TypeScript
43 lines
1.7 KiB
TypeScript
import { NextResponse, type NextRequest } from "next/server";
|
|
import { isAdmin } from "@/lib/auth";
|
|
|
|
// Game search for the admin "favorite games" picker. Backed by Steam's public
|
|
// store-search endpoint: keyless, returns capsule cover art, and its catalog is
|
|
// broad enough to cover PC plus the countless console/retro titles that have
|
|
// since been re-released on Steam. The picker also allows free-text entry for
|
|
// anything the search can't find, so console-only games are never blocked.
|
|
//
|
|
// Admin-gated (it lives under the panel layout, but route handlers don't inherit
|
|
// that guard, so we check here too) to avoid turning the blog into an open proxy.
|
|
|
|
const STORE_SEARCH = "https://store.steampowered.com/api/storesearch/";
|
|
const TIMEOUT = 6000;
|
|
|
|
type StoreItem = { id: number; name: string; tiny_image?: string };
|
|
|
|
export async function GET(req: NextRequest) {
|
|
if (!(await isAdmin())) {
|
|
return NextResponse.json({ error: "unauthorized" }, { status: 401 });
|
|
}
|
|
|
|
const q = (new URL(req.url).searchParams.get("q") ?? "").trim();
|
|
if (q.length < 2) return NextResponse.json({ games: [] });
|
|
|
|
const url = `${STORE_SEARCH}?term=${encodeURIComponent(q)}&cc=us&l=en`;
|
|
try {
|
|
const res = await fetch(url, { signal: AbortSignal.timeout(TIMEOUT) });
|
|
if (!res.ok) {
|
|
return NextResponse.json({ games: [], error: `Steam ${res.status}` });
|
|
}
|
|
const json = (await res.json()) as { items?: StoreItem[] };
|
|
const games = (json.items ?? []).slice(0, 10).map((it) => ({
|
|
name: it.name,
|
|
image: it.tiny_image,
|
|
url: `https://store.steampowered.com/app/${it.id}`,
|
|
}));
|
|
return NextResponse.json({ games });
|
|
} catch {
|
|
return NextResponse.json({ games: [], error: "Search timed out." });
|
|
}
|
|
}
|