137 lines
4.3 KiB
TypeScript
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 });
|
|
}
|
|
}
|