import { NextResponse } from "next/server"; import { prisma } from "@/lib/prisma"; import { requireAdmin } from "@/lib/admin-auth"; import { fetchLogoWithMethod } from "@/lib/logo-fetcher"; import { downloadLogo } from "@/lib/logo-storage"; export async function POST(request: Request) { const authError = await requireAdmin(); if (authError) return authError; try { const body = await request.json(); const { type } = body as { type: "published" | "pending" }; if (!type || !["published", "pending"].includes(type)) { return NextResponse.json( { error: "type 参数无效,必须为 published 或 pending" }, { status: 400 } ); } const tools = type === "published" ? await prisma.tool.findMany({ select: { id: true, name: true, websiteUrl: true, logoUrl: true }, }) : await prisma.pendingTool.findMany({ select: { id: true, name: true, websiteUrl: true, logoUrl: true }, }); const results: { id: number; name: string; url: string; accessible: boolean; logoFetched: boolean; logoMethod: string; logoUrl: string; }[] = []; for (const tool of tools) { const url = tool.websiteUrl; let accessible = false; let html: string | null = null; if (url) { try { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 8000); const res = await fetch(url, { method: "GET", signal: controller.signal, redirect: "follow", headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", }, }); clearTimeout(timeout); accessible = res.status >= 200 && res.status < 400; if (accessible) { const ct = res.headers.get("content-type") || ""; if (ct.includes("text/html")) { html = await res.text(); } } } catch { accessible = false; } } let logoFetched = false; let logoMethod = ""; let logoUrl = tool.logoUrl || ""; const needsLogo = !logoUrl || logoUrl === "" || (!logoUrl.startsWith("/logos/") && !logoUrl.startsWith("data:")); if (accessible && url && needsLogo) { try { let logoResult = { url: "", method: "" }; if (html) { logoResult = await fetchLogoWithMethod(url, html); } if (!logoResult.url) { logoResult = await fetchLogoWithMethod(url); } if (logoResult.url) { const localPath = await downloadLogo(logoResult.url, tool.name.replace(/[^a-z0-9]/gi, "_").substring(0, 20)); if (type === "published") { await prisma.tool.update({ where: { id: tool.id }, data: { logoUrl: localPath }, }); } else { await prisma.pendingTool.update({ where: { id: tool.id }, data: { logoUrl: localPath }, }); } logoFetched = true; logoMethod = logoResult.method; logoUrl = localPath; } } catch { // Logo fetch failed silently } } results.push({ id: tool.id, name: tool.name, url: url || "", accessible, logoFetched, logoMethod, logoUrl, }); } return NextResponse.json({ results }); } catch (error: unknown) { const err = error as { message?: string }; return NextResponse.json({ error: err.message }, { status: 500 }); } }