import { NextResponse } from "next/server"; import { prisma } from "@/lib/db"; import { getActiveAccountId } from "@/lib/account-context"; import { apiRouteErrorResponse } from "@/lib/auth/api"; import { requireUserAccountScope } from "@/lib/auth/user-scope"; function escapeCsv(value: string): string { if (value.includes(",") || value.includes('"') || value.includes("\n")) { return `"${value.replace(/"/g, '""')}"`; } return value; } export async function GET(request: Request) { try { const { searchParams } = new URL(request.url); const topicId = searchParams.get("topicId"); const accountId = await getActiveAccountId(); const { where: accountWhere } = await requireUserAccountScope(accountId); const drafts = await prisma.draft.findMany({ where: { sortOrder: { not: null }, ...accountWhere, ...(topicId ? { topicId } : {}), }, orderBy: [{ sortOrder: "asc" }, { createdAt: "desc" }], }); const headers = [ "序號", "主題標籤", "切角", "Hook", "貼文內文", "參考重點", "配圖說明", "參考來源", "撰寫理由", "狀態", "建立時間", ]; const rows = drafts.map((draft) => { const sources: string[] = []; if (draft.sources) { try { sources.push(...(JSON.parse(draft.sources) as string[])); } catch { sources.push(draft.sources); } } return [ String(draft.sortOrder ?? ""), draft.searchTag ?? "", draft.angle ?? "", draft.hook ?? "", draft.text, draft.referenceNotes ?? "", draft.imageBrief ?? "", sources.join(" | "), draft.rationale ?? "", draft.status, new Date(draft.createdAt).toLocaleString("zh-TW"), ].map(escapeCsv); }); const csv = [headers.join(","), ...rows.map((r) => r.join(","))].join("\n"); const bom = "\uFEFF"; return new NextResponse(bom + csv, { headers: { "Content-Type": "text/csv; charset=utf-8", "Content-Disposition": `attachment; filename="content-matrix-${Date.now()}.csv"`, }, }); } catch (error) { return apiRouteErrorResponse(error, "matrix/export"); } }