SPB Git forge

spb/fetcha

Public
11commits 1branches 0releases
1.5 MBsize
maindefault branch
16 days agolast push
TypeScript 97.5% SQL 1.4% Python 0.8%
2.4 KB · 40 lines typescript
Raw Blame History
1import { NextResponse, type NextRequest } from "next/server";2import { getUser, isAdmin } from "@/lib/session";3import { getEconomics, parseRange } from "@/lib/queries/admin";4import { writeAudit } from "@/lib/audit";56export const dynamic = "force-dynamic";78function csvCell(v: unknown): string {9  const s = v === null || v === undefined ? "" : String(v);10  return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;11}1213/** CSV export of unit economics (admin only). Returns 403 for non-admins rather than redirecting. */14export async function GET(req: NextRequest) {15  const user = await getUser();16  if (!user || !isAdmin(user)) return NextResponse.json({ error: { code: "FORBIDDEN", message: "Admin only." } }, { status: 403 });17  const range = parseRange(req.nextUrl.searchParams.get("range") ?? undefined);18  const e = await getEconomics(range);19  const lines: string[] = ["section,key,label,requests,successes,revenue_usd,upstream_cost_usd,margin_usd,margin_pct,bytes"];20  const push = (section: string, key: string, label: string, r: { requests: number; successes: number; revenue: number; cost: number; margin: number; marginPct: number | null; bytes: number }) =>21    lines.push([section, key, label, r.requests, r.successes, r.revenue.toFixed(6), r.cost.toFixed(6), r.margin.toFixed(6), r.marginPct === null ? "" : r.marginPct.toFixed(2), r.bytes].map(csvCell).join(","));22  for (const r of e.byBucket) push(e.unit, r.key, r.key, r);23  for (const r of e.byPlan) push("plan", r.key, r.key, r);24  for (const r of e.byNetwork) push("network", r.key, r.key, r);25  for (const r of e.topCustomers) push("customer", r.key, r.name, r);26  for (const p of e.byProvider) {27    const margin = p.attributedRevenue - p.cost;28    push("provider", p.provider, p.provider, { requests: p.attempts, successes: p.successes, revenue: p.attributedRevenue, cost: p.cost, margin, marginPct: p.attributedRevenue > 0 ? (margin / p.attributedRevenue) * 100 : null, bytes: p.bytes });29  }30  await writeAudit({ userId: user.id, action: "admin.action", target: "usage.export", metadata: { type: "usage.export", range } });31  const stamp = new Date().toISOString().slice(0, 10);32  return new NextResponse(lines.join("\n") + "\n", {33    headers: {34      "content-type": "text/csv; charset=utf-8",35      "content-disposition": `attachment; filename="fetcha-unit-economics-${range}-${stamp}.csv"`,36      "cache-control": "no-store",37    },38  });39}40