Files
agent-park/src/app/api/discovery/lib/discovery-service.ts
T
mzaxdandClaude e3e72006b0 feat: 新增项目发现任务系统并修复代码审查发现的高优先级问题
新增功能:
- 新增 ProjectDiscoveryTask 数据模型,支持异步项目探索任务管理
- 新增 /api/discovery/tasks CRUD API,支持任务创建、查询和状态更新
- 新增 /api/discovery/tasks/:id/complete API,完成探索并自动创建项目
- 新增 discovery-service 服务层,封装多级去重和标签处理逻辑
- 新增 discover-projects 命令,支持批量探索项目
- 新增 PROJECT_CONTENT_STANDARD.md 内容标准模板

代码质量修复:
- 修复状态转换验证未使用问题,防止非法状态转换
- 修复 explorationData 类型安全问题,使用 z.record(z.unknown()) 替代 z.any()
- 添加事务保护到 complete API,确保项目创建和任务状态更新的原子性

代码重构:
- 将 webhook/projects 的去重逻辑迁移到 discovery-service
- 统一探索任务相关的验证模式到 validations.ts

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-17 15:44:33 +08:00

134 lines
3.5 KiB
TypeScript

import { prisma } from '@/lib/prisma'
import type { ProjectInput } from '@/lib/validations'
import { generateSlug } from '@/lib/slug'
/**
* 多级去重策略:查找已存在的项目
*
* 优先级:
* 1. GitHub URL 完全匹配(最准确)
* 2. Website URL 完全匹配
* 3. slug 匹配(兜底)
*
* @param projectData - 项目数据
* @returns 已存在的项目,如果不存在则返回 null
*/
export 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: {
include: {
links: true,
},
},
},
})
if (existingByGithub) {
console.warn(
`[Discovery] 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: {
include: {
links: true,
},
},
},
})
if (existingByWebsite) {
console.warn(
`[Discovery] Found existing project by Website URL: ${websiteLink.url}`
)
return existingByWebsite.project
}
}
// 优先级3: 通过 slug 匹配(兜底)
const slug = generateSlug(projectData.name, projectData.nameEn)
const existingBySlug = await prisma.project.findUnique({
where: { slug },
include: {
links: true,
},
})
if (existingBySlug) {
console.warn(`[Discovery] Found existing project by slug: ${slug}`)
return existingBySlug
}
console.warn(`[Discovery] No existing project found, will create new one`)
return null
}
/**
* 批量创建或获取标签
*
* @param tags - 标签数组
* @returns 标签连接对象数组
*/
export async function upsertTags(tags: ProjectInput['tags']) {
// 优化:批量查询所有已存在的标签,避免 N+1 问题
const allTagNames = tags.map((t) => t.name)
const existingTags = await prisma.tag.findMany({
where: { name: { in: allTagNames } },
})
const existingTagNames = new Set(existingTags.map((t) => t.name))
// Upsert tags - 优化后只查询不存在的标签
return await Promise.all(
tags.map(async (tag) => {
const tagSlug = generateSlug(tag.name, tag.nameEn)
// 首先从批量查询结果中查找
if (existingTagNames.has(tag.name)) {
return existingTags.find((t) => t.name === tag.name)!
}
// 只有标签不存在时才尝试 upsert
try {
return await prisma.tag.upsert({
where: { slug: tagSlug },
update: {},
create: {
name: tag.name,
nameEn: tag.nameEn || null,
slug: tagSlug,
},
})
} catch (error) {
// 如果 slug 冲突,查找并使用已存在的标签
const existingBySlug = await prisma.tag.findUnique({
where: { slug: tagSlug },
})
if (existingBySlug) {
return existingBySlug
}
throw error
}
})
)
}