From 23d4c7df495b33a73981d4fd4b9d579dee4091c6 Mon Sep 17 00:00:00 2001 From: Caihaohan Date: Sat, 27 Dec 2025 10:29:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=20webhook=20?= =?UTF-8?q?=E5=8E=BB=E9=87=8D=E7=AD=96=E7=95=A5=E5=AE=9E=E7=8E=B0=E5=A4=9A?= =?UTF-8?q?=E7=BA=A7=E5=8C=B9=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 findExistingProject 函数实现智能去重 - 优先级1: GitHub URL 完全匹配(最准确) - 优先级2: Website URL 完全匹配 - 优先级3: slug 匹配(兜底) - 增强日志输出,清晰显示匹配方式和项目 ID 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/app/api/webhook/projects/route.ts | 99 +++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 5 deletions(-) diff --git a/src/app/api/webhook/projects/route.ts b/src/app/api/webhook/projects/route.ts index 56d99a4..ddb1844 100644 --- a/src/app/api/webhook/projects/route.ts +++ b/src/app/api/webhook/projects/route.ts @@ -7,6 +7,80 @@ import { type ProjectInput, } from '@/lib/validations' +/** + * 多级去重策略:查找已存在的项目 + * + * 优先级: + * 1. GitHub URL 完全匹配(最准确) + * 2. Website URL 完全匹配 + * 3. slug 匹配(兜底) + * + * @param projectData - 项目数据 + * @returns 已存在的项目,如果不存在则返回 null + */ +async function findExistingProject(projectData: ProjectInput) { + // 优先级1: 通过 GitHub URL 匹配 + const githubLink = projectData.links.find((link) => link.type === 'GITHUB') + if (githubLink) { + const existingByGithub = await prisma.externalLink.findFirst({ + where: { + type: 'GITHUB', + url: githubLink.url, + }, + include: { + project: true, + }, + }) + + if (existingByGithub) { + console.log( + `[Webhook] Found existing project by GitHub URL: ${githubLink.url}` + ) + return existingByGithub.project + } + } + + // 优先级2: 通过 Website URL 匹配 + const websiteLink = projectData.links.find( + (link) => link.type === 'WEBSITE' + ) + if (websiteLink) { + const existingByWebsite = await prisma.externalLink.findFirst({ + where: { + type: 'WEBSITE', + url: websiteLink.url, + }, + include: { + project: true, + }, + }) + + if (existingByWebsite) { + console.log( + `[Webhook] Found existing project by Website URL: ${websiteLink.url}` + ) + return existingByWebsite.project + } + } + + // 优先级3: 通过 slug 匹配(兜底) + const slug = + projectData.nameEn?.toLowerCase().replace(/\s+/g, '-') || + projectData.name.toLowerCase().replace(/\s+/g, '-') + + const existingBySlug = await prisma.project.findUnique({ + where: { slug }, + }) + + if (existingBySlug) { + console.log(`[Webhook] Found existing project by slug: ${slug}`) + return existingBySlug + } + + console.log(`[Webhook] No existing project found, will create new one`) + return null +} + export async function POST(request: NextRequest) { const startTime = Date.now() @@ -77,6 +151,9 @@ export async function POST(request: NextRequest) { try { const validProject = projectValidation.data as ProjectInput + // 多级去重:查找已存在的项目 + const existingProject = await findExistingProject(validProject) + // Upsert tags const tagConnections = await Promise.all( validProject.tags.map(async (tag) => { @@ -101,13 +178,21 @@ export async function POST(request: NextRequest) { validProject.nameEn?.toLowerCase().replace(/\s+/g, '-') || validProject.name.toLowerCase().replace(/\s+/g, '-') - // Upsert project - const existingProject = await prisma.project.findUnique({ - where: { slug }, - }) - if (existingProject) { // Update existing project + const matchMethod = existingProject.slug === slug + ? 'slug' + : validProject.links.some( + (l) => + existingProject.links.some((el) => el.url === l.url) + ) + ? 'url' + : 'unknown' + + console.log( + `[Webhook] Updating project "${validProject.name}" (matched by ${matchMethod}, id: ${existingProject.id})` + ) + await prisma.project.update({ where: { id: existingProject.id }, data: { @@ -142,6 +227,10 @@ export async function POST(request: NextRequest) { results.updated++ } else { // Create new project + console.log( + `[Webhook] Creating new project "${validProject.name}" (slug: ${slug})` + ) + await prisma.project.create({ data: { name: validProject.name,