feat: 优化 webhook 去重策略实现多级匹配
- 新增 findExistingProject 函数实现智能去重 - 优先级1: GitHub URL 完全匹配(最准确) - 优先级2: Website URL 完全匹配 - 优先级3: slug 匹配(兜底) - 增强日志输出,清晰显示匹配方式和项目 ID 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,80 @@ import {
|
|||||||
type ProjectInput,
|
type ProjectInput,
|
||||||
} from '@/lib/validations'
|
} 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) {
|
export async function POST(request: NextRequest) {
|
||||||
const startTime = Date.now()
|
const startTime = Date.now()
|
||||||
|
|
||||||
@@ -77,6 +151,9 @@ export async function POST(request: NextRequest) {
|
|||||||
try {
|
try {
|
||||||
const validProject = projectValidation.data as ProjectInput
|
const validProject = projectValidation.data as ProjectInput
|
||||||
|
|
||||||
|
// 多级去重:查找已存在的项目
|
||||||
|
const existingProject = await findExistingProject(validProject)
|
||||||
|
|
||||||
// Upsert tags
|
// Upsert tags
|
||||||
const tagConnections = await Promise.all(
|
const tagConnections = await Promise.all(
|
||||||
validProject.tags.map(async (tag) => {
|
validProject.tags.map(async (tag) => {
|
||||||
@@ -101,13 +178,21 @@ export async function POST(request: NextRequest) {
|
|||||||
validProject.nameEn?.toLowerCase().replace(/\s+/g, '-') ||
|
validProject.nameEn?.toLowerCase().replace(/\s+/g, '-') ||
|
||||||
validProject.name.toLowerCase().replace(/\s+/g, '-')
|
validProject.name.toLowerCase().replace(/\s+/g, '-')
|
||||||
|
|
||||||
// Upsert project
|
|
||||||
const existingProject = await prisma.project.findUnique({
|
|
||||||
where: { slug },
|
|
||||||
})
|
|
||||||
|
|
||||||
if (existingProject) {
|
if (existingProject) {
|
||||||
// Update existing project
|
// 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({
|
await prisma.project.update({
|
||||||
where: { id: existingProject.id },
|
where: { id: existingProject.id },
|
||||||
data: {
|
data: {
|
||||||
@@ -142,6 +227,10 @@ export async function POST(request: NextRequest) {
|
|||||||
results.updated++
|
results.updated++
|
||||||
} else {
|
} else {
|
||||||
// Create new project
|
// Create new project
|
||||||
|
console.log(
|
||||||
|
`[Webhook] Creating new project "${validProject.name}" (slug: ${slug})`
|
||||||
|
)
|
||||||
|
|
||||||
await prisma.project.create({
|
await prisma.project.create({
|
||||||
data: {
|
data: {
|
||||||
name: validProject.name,
|
name: validProject.name,
|
||||||
|
|||||||
Reference in New Issue
Block a user