Vercel deploy faalde met: > Hobby accounts are limited to daily cron jobs. > This cron expression (0 */6 * * *) would run more than once per day. Schedule van 4×/dag (0 */6 * * *) naar 1×/dag (0 4 * * * — 04:00 UTC, rustig tijdstip). Functioneel acceptabel: ClaudeQuestion TTL is 24u, dus daily cleanup pakt alles dat in de afgelopen 24u verlopen is. Login-pairings TTL is 2 min — die zijn al onbruikbaar zodra ze expiren, cron is alleen voor status-housekeeping. Schedule-referenties consistent bijgewerkt in docs (API.md, architecture, backlog M11-sectie, plan-doc, pattern-doc) + comment in route.ts. Vermelding overal dat dit een Hobby-plan-beperking is en Pro fijnmaziger ondersteunt. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
46 lines
1.7 KiB
TypeScript
46 lines
1.7 KiB
TypeScript
// ST-1107: Vercel cron handler die verlopen Claude-vragen op 'expired' zet.
|
|
//
|
|
// Wordt dagelijks om 04:00 UTC door Vercel POST'd (zie vercel.json crons-
|
|
// config; Vercel Hobby-plan staat alleen daily crons toe — Pro ondersteunt
|
|
// fijnmaziger). Auth is
|
|
// via een gedeeld secret in de Authorization-header — Vercel injecteert
|
|
// `Authorization: Bearer <CRON_SECRET>` automatisch wanneer de env-var op de
|
|
// project-omgeving staat.
|
|
//
|
|
// Bonus (ST-1107.4): zelfde route ruimt ook M10's verlopen `pending`
|
|
// login_pairings op. Reden: één cron-job is goedkoper qua Vercel-budget en
|
|
// houdt de cleanup-strategie centraal.
|
|
|
|
import { prisma } from '@/lib/prisma'
|
|
|
|
export const runtime = 'nodejs'
|
|
|
|
export async function POST(request: Request) {
|
|
const auth = request.headers.get('authorization')
|
|
const expected = process.env.CRON_SECRET
|
|
if (!expected || auth !== `Bearer ${expected}`) {
|
|
return Response.json({ error: 'Unauthorized' }, { status: 401 })
|
|
}
|
|
|
|
const now = new Date()
|
|
|
|
// M11: open Claude-vragen → expired
|
|
const expiredQuestions = await prisma.claudeQuestion.updateMany({
|
|
where: { status: 'open', expires_at: { lt: now } },
|
|
data: { status: 'expired' },
|
|
})
|
|
|
|
// M10: pending login_pairings die niet meer bruikbaar zijn → cancelled
|
|
// (status='expired' bestaat niet voor pairings; cancelled heeft hetzelfde
|
|
// resultaat: niet-claimable, niet meer in de SSE-listener.)
|
|
const expiredPairings = await prisma.loginPairing.updateMany({
|
|
where: { status: 'pending', expires_at: { lt: now } },
|
|
data: { status: 'cancelled' },
|
|
})
|
|
|
|
return Response.json({
|
|
expired_questions: expiredQuestions.count,
|
|
expired_pairings: expiredPairings.count,
|
|
ran_at: now.toISOString(),
|
|
})
|
|
}
|