diff --git a/src/app/api/cron/round-scheduler/route.ts b/src/app/api/cron/round-scheduler/route.ts new file mode 100644 index 0000000..256f583 --- /dev/null +++ b/src/app/api/cron/round-scheduler/route.ts @@ -0,0 +1,82 @@ +import { NextResponse } from 'next/server' +import type { NextRequest } from 'next/server' +import { prisma } from '@/lib/prisma' +import { activateRound, closeRound } from '@/server/services/round-engine' + +export async function GET(request: NextRequest): Promise { + const cronSecret = request.headers.get('x-cron-secret') + + if (!cronSecret || cronSecret !== process.env.CRON_SECRET) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + try { + const now = new Date() + const results: Array<{ roundId: string; action: string; success: boolean; errors?: string[] }> = [] + + // Find a SUPER_ADMIN to use as the actor for audit logging + const systemActor = await prisma.user.findFirst({ + where: { role: 'SUPER_ADMIN' }, + select: { id: true }, + }) + + if (!systemActor) { + return NextResponse.json({ + ok: false, + error: 'No SUPER_ADMIN user found for system actions', + }, { status: 500 }) + } + + // 1. Activate DRAFT rounds whose windowOpenAt has arrived + const roundsToOpen = await prisma.round.findMany({ + where: { + status: 'ROUND_DRAFT', + windowOpenAt: { lte: now }, + competition: { status: { not: 'ARCHIVED' } }, + }, + select: { id: true, name: true }, + }) + + for (const round of roundsToOpen) { + const result = await activateRound(round.id, systemActor.id, prisma) + results.push({ + roundId: round.id, + action: `activate: ${round.name}`, + success: result.success, + errors: result.errors, + }) + } + + // 2. Close ACTIVE rounds whose windowCloseAt has passed + const roundsToClose = await prisma.round.findMany({ + where: { + status: 'ROUND_ACTIVE', + windowCloseAt: { lte: now }, + }, + select: { id: true, name: true }, + }) + + for (const round of roundsToClose) { + const result = await closeRound(round.id, systemActor.id, prisma) + results.push({ + roundId: round.id, + action: `close: ${round.name}`, + success: result.success, + errors: result.errors, + }) + } + + return NextResponse.json({ + ok: true, + processed: results.length, + results, + timestamp: now.toISOString(), + }) + } catch (error) { + console.error('Cron round-scheduler failed:', error) + return NextResponse.json( + { error: 'Internal server error' }, + { status: 500 } + ) + } +}