返回 CodeWhale
route.ts
根目录 / web / app / api / cron / route.ts
1 import { NextResponse } from "next/server";
2 import { getEnv } from "@/lib/kv";
3 import {
4 runCurate,
5 runTriage,
6 runPrReview,
7 runStale,
8 runDupes,
9 runDigest,
10 type AgentEnv,
11 } from "@/lib/community-agent-tasks";
12 import { runFactsDrift } from "@/lib/facts-drift";
13 import { runLinkCheck, runSemanticDrift } from "@/lib/content-watch";
14 import { safeEqual } from "@/lib/community-agent";
15
16 export const dynamic = "force-dynamic";
17
18 const TASKS = ["curate", "triage", "pr-review", "stale", "dupes", "digest", "facts-drift", "linkcheck", "semantic-drift"] as const;
19 type Task = (typeof TASKS)[number];
20
21 /**
22 * Manual trigger surface for community-agent tasks.
23 *
24 * Usage:
25 * GET /api/cron?task=curate
26 * Header: x-cron-secret: <CRON_SECRET>
27 *
28 * Real cron scheduling is handled by worker.ts's scheduled() handler.
29 */
30 export async function GET(req: Request) {
31 const env = await getEnv();
32
33 // Always require auth
34 if (!env.CRON_SECRET) {
35 return NextResponse.json(
36 { error: "manual trigger disabled in production" },
37 { status: 503 }
38 );
39 }
40
41 const auth = req.headers.get("x-cron-secret") ?? "";
42 if (!(await safeEqual(auth, env.CRON_SECRET))) {
43 return NextResponse.json({ error: "unauthorized" }, { status: 401 });
44 }
45
46 const { searchParams } = new URL(req.url);
47 const task = searchParams.get("task");
48
49 if (!task || !TASKS.includes(task as Task)) {
50 return NextResponse.json(
51 { error: `missing or invalid task. Allowed: ${TASKS.join(", ")}` },
52 { status: 400 }
53 );
54 }
55
56 // Build AgentEnv from the same shape expected by the task functions
57 const agentEnv: AgentEnv = {
58 CURATED_KV: env.CURATED_KV,
59 DEEPSEEK_API_KEY: env.DEEPSEEK_API_KEY,
60 DEEPSEEK_BASE_URL: env.DEEPSEEK_BASE_URL ?? process.env.DEEPSEEK_BASE_URL,
61 DEEPSEEK_MODEL: env.DEEPSEEK_MODEL ?? process.env.DEEPSEEK_MODEL,
62 GITHUB_TOKEN: env.GITHUB_TOKEN,
63 CRON_SECRET: env.CRON_SECRET,
64 GITHUB_REPO: env.GITHUB_REPO,
65 MAINTAINER_TOKEN: undefined,
66 MAINTAINER_GITHUB_PAT: undefined,
67 };
68
69 try {
70 let result: Record<string, unknown>;
71 switch (task) {
72 case "curate":
73 result = await runCurate(agentEnv);
74 break;
75 case "triage":
76 result = await runTriage(agentEnv);
77 break;
78 case "pr-review":
79 result = await runPrReview(agentEnv);
80 break;
81 case "stale":
82 result = await runStale(agentEnv);
83 break;
84 case "dupes":
85 result = await runDupes(agentEnv);
86 break;
87 case "digest":
88 result = await runDigest(agentEnv);
89 break;
90 case "facts-drift":
91 result = await runFactsDrift(agentEnv) as unknown as Record<string, unknown>;
92 break;
93 case "linkcheck":
94 result = await runLinkCheck(agentEnv) as unknown as Record<string, unknown>;
95 break;
96 case "semantic-drift":
97 result = await runSemanticDrift(agentEnv) as unknown as Record<string, unknown>;
98 break;
99 default:
100 // unreachable — guarded by TASKS check above
101 result = { error: "unknown task" };
102 }
103 return NextResponse.json({ ok: true, task, result });
104 } catch (e) {
105 return NextResponse.json({ ok: false, error: String(e) }, { status: 200 });
106 }
107 }
108
108 lines TYPESCRIPT