/** * POST /api/raffle/buy { handle, quantity } * Deducts VOID credits (1 VOID = 1 ticket ≈ $1) and registers raffle entries. * * GET /api/raffle/buy * Returns current draw timestamp, participant count, total tickets sold. */ import { NextResponse } from "next/server"; import fs from "fs"; import path from "path"; import os from "os"; import { deductVoid, getVoidBalance } from "@/lib/serverLedger"; const TICKET_COST_VOID = 1; const MAX_PER_BUY = 100; export type RaffleEntry = { handle: string; tickets: number; boughtAt: number }; export type RaffleData = { v: 1; entries: RaffleEntry[]; drawAt: number }; const VAR_DIR = path.join(process.cwd(), "var"); export const RAFFLE_PATH = path.join(VAR_DIR, "raffle.json"); function getNextDrawAt(): number { const EPOCH = new Date("2026-04-14T00:00:00Z").getTime(); const INTERVAL = 7 * 24 * 60 * 60 * 1000; const now = Date.now(); return EPOCH + (Math.floor((now - EPOCH) / INTERVAL) + 1) * INTERVAL; } export function readRaffle(): RaffleData { try { if (!fs.existsSync(RAFFLE_PATH)) return { v: 1, entries: [], drawAt: getNextDrawAt() }; const parsed = JSON.parse(fs.readFileSync(RAFFLE_PATH, "utf8")) as RaffleData; if (parsed?.v !== 1) return { v: 1, entries: [], drawAt: getNextDrawAt() }; if (parsed.drawAt < Date.now()) parsed.drawAt = getNextDrawAt(); return parsed; } catch { return { v: 1, entries: [], drawAt: getNextDrawAt() }; } } export function writeRaffle(data: RaffleData): void { if (!fs.existsSync(VAR_DIR)) fs.mkdirSync(VAR_DIR, { recursive: true }); const tmp = path.join(os.tmpdir(), `cyberlux-raffle-${Date.now()}.json`); fs.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf8"); fs.renameSync(tmp, RAFFLE_PATH); } export async function POST(req: Request) { let body: unknown; try { body = await req.json(); } catch { return NextResponse.json({ ok: false, error: "Invalid JSON" }, { status: 400 }); } const { handle, quantity } = (body as Record) ?? {}; if (typeof handle !== "string" || !handle.trim()) return NextResponse.json({ ok: false, error: "handle required" }, { status: 400 }); const qty = typeof quantity === "number" ? Math.floor(quantity) : 1; if (qty < 1 || qty > MAX_PER_BUY) return NextResponse.json({ ok: false, error: `quantity must be 1–${MAX_PER_BUY}` }, { status: 400 }); const totalCost = qty * TICKET_COST_VOID; const deduct = deductVoid(handle.trim(), totalCost, "raffle tickets"); if (!deduct.ok) return NextResponse.json({ ok: false, error: deduct.error }, { status: 402 }); const raffle = readRaffle(); const hk = handle.trim().toLowerCase(); const existing = raffle.entries.find((e) => e.handle === hk); if (existing) { existing.tickets += qty; existing.boughtAt = Date.now(); } else raffle.entries.push({ handle: hk, tickets: qty, boughtAt: Date.now() }); writeRaffle(raffle); return NextResponse.json({ ok: true, ticketsBought: qty, totalTickets: raffle.entries.find((e) => e.handle === hk)?.tickets ?? qty, voidBalance: deduct.newBalance, drawAt: raffle.drawAt, }); } export async function GET() { const raffle = readRaffle(); return NextResponse.json({ ok: true, drawAt: raffle.drawAt, totalTickets: raffle.entries.reduce((s, e) => s + e.tickets, 0), participants: raffle.entries.length, }); }