haixunMaster/app/api/matrix/export/route.ts

80 lines
2.2 KiB
TypeScript

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");
}
}