292 lines
23 KiB
Markdown
292 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/note/api/external/documents` (แอป serve ใต้ basePath `/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/note/api/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/note/api/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/note/api/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/note/api/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/note/api/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
|