TypeScript 97.5%
SQL 1.4%
Python 0.8%
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