Stand-alone API group at /api/documents/{docId}/versions — one document
per docId (unique), each version stores .docx + optional .pdf as MinIO
objects with uploader userId/userName/createdAt (no auth, like
api/template; callers identify themselves per docs/PLAN-DOCVER.md).
- POST versions (multipart): validates fields/magic bytes/100MB cap,
uploads to MinIO first, cleans up objects if the DB insert fails;
returns {hasPdf, versionId, versionNumber} with server-authoritative
numbering (tx: upsert + SELECT FOR UPDATE, safe under parallel calls)
- GET versions: DocumentVersionDTO[] newest-first (404 for unknown docId)
- GET versions/{versionId}/download-link?format=docx|pdf: presigned URL
(docId+versionId matched together; pdf-missing = 404)
- tables documents/document_versions + migration drizzle/0005
- docker-compose.yml: judes-note-db + judes-note-minio (quay.io image,
host ports 19000/19001, POSTGRES_DB ecourt-note, pinned volume names)
- lib/openapi.ts: documents tag + paths for Swagger UI
- middleware: exempt api/documents from login redirect
- unit tests tests/lib/document-versioning.test.ts (23 cases)
wordaddin untouched — swap the mock later via NEXT_PUBLIC_ADDIN_API_BASE_URL.
Co-Authored-By: Claude Code <noreply@anthropic.com>
42 lines
3.2 KiB
TypeScript
42 lines
3.2 KiB
TypeScript
import { NextResponse, type NextRequest } from "next/server";
|
|
|
|
import { OAC_CASE_COOKIE, parseOacCase } from "@/utils/user";
|
|
|
|
// บังคับ login (mock e-Court) ทุกหน้า — ไม่มี cookie oacCase ที่ parse ได้ = redirect /login
|
|
// (parse จริง ไม่ใช่แค่มี cookie — cookie ขยะ/ครึ่ง ๆ ถือว่ายังไม่เข้าสู่ระบบ)
|
|
export function middleware(req: NextRequest) {
|
|
const user = parseOacCase(req.cookies.get(OAC_CASE_COOKIE)?.value);
|
|
if (!user) {
|
|
const url = req.nextUrl.clone();
|
|
// N8 D5: เก็บ path+query ไว้วนกลับหลัง login (deep link จาก e-Court ไม่หาย)
|
|
// — ไม่ใส่กรณี next เป็น "/" ไปเฉย ๆ ไม่ให้ /note/login มี query เกินจำเป็น
|
|
// (path ตรงนี้ตัด basePath แล้ว จึงขึ้นต้น /note/… เสมอ / NextURL ใส่ basePath คืนตอน
|
|
// redirect เอง — invariant #12)
|
|
const next = url.pathname + url.search;
|
|
url.pathname = "/note/login";
|
|
url.search = "";
|
|
if (next !== "/") url.searchParams.set("next", next);
|
|
return NextResponse.redirect(url);
|
|
}
|
|
return NextResponse.next();
|
|
}
|
|
|
|
// ยกเว้น: หน้า login เอง, API/SSE/export (อยู่ใต้ api/note/**), API template
|
|
// (api/template/** — เปิดกว้างให้ทางศาล/ผู้ทดสอบภายนอกยิงได้เลย ไม่แตะ DB
|
|
// ไม่มีข้อมูลรับ), API เอกสาร versioning (api/documents/** — กลุ่ม docver
|
|
// stand-alone ของ wordaddin: ไม่มี session ผู้เรียกบอก userId/userName เอง —
|
|
// docs/PLAN-DOCVER.md), หน้า Swagger UI (api/swagger — document ของทุกกลุ่ม),
|
|
// taskpane ของ wordaddin (wordaddin/** — รันใน webview ของ Word ไม่มี cookie
|
|
// oacCase และมี auth ของตัวเองแยกจากแอป note), ไฟล์อัปโหลด, Next internal,
|
|
// และ static ใน public/note/ (pdf.worker, samples, asset Swagger UI)
|
|
// + public/ ราก (fonts, favicon)
|
|
// — matcher ทำงานบน path หลังตัด basePath (/docedit): หน้า/ไฟล์ของแอปขึ้นต้น note/
|
|
// ส่วน API ขึ้นต้น api/note/ (URL /docedit/api/note/…)
|
|
// — root "/" ไม่ถูก match (ใช้ .+ ไม่ใช่ .*) เพราะเป็น redirect สู่ taskpane
|
|
// wordaddin ซึ่ง public (คำขอผู้ใช้ 2026-10-14) — ลิงก์ e-Court N8 ที่ root
|
|
// ยังถูกคุมอยู่ดีเพราะ root page ส่งต่อให้ /note ซึ่ง middleware จับตามปกติ
|
|
export const config = {
|
|
matcher: [
|
|
"/((?!note/login|api/note|api/template|api/documents|api/swagger|wordaddin|note/uploads|note/samples|note/swagger-ui|note/pdf\\.worker\\.min\\.mjs|_next|fonts|favicon\\.ico).+)",
|
|
],
|
|
};
|