From 6c65cb7cc072454e26f8d466167857f704bfe84d Mon Sep 17 00:00:00 2001 From: Caihaohan Date: Mon, 26 Jan 2026 15:14:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E9=87=8D=E7=BD=AE=E6=8E=A2=E7=B4=A2=E4=BB=BB=E5=8A=A1=20API=20?= =?UTF-8?q?=E7=AB=AF=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 POST /api/discovery/tasks/batch-reset 接口,用于 n8n 探索流程失败后批量重置任务状态。 功能: - 支持按任务 ID 列表重置 - 支持按状态筛选重置(默认 IN_PROGRESS 和 FAILED) - 重置时清除 startedAt、completedAt、errorMessage Co-Authored-By: Claude --- .../api/discovery/tasks/batch-reset/route.ts | 76 +++++++++++++++++++ src/lib/validations.ts | 15 ++++ 2 files changed, 91 insertions(+) create mode 100644 src/app/api/discovery/tasks/batch-reset/route.ts diff --git a/src/app/api/discovery/tasks/batch-reset/route.ts b/src/app/api/discovery/tasks/batch-reset/route.ts new file mode 100644 index 0000000..51c2ed4 --- /dev/null +++ b/src/app/api/discovery/tasks/batch-reset/route.ts @@ -0,0 +1,76 @@ +import { NextRequest, NextResponse } from 'next/server' +import crypto from 'crypto' +import { prisma } from '@/lib/prisma' +import { BatchResetTasksSchema } from '@/lib/validations' + +/** + * POST /api/discovery/tasks/batch-reset + * 批量重置任务状态为 PENDING + * + * 用于 n8n 探索流程失败后重置任务,支持两种模式: + * 1. 按任务 ID 列表重置:{ taskIds: ["id1", "id2"] } + * 2. 按状态筛选重置:{ statuses: ["IN_PROGRESS", "FAILED"] } + * 不传 statuses 则默认重置 IN_PROGRESS 和 FAILED + */ +export async function POST(request: NextRequest) { + try { + const body = await request.json() + const validation = BatchResetTasksSchema.safeParse(body) + + if (!validation.success) { + return NextResponse.json( + { + success: false, + error: 'Validation error', + details: validation.error.errors.map((e) => e.message), + }, + { status: 400 } + ) + } + + const { apiKey, taskIds, statuses } = validation.data + + // 验证API密钥 + const validApiKey = process.env.WEBHOOK_API_KEY + if ( + !validApiKey || + !crypto.timingSafeEqual(Buffer.from(apiKey), Buffer.from(validApiKey)) + ) { + return NextResponse.json( + { success: false, error: 'Unauthorized' }, + { status: 401 } + ) + } + + // 构建查询条件 + const where = taskIds + ? { id: { in: taskIds } } + : { status: { in: statuses || ['IN_PROGRESS', 'FAILED'] } } + + // 批量更新任务状态 + const result = await prisma.projectDiscoveryTask.updateMany({ + where, + data: { + status: 'PENDING', + startedAt: null, + completedAt: null, + errorMessage: null, + }, + }) + + console.warn( + `[Discovery] Batch reset ${result.count} tasks to PENDING. Condition: ${JSON.stringify(where)}` + ) + + return NextResponse.json({ + success: true, + reset: result.count, + }) + } catch (error) { + console.error('[Discovery] Error batch resetting tasks:', error) + return NextResponse.json( + { success: false, error: 'Internal server error' }, + { status: 500 } + ) + } +} diff --git a/src/lib/validations.ts b/src/lib/validations.ts index ab296db..855786e 100644 --- a/src/lib/validations.ts +++ b/src/lib/validations.ts @@ -91,6 +91,20 @@ export const GetDiscoveryTasksQuerySchema = z.object({ offset: z.coerce.number().int().nonnegative().default(0), }) +/** + * 批量重置任务 Schema + * 支持两种模式:按 ID 列表重置 或 按状态筛选重置 + */ +export const BatchResetTasksSchema = WebhookAuthSchema.extend({ + // 模式1: 指定任务 ID 列表 + taskIds: z.array(z.string()).optional(), + // 模式2: 按状态筛选(不传则默认重置 IN_PROGRESS 和 FAILED) + statuses: z.array(TaskStatusEnum).optional(), +}).refine( + (data) => data.taskIds || data.statuses, + { message: '必须提供 taskIds 或 statuses 之一' } +) + /** * 检查任务去重 Schema * 用于在创建任务前检查 URL 是否已存在 @@ -125,3 +139,4 @@ export type TaskStatus = z.infer export type CreateDiscoveryTask = z.infer export type UpdateDiscoveryTask = z.infer export type GetDiscoveryTasksQuery = z.infer +export type BatchResetTasks = z.infer