All checks were successful
Deploy eCourt Note Web to Server / Validate Database Schema (Drizzle) (push) Successful in 3s
Deploy eCourt Note Web to Server / Build Web Docker Image (push) Successful in 52s
Deploy eCourt Note Web to Server / Deploy eCourt Note Web to Server (push) Successful in 13s
Deploy eCourt Note Web to Server / Notify Deployment Status (push) Successful in 1s
เตรียม basePath /docedit เป็นพื้นที่กลางให้แอปพี่น้อง (wordaddin ใน อนาคต) — เดิม basePath ครอบ /docedit/note ทั้งหมดจนแชร์ prefix ไม่ได้ (Next มี basePath ได้ทางเดียว) และจัด API แยกต้นไม้ตามที่ผู้ใช้ขอ (1) โครงสร้างใหม่: basePath = /docedit + route หน้าทั้งหมดย้ายเข้า app/note/** + API 4 เส้นย้ายเข้า app/api/note/** + statics ย้ายเข้า public/note/** (samples, uploads, pdf.worker) — หน้า/statics URL ปลายทาง คงเดิมเป๊ะ (/docedit/note/…) ส่วน URL API เปลี่ยนเป็น /docedit/api/note/… ตามคำขอผู้ใช้ (N10 endpoint เปลี่ยน — ต้องแจ้ง backend e-Court ก่อนใช้) (2) กลไก: withBasePath (utils/base-path.ts) เติม base + app path /note ยกเว้น path ที่ขึ้นต้น /api/ เติมแค่ base (caller เขียน /api/note/… เต็ม); resolvePublicPdfPath root ที่ public/note (traversal guard เดิม); middleware matcher ยกเว้นใหม่ (note/login, api/note, note/uploads, note/samples, note/pdf.worker, _next, fonts, favicon) + redirect /note/login; next.config เพิ่ม redirects /docedit -> /docedit/note; navigation ภายใน 19 จุด + revalidatePath 3 จุด ใส่ prefix /note; pdf_url ใน DB เป็น logical path ไม่แก้แม้แต่ตัวเดียว (seed/upload เขียนค่าเดิม, browser เติมผ่าน withBasePath, disk เติมผ่าน guard) (3) ตามแก้: patch-pdf-worker (DEST public/note + mkdirSync ปลายทาง), seed-samples (SAMPLES_DIR), uploadFile (UPLOAD_DIR), Dockerfile/ .dockerignore/.gitignore/docker-compose.test.yml, tests 2 ไฟล์ที่อ้าง disk path จริง, sync เอกสารทั้งหมด (CLAUDE.md invariant #6/#12 + tree, README, docs/API-DOCUMENTS.md, .claude/rules/*, sync-db skill) (4) !! ตอน deploy ต้องย้าย volume ตาม: mount /app/public/uploads -> /app/public/note/uploads + ย้ายไฟล์ใน volume ให้ตรงตำแหน่ง ไม่งั้นไฟล์ ที่เคยอัปโหลด (พร้อม annotation ที่ผูกอยู่) 404 ทันทีที่สลับ image (5) verify: build ผ่าน (route table /api/note/** + /note/** ครบ), tsc, vitest 215/215, curl ครบทุก endpoint (login/redirect chain, worker, samples, uploads, SSE, api/pdf, export, N8 register, N10 bundle + deep link), Playwright browser จริง (canvas + textLayer ไทย render, browser ยิง URL ใหม่ทั้งหมดไม่มี request ไป URL เก่า), plan-reviewer + code-reviewer 3 รายงาน 0 blocking Co-Authored-By: Claude Code <noreply@anthropic.com>
74 lines
2.3 KiB
TypeScript
74 lines
2.3 KiB
TypeScript
import { asc, eq, inArray } from "drizzle-orm";
|
|
import { notFound, redirect } from "next/navigation";
|
|
|
|
import { db } from "@/db";
|
|
import {
|
|
annotationReplies,
|
|
annotations,
|
|
bookmarks,
|
|
files,
|
|
toAnnotationDTO,
|
|
toBookmarkDTO,
|
|
toReplyDTO,
|
|
} from "@/db/schema";
|
|
import PDFViewer from "@/components/pdf/PDFViewer";
|
|
import DebugConsole from "@/components/DebugConsole"; // TEMP-DEBUG (ลบพร้อมไฟล์)
|
|
import { getSessionUser } from "@/lib/session";
|
|
|
|
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 const dynamic = "force-dynamic";
|
|
|
|
export default async function AnnotatePage({
|
|
params,
|
|
}: {
|
|
params: { id: string };
|
|
}) {
|
|
const { id } = params;
|
|
if (!UUID_RE.test(id)) notFound();
|
|
|
|
// defense ซ้อน middleware — identity จาก cookie oacCase ส่งเข้า viewer ตั้งแต่ first paint
|
|
const user = getSessionUser();
|
|
if (!user) redirect("/note/login");
|
|
|
|
const [file] = await db.select().from(files).where(eq(files.id, id));
|
|
if (!file) notFound();
|
|
|
|
const [annotationRows, bookmarkRows] = await Promise.all([
|
|
db
|
|
.select()
|
|
.from(annotations)
|
|
.where(eq(annotations.fileId, id))
|
|
.orderBy(asc(annotations.createdAt)),
|
|
db
|
|
.select()
|
|
.from(bookmarks)
|
|
.where(eq(bookmarks.fileId, id))
|
|
.orderBy(asc(bookmarks.createdAt)),
|
|
]);
|
|
|
|
// replies ของทุก comment ในไฟล์นี้ (F9) — แม่ถูก CASCADE ไปแล้ว row ก็หายตาม
|
|
const annotationIds = annotationRows.map((r) => r.id);
|
|
const replyRows = annotationIds.length
|
|
? await db
|
|
.select()
|
|
.from(annotationReplies)
|
|
.where(inArray(annotationReplies.annotationId, annotationIds))
|
|
.orderBy(asc(annotationReplies.createdAt))
|
|
: [];
|
|
|
|
return (
|
|
<>
|
|
{/* TEMP-DEBUG: อ่าน log บนจอ iPad ผ่าน Eruda เมื่อเปิดหน้าพร้อม ?debug */}
|
|
<DebugConsole />
|
|
<PDFViewer
|
|
file={{ id: file.id, title: file.title, pdfUrl: file.pdfUrl }}
|
|
initialAnnotations={annotationRows.map(toAnnotationDTO)}
|
|
initialBookmarks={bookmarkRows.map(toBookmarkDTO)}
|
|
initialReplies={replyRows.map(toReplyDTO)}
|
|
user={user}
|
|
/>
|
|
</>
|
|
);
|
|
}
|