import { asc, eq } from "drizzle-orm"; import { db } from "@/db"; import { bookmarks, files, toBookmarkDTO } from "@/db/schema"; import { encodeRFC5987 } from "@/utils/pdf-export"; // ส่งออกรายการ bookmark เป็น JSON (N7 — เดิมเป็นรายงาน PDF ตาม requirement 5.3.15 // เปลี่ยนตามคำขอผู้ใช้ 2026-09-17) // - ข้อมูลล้วนจาก DB ไม่โหลดไฟล์เอกสารต้นฉบับเลย (invariant #1) // - array flat เก็บ parentId ครบ (lossless) เรียงตามลำดับ N5 เหมือน sidebar // ต้นไม้ reconstruct จาก parentId ได้เอง จึงไม่ต้อง build tree/cycle guard // - read-only: ไม่ publish event ไม่ revalidatePath (ไม่เขียน DB) const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; export async function GET(req: Request) { const fileId = new URL(req.url).searchParams.get("fileId") ?? ""; if (!UUID_RE.test(fileId)) { return new Response("fileId ไม่ถูกต้อง", { status: 400 }); } const [file] = await db.select().from(files).where(eq(files.id, fileId)); if (!file) return new Response("ไม่พบไฟล์นี้", { status: 404 }); const rows = await db .select() .from(bookmarks) .where(eq(bookmarks.fileId, fileId)) .orderBy( asc(bookmarks.pageIndex), asc(bookmarks.yOffset), asc(bookmarks.createdAt), ); // item เป็น BookmarkDTO ตรง ๆ (camelCase + ISO string — invariant #5) const payload = { fileId: file.id, title: file.title, exportedAt: new Date().toISOString(), bookmarks: rows.map(toBookmarkDTO), }; const filename = encodeRFC5987(`${file.title}-bookmarks.json`); return new Response(JSON.stringify(payload, null, 2) + "\n", { headers: { "content-type": "application/json; charset=utf-8", // filename เป็น ASCII fallback + filename* UTF-8 สำหรับชื่อไทย (pattern F8/F16) "content-disposition": `attachment; filename="bookmarks.json"; filename*=UTF-8''${filename}`, }, }); }