Files
zhuiguang-ai/src/app/api/admin/pending/batch-publish/route.ts
T

137 lines
4.3 KiB
TypeScript

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<string>();
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 });
}
}