import { NextResponse } from "next/server"; import { prisma } from "@/lib/prisma"; import { requireAdmin } from "@/lib/admin-auth"; export async function POST(request: Request) { const authError = await requireAdmin(); if (authError) return authError; try { const body = await request.json(); const ids: number[] = body.ids; if (!Array.isArray(ids) || ids.length === 0) { return NextResponse.json({ error: "ids数组不能为空" }, { status: 400 }); } const pendings = await prisma.pendingTool.findMany({ where: { id: { in: ids } }, include: { category: { select: { id: true } } }, }); if (pendings.length === 0) { return NextResponse.json({ success: true, total: ids.length, published: 0, skipped: ids.length, details: [] }); } const existingNames = await prisma.tool.findMany({ where: { name: { in: pendings.map((p) => p.name) } }, select: { name: true }, }); const nameSet = new Set(existingNames.map((t) => t.name)); const existingUrls = pendings.filter((p) => p.websiteUrl).map((p) => p.websiteUrl!); let urlSet = new Set(); if (existingUrls.length > 0) { const urlMatches = await prisma.tool.findMany({ where: { websiteUrl: { in: existingUrls } }, select: { websiteUrl: true }, }); urlSet = new Set(urlMatches.map((t) => t.websiteUrl)); } const toPublish: typeof pendings = []; const results: { id: number; name: string; status: string }[] = []; const skipIds: number[] = []; for (const pending of pendings) { if (nameSet.has(pending.name)) { skipIds.push(pending.id); results.push({ id: pending.id, name: pending.name, status: "跳过-已存在同名工具" }); continue; } if (pending.websiteUrl && urlSet.has(pending.websiteUrl)) { skipIds.push(pending.id); results.push({ id: pending.id, name: pending.name, status: "跳过-已存在同网址工具" }); continue; } toPublish.push(pending); } if (skipIds.length > 0) { await prisma.pendingTool.deleteMany({ where: { id: { in: skipIds } } }); } if (toPublish.length === 0) { return NextResponse.json({ success: true, total: ids.length, published: 0, skipped: results.length, details: results, }); } const candidateSlugs = toPublish.map((p) => { let slug = p.name .toLowerCase() .replace(/[^a-z0-9\u4e00-\u9fff]+/g, "-") .replace(/^-|-$/g, ""); if (!slug || /^[\u4e00-\u9fff-]+$/.test(slug)) { slug = "tool-" + Date.now().toString(36) + Math.random().toString(36).slice(2, 6); } return slug; }); const slugConflicts = new Set( (await prisma.tool.findMany({ where: { slug: { in: candidateSlugs } }, select: { slug: true }, })).map((t) => t.slug) ); const toolsData = toPublish.map((pending, i) => { let slug = candidateSlugs[i]; if (slugConflicts.has(slug)) { slug = slug + "-" + Math.floor(Math.random() * 10000); } return { name: pending.name, slug, description: pending.description || "", websiteUrl: pending.websiteUrl || "", logoUrl: pending.logoUrl, categoryId: pending.categoryId || 1, pricingModel: (pending.pricingModel || "freemium") as "free" | "freemium" | "paid" | "subscription", pricingDetail: pending.pricingDetail, features: pending.features ?? undefined, tags: pending.tags ?? undefined, status: "published" as const, }; }); const published = toolsData.length; await prisma.$transaction(async (tx) => { await tx.tool.createMany({ data: toolsData }); await tx.pendingTool.deleteMany({ where: { id: { in: toPublish.map((p) => p.id) } }, }); }); for (const pending of toPublish) { results.push({ id: pending.id, name: pending.name, status: "已发布" }); } return NextResponse.json({ success: true, total: ids.length, published, skipped: results.filter((r) => r.status !== "已发布").length, details: results, }); } catch (error: unknown) { const err = error as { message?: string }; return NextResponse.json({ error: err.message }, { status: 500 }); } }