Scrum4Me/app/api/cron/expire-questions/route.ts
Madhura68 a408ff37c3 fix(ST-1107): cron schedule daily — Vercel Hobby allows only 1 run/day
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>
2026-04-29 11:33:37 +02:00

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(),
})
}