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>
291 lines
23 KiB
Markdown
291 lines
23 KiB
Markdown
# คู่มือการใช้ Documents API (N10)
|
||
|
||
> สำหรับ: ทีม backend ของ e-Court ที่จะเรียก API นี้แบบ **server-to-server** (ไม่มี browser/หน้า UI)
|
||
> ใช้กับโค้ดเวอร์ชัน 2026-09-25 · สเปกฉบับเต็ม/เหตุผลการออกแบบ: [`docs/PLAN-API.md`](./PLAN-API.md)
|
||
|
||
## 1. API นี้ทำอะไรให้
|
||
|
||
ดึงข้อมูลที่เกิดในระบบ Judge's Note กลับไปใช้ต่อ โดยถามได้ 2 แบบจาก **endpoint เดียว**:
|
||
|
||
| ต้องการ | ส่งอะไร | ได้อะไร |
|
||
|---|---|---|
|
||
| เอกสารเดียว | `fileId` (id ของ e-Court) | `documents` ยาว 1 ชิ้น พร้อม annotation + bookmark ของไฟล์นั้น |
|
||
| ทุกเอกสารในกลุ่ม | `typeId` | `documents` ทุกไฟล์ในกลุ่ม เรียงตามวันที่เปิดเข้าระบบ |
|
||
| เอกสารเดียว (ยืนยันกลุ่มด้วย) | `fileId` + `typeId` พร้อมกัน | เหลือเฉพาะเอกสารเดียวที่ตรง**ทั้งสอง**เงื่อนไข (AND) — ไม่ตรงคู่ = 404 |
|
||
|
||
เป็น **read-only** ทั้งหมด — ข้อมูลที่ได้เป็น snapshot ณ ตอนเรียก ไม่มี webhook/event push แจ้งเมื่อมีรายการใหม่ (ต้องยิงซ้ำเองตามความจำเป็น)
|
||
|
||
## 2. สิ่งที่ต้องมีก่อนเรียก
|
||
|
||
| สิ่งที่ต้องมี | ค่า |
|
||
|---|---|
|
||
| URL | `https://<host>/docedit/api/note/external/documents` (API อยู่ใต้ `app/api/note/**` ของ basePath `/docedit` — ต่างจากหน้า UI ที่อยู่ใต้ `/docedit/note/…`) |
|
||
| Method | `POST` (อ่านอย่างเดียว แต่ใช้ POST เพราะถามด้วย JSON body — GET ที่แนบ body ไม่เป็นมาตรฐาน) |
|
||
| API Key | ขอจากผู้ดูแลระบบ Judge's Note — เก็บเป็น secret ฝั่ง backend e-Court อย่าใส่ใน log/URL |
|
||
| Content-Type | `application/json` |
|
||
|
||
## 3. Authentication
|
||
|
||
ส่ง API Key ผ่าน header **`x-api-key`** ทุก request:
|
||
|
||
```http
|
||
POST /docedit/api/note/external/documents HTTP/1.1
|
||
Host: <host>
|
||
x-api-key: <API Key ที่ได้รับ>
|
||
content-type: application/json
|
||
```
|
||
|
||
- ไม่ใส่ header หรือ key ผิด → **401** พร้อมข้อความเดียวกันทั้งสองกรณี (`{"error":"API key ไม่ถูกต้อง"}`)
|
||
- Key นี้เชื่อถือได้ระดับ **backend e-Court เท่านั้น** — ห้ามส่งต่อให้ browser/แอปฝั่งผู้ใช้ (API ไม่ได้ใส่ CORS headers เพราะออกแบบให้เรียกจาก server เท่านั้น)
|
||
- ผลลัพธ์รวมตำแหน่งกล่องปิดพราง (REDACTION) ด้วย — กล่าวคือผู้ถือ key เห็นว่ามีการปิดพรางตรงไหนของหน้า (เห็นเฉพาะพิกัด เนื้อหาใต้กล่องไม่ได้)
|
||
|
||
## 4. Request body
|
||
|
||
```jsonc
|
||
{
|
||
"fileId": "<fileId ของ e-Court>", // ถามเอกสารเดียว
|
||
"typeId": "<typeId ของกลุ่ม>", // ถามทุกเอกสารในกลุ่ม
|
||
"filter": { // optional ทั้งก้อน
|
||
"type": "bookmarks", // "bookmarks" | "annotations" — ไม่ส่ง = ทั้งสอง
|
||
"userId": "<userId ของระบบนี้>", // หรือ array — กรองตามเจ้าของรายการ
|
||
"annotationTypes": ["COMMENT", "HIGHLIGHT"] // หรือค่าเดียว — กรองชนิดของ annotation
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.1 field ระดับบนสุด
|
||
|
||
| field | ชนิด | ค่าเริ่มต้น | คำอธิบาย |
|
||
|---|---|---|---|
|
||
| `fileId` | string | — | id ของเอกสาร **ตามที่ e-Court ส่งมาในลิงก์** (`?fileId=`) ไม่ใช่ UUID ของระบบนี้ |
|
||
| `typeId` | string | — | รหัสกลุ่มเอกสาร ตามที่ส่งมาในลิงก์ (`?typeId=`) |
|
||
| `filter` | object | ไม่กรอง | ดู §4.2 |
|
||
|
||
กติกา:
|
||
|
||
- ต้องมี `fileId` **หรือ** `typeId` อย่างน้อยหนึ่งอย่าง — ขาดทั้งคู่ = 400
|
||
- ส่ง**ทั้งคู่**ได้ = AND กรองเฉพาะเอกสารเดียวที่ตรงทั้งสองเงื่อนไข (เช่น กันเข้าผิดกลุ่ม) — ไม่ตรงคู่ = 404
|
||
- ค่า string จะถูก trim — ส่งมาเป็นค่าว่าง/whitespace ล้วน = ถือว่าไม่ได้ส่ง field นั้น
|
||
- รับเฉพาะ 3 field นี้ — field อื่น (พิมพ์ผิด เช่น `fileID`, `filters`) = 400 บอกชื่อ field ที่ไม่รู้จัก
|
||
|
||
### 4.2 `filter`
|
||
|
||
| field | ชนิด | ค่าเริ่มต้น | คำอธิบาย |
|
||
|---|---|---|---|
|
||
| `type` | `"bookmarks"` \| `"annotations"` | ทั้งสอง | เลือกเอาเฉพาะส่วน — ส่วนที่ไม่เลือกคืนเป็น array ว่าง ไม่ใช่หายไปจาก response |
|
||
| `userId` | string หรือ array ของ string | ทุกคน | กรองตาม**เจ้าของรายการ**ทั้ง annotations และ bookmarks (`userId` ระบบนี้ = `u_userid` ที่เห็นใน DTO) — ไม่กรองตามผู้ตอบ reply (reply ตามมาครบกับ annotation ที่ผ่านตัวกรอง) |
|
||
| `annotationTypes` | string หรือ array ของ enum | ทุกชนิด | กรองชนิดของ annotation — มีผลเฉพาะเมื่อผลลัพธ์รวม annotations (เลือก `type:"bookmarks"` อย่างเดียว = เพิกเฉยเงียบ ๆ ไม่ใช่ 400) |
|
||
|
||
ค่า enum ของ `annotationTypes`: `HIGHLIGHT` `DRAW` `COMMENT` `SHAPE` `REDACTION` `TEXTBOX` `STAMP` — ค่าอื่น = 400
|
||
|
||
`filter` รับเฉพาะ 3 field นี้เช่นกัน (`type`/`userId`/`annotationTypes`) — field อื่น = 400
|
||
|
||
## 5. Response 200
|
||
|
||
รูปเดียวเสมอ (ไม่ว่าถามแบบไหน): `{ "documents": [ … ] }` — `documents` เป็น array ยาว 1 ตอนถามด้วย `fileId`, หลายชิ้นตอนถามด้วย `typeId` (กลุ่มว่าง = array ว่าง ไม่ใช่ error)
|
||
|
||
### 5.1 field ของแต่ละเอกสาร
|
||
|
||
| field | ชนิด | คำอธิบาย |
|
||
|---|---|---|
|
||
| `id` | string | UUID ของไฟล์ในระบบนี้ (ใช้ประกอบ `url` — ไม่ต้องจำ) |
|
||
| `externalFileId` | string \| null | `fileId` ของ e-Court ตามที่ส่งมาในลิงก์ — **null** ได้: ไฟล์ในกลุ่มที่มาโดยไม่ผ่าน fileId (เข้าจากลิงก์ไม่มี fileId หรืออัปโหลดในระบบนี้) |
|
||
| `title` | string | ชื่อเอกสาร |
|
||
| `typeId` | string \| null | กลุ่ม — null = ไม่มีกลุ่ม |
|
||
| `createdAt` | string | ISO 8601 (วันที่เปิดเข้าระบบครั้งแรก) |
|
||
| `annotations` | array | ดู §5.2 — เรียง `(pageIndex, createdAt)` |
|
||
| `bookmarks` | array | ดู §5.3 — เรียง `pageIndex → yOffset → createdAt` |
|
||
|
||
ลำดับ `documents` เมื่อถามด้วย `typeId`: ตาม `createdAt` ของไฟล์ (เก่า → ใหม่)
|
||
|
||
### 5.2 annotation แต่ละรายการ
|
||
|
||
| field | ชนิด | คำอธิบาย |
|
||
|---|---|---|
|
||
| `id` | string | UUID ของรายการ |
|
||
| `fileId` | string | UUID ของไฟล์ |
|
||
| `pageIndex` | number | **0-based** — หน้า 1 ของเอกสาร = `0` |
|
||
| `userId` / `userName` | string | ผู้สร้างรายการ (`userName` = `u_userlogin` ของ e-Court) |
|
||
| `type` | string | หนึ่งใน 7 enum ด้านบน |
|
||
| `data` | object | รายละเอียดตามชนิด — ดูตาราง §5.4 |
|
||
| `createdAt` / `updatedAt` | string | ISO 8601 |
|
||
| `resolved` | boolean | สถานะ "จัดการแล้ว" (F10) |
|
||
| `resolvedAt` | string \| null | ISO 8601 / null |
|
||
| `url` | string | deep link กลับมาหน้าอ่านเอกสารของระบบนี้ ไปตรงรายการนี้ (`…/annotate/<id>?ann=<annotationId>`) — เป็น **URL เต็ม** ใช้ได้ทันที |
|
||
| `replies` | array | คำตอบในเธรดของ comment — ดู §5.5 — เรียง `createdAt` |
|
||
|
||
รวม annotation **ทุกสถานะ** (ดู `resolved`) และ**ทุกผู้ใช้** (เว้นแต่กรองด้วย `filter.userId`)
|
||
|
||
### 5.3 bookmark แต่ละรายการ
|
||
|
||
| field | ชนิด | คำอธิบาย |
|
||
|---|---|---|
|
||
| `id` | string | UUID ของรายการ |
|
||
| `fileId` | string | UUID ของไฟล์ |
|
||
| `pageIndex` | number | **0-based** เช่นเดียวกับ annotation |
|
||
| `userId` / `userName` | string | ผู้สร้าง (`userName` อาจเป็น `""` สำหรับรายการเก่า = ไม่ทราบชื่อ) |
|
||
| `title` | string | ชื่อหัวข้อ bookmark |
|
||
| `parentId` | string \| null | โครงสร้าง 3 ระดับ — null = ระดับบนสุด, มีค่า = เป็นหัวข้อย่อยของ bookmark นั้น |
|
||
| `note` | string \| null | โน้ตย่อยของ bookmark |
|
||
| `yOffset` | number | ตำแหน่งบนหน้า เป็น % ของความสูงหน้า (0–100) |
|
||
| `createdAt` | string | ISO 8601 |
|
||
| `url` | string | deep link กลับมาหน้าอ่าน (`…/annotate/<id>?page=<หน้า>` เป็น **1-based** — `?page=1` = `pageIndex` 0) |
|
||
|
||
### 5.4 field `data` ของ annotation ตามชนิด
|
||
|
||
พิกัดทุกค่าเป็น **% ของขนาดหน้า** (ไม่ใช่ pixel — วาดทับตำแหน่งเดิมได้ทุกขนาดจอ): `Rect` = `{x, y, width, height}`, `Point` = `{x, y}`
|
||
|
||
| `type` | field ใน `data` | หมายเหตุ |
|
||
|---|---|---|
|
||
| `HIGHLIGHT` | `text`, `color`, `rects[]`, `style?`, `opacity?`, `note?` | `text` = ข้อความที่คลุม, `rects` = 1 rect ต่อบรรทัด, `style` = `highlight`\|`underline`\|`strikethrough`\|`squiggle` (ไม่มี = ไฮไลต์ทึบ) |
|
||
| `DRAW` | `strokeColor`, `strokeWidth`, `paths[]`, `pen?`, `widths?`, `opacity?`, `note?` | เส้นวาดมือ — `strokeWidth` เป็น % ของความกว้างหน้า, `pen` = `pencil`\|`highlighter`, `widths` = ความหนาต่อจุดจากสไตลัส (ยาวเท่า `paths`) |
|
||
| `COMMENT` | `position`, `message` | sticky note — `position` = ตำแหน่ง pin |
|
||
| `SHAPE` | `shape`, `strokeColor`, `strokeWidth`, `note?` + `rect` (rect/ellipse) **หรือ** `from`/`to` (line/arrow) | `shape` = `rect`\|`ellipse`\|`line`\|`arrow` |
|
||
| `REDACTION` | `rect`, `note?` | กล่องปิดพราง — เห็นเฉพาะตำแหน่ง ไม่มีเนื้อหาใต้กล่องใน DB |
|
||
| `TEXTBOX` | `position`, `text`, `color`, `fontSize`, `bold?`, `opacity?`, `align?`, `segments?`, `width?`, `note?` | `position` = มุมซ้ายบนของกล่อง, `fontSize` = % ของความกว้างหน้า, `text` เก็บข้อความรวมเสมอ (แม้มี `segments` ตัวหนาเฉพาะส่วน) |
|
||
| `STAMP` | `label`, `position`, `scale`, `color?`, `note?` | ตรายางข้อความ preset — `scale` = ความกว้างกรอบ % ของความกว้างหน้า |
|
||
|
||
`note` (optional ทุกชนิด) = โน้ตคำอธิบายของรายการ เช่น เหตุผลที่ไฮไลต์
|
||
|
||
### 5.5 reply แต่ละรายการ (ใน `replies` ของ annotation)
|
||
|
||
| field | ชนิด | คำอธิบาย |
|
||
|---|---|---|
|
||
| `id` | string | UUID ของคำตอบ |
|
||
| `annotationId` | string | UUID ของ annotation แม่ |
|
||
| `userId` / `userName` | string | ผู้ตอบ |
|
||
| `message` | string | ข้อความ |
|
||
| `createdAt` | string | ISO 8601 |
|
||
|
||
## 6. Error
|
||
|
||
error ทุกสถานะเป็น JSON รูปเดียวกัน: `{ "error": "<ข้อความไทย>" }`
|
||
|
||
| สถานะ | เมื่อไร | ข้อความ |
|
||
|---|---|---|
|
||
| 400 | body ไม่ใช่ JSON ที่ถูกต้อง | `body ต้องเป็น JSON ที่ถูกต้อง` |
|
||
| 400 | body ไม่ใช่ object | `body ต้องเป็น JSON object` |
|
||
| 400 | มี field ที่ไม่รู้จัก | `ไม่รู้จัก field '<field>' (รับเฉพาะ fileId, typeId, filter)` |
|
||
| 400 | `fileId`/`typeId` ไม่ใช่ string | `fileId ต้องเป็น string` / `typeId ต้องเป็น string` |
|
||
| 400 | ไม่มีทั้ง `fileId` และ `typeId` | `ต้องระบุ fileId หรือ typeId อย่างน้อยหนึ่งอย่าง` |
|
||
| 400 | `filter` ไม่ใช่ object / มี field อื่น | `filter ต้องเป็น object เช่น { "type": "bookmarks" }` / `filter รับเฉพาะ field 'type', 'userId', 'annotationTypes'` |
|
||
| 400 | `filter.type` ค่าไม่รู้จัก (รวมเอกพจน์ `annotation`/`bookmark`) | `filter.type รับค่า bookmarks หรือ annotations เท่านั้น` |
|
||
| 400 | `filter.userId` ไม่ใช่ string/array ของ string | `filter.userId ต้องเป็น string หรือ array ของ string` |
|
||
| 400 | `filter.annotationTypes` ค่านอก enum | `filter.annotationTypes รับค่า HIGHLIGHT, DRAW, COMMENT, SHAPE, REDACTION, TEXTBOX, STAMP เท่านั้น` |
|
||
| 401 | ไม่ใส่ `x-api-key` หรือ key ผิด | `API key ไม่ถูกต้อง` (ข้อความเดียวกันกัน enumeration) |
|
||
| 401 | ฝั่ง server ยังไม่ตั้ง key | `API ยังไม่ได้ตั้งค่าคีย์` |
|
||
| 404 | ถาม `fileId` ที่ไม่เคยเปิดในระบบ | `ไม่พบเอกสารนี้ในระบบ (ยังไม่เคยเปิด)` |
|
||
| 404 | ส่งคู่ `fileId`+`typeId` แต่ไม่ตรงคู่ | `ไม่พบเอกสารนี้ในระบบ (ยังไม่เคยเปิด หรือไม่ได้อยู่ในกลุ่ม typeId ที่ระบุ)` |
|
||
| 500 | ระบบนี้มีปัญหาฐานข้อมูล ณ ตอนนั้น | `เกิดข้อผิดพลาดของระบบ โปรดลองใหม่อีกครั้ง` (รายละเอียดอยู่ใน log ฝั่ง server อย่างเดียว) |
|
||
|
||
การแยก "ไม่มีข้อมูล" ออกจาก "ผิดพลาด":
|
||
|
||
- เอกสารเคยเปิดแต่ไม่มีรายการ → **200** พร้อม `annotations: []` / `bookmarks: []`
|
||
- กลุ่มไม่มีเอกสารเลย → **200** พร้อม `documents: []`
|
||
- `filter.userId` ไม่ตรงกับผู้ใช้ใดในระบบ → **200** รายการว่าง (ไม่ใช่ 400)
|
||
|
||
## 7. ตัวอย่างการเรียก
|
||
|
||
### 7.1 เอกสารเดียว เอาทุกอย่าง
|
||
|
||
```bash
|
||
curl -X POST -H "x-api-key: $EXTERNAL_API_KEY" -H "content-type: application/json" \
|
||
-d '{"fileId":"1234"}' \
|
||
"http://localhost:3000/docedit/api/note/external/documents"
|
||
```
|
||
|
||
Response (ตัดย่อ):
|
||
|
||
```jsonc
|
||
{
|
||
"documents": [
|
||
{
|
||
"id": "3f2a9c1e-8b7d-4e2a-9f10-11a2b3c4d5e6",
|
||
"externalFileId": "1234",
|
||
"title": "คำพิพากษา ผบ.123/2569",
|
||
"typeId": "CASE-2569-0001",
|
||
"createdAt": "2026-09-24T09:15:33.120Z",
|
||
"annotations": [
|
||
{
|
||
"id": "7c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
|
||
"fileId": "3f2a9c1e-8b7d-4e2a-9f10-11a2b3c4d5e6",
|
||
"pageIndex": 0,
|
||
"userId": "u_1234",
|
||
"userName": "somchai.j",
|
||
"type": "COMMENT",
|
||
"data": { "position": { "x": 41.2, "y": 37.8 }, "message": "ข้อเท็จจริงตรงนี้ขัดกับคำให้การ ร.2" },
|
||
"createdAt": "2026-09-24T10:02:11.530Z",
|
||
"updatedAt": "2026-09-24T10:02:11.530Z",
|
||
"resolved": false,
|
||
"resolvedAt": null,
|
||
"url": "http://localhost:3000/docedit/note/annotate/3f2a9c1e-…?ann=7c1d2e3f-…",
|
||
"replies": [
|
||
{
|
||
"id": "9e8f0a1b-2c3d-4e5f-6a7b-8c9d0e1f2a3b",
|
||
"annotationId": "7c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
|
||
"userId": "u_2200",
|
||
"userName": "somsri.k",
|
||
"message": "บันทึกไว้ก่อน ค่อยถามผู้เบิกความวันพรุ่งนี้",
|
||
"createdAt": "2026-09-25T08:44:02.310Z"
|
||
}
|
||
]
|
||
}
|
||
],
|
||
"bookmarks": [
|
||
{
|
||
"id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
|
||
"fileId": "3f2a9c1e-8b7d-4e2a-9f10-11a2b3c4d5e6",
|
||
"pageIndex": 2,
|
||
"userId": "u_1234",
|
||
"userName": "somchai.j",
|
||
"title": "บทวินิจฉัย",
|
||
"parentId": null,
|
||
"note": null,
|
||
"yOffset": 12.4,
|
||
"createdAt": "2026-09-24T10:05:47.900Z",
|
||
"url": "http://localhost:3000/docedit/note/annotate/3f2a9c1e-…?page=3"
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### 7.2 เอกสารเดียว เฉพาะ bookmark ของผู้ใช้หนึ่ง
|
||
|
||
```bash
|
||
curl -X POST -H "x-api-key: $EXTERNAL_API_KEY" -H "content-type: application/json" \
|
||
-d '{"fileId":"1234","filter":{"type":"bookmarks","userId":"u_1234"}}' \
|
||
"http://localhost:3000/docedit/api/note/external/documents"
|
||
```
|
||
|
||
(`userId` เป็นค่าของระบบนี้ — เก็บ collect จาก response ก่อนหน้า หรือดึงมาทั้งหมดแล้วกรองฝั่งผู้เรียกเองก็ได้)
|
||
|
||
### 7.3 ทุกเอกสารในกลุ่ม เฉพาะ COMMENT และ HIGHLIGHT
|
||
|
||
```bash
|
||
curl -X POST -H "x-api-key: $EXTERNAL_API_KEY" -H "content-type: application/json" \
|
||
-d '{"typeId":"CASE-2569-0001","filter":{"annotationTypes":["COMMENT","HIGHLIGHT"]}}' \
|
||
"http://localhost:3000/docedit/api/note/external/documents"
|
||
```
|
||
|
||
ได้ `documents` หลายชิ้นเรียงตามวันที่เปิดเข้าระบบ — ไฟล์ที่มาโดยไม่ผ่าน fileId (อัปโหลดในระบบนี้) ก็รวมอยู่ในกลุ่ม โดย `externalFileId: null`
|
||
|
||
### 7.4 fileId + typeId พร้อมกัน (AND)
|
||
|
||
```bash
|
||
curl -X POST -H "x-api-key: $EXTERNAL_API_KEY" -H "content-type: application/json" \
|
||
-d '{"fileId":"1234","typeId":"CASE-2569-0001"}' \
|
||
"http://localhost:3000/docedit/api/note/external/documents"
|
||
```
|
||
|
||
ได้เฉพาะเอกสารเดียวที่ตรงทั้งคู่ — ไฟล์นี้ไม่เคยเปิดหรืออยู่คนละกลุ่มกับ typeId ที่ระบุ = 404
|
||
|
||
## 8. ข้อควรรู้/ข้อจำกัด
|
||
|
||
1. **เอกสารต้องเคยเปิดผ่านลิงก์ e-Court ก่อนอย่างน้อยหนึ่งครั้ง** — ระบบนี้ลงทะเบียนเอกสาร (metadata-only) เมื่อมีผู้เปิดจากลิงก์ `?fileId=&fileUrl=&typeId=` ครั้งแรกเท่านั้น ยังไม่เคยเปิด = 404 (ไม่ใช่ error ฝั่ง e-Court) · ลิงก์ที่ไม่มี `fileId` (ส่งแต่ `fileUrl`) จะค้นด้วยวิธีนี้ไม่ได้
|
||
2. **`pageIndex` เป็น 0-based ส่วน `?page=` ใน url เป็น 1-based** — `pageIndex: 0` กับ `?page=1` คือหน้าเดียวกัน
|
||
3. **`url` อ่าน origin จาก request ของผู้เรียก** — ถ้าระบบนี้ deploy หลัง reverse proxy ต้องตั้งให้ proxy ส่งต่อ `x-forwarded-proto`/`x-forwarded-host` ให้ถูกต้อง ไม่งั้นลิงก์จะชี้ origin ของ proxy แทน public URL
|
||
4. **timestamp เป็น ISO 8601 (UTC) ทุกจุด**
|
||
5. ไม่มี pagination/rate limiting/caching — กลุ่มใหญ่ได้ response ยาวตามจำนวนรายการจริง
|
||
6. ไม่มี API ฝั่งเขียน (สร้าง/แก้ annotation ผ่าน API) และไม่มีการดึงไฟล์ PDF/bytes ผ่าน endpoint นี้ — ข้อมูลที่ได้มีเฉพาะ metadata + พิกัด + ข้อความของ annotation/bookmark
|