import { NextRequest, NextResponse } from 'next/server'; import { listDueTasks, deleteTask } from '@/lib/task-store'; import { sanitizeHeaders } from '@/lib/ssrf-guard'; const CRON_SECRET = process.env.CRON_SECRET; export async function GET(request: NextRequest) { // Protect cron endpoint: require Authorization header with CRON_SECRET const auth = request.headers.get('authorization') || ''; const expected = CRON_SECRET ? `Bearer ${CRON_SECRET}` : ''; if (!CRON_SECRET || auth !== expected) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const nowSec = Math.floor(Date.now() / 1000); let tasks: Awaited>; try { tasks = await listDueTasks(nowSec); } catch (e: any) { return NextResponse.json({ error: e?.message || 'Failed to fetch tasks' }, { status: 500 }); } const results: Array<{ id: string; ok: boolean; error?: string }> = []; for (const task of tasks) { let ok = false; let lastError: string | undefined; for (let attempt = 0; attempt <= task.retries; attempt++) { if (attempt > 0) { await sleep(task.retryInterval); } try { const bodyBytes = encodePayload(task.payload); const res = await fetch(task.url, { method: 'POST', headers: { ...sanitizeHeaders(task.headers), 'content-type': guessContentType(task.payload), }, body: bodyBytes, signal: AbortSignal.timeout(15000), }); if (res.ok) { ok = true; break; } lastError = `HTTP ${res.status}`; } catch (e: any) { lastError = e?.message || 'Network error'; } } if (ok) { try { await deleteTask(task.id); } catch { // best-effort cleanup } } results.push({ id: task.id, ok, error: ok ? undefined : lastError }); } return NextResponse.json({ executed: results.length, results }); } function encodePayload(payload: any): string { if (payload === null || payload === undefined) return ''; if (typeof payload === 'string') return payload; return JSON.stringify(payload); } function guessContentType(payload: any): string { if (typeof payload === 'string') return 'text/plain'; return 'application/json'; } function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); }