import { NextResponse, type NextRequest } from "next/server"; import { getUser, isAdmin } from "@/lib/session"; import { getEconomics, parseRange } from "@/lib/queries/admin"; import { writeAudit } from "@/lib/audit"; export const dynamic = "force-dynamic"; function csvCell(v: unknown): string { const s = v === null || v === undefined ? "" : String(v); return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s; } /** CSV export of unit economics (admin only). Returns 403 for non-admins rather than redirecting. */ export async function GET(req: NextRequest) { const user = await getUser(); if (!user || !isAdmin(user)) return NextResponse.json({ error: { code: "FORBIDDEN", message: "Admin only." } }, { status: 403 }); const range = parseRange(req.nextUrl.searchParams.get("range") ?? undefined); const e = await getEconomics(range); const lines: string[] = ["section,key,label,requests,successes,revenue_usd,upstream_cost_usd,margin_usd,margin_pct,bytes"]; const push = (section: string, key: string, label: string, r: { requests: number; successes: number; revenue: number; cost: number; margin: number; marginPct: number | null; bytes: number }) => 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(",")); for (const r of e.byBucket) push(e.unit, r.key, r.key, r); for (const r of e.byPlan) push("plan", r.key, r.key, r); for (const r of e.byNetwork) push("network", r.key, r.key, r); for (const r of e.topCustomers) push("customer", r.key, r.name, r); for (const p of e.byProvider) { const margin = p.attributedRevenue - p.cost; 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 }); } await writeAudit({ userId: user.id, action: "admin.action", target: "usage.export", metadata: { type: "usage.export", range } }); const stamp = new Date().toISOString().slice(0, 10); return new NextResponse(lines.join("\n") + "\n", { headers: { "content-type": "text/csv; charset=utf-8", "content-disposition": `attachment; filename="fetcha-unit-economics-${range}-${stamp}.csv"`, "cache-control": "no-store", }, }); }