randomayzer/src/app/api/giveaways/[id]/draw/route.ts

70 lines
2.5 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server';
import { GiveawayStore } from '@/lib/giveaway-store';
import { executeDeterministicDrawV1 } from '@/core/randomizer/deterministic';
import { generateCryptoSecureSeed } from '@/core/randomizer/hasher';
import { GiveawayFSM } from '@/core/fsm/giveaway-fsm';
export async function POST(
req: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const { id } = params;
const body = await req.json().catch(() => ({}));
const giveaway = await GiveawayStore.getById(id);
if (!giveaway) {
return NextResponse.json({ error: 'Giveaway not found' }, { status: 404 });
}
// 1. Guard check with FSM
if (giveaway.status === 'DRAWN') {
return NextResponse.json({
error: 'Розыгрыш уже проведен. Повторный запуск строго запрещен.',
}, { status: 400 });
}
// 2. Fetch locked snapshot (or lock current eligible if ready)
let snapshot = await GiveawayStore.getLatestSnapshot(id);
if (!snapshot) {
const eligible = giveaway.participants.filter(p => p.eligible);
if (eligible.length === 0) {
return NextResponse.json({
error: 'Нет допущенных участников для создания слепка и розыгрыша'
}, { status: 400 });
}
snapshot = await GiveawayStore.createAndLockSnapshot(id, eligible, giveaway.filterRules);
}
// Validate status after snapshot lock
GiveawayFSM.assertCanDraw('SNAPSHOT_LOCKED');
const winnersCount = body.winnersCount || giveaway.winnersCount || 1;
const reserveWinnersCount = body.reserveWinnersCount ?? giveaway.reserveWinnersCount ?? 0;
// Seed must be generated with CSPRNG if not provided
const seed = body.seed?.trim() || giveaway.seed || generateCryptoSecureSeed();
// 3. Execute Provably Fair Randomizer V1
const drawResult = executeDeterministicDrawV1({
giveawayId: id,
snapshot,
totalLoadedCount: giveaway.participants.length,
winnersCount,
reserveWinnersCount,
seed,
filterRules: giveaway.filterRules,
});
// 4. Persist DrawResult and AuditRecord atomically
const updatedGiveaway = await GiveawayStore.saveDrawResult(id, snapshot.id, drawResult);
return NextResponse.json({
success: true,
drawResult,
giveaway: updatedGiveaway,
});
} catch (error: any) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}