Files
dark-lord/app/api/raffle/buy/route.ts
2026-04-26 22:28:40 -07:00

92 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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<string, unknown>) ?? {};
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,
});
}