import { NextResponse } from "next/server"; import { z } from "zod"; import { auth } from "@/auth"; import { isAdminRole } from "@/lib/admin"; import { creditDisplayName } from "@/lib/credits-brand"; import { prisma } from "@/lib/prisma"; import { debitWalletCredits, INSUFFICIENT_CREDITS } from "@/lib/wallet-safety"; const ISSUE_CATALOG = [ { slug: "voting-rights", title: "Voting Rights & Access" }, { slug: "healthcare", title: "Affordable Healthcare" }, { slug: "climate", title: "Climate & Clean Energy Jobs" }, { slug: "education", title: "Public Education Funding" }, { slug: "gun-safety", title: "Common-Sense Gun Safety" }, { slug: "workers-rights", title: "Workers Rights & Wages" }, { slug: "housing", title: "Affordable Housing" }, { slug: "democracy-reform", title: "Campaign Finance Reform" }, ] as const; function currentWeekOf(): string { const d = new Date(); const day = d.getUTCDay(); const diff = d.getUTCDate() - day + (day === 0 ? -6 : 1); const mon = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), diff)); return mon.toISOString().split("T")[0]; } const postSchema = z.object({ issueSlug: z.string().min(2).max(80), creditsSpent: z.number().int().min(5).max(10000), }); export async function GET() { const weekOf = currentWeekOf(); const bids = await prisma.spotlightBid.groupBy({ by: ["issueSlug", "issueTitle"], where: { weekOf }, _sum: { creditsSpent: true }, _count: true, orderBy: { _sum: { creditsSpent: "desc" } }, }); const totals = bids.map((b) => ({ issueSlug: b.issueSlug, issueTitle: b.issueTitle, total: b._sum.creditsSpent ?? 0, backers: b._count, })); const leader = totals[0] ?? null; return NextResponse.json({ weekOf, totals, leader, catalog: ISSUE_CATALOG }); } export async function POST(req: Request) { const session = await auth(); if (!session?.user?.id) { return NextResponse.json({ error: "Sign in to bid on a Spotlight." }, { status: 401 }); } const admin = isAdminRole(session.user.role); const creditName = creditDisplayName(); try { const json = await req.json(); const { issueSlug, creditsSpent } = postSchema.parse(json); const issue = ISSUE_CATALOG.find((i) => i.slug === issueSlug); if (!issue) return NextResponse.json({ error: "Unknown issue." }, { status: 400 }); const weekOf = currentWeekOf(); const spent = admin ? 0 : creditsSpent; await prisma.$transaction(async (tx) => { const bid = await tx.spotlightBid.create({ data: { userId: session.user.id, issueSlug: issue.slug, issueTitle: issue.title, creditsSpent: spent, weekOf, }, }); if (!admin) { await debitWalletCredits(tx, session.user.id, creditsSpent); await tx.ledgerEntry.create({ data: { userId: session.user.id, delta: -creditsSpent, type: "DEBIT_SPOTLIGHT", spotlightBidId: bid.id, memo: `Spotlight bid: ${issue.title}`, }, }); } }); return NextResponse.json({ ok: true, issue, weekOf, spent }); } catch (e) { if (e instanceof z.ZodError) return NextResponse.json({ error: "Invalid input", issues: e.issues }, { status: 400 }); if (e instanceof Error && e.message === INSUFFICIENT_CREDITS) { return NextResponse.json({ error: `Not enough ${creditName}.` }, { status: 402 }); } console.error(e); return NextResponse.json({ error: "Could not place bid." }, { status: 500 }); } }