134 lines
3.8 KiB
TypeScript
134 lines
3.8 KiB
TypeScript
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 });
|
|
}
|
|
}
|