52 lines
2.2 KiB
TypeScript
52 lines
2.2 KiB
TypeScript
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}`,
|
|
},
|
|
});
|
|
}
|