Add Democratic fundraising platform.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
109
src/app/api/spotlight/route.ts
Normal file
109
src/app/api/spotlight/route.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
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 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user