From 4bcd01a95f138a3974caba974e7c046555c3364a Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 16 Oct 2026 13:54:25 +0700 Subject: [PATCH 01/21] feat(wordaddin): serve manifest with automatic origin rewrite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit manifest ของ add-in เดิมเป็น static ใน public/ ฝัง URL dev (localhost:3000) ทั้งไฟล์ — deploy จริงต้อง rewrite มือเองทุกครั้ง ก่อนนำไป sideload ใน Word ตอนนี้ serve ผ่าน route แทน: app/wordaddin/manifest.xml/route.ts อ่าน template (public/wordaddin/manifest.template.xml — ย้ายจาก manifest.xml ด้วย git mv) แล้วเขียนทับ origin ให้ตรงโฮสต์ที่ขอ ไฟล์ — dev/UAT/production ดาวน์โหลด manifest.xml ขอ environment ตัวเองแล้ว sideload ได้ทันที - ถอด requestOrigin จาก N10 route เป็น utils/request-origin.ts ใช้ร่วม + เพิ่ม heuristic: โฮสต์ไม่ใช่ localhost + proto http = บังคับ https (Office รับ https เท่านั้น และ hop ภายในหลัง IIS เป็น http เสมอ — Next server ก็ฉีด x-forwarded-proto เอง จึง ชี้ขาดจากโฮสต์ปลายทาง ไม่เดาว่า header มาจาก proxy หรือถูกฉีด) deep link ของ N10 ได้พฤติกรรมเดียวกันเป็นผลพวง - ตรวจ nft trace แล้ว template ถูกดึงเข้า standalone ด้วย (คู่ กับ COPY public จาก commit ก่อน = ชัวร์สองชั้น) ยืนยัน: tsc + vitest 215 tests ผ่าน · ทดสอบบน dev server ด้วย curl 3 โหมด — direct ได้ http://localhost:3000 (Word ยกเว้น localhost), ผ่าน proxy ส่ง header ครบได้ https://<โฮสต์>, ผ่าน proxy ไม่ส่ง proto (กรณี IIS) ได้ https://<โฮสต์> จากการ bump · next build compile route ผ่าน (ขั้น standalone symlink พังเฉพาะเครื่อง local Windows สิทธิ์ symlink — ไม่เกี่ยวกับโค้ด Docker build ผ่าน) Co-Authored-By: Claude Code --- CLAUDE.md | 1 + app/api/note/external/documents/route.ts | 18 ++------ app/wordaddin/manifest.xml/route.ts | 33 +++++++++++++++ docs/WORDADDIN-DEV.md | 5 ++- .../{manifest.xml => manifest.template.xml} | 10 +++-- utils/request-origin.ts | 41 +++++++++++++++++++ 6 files changed, 87 insertions(+), 21 deletions(-) create mode 100644 app/wordaddin/manifest.xml/route.ts rename public/wordaddin/{manifest.xml => manifest.template.xml} (88%) create mode 100644 utils/request-origin.ts diff --git a/CLAUDE.md b/CLAUDE.md index 84b17d9..8023344 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -81,6 +81,7 @@ app/note/login/page.tsx ← หน้า login (มี session แ app/note/annotate/[id]/page.tsx ← โหลด file + initial data → PDFViewer app/note/annotate/[id]/summary/page.tsx ← หน้าสรุป Annotation+Bookmark ของไฟล์เดียว (server component — query ทั้งหมดฝั่ง server แล้วส่ง `ExplorerItem[]` เรียง หน้า → หน้าเดียวกัน bookmark ก่อน annotation (bookmark ต่อหน้าเรียงตาม yOffset N5 เหมือนเดิม, annotation ตาม createdAt) ให้ `SummaryExplorer` กรองใน memory; design "แฟ้มสำนวน" 2026-09-17 แถบหัวน้ำเงิน + การ์ดรายหน้าขอบ folio + รายการตามหน้าจุดไข่ปลา → **redesign 2026-09-24 ตามคำขอผู้ใช้เป็น explorer 2 คอลัมน์แบบหน้ากลุ่ม N9 โดยมิติแรกของตัวกรองเป็น "หน้า" แทน "ไฟล์"** — แถบหัวน้ำเงิน + action bar (กลับหน้าอ่าน — ปุ่มส่งออก CSV F16/ส่งออก Bookmark JSON N7 ถอด 2026-09-25 แทนด้วย N10 API) + empty state คงเดิม, ไฟล์ว่าง = ไม่ mount explorer เลย; generateMetadata ตั้งชื่อแท็บ browser ตามคดี; ตอนพิมพ์ header เป็นกรอบเส้นน้ำเงิน + แผงตัวกรอง/แถบ action ซ่อนด้วย print:hidden — กฎ @media print อยู่ใน globals.css) — อย่าถอด class `summary-page` ของ
(กฎ coarse 44px ใน globals.css ผูกอยู่กับ selector นี้ ครอบทั้ง `a` และ `button`) app/wordaddin/layout.tsx + page.tsx ← taskpane ของ Word add-in (URL `/docedit/wordaddin` — nested layout แรกของ repo override title แท็บ browser; page เป็น server component บาง ๆ render WordAddinGate ทั้งหมดฝั่ง client) +app/wordaddin/manifest.xml/route.ts ← GET serve manifest ของ add-in พร้อม rewrite origin ตามโฮสต์ที่ขอ (template `public/wordaddin/manifest.template.xml` ค่า dev — dev/UAT/production โหลด manifest.xml ขอ environment ตัวเองแล้วใช้ได้เลย ไม่ต้อง rewrite มือ; origin จาก `utils/request-origin.ts` แชร์กับ deep link N10 — x-forwarded-proto/host ก่อน fallback host ตรง + บังคับ https โฮสต์ที่ไม่ใช่ localhost เพราะ Office รับ https เท่านั้น) components/wordaddin/WordAddinGate.tsx ← client gate: ฉีด script office.js self-host (ผ่าน withBasePath "/wordaddin/office-js/office.js") → Office.onReady → mount TaskpaneApp · `?addindemo=untracked|tracking` (dev-only) ข้าม office.js + mock sign in + ตั้ง phase ตรง เพื่อดู UI ใน browser ธรรมดา — flow ที่แตะ Word จริงจะจบที่ error ตาม expected (FR-17: ใน browser ปกติ banner auto-save error โผล่หลัง ~30 วิ — ถูกต้อง) components/wordaddin/TaskpaneApp.tsx ← state machine checking/signed-out/untracked/tracking + handler ทุกตัว พอร์ตจาก App.tsx ต้นฉบับ (state/handlers คงเดิม เปลี่ยนแค่ JSX → Genesis) · SaveControls/MessageBar/Spinner/ConfirmVersionDialog(portal แบบ AnnotationSidebar)/VersionHistoryPanel components/summary/SummaryExplorer.tsx ← client root ของหน้าสรุป (redesign 2026-09-24) — state ตัวกรอง `FacetFilterState` จากแผงกลาง explorer + กรองใน memory + facet count/static users derive เอง + มิติ "หน้า" เป็นตัวเลือกแบบ single-select เฉพาะหน้าที่มีรายการ ≥1 เรียงน้อย→มาก (`optionsScroll` ให้ scroll ภายในกลุ่ม max-h-64 กันหน้าเยอะดันกลุ่มอื่นออกจอ) + layout 2 คอลัมน์ (มือถือ < lg ตัวกรองหุบเป็น `
`, desktop aside sticky 15rem — pattern เดียวกับ GroupExplorer; คอลัมน์ content ครอบด้วย `
` ไม่ใช่ `
` เพราะกฎ print break-inside ของ `main.summary-page section` มุ่งการ์ดรายหน้าข้างใน) diff --git a/app/api/note/external/documents/route.ts b/app/api/note/external/documents/route.ts index 3d9f4a1..8a4faff 100644 --- a/app/api/note/external/documents/route.ts +++ b/app/api/note/external/documents/route.ts @@ -4,6 +4,7 @@ import { db } from "@/db"; import { files } from "@/db/schema"; import { externalApiError, requireExternalApiKey } from "@/lib/external-api-auth"; import { loadDocumentBundles, parseQueryBody } from "@/lib/external-api"; +import { requestOrigin } from "@/utils/request-origin"; // N10 (PLAN-API.md D2) — POST /api/note/external/documents // (เดิมชื่อ /api/external/query ถึงรอบสาม — เปลี่ยนชื่อตาม resource ที่คืน 2026-09-25) @@ -19,21 +20,8 @@ import { loadDocumentBundles, parseQueryBody } from "@/lib/external-api"; const JSON_HEADERS = { "content-type": "application/json; charset=utf-8" }; -/** origin เต็มของ request นี้ (ใช้ประกอบ url เต็มใน response — คำขอผู้ใช้ - * 2026-09-25): เชื่อ header ของ reverse proxy ก่อน (x-forwarded-proto / - * x-forwarded-host — proxy หลายชั้นส่งเป็น comma list ใช้ค่าแรก) แล้วค่อย - * fallback จาก host/protocol ของ request ตรง ๆ */ -function requestOrigin(req: Request): string { - const url = new URL(req.url); - const proto = - req.headers.get("x-forwarded-proto")?.split(",")[0]?.trim() ?? - url.protocol.replace(":", ""); - const host = - req.headers.get("x-forwarded-host")?.split(",")[0]?.trim() ?? - req.headers.get("host") ?? - url.host; - return `${proto}://${host}`; -} +// origin เต็มของ request สำหรับ url เต็มใน response — ถอดเป็น helper ร่วม +// (utils/request-origin.ts) ให้ route manifest ของ wordaddin ใช้เหมือนกัน (2026-10-16) export async function POST(req: Request) { const unauthorized = requireExternalApiKey(req); diff --git a/app/wordaddin/manifest.xml/route.ts b/app/wordaddin/manifest.xml/route.ts new file mode 100644 index 0000000..9b19dbd --- /dev/null +++ b/app/wordaddin/manifest.xml/route.ts @@ -0,0 +1,33 @@ +import fs from "node:fs"; +import path from "node:path"; + +import { requestOrigin } from "@/utils/request-origin"; + +// GET /wordaddin/manifest.xml — manifest ของ Word add-in สำหรับ sideload +// +// URL ใน manifest เป็น absolute https ตามข้อบังคับของ Office แต่ต้องต่างกันตาม +// environment — เดิมใช้ไฟล์ static ใน public/ ค่า dev (localhost:3000) แล้วให้ผู้ +// deploy rewrite มือเองทุกครั้ง (docs/WORDADDIN-DEV.md) ตอนนี้ serve ผ่าน route +// แทน: อ่าน template (public/wordaddin/manifest.template.xml — ค่า dev) แล้ว +// เขียนทับ origin ให้ตรงโฮสต์ที่ขอไฟล์ (dev/UAT/production ใช้ template +// เดียวกัน — โหลดจาก environment ไหน manifest ก็ชี้ environment นั้น) +// +// อ่านจาก public/ เพราะเป็นที่เดียวที่การันตีว่ามีใน standalone ของ Docker +// (runner stage COPY public — ไฟล์ app/ ต้นฉบับไม่ถูกก๊อปเข้า image) + +/** origin dev ที่ฝังใน template — สตริงเดียวที่ถูกแทนที่ทั้งไฟล์ (6 จุด) */ +const DEV_ORIGIN = "https://localhost:3000"; + +const TEMPLATE = path.join(process.cwd(), "public", "wordaddin", "manifest.template.xml"); + +export function GET(req: Request): Response { + const template = fs.readFileSync(TEMPLATE, "utf8"); + const manifest = template.replaceAll(DEV_ORIGIN, requestOrigin(req)); + return new Response(manifest, { + headers: { + "content-type": "application/xml; charset=utf-8", + // origin ขึ้นกับ header ของผู้ขอ — ห้ามแคกลามผู้ใช้เกินครั้งเดียวต่อ download + "cache-control": "no-store", + }, + }); +} diff --git a/docs/WORDADDIN-DEV.md b/docs/WORDADDIN-DEV.md index 5baa0f4..eb8efbc 100644 --- a/docs/WORDADDIN-DEV.md +++ b/docs/WORDADDIN-DEV.md @@ -11,9 +11,10 @@ Taskpane ของ Word add-in "e-Court Documents Versioning" พอร์ตม | ที่ | คือ | |---|---| | `app/wordaddin/page.tsx` + `components/wordaddin/WordAddinGate.tsx` | gate โหลด Office.js (self-host) → `Office.onReady` → mount `TaskpaneApp` | +| `app/wordaddin/manifest.xml/route.ts` | serve manifest พร้อม **rewrite origin ตามโฮสต์ที่ขอ** (อ่าน `x-forwarded-proto`/`x-forwarded-host` ก่อนแล้ว fallback host ตรง ผ่าน `utils/request-origin.ts` ที่แชร์กับ origin ของ deep link N10 — โฮสต์ที่ไม่ใช่ localhost บังคับ https เพราะ Office รับ https เท่านั้น) | | `components/wordaddin/TaskpaneApp.tsx` | state machine `checking/signed-out/untracked/tracking` + ทุก handler | | `wordaddin/services/*` | ชั้น Office.js พอร์ตดิบจากต้นฉบับ + `authService`/`versioningService` เป็น mock | -| `public/wordaddin/manifest.xml` | manifest sideload (URL เป็น absolute https ต่อ environment) | +| `public/wordaddin/manifest.template.xml` | template ของ manifest (ค่า default dev `https://localhost:3000`) — serve ผ่าน route ด้านบนเท่านั้น ไม่ได้ใช้ตรง ๆ | | `public/wordaddin/office-js/` | Office.js runtime — สร้างโดย postinstall `scripts/fetch-office-js.mjs` ดาวน์โหลดฉบับล่าสุดจาก CDN ของ Microsoft (**gitignore** — รัน `npm install` ทุกครั้งหลัง clone เครื่องต้องออกอินเทอร์เน็ตตอนติดตั้ง ส่วนตัวระบบ deploy ใช้ intranet ล้วน) | | `wordaddin/config.ts` | env `NEXT_PUBLIC_ADDIN_*` (ตารางด้านล่าง) | @@ -38,7 +39,7 @@ manifest ต้องชี้ https — 2 ทางเลือก: 3. Word: **File > Options > Trust Center > Trust Center Settings > Trusted Add-in Catalogs** เพิ่มโฟลเดอร์นั้น > restart Word 4. **Insert > My Add-ins > SHARED FOLDER** > เปิด "e-Court Documents Versioning" -Deploy จริง (intranet): rewrite URL ทุกตัวใน manifest จาก `https://localhost:3000/docedit/wordaddin` เป็น `https://<โฮสต์>/docedit/wordaddin` (และ AppDomain + icons ด้วย) +Deploy จริง (intranet): **ไม่ต้อง rewrite อะไรแล้ว** — manifest serve ผ่าน `app/wordaddin/manifest.xml/route.ts` ซึ่งเขียนทับ origin ให้ตรงโฮสต์ที่ขอไฟล์อัตโนมัติ ดาวน์โหลด `https://<โฮสต์>/docedit/wordaddin/manifest.xml` แล้วใช้ sideload ได้ทันทีทุก environment (dev/UAT/production) · proxy ฝั่งหน้าควรส่ง `X-Forwarded-Host`/`X-Forwarded-Proto` ตามมาตรฐาน — ถ้าไม่ส่ง route ก็ยังใช้ Host ของ request และบังคับ https ให้โฮสต์ที่ไม่ใช่ localhost อยู่ดี ## ทดสอบใน browser ธรรมดา (ไม่ต้องเปิด Word) diff --git a/public/wordaddin/manifest.xml b/public/wordaddin/manifest.template.xml similarity index 88% rename from public/wordaddin/manifest.xml rename to public/wordaddin/manifest.template.xml index e9cbb7f..6cc87ef 100644 --- a/public/wordaddin/manifest.xml +++ b/public/wordaddin/manifest.template.xml @@ -2,10 +2,12 @@ 4a1e184a-ab31-4fa4-b3c7-9a2283051c3f - 1.0.0.1 + 1.0.0.2 e-Court Documents Versioning en-US @@ -33,11 +33,16 @@ - + From a172b111a36ef378a30b88d97e06730e1bd081a2 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 16 Oct 2026 14:48:04 +0700 Subject: [PATCH 03/21] =?UTF-8?q?docs(wordaddin):=20sideload=20dev=20?= =?UTF-8?q?=E0=B8=8A=E0=B8=B5=E0=B9=89=20trusted=20catalog=20=E0=B8=97?= =?UTF-8?q?=E0=B8=B5=E0=B9=88=20public/wordaddin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit เครื่อง dev ที่มี repo ไม่ต้องดาวน์โหลด/คัดลอก manifest แยกแล้ว — ชี้ Trusted Add-in Catalogs ไปที่ public/wordaddin ตรง ๆ: manifest.template.xml คือ dev manifest อยู่แล้ว (URL localhost) และเนื้อไฟล์อัปเดตตาม branch ที่ checkout เสมอ ห้ามวาง xml อื่นเพิ่มในโฟลเดอร์นี้ — Word สแกน *.xml ทุกไฟล์ GUID เดียวกันสองไฟล์ทำให้ Word สับสน เครื่องที่ไม่มี repo (production/ผู้ใช้) ยังใช้ flow ดาวน์โหลด จาก URL ของ environment นั้นแล้ววางโฟลเดอร์แยกตามเดิม Co-Authored-By: Claude Code --- docs/WORDADDIN-DEV.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/WORDADDIN-DEV.md b/docs/WORDADDIN-DEV.md index 65edac4..23af799 100644 --- a/docs/WORDADDIN-DEV.md +++ b/docs/WORDADDIN-DEV.md @@ -34,10 +34,17 @@ manifest ต้องชี้ https — 2 ทางเลือก: ## Sideload ใน Word Desktop (Windows) -1. รัน dev server (https) แล้วดาวน์โหลด manifest: `https://localhost:3000/docedit/wordaddin/manifest.xml` -2. เอาไฟล์ manifest ไว้โฟลเดอร์ใดโฟลเดอร์หนึ่ง เช่น `%USERPROFILE%\Documents\wordaddin-manifest\` -3. Word: **File > Options > Trust Center > Trust Center Settings > Trusted Add-in Catalogs** เพิ่มโฟลเดอร์นั้น > restart Word -4. **Insert > My Add-ins > SHARED FOLDER** > เปิด "e-Court Documents Versioning" +เครื่อง dev ที่มี repo (แนะนำ — ไม่ต้องดาวน์โหลด/คัดลอกอะไรเลย): + +1. Word: **File > Options > Trust Center > Trust Center Settings > Trusted Add-in Catalogs** เพิ่มโฟลเดอร์ `public/wordaddin` ของ repo (เช่น `D:\dms\judes-note\public\wordaddin`) > restart Word +2. **Insert > My Add-ins > SHARED FOLDER** > เปิด "e-Court Documents Versioning" +3. ชื่อไฟล์ไม่สำคัญ — Word สแกน `*.xml` ทุกไฟล์ในโฟลเดอร์ โดย `manifest.template.xml` คือ dev manifest อยู่แล้ว (URL localhost:3000) และเนื้อไฟล์อัปเดตตาม branch ที่ checkout อยู่เสมอ — **ห้ามวาง xml manifest อื่นเพิ่มในโฟลเดอร์นี้** (GUID เดียวกันสองไฟล์ = Word สับสน) + +เครื่องที่ไม่มี repo (production/เครื่องผู้ใช้): + +1. ดาวน์โหลด manifest จาก environment นั้น: `https://<โฮสต์>/docedit/wordaddin/manifest.xml` +2. เอาไฟล์ไว้โฟลเดอร์ใดโฟลเดอร์หนึ่ง เช่น `%USERPROFILE%\Documents\wordaddin-manifest\` +3. ทำขั้น Trust Center ข้างบนด้วยโฟลเดอร์นั้น > restart Word > **Insert > My Add-ins > SHARED FOLDER** > เปิด "e-Court Documents Versioning" Deploy จริง (intranet): **ไม่ต้อง rewrite อะไรแล้ว** — manifest serve ผ่าน `app/wordaddin/manifest.xml/route.ts` ซึ่งเขียนทับ origin ให้ตรงโฮสต์ที่ขอไฟล์อัตโนมัติ ดาวน์โหลด `https://<โฮสต์>/docedit/wordaddin/manifest.xml` แล้วใช้ sideload ได้ทันทีทุก environment (dev/UAT/production) · proxy ฝั่งหน้าควรส่ง `X-Forwarded-Host`/`X-Forwarded-Proto` ตามมาตรฐาน — ถ้าไม่ส่ง route ก็ยังใช้ Host ของ request และบังคับ https ให้โฮสต์ที่ไม่ใช่ localhost อยู่ดี From 5917c0532ced37010953ca34e5189b7b8f2df06d Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 20 Oct 2026 09:35:25 +0700 Subject: [PATCH 04/21] file --- package-lock.json | 654 +++++++++++++++++--- public/note/swagger-ui/swagger-ui-bundle.js | 2 +- 2 files changed, 558 insertions(+), 98 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9096355..748a8c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,6 +61,7 @@ "integrity": "sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", @@ -79,6 +80,7 @@ "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" } @@ -88,7 +90,8 @@ "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@apidevtools/swagger-parser": { "version": "10.1.1", @@ -96,6 +99,7 @@ "integrity": "sha512-u/kozRnsPO/x8QtKYJOqoGtC4kH6yg1lfYkB9Au0WhYB0FNLpyFusttQtvhlwjtG3rOwiRz4D8DnnXa8iEpIKA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@apidevtools/json-schema-ref-parser": "11.7.2", "@apidevtools/openapi-schemas": "^2.1.0", @@ -166,6 +170,7 @@ "integrity": "sha512-fNAjWnA/nZ2jz31kxR/AqRaUT8ewHBw/WuBIosK0moMy1C9e5ValbDfFdIxJzVOOYaYkV/b2F1S4H/aHiqfVQg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -179,6 +184,7 @@ "integrity": "sha512-Vu011o3/bikQNwtjouwmUJud+Z6Brcjij2D0omPWClRGg8i5gBfOYSpDkFGkHbhGlaky4fgvfkxD0uHGq34uYA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@azure/abort-controller": "^1.0.0", "@azure/core-auth": "^1.3.0", @@ -198,6 +204,7 @@ "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.2.0" }, @@ -211,6 +218,7 @@ "integrity": "sha512-JbZtIqfEulsIA0rC3zM7jfF4KkOnye9aKcaO/jJqxJRm/gM6lAjEv7sL4njW8D+35l50P1f+UuH5OqN+UKJqNA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@azure/abort-controller": "^1.0.0", "@azure/core-auth": "^1.3.0", @@ -230,6 +238,7 @@ "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.2.0" }, @@ -243,6 +252,7 @@ "integrity": "sha512-J2jmTPv8ZraSHDTz9l2Bx8gNL3ktfDDWo2mxWfzarn64O9Fjhb+l85YWyubGy2xUdeGuZPKzvQLltGv8bSu8eQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@azure/abort-controller": "^1.0.0", "@azure/core-auth": "^1.3.0", @@ -262,6 +272,7 @@ "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.2.0" }, @@ -275,6 +286,7 @@ "integrity": "sha512-DR/H2nfKtHNqfpuJ4L/B4irX1nX77QizulmfrxcLNZmkfinm0SdZpypXSvzaI5rHZSXfhXNUfBvMfi+jMkjWtw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", @@ -294,6 +306,7 @@ "integrity": "sha512-IUZydyTUkDnYdstOW9pFOOUQlBjAepK5teihDE3x6yxsPJs/hsAaaYpeGxdxrgtOiJbBKSjKW7MDk7AEhb4LRg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-util": "^1.13.0", @@ -329,6 +342,7 @@ "integrity": "sha512-BoSmXPx2er1Ai+wKlDvj29jIQespCNBwEmKyZVHO2kEFsWbGjAjwMCGzug3DJM5/QYIV3vej0S1zcU5bq9fa8w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@azure/abort-controller": "^2.1.2" }, @@ -346,6 +360,7 @@ "integrity": "sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-util": "^1.2.0", @@ -362,6 +377,7 @@ "integrity": "sha512-7GEAoIsaoBr6KELNRb8nypowCqvk8dnCHFCYg4XD4lOQGY2GqjQg5IhkRjyBFRO18CGSMq05PaNqSOE9GQro3g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -375,6 +391,7 @@ "integrity": "sha512-/shnJ+ooO8WPxDhPEeI/2oRQuubn16gZ6CvlbpWbEswZfzwI9tI/sMAHmF3x1LuQ9yZYXfLW3TjzGMLEC5blKg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=22.0.0" } @@ -405,6 +422,7 @@ "integrity": "sha512-eGwxD0AtncrxeBM4tG8R55Pc3rdX1hNW2WibJAgYpCVA6E93mvvVH+LcssoVjOBrSKWS55yEIHsk0X8ctHmfOQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -418,6 +436,7 @@ "integrity": "sha512-9n2pWK61veAuN0V20t9lOuoV4CFMdyAZ1ygZzvBGk/pBBJRib/PjL9PLXa/aI2CcPpyHfqVsxxqLCYl6uZlfDw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@azure/abort-controller": "^2.1.2", "@typespec/ts-http-runtime": "^0.3.0", @@ -433,6 +452,7 @@ "integrity": "sha512-e7lX/dk//F6Qf7BB6PTY4+p2yuOQtyOeHGyapYHNwqSp2OnYpwQt49A/Nin2XmKBQ69pwagR4k/lQBq8lbHQkA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-xml-parser": "^5.5.9", "tslib": "^2.8.1" @@ -447,6 +467,7 @@ "integrity": "sha512-8utCLdTZn14CiAe0HW4JbtzUMsc0mdDwnjUvStFMrR4IxQP8S2eL9zVAdplpfORVJGYmidZElpeXRYjJQ4UyUw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-auth": "^1.9.0", @@ -471,6 +492,7 @@ "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -484,6 +506,7 @@ "integrity": "sha512-++Zlftm0kVLPmzC06t6epuWmcRMDbI4z5P3NNX979WA/k23+NtSOynEGzsVfZwguKw2mi5umVgnBlJQMwRz4Pg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "default-browser": "^5.5.1", "define-lazy-prop": "^3.0.0", @@ -505,6 +528,7 @@ "integrity": "sha512-rbAE25KUfjU/s3XHUdJgceoCP5dEOpMx85J04kF+QMdta73XkuG9JGHHinch+XIoKpBdqljin+KqURpJriSzLA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" @@ -519,6 +543,7 @@ "integrity": "sha512-NDW2/bz2Pz71EJAlhSw+jg1kpRIWtrzj0gCzcHuG1u7iLvSOHpUc5lICkDA9x0bbeityxTneiunSBluvS+/y4w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@azure/msal-common": "16.14.2" }, @@ -532,6 +557,7 @@ "integrity": "sha512-HOBR0Gfo39TMrkxNG8k6cfjRkrRjXZc3JQs3ebMWQc9S4cihuknMYra3sgmzbZB752oQBqBN9+WF7c826p2vIQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.8.0" } @@ -542,6 +568,7 @@ "integrity": "sha512-ixSO1Y/kCVRthRs+hSx/5qkwaunX1/RAePhlMN0wIpIQ4WEZ6AREGGnGd1AP0qspHVsAwzWQKGubpjh50JyJIQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@azure/msal-common": "16.14.1", "jsonwebtoken": "^9.0.0" @@ -557,6 +584,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@azure/msal-common": "15.17.0", "@azure/msal-node-runtime": "^0.20.0", @@ -572,6 +600,7 @@ "integrity": "sha512-VQ5/gTLFADkwue+FohVuCqlzFPUq4xSrX8jeZe+iwZuY6moliNC8xt86qPVNYdtbQfELDf2Nu6LI+demFPHGgw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.8.0" } @@ -581,7 +610,8 @@ "resolved": "https://registry.npmjs.org/@azure/msal-node-runtime/-/msal-node-runtime-0.20.6.tgz", "integrity": "sha512-89Al7l5c8sOEA954d35WZOg1sCa2WKfgf7KyTbN9vIkKsoZrXdJ5+CPjy8+tQqWcsJTAMbQD4OWr7V09/xN8KA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@azure/msal-node/node_modules/@azure/msal-common": { "version": "16.14.1", @@ -589,6 +619,7 @@ "integrity": "sha512-Or6xhPNyi4zHW25158yxBoyxuCqNSPa5YBVqfF1J5Ks4MJWBo/USXdp05DQIPu1Zli00YZu6t0+h6KvHJealxQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.8.0" } @@ -599,6 +630,7 @@ "integrity": "sha512-cXDTCAMwDx8Ng9yUe82kOOd2kPnZ8AQqe4FkLMcpaQ3Mvr3/PXVdvRhyP0FtY5/U5Oh6JGk3EcEibaFOymRLFw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", @@ -626,6 +658,7 @@ "integrity": "sha512-bttzuhQiCIwrkzjPDA+AtAR7dg19L/CC6ztcqJ5LfvWpXuys9mHp0UQ0udYnoUvv9SCT9KTR5kqFvFr0e6k0lQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", @@ -647,6 +680,7 @@ "integrity": "sha512-NpugTzbKgGrNe45Ui32cN8vCFFMxVtP/9rtfwv0nP5FRDAjvYBxJcLVpYA9mr1jxP6cnIT7x9LUzuDTbWYYNRA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6.9.0" } @@ -671,6 +705,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">=0.1.90" } @@ -763,7 +798,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=20.19.0" }, @@ -812,7 +846,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=20.19.0" } @@ -1725,7 +1758,8 @@ "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.3.0.tgz", "integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@feathersjs/hooks": { "version": "0.6.5", @@ -1733,6 +1767,7 @@ "integrity": "sha512-WtcEoG/imdHRvC3vofGi/OcgH+cjHHhO0AfEeTlsnrKLjVKKBXV6aoIrB2nHZPpE7iW5sA7AZMR6bPD8ytxN+w==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 10" } @@ -1743,6 +1778,7 @@ "integrity": "sha512-MgP/I06GdvsilDspMk/qmRzpk8LOUStP6jFAbSRi40W09DhoyUn+Mj7eyKICPqeYKybOjVFLVIAQzvu1bfIa7Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=20" }, @@ -1925,6 +1961,7 @@ "integrity": "sha512-w3PMZH5rahrukn8/I7P9Ihil+twgLTUHDZtJlJyBbUKyPaOSSQjLZkb0PpncVhin1gCaMgOFXy6iNPgcZUoo2w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@inquirer/type": "^1.1.6", "@types/mute-stream": "^0.0.4", @@ -1951,6 +1988,7 @@ "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -1960,7 +1998,8 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@inquirer/editor": { "version": "4.2.23", @@ -2663,6 +2702,7 @@ "integrity": "sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "mute-stream": "^1.0.0" }, @@ -2676,6 +2716,7 @@ "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "minipass": "^7.0.4" }, @@ -2710,6 +2751,7 @@ "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -2738,7 +2780,8 @@ "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@microsoft/app-manifest": { "version": "1.1.3", @@ -2762,6 +2805,7 @@ "integrity": "sha512-OayhehwI+CnO0Wr53e29ZJZWGsNA5yVG7r54qmZSLc5HxA5Cozk4hP7EbYDCXkxh4NbQoT1dhTzC8bkRo+wWXw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "buffer": "^5.2.1", "debug": "^4.1.1", @@ -2774,6 +2818,7 @@ "integrity": "sha512-wGuFEzvRiWZmDxQMGKEjOKhEIVnLiG6vRUuM9Hwqxpe/kbiyA2WiUyEVpniNPaaw8gDHTf9zJHnPNNj0JiL5mA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@microsoft/dev-tunnels-contracts": ">1.1.8", "axios": "^1.6.2", @@ -2788,6 +2833,7 @@ "integrity": "sha512-YlAI2jC0grK2p2fvxDV9Hin5EtRXL6yOMhLKqtjRVlH8Q0+hI9UWFl7RhLWSZjsbDyl+0LIlI2HjlcbsUhXGsQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "adm-zip": "^0.5.17", "original-fs": "^1.2.0", @@ -2801,6 +2847,7 @@ "integrity": "sha512-ufJnssQGbxzLNS1Ho9bCtX4rQKCCvoVuDLHoJyc3F9dOGDB4BkWs2Ci0kv53lqocAEQ/Cbi+I2XCsNYGqVYqng==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12.0" } @@ -2815,6 +2862,7 @@ "https://github.com/sponsors/ctavan" ], "license": "MIT", + "peer": true, "bin": { "uuid": "dist-node/bin/uuid" } @@ -2825,6 +2873,7 @@ "integrity": "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=14.0.0" } @@ -2835,6 +2884,7 @@ "integrity": "sha512-mih5trnmvIKpjLgqzsoxqiWFvG5eMeETPCAdtFvDaXyferWzFt9UrdARfRoYboPhfFjYRYw8EoF3EA4yFVWCsQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@apidevtools/swagger-parser": "^10.1.1", "@microsoft/app-manifest": "1.1.3", @@ -2853,6 +2903,7 @@ "integrity": "sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -2917,6 +2968,7 @@ "integrity": "sha512-cSVQHqK7oYG+KvSR8y+nvOiXcLNU/t/8ihDyXbdJtZCnRft7ZtHpnt8yxL96xdFe2UUHLSipHbZSJBotQrIB+g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@azure/core-auth": "^1.4.0", "@microsoft/app-manifest": "1.1.3", @@ -2932,6 +2984,7 @@ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": "*" } @@ -2942,6 +2995,7 @@ "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.3", @@ -2961,6 +3015,7 @@ "integrity": "sha512-156FXuXXl8DZ6aPU/GR0qFpo7JIHK9sBTqhUBOiO0ljRl+asP/S9nREQG4Vg4lPWL1UCCd6WeTpTcOPyyYQdJQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@apidevtools/swagger-parser": "^10.1.0", "@azure/arm-appservice": "^13.0.0", @@ -3031,6 +3086,7 @@ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -3044,6 +3100,7 @@ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -3054,6 +3111,7 @@ "integrity": "sha512-2DdE+SJDtzLEEWzY1ZjY7Q+VcPhcV1KisD3zI4u0XZyktsjHum1mwbMI+JaulUBi2OZk+KJAi2uPXzxichPkdw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@hono/node-server": "^1.19.9 || ^2.0.5", "ajv": "^8.17.1", @@ -3095,6 +3153,7 @@ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" @@ -3109,6 +3168,7 @@ "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "bytes": "^3.1.2", "content-type": "^2.0.0", @@ -3134,6 +3194,7 @@ "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -3148,6 +3209,7 @@ "integrity": "sha512-e+H0ZXHSWYrENhQzw1LPuP4oF5MzVKmDU6d3hxlvaPEYLLg62MxtQNPRx4SYSuYJSBUgnQIG4HIN2tEtNv7Dog==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -3162,6 +3224,7 @@ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6.6.0" } @@ -3172,6 +3235,7 @@ "integrity": "sha512-qCqy1xSoaugxOD4IVHb1ZWdkvCF2W3aE2okKouWj8VwJ+ztskSiXoBnCSJqfKT+cVPJhuJqe9YLbY425+Zh00g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.3.0", @@ -3216,6 +3280,7 @@ "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -3230,6 +3295,7 @@ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", @@ -3252,6 +3318,7 @@ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } @@ -3262,6 +3329,7 @@ "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" }, @@ -3276,6 +3344,7 @@ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -3289,6 +3358,7 @@ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -3299,6 +3369,7 @@ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "mime-db": "^1.54.0" }, @@ -3316,6 +3387,7 @@ "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "content-type": "^2.1.0" }, @@ -3333,6 +3405,7 @@ "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -3347,6 +3420,7 @@ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", @@ -3374,6 +3448,7 @@ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", @@ -3394,6 +3469,7 @@ "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "content-type": "^2.0.0", "media-typer": "^1.1.0", @@ -3413,6 +3489,7 @@ "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -3832,7 +3909,8 @@ "url": "https://github.com/sponsors/nodable" } ], - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", @@ -3878,6 +3956,7 @@ "integrity": "sha512-Efcc+iF0j3Bf67YjEqIqWXbX5XddXoK/Mw4K1/JuXwRCZ8N16VR7iT23nlCc9XrveFVh/E5Rqs2StT0V8v9LdA==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/oxc-project" } @@ -3922,6 +4001,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -3939,6 +4019,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -3956,6 +4037,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -3973,6 +4055,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -3990,6 +4073,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4007,6 +4091,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4024,6 +4109,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4041,6 +4127,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4058,6 +4145,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4075,6 +4163,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4092,6 +4181,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4109,6 +4199,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4126,6 +4217,7 @@ "os": [ "openharmony" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4143,6 +4235,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4160,6 +4253,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4169,7 +4263,8 @@ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@scarf/scarf": { "version": "1.4.0", @@ -4235,7 +4330,8 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/jsonfile": { "version": "6.1.4", @@ -4253,6 +4349,7 @@ "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*" } @@ -4297,7 +4394,6 @@ "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -4329,7 +4425,8 @@ "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@typespec/ts-http-runtime": { "version": "0.3.9", @@ -4337,6 +4434,7 @@ "integrity": "sha512-edSdeAqkdxBVzA1yL1LrLCml1YjyCVvPMtMqJpbF+6K609tHe8V6sQUzFQSGcYNhcuhOceZtjvN32+mpIth30A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", @@ -4399,6 +4497,7 @@ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" @@ -4413,6 +4512,7 @@ "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4426,6 +4526,7 @@ "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 10.0.0" } @@ -4446,6 +4547,7 @@ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 14" } @@ -4506,6 +4608,7 @@ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "type-fest": "^0.21.3" }, @@ -4574,7 +4677,8 @@ "url": "https://github.com/sponsors/NaturalIntelligence" } ], - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/apache-arrow": { "version": "21.2.0", @@ -4582,6 +4686,7 @@ "integrity": "sha512-Hxe6Agq26gQOM954qpzYSllJBPJl+e16U5CkfuMUhLrNba+5nKkttIVlflaovN6oaTratqMGAO8H5u/aNhmHWQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@types/node": "^25.2.0", "flatbuffers": "^25.1.24", @@ -4598,6 +4703,7 @@ "integrity": "sha512-b4e2xxj/yMeT2hNlD6x7zNFiQK2Dlmkb9CQ6v3/1G2RezKN4YS2lcEv8lzGm1mhs4f2lM4yNAMI/zUDBZBBt7w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } @@ -4607,7 +4713,8 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/applicationinsights": { "version": "1.8.10", @@ -4615,6 +4722,7 @@ "integrity": "sha512-ZLDA7mShh4mP2Z/HlFolmvhBPX1LfnbIWXrselyYVA7EKjHhri1fZzpu2EiWAmfbRxNBY6fRjoPJWbx5giKy4A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cls-hooked": "^4.2.2", "continuation-local-storage": "^3.2.1", @@ -4634,21 +4742,24 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, - "license": "Python-2.0" + "license": "Python-2.0", + "peer": true }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/array-timsort": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/assertion-error": { "version": "2.0.1", @@ -4666,6 +4777,7 @@ "integrity": "sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "stack-chain": "^1.3.7" }, @@ -4679,6 +4791,7 @@ "integrity": "sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "semver": "^5.3.0", "shimmer": "^1.1.0" @@ -4693,6 +4806,7 @@ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "license": "ISC", + "peer": true, "bin": { "semver": "bin/semver" } @@ -4703,6 +4817,7 @@ "integrity": "sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.3.1" } @@ -4712,7 +4827,8 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/at-least-node": { "version": "1.0.0", @@ -4720,6 +4836,7 @@ "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", "dev": true, "license": "ISC", + "peer": true, "engines": { "node": ">= 4.0.0" } @@ -4767,6 +4884,7 @@ "integrity": "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.6", @@ -4780,6 +4898,7 @@ "integrity": "sha512-8PJDLJv7qTTMMwdnbMvrLYuvB47M81wRtxQmEdV5w4rgbTXTt+vtPkXwajOfOdSyv/wZICJOC+/UhXH4aQ/R+w==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@babel/runtime": "^7.15.4", "is-retry-allowed": "^2.2.0" @@ -4791,6 +4910,7 @@ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "debug": "4" }, @@ -4804,6 +4924,7 @@ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -4818,6 +4939,7 @@ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": "18 || 20 || >=22" } @@ -4841,7 +4963,8 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/baseline-browser-mapping": { "version": "2.11.21", @@ -4885,6 +5008,7 @@ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -4897,6 +5021,7 @@ "integrity": "sha512-JNcyFQ64OiijEkPzUBTCe+hyPXUD/3LEldGQ6iF5LR1w00mx9o7xtDWHXBY2iItjdCFGoilOLNQbH943ut7pHA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", @@ -4922,6 +5047,7 @@ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ms": "2.0.0" } @@ -4932,6 +5058,7 @@ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -4944,7 +5071,8 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/body-parser/node_modules/raw-body": { "version": "2.5.3", @@ -4952,6 +5080,7 @@ "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", @@ -4968,6 +5097,7 @@ "integrity": "sha512-DkVaaQHymRhpYEYo9x1oo7Q7B0Y6KJUsjm3c9eTyFDby4MHLBTwZ6ZDWBel5zrYxj1WsZgC5oLpiz+93MluXeA==", "dev": true, "license": "ISC", + "peer": true, "engines": { "node": ">=20.19.0" }, @@ -4982,6 +5112,7 @@ "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "balanced-match": "^4.0.2" }, @@ -5022,7 +5153,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.11.20", "caniuse-lite": "^1.0.30001810", @@ -5057,6 +5187,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -5067,7 +5198,8 @@ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "dev": true, - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/buffer-from": { "version": "1.1.2", @@ -5082,6 +5214,7 @@ "integrity": "sha512-DdH81/zPLVS11EUgWq3tEu/xn+EzljlMYooDNdzWEnFha3R3NBMpMV1UqYIpjYHV/SgpFKMIX1Oh7o06SjM/oA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "run-applescript": "^7.0.0" }, @@ -5109,6 +5242,7 @@ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } @@ -5119,6 +5253,7 @@ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -5133,6 +5268,7 @@ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" @@ -5149,7 +5285,8 @@ "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/camelcase-css": { "version": "2.0.1", @@ -5197,6 +5334,7 @@ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -5221,6 +5359,7 @@ "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "engines": { "node": "*" } @@ -5231,6 +5370,7 @@ "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "get-func-name": "^2.0.2" }, @@ -5282,6 +5422,7 @@ "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", "dev": true, "license": "BlueOak-1.0.0", + "peer": true, "engines": { "node": ">=18" } @@ -5292,6 +5433,7 @@ "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6" }, @@ -5305,6 +5447,7 @@ "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "string-width": "^4.2.0" }, @@ -5337,6 +5480,7 @@ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -5352,6 +5496,7 @@ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -5370,6 +5515,7 @@ "integrity": "sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "async-hook-jl": "^1.7.6", "emitter-listener": "^1.0.1", @@ -5385,6 +5531,7 @@ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "license": "ISC", + "peer": true, "bin": { "semver": "bin/semver" } @@ -5415,6 +5562,7 @@ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -5438,6 +5586,7 @@ "integrity": "sha512-R2rze/hDX30uul4NZoIZ76ImSJLFxn/1/ZxtKC1L77y2X1k+yYu1joKbAtMA2Fg3hZrTOiw0I5mwVMo0cf250w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "array-timsort": "^1.0.3", "esprima": "^4.0.1" @@ -5452,6 +5601,7 @@ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "safe-buffer": "5.2.1" }, @@ -5465,6 +5615,7 @@ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -5475,6 +5626,7 @@ "integrity": "sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "async-listener": "^0.6.0", "emitter-listener": "^1.1.1" @@ -5486,6 +5638,7 @@ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -5495,7 +5648,8 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/cors": { "version": "2.8.6", @@ -5503,6 +5657,7 @@ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "object-assign": "^4", "vary": "^1" @@ -5521,6 +5676,7 @@ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -5536,6 +5692,7 @@ "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "engines": { "node": "*" } @@ -5545,7 +5702,8 @@ "resolved": "https://registry.npmjs.org/cryptr/-/cryptr-6.4.0.tgz", "integrity": "sha512-9jpMU9HMt1vhMUqNO+MPuGEpbh/f7HHZdxrd6L2DMwTuYGyt9pgUJfQyTS1Ei4/sn7qPM4FkjxUoiW79k0x8sA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/css-select": { "version": "7.0.0", @@ -5553,6 +5711,7 @@ "integrity": "sha512-snmjEVXy+1LnwXdxhYvTMj1d9tOh4HxkA1YmoayVBeeyR2C14Pum7fcxJIm4SswYspVy866eYNwlH6xC3/VH5g==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "boolbase": "^2.0.0", "css-what": "^8.0.0", @@ -5588,6 +5747,7 @@ "integrity": "sha512-DH0Bqq3DNp5tdOReuNyAA+Ev4Y2GS5FMbZpeTLP6C4CDi0h5nL0BmUPChXw3o/qbHLDWHl49sbNqQVY7bMSDdw==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=20.19.0" }, @@ -5614,7 +5774,8 @@ "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/csstype": { "version": "3.2.3", @@ -5653,6 +5814,7 @@ "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=4.0" } @@ -5663,6 +5825,7 @@ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ms": "^2.1.3" }, @@ -5688,6 +5851,7 @@ "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "mimic-response": "^3.1.0" }, @@ -5704,7 +5868,8 @@ "integrity": "sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==", "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/deep-eql": { "version": "4.1.4", @@ -5712,6 +5877,7 @@ "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "type-detect": "^4.0.0" }, @@ -5725,6 +5891,7 @@ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=4.0.0" } @@ -5735,6 +5902,7 @@ "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" @@ -5752,6 +5920,7 @@ "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -5765,6 +5934,7 @@ "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -5775,6 +5945,7 @@ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.4.0" } @@ -5785,6 +5956,7 @@ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } @@ -5795,6 +5967,7 @@ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" @@ -5806,6 +5979,7 @@ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8" } @@ -5816,6 +5990,7 @@ "integrity": "sha512-CmnVc+Hek2egPx1PeTFVta2W78xy2K/9Rkf6cC4T59S50tVnzKj+tnx5mmx5lwvCkujZ4uRrpRSuV+IVs3f90Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "address": "^1.0.1", "debug": "4" @@ -5834,6 +6009,7 @@ "integrity": "sha512-6eb9YRrimz8oTr5+JDzGmSYnXy5V7YnK5y/hd8AUDK1MssHjQKm9LlD6NSrHx4vMDF3+e/spI2hmWTviElgWZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "semver": "^5.3.0" } @@ -5844,6 +6020,7 @@ "integrity": "sha512-l126t01d2ZS9EreskvEtZPrcgstuvH3rbKy82oUhUrVmBaGx4hO9wECdl3cvZbKDYjMF3QJDB5z5dL9yWAjvZQ==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "diagnostic-channel": "*" } @@ -5854,6 +6031,7 @@ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "license": "ISC", + "peer": true, "bin": { "semver": "bin/semver" } @@ -5878,6 +6056,7 @@ "integrity": "sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "domelementtype": "^3.0.0", "domhandler": "^6.0.0", @@ -5903,6 +6082,7 @@ } ], "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=20.19.0" } @@ -5913,6 +6093,7 @@ "integrity": "sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "domelementtype": "^3.0.0" }, @@ -5930,6 +6111,7 @@ "integrity": "sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "dom-serializer": "^3.0.0", "domelementtype": "^3.0.0", @@ -5949,6 +6131,7 @@ "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=10" } @@ -6584,6 +6767,7 @@ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -6599,6 +6783,7 @@ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "safe-buffer": "^5.0.1" } @@ -6608,7 +6793,8 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/electron-to-chromium": { "version": "1.5.423", @@ -6623,6 +6809,7 @@ "integrity": "sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "shimmer": "^1.2.0" } @@ -6640,6 +6827,7 @@ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } @@ -6650,6 +6838,7 @@ "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "once": "^1.4.0" } @@ -6673,6 +6862,7 @@ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.4" } @@ -6700,6 +6890,7 @@ "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "es-errors": "^1.3.0" }, @@ -6713,6 +6904,7 @@ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", @@ -6728,7 +6920,8 @@ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/esbuild": { "version": "0.28.2", @@ -6787,7 +6980,8 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/escape-string-regexp": { "version": "1.0.5", @@ -6805,6 +6999,7 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -6829,6 +7024,7 @@ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -6839,6 +7035,7 @@ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.8.x" } @@ -6849,6 +7046,7 @@ "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "eventsource-parser": "^3.0.1" }, @@ -6862,6 +7060,7 @@ "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18.0.0" } @@ -6872,6 +7071,7 @@ "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", "dev": true, "license": "(MIT OR WTFPL)", + "peer": true, "engines": { "node": ">=6" } @@ -6892,6 +7092,7 @@ "integrity": "sha512-Bdcs4+3qlpVlx2NRn6fgX2Ue2/gGRaPeawebgclM0ERSCqDpA+owF1fdPwjJUTAJWMTuAaxjDf+hzb0/4eKvvw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -6939,6 +7140,7 @@ "integrity": "sha512-2FXbqkygil3mZiXzEtM0IjE/UBmBNot0J1GEVrJT4JbrvwOSoEtqtO+wHbiMQELWe1eNvHoYRg2dkjOid+6JkA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "debug": "^4.4.3", "ip-address": "^10.2.0" @@ -6959,6 +7161,7 @@ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ms": "2.0.0" } @@ -6968,7 +7171,8 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -7012,7 +7216,8 @@ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fast-uri": { "version": "3.1.8", @@ -7043,6 +7248,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "path-expression-matcher": "^1.6.2", "xml-naming": "^0.3.0" @@ -7060,6 +7266,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@nodable/entities": "^3.0.1", "fast-xml-builder": "^1.2.0", @@ -7112,6 +7319,7 @@ "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "escape-string-regexp": "^1.0.5" }, @@ -7141,6 +7349,7 @@ "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", @@ -7160,6 +7369,7 @@ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ms": "2.0.0" } @@ -7169,21 +7379,24 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/flatbuffers": { "version": "25.9.23", "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-25.9.23.tgz", "integrity": "sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==", "dev": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/flatted": { "version": "3.4.4", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", "dev": true, - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/follow-redirects": { "version": "1.16.1", @@ -7197,6 +7410,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=4.0" }, @@ -7212,6 +7426,7 @@ "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -7242,6 +7457,7 @@ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -7266,6 +7482,7 @@ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -7275,7 +7492,8 @@ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fs-extra": { "version": "9.1.0", @@ -7283,6 +7501,7 @@ "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", @@ -7324,6 +7543,7 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, "license": "ISC", + "peer": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -7334,6 +7554,7 @@ "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": "*" } @@ -7344,6 +7565,7 @@ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", @@ -7369,6 +7591,7 @@ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -7395,7 +7618,8 @@ "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/glob": { "version": "13.0.6", @@ -7403,6 +7627,7 @@ "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, "license": "BlueOak-1.0.0", + "peer": true, "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", @@ -7434,6 +7659,7 @@ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.4" }, @@ -7453,6 +7679,7 @@ "integrity": "sha512-P5VJMVM7qgBn6vjXMw8WG9uVI+ncf2pi72j4de4yz5ZULLj2RGqLYaKOYGsgyrViQ0tePOVlN1tDCCXXtFqXKg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "minimist": "^1.2.8", "neo-async": "^2.6.2", @@ -7475,6 +7702,7 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -7485,6 +7713,7 @@ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.4" }, @@ -7498,6 +7727,7 @@ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "has-symbols": "^1.0.3" }, @@ -7550,7 +7780,8 @@ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/htmlparser2": { "version": "10.1.0", @@ -7565,6 +7796,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", @@ -7578,6 +7810,7 @@ "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -7593,6 +7826,7 @@ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=0.12" }, @@ -7611,7 +7845,8 @@ "url": "https://github.com/sponsors/fb55" } ], - "license": "BSD-2-Clause" + "license": "BSD-2-Clause", + "peer": true }, "node_modules/htmlparser2/node_modules/domhandler": { "version": "5.0.3", @@ -7619,6 +7854,7 @@ "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "domelementtype": "^2.3.0" }, @@ -7635,6 +7871,7 @@ "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -7650,6 +7887,7 @@ "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=0.12" }, @@ -7663,6 +7901,7 @@ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", @@ -7684,6 +7923,7 @@ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" @@ -7697,7 +7937,8 @@ "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz", "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/https-proxy-agent": { "version": "7.0.6", @@ -7705,6 +7946,7 @@ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "agent-base": "^7.1.2", "debug": "4" @@ -7749,7 +7991,8 @@ "url": "https://feross.org/support" } ], - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/ignore": { "version": "5.3.2", @@ -7757,6 +8000,7 @@ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 4" } @@ -7766,14 +8010,16 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true, - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true, - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/inquirer": { "version": "12.11.1", @@ -7874,6 +8120,7 @@ "integrity": "sha512-DWBMKyeM25D70nrIiTkgQth59/G+naKOTep6huF9j+GmeK3DMD60Zvxg5d5qQnz+38AHgou6ean2LECX+lrKDg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 12" } @@ -7884,6 +8131,7 @@ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.10" } @@ -7906,7 +8154,8 @@ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/is-core-module": { "version": "2.16.2", @@ -7930,6 +8179,7 @@ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "is-docker": "cli.js" }, @@ -7979,6 +8229,7 @@ "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=20" }, @@ -7992,6 +8243,7 @@ "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "is-docker": "^3.0.0" }, @@ -8011,6 +8263,7 @@ "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "is-docker": "cli.js" }, @@ -8043,7 +8296,8 @@ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/is-retry-allowed": { "version": "2.2.0", @@ -8051,6 +8305,7 @@ "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -8069,7 +8324,8 @@ "url": "https://github.com/sponsors/NaturalIntelligence" } ], - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/is-wsl": { "version": "2.2.0", @@ -8077,6 +8333,7 @@ "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "is-docker": "^2.0.0" }, @@ -8089,7 +8346,8 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/jiti": { "version": "1.21.7", @@ -8097,7 +8355,6 @@ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, "license": "MIT", - "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -8108,6 +8365,7 @@ "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/panva" } @@ -8117,7 +8375,8 @@ "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.9.4.tgz", "integrity": "sha512-PtOMXpEGuP0RRiRXsjzHzl44dMHxSu2CPvAhinupR1tBa88me+1DPqsobl7eupn5UukjLkln1ZnAOAOXOrYG0Q==", "dev": true, - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/js-tokens": { "version": "4.0.0", @@ -8141,6 +8400,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "argparse": "^2.0.1" }, @@ -8154,7 +8414,6 @@ "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@asamuzakjp/css-color": "^5.1.11", "@asamuzakjp/dom-selector": "^7.1.1", @@ -8209,14 +8468,16 @@ "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", "dev": true, - "license": "BSD-2-Clause" + "license": "BSD-2-Clause", + "peer": true }, "node_modules/json-with-bigint": { "version": "3.5.12", "resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.12.tgz", "integrity": "sha512-uwbF/wSSuOgC7qqlq27Xp5B6a2MHVug3t0idZdTqu0JnlFvgJuH7ju+KAk/J06C7GfhoYy2gnb9wz2INqcne7w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/jsonfile": { "version": "6.2.1", @@ -8237,6 +8498,7 @@ "integrity": "sha512-K+A9hhqbn0f3pJX17Q/7H6yQfD/5OXgdrR5UE12gMXCiN9D5Xq2o5mddV2QEcX/bjla99ASsAAQUyMCCRWAEhw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": "*" } @@ -8247,6 +8509,7 @@ "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", @@ -8280,6 +8543,7 @@ "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", @@ -8292,6 +8556,7 @@ "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" @@ -8304,6 +8569,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "node-addon-api": "^4.3.0", "prebuild-install": "^7.0.1" @@ -8315,6 +8581,7 @@ "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "graceful-fs": "^4.1.9" } @@ -8325,6 +8592,7 @@ "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "dev": true, "license": "MPL-2.0", + "peer": true, "dependencies": { "detect-libc": "^2.0.3" }, @@ -8362,6 +8630,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8383,6 +8652,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8404,6 +8674,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8425,6 +8696,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8446,6 +8718,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8467,6 +8740,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8488,6 +8762,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8509,6 +8784,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8530,6 +8806,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8551,6 +8828,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8572,6 +8850,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8606,6 +8885,7 @@ "integrity": "sha512-ES/o9qotMpzpN2MHs+Iq/JcVoOj8Fa5wiQYrTdFpvAnwXL0g66XHHUc9WUMk6nAlBtGsFQ24ne+SYnvnaQ2FSw==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "css-select": "^7.0.0", "cssom": "^0.5.0", @@ -8630,56 +8910,64 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.isnumber": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.isstring": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/log4js": { "version": "6.9.1", @@ -8687,6 +8975,7 @@ "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "date-format": "^4.0.14", "debug": "^4.3.4", @@ -8716,6 +9005,7 @@ "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "get-func-name": "^2.0.1" } @@ -8755,6 +9045,7 @@ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.4" } @@ -8765,6 +9056,7 @@ "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { "charenc": "0.0.2", "crypt": "0.0.2", @@ -8784,6 +9076,7 @@ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -8794,6 +9087,7 @@ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -8814,6 +9108,7 @@ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -8838,6 +9133,7 @@ "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "mime": "cli.js" }, @@ -8851,6 +9147,7 @@ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -8861,6 +9158,7 @@ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -8874,6 +9172,7 @@ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -8887,6 +9186,7 @@ "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "dev": true, "license": "BlueOak-1.0.0", + "peer": true, "dependencies": { "brace-expansion": "^5.0.8" }, @@ -8903,6 +9203,7 @@ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -8913,6 +9214,7 @@ "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, "license": "BlueOak-1.0.0", + "peer": true, "engines": { "node": ">=16 || 14 >=14.17" } @@ -8923,6 +9225,7 @@ "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "minipass": "^7.1.2" }, @@ -8962,14 +9265,16 @@ "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/mustache": { "version": "4.2.0", @@ -8977,6 +9282,7 @@ "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "mustache": "bin/mustache" } @@ -8987,6 +9293,7 @@ "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", "dev": true, "license": "ISC", + "peer": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -9026,7 +9333,8 @@ "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/negotiator": { "version": "0.6.3", @@ -9034,6 +9342,7 @@ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -9043,14 +9352,16 @@ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/neverthrow": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/neverthrow/-/neverthrow-3.2.0.tgz", "integrity": "sha512-AINA32QbYO83L+3CBI6I5lH4LpBSlLwWteJ+uI25s4AQy6g/xz3RZuedmuNo91lLw2rY+AbPEPQdxd7mg1rXoQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/next": { "version": "14.2.35", @@ -9136,6 +9447,7 @@ "integrity": "sha512-lLdwkeg2XUVPTHqWjPltTxH81zbQNbUWC+AEb9ni50KjxkUzAAx1HGtMT21sbntzfUGfIphhcklvs3imJeqDXw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "semver": "^7.3.5" }, @@ -9148,7 +9460,8 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/node-domexception": { "version": "1.0.0", @@ -9196,6 +9509,7 @@ "integrity": "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "http2-client": "^1.2.5" }, @@ -9218,7 +9532,8 @@ "resolved": "https://registry.npmjs.org/node-machine-id/-/node-machine-id-1.1.12.tgz", "integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/node-readfiles": { "version": "0.2.0", @@ -9226,6 +9541,7 @@ "integrity": "sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "es6-promise": "^3.2.1" } @@ -9263,6 +9579,7 @@ "integrity": "sha512-GX0gsdbGVCgnRgbeGaubfjpBXyYRWOOCVeYh08bSQvDZqxz5ndXs1OTfAt/h36G1xvI94YIspsI0sVFqAV9+RQ==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "boolbase": "^2.0.0" }, @@ -9280,6 +9597,7 @@ "integrity": "sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { "fast-safe-stringify": "^2.0.7" } @@ -9290,6 +9608,7 @@ "integrity": "sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { "@exodus/schemasafe": "^1.0.0-rc.2", "should": "^13.2.1", @@ -9305,6 +9624,7 @@ "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "dev": true, "license": "ISC", + "peer": true, "engines": { "node": ">= 6" } @@ -9315,6 +9635,7 @@ "integrity": "sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { "node-fetch-h2": "^2.3.0", "oas-kit-common": "^1.0.8", @@ -9335,6 +9656,7 @@ "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "dev": true, "license": "ISC", + "peer": true, "engines": { "node": ">= 6" } @@ -9345,6 +9667,7 @@ "integrity": "sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "funding": { "url": "https://github.com/Mermade/oas-kit?sponsor=1" } @@ -9355,6 +9678,7 @@ "integrity": "sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { "call-me-maybe": "^1.0.1", "oas-kit-common": "^1.0.8", @@ -9375,6 +9699,7 @@ "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "dev": true, "license": "ISC", + "peer": true, "engines": { "node": ">= 6" } @@ -9405,6 +9730,7 @@ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.4" }, @@ -10054,6 +10380,7 @@ "integrity": "sha512-kxV247eRDdGhBKOZYBhAvgzm/PveXdUXvZR5bi0mDYWFeY6wHoDuCSjRw00khRR///1ZvgixfvS4CqSfezyptQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@microsoft/app-manifest": "^1.0.2", "adm-zip": "0.5.16", @@ -10075,6 +10402,7 @@ "integrity": "sha512-2eOdCCYJ5bhCe2p9KKETdg1UNshsKaT0lDU/jNopAg3t7zC1WxwvofTSO/+4Log5L4Re+wUdV8MqrQikZBa7+Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@xmldom/xmldom": "^0.8.5", "commander": "^9.0.0", @@ -10090,6 +10418,7 @@ "integrity": "sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" } @@ -10100,6 +10429,7 @@ "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": "^12.20.0 || >=14" } @@ -10110,6 +10440,7 @@ "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12.0" } @@ -10120,6 +10451,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -10133,6 +10465,7 @@ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -10148,6 +10481,7 @@ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "color-name": "1.1.3" } @@ -10157,7 +10491,8 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/office-addin-manifest/node_modules/commander": { "version": "13.1.0", @@ -10165,6 +10500,7 @@ "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -10175,6 +10511,7 @@ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -10190,6 +10527,7 @@ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=4" } @@ -10200,6 +10538,7 @@ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "dev": true, "license": "MIT", + "peer": true, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -10210,6 +10549,7 @@ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -10231,6 +10571,7 @@ "integrity": "sha512-MUbcqOodtlOUNhLKjSdqeLBHFVd7Qh5pUODqmQe2VLBKk7PuRgl4dCk6bp2dU79vl6BcDyrRERpkyVnQg6zdFg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "commander": "^13.0.0" }, @@ -10244,6 +10585,7 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -10256,7 +10598,8 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/office-addin-manifest/node_modules/universalify": { "version": "0.1.2", @@ -10264,6 +10607,7 @@ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 4.0.0" } @@ -10278,6 +10622,7 @@ "https://github.com/sponsors/ctavan" ], "license": "MIT", + "peer": true, "bin": { "uuid": "dist-node/bin/uuid" } @@ -10287,7 +10632,8 @@ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "dev": true, - "license": "BSD-2-Clause" + "license": "BSD-2-Clause", + "peer": true }, "node_modules/office-addin-manifest/node_modules/whatwg-url": { "version": "5.0.0", @@ -10295,6 +10641,7 @@ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -10378,6 +10725,7 @@ "integrity": "sha512-WjthzxXY5gl78+jh91LczM9zg26zU8tc3afLi2bUcrMaPc6LaRaKJhw6NsyRB73kVDx76DGTe6Ws6sxufghOdQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "adm-zip": "0.5.12", "commander": "^13.0.0", @@ -10397,6 +10745,7 @@ "integrity": "sha512-6TVU49mK6KZb4qG6xWaaM4C7sA/sgUMLy/JYMOzkcp3BvVLpW0fXDFQiIzAuxFCt/2+xD7fNIiPFAoLZPhVNLQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6.0" } @@ -10407,6 +10756,7 @@ "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -10417,6 +10767,7 @@ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -10432,6 +10783,7 @@ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "dev": true, "license": "MIT", + "peer": true, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -10442,6 +10794,7 @@ "integrity": "sha512-MUbcqOodtlOUNhLKjSdqeLBHFVd7Qh5pUODqmQe2VLBKk7PuRgl4dCk6bp2dU79vl6BcDyrRERpkyVnQg6zdFg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "commander": "^13.0.0" }, @@ -10455,6 +10808,7 @@ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 4.0.0" } @@ -10488,6 +10842,7 @@ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ee-first": "1.1.1" }, @@ -10501,6 +10856,7 @@ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "wrappy": "1" } @@ -10511,6 +10867,7 @@ "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", @@ -10536,7 +10893,8 @@ "resolved": "https://registry.npmjs.org/original-fs/-/original-fs-1.2.0.tgz", "integrity": "sha512-IGo+qFumpIV65oDchJrqL0BOk9kr82fObnTesNJt8t3YgP6vfqcmRs0ofPzg3D9PKMeBHt7lrg1k/6L+oFdS8g==", "dev": true, - "license": "Unlicense" + "license": "Unlicense", + "peer": true }, "node_modules/pako": { "version": "1.0.11", @@ -10563,6 +10921,7 @@ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } @@ -10579,6 +10938,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=14.0.0" } @@ -10589,6 +10949,7 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -10606,6 +10967,7 @@ "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "dev": true, "license": "BlueOak-1.0.0", + "peer": true, "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" @@ -10622,7 +10984,8 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/pathval": { "version": "1.1.1", @@ -10630,6 +10993,7 @@ "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": "*" } @@ -10724,6 +11088,7 @@ "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=16.20.0" } @@ -10757,7 +11122,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.18", "picocolors": "^1.1.1", @@ -10906,7 +11270,6 @@ "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.9.tgz", "integrity": "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw==", "license": "Unlicense", - "peer": true, "engines": { "node": ">=12" }, @@ -10921,6 +11284,7 @@ "integrity": "sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=20" }, @@ -10935,6 +11299,7 @@ "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", @@ -10962,6 +11327,7 @@ "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", @@ -10973,7 +11339,8 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/proxy-addr": { "version": "2.0.8", @@ -10981,6 +11348,7 @@ "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -10999,6 +11367,7 @@ "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" } @@ -11009,6 +11378,7 @@ "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -11030,6 +11400,7 @@ "integrity": "sha512-5Px6J7rfHBAGUhNQ3es4YzcOdBBw0SDcVtThJml4RdnKw4tKs2epA0kPZ+K8X+WiHktT93RHVcQ/3ym5aTCgtg==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { "es-define-property": "^1.0.1", "side-channel": "^1.1.1" @@ -11068,6 +11439,7 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -11078,6 +11450,7 @@ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", @@ -11094,6 +11467,7 @@ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "peer": true, "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -11109,7 +11483,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -11122,7 +11495,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -11158,6 +11530,7 @@ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -11185,7 +11558,8 @@ "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==", "dev": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/reftools": { "version": "1.1.9", @@ -11193,6 +11567,7 @@ "integrity": "sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "funding": { "url": "https://github.com/Mermade/oas-kit?sponsor=1" } @@ -11203,6 +11578,7 @@ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -11255,6 +11631,7 @@ "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 4" } @@ -11275,7 +11652,8 @@ "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/rolldown": { "version": "1.2.8", @@ -11283,6 +11661,7 @@ "integrity": "sha512-Z67nTmhZe7anqnM/EjI392w5i/ANUinjip7QYsOyN37oayduxt3ksdX0hf5OOamkAd53BiIHfbfSzfUmzKFQqQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@oxc-project/types": "=0.149.0", "@rolldown/pluginutils": "^1.0.0" @@ -11317,6 +11696,7 @@ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", @@ -11334,6 +11714,7 @@ "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" @@ -11345,6 +11726,7 @@ "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -11358,6 +11740,7 @@ "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.12.0" } @@ -11415,7 +11798,8 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/safer-buffer": { "version": "2.1.2", @@ -11462,6 +11846,7 @@ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", + "peer": true, "bin": { "semver": "bin/semver.js" }, @@ -11475,6 +11860,7 @@ "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -11500,6 +11886,7 @@ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ms": "2.0.0" } @@ -11509,7 +11896,8 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/send/node_modules/mime": { "version": "1.6.0", @@ -11517,6 +11905,7 @@ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "mime": "cli.js" }, @@ -11530,6 +11919,7 @@ "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", @@ -11545,7 +11935,8 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true, - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/shebang-command": { "version": "2.0.0", @@ -11553,6 +11944,7 @@ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -11566,6 +11958,7 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -11576,6 +11969,7 @@ "integrity": "sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.4" }, @@ -11588,7 +11982,8 @@ "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", "dev": true, - "license": "BSD-2-Clause" + "license": "BSD-2-Clause", + "peer": true }, "node_modules/should": { "version": "13.2.3", @@ -11596,6 +11991,7 @@ "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "should-equal": "^2.0.0", "should-format": "^3.0.3", @@ -11610,6 +12006,7 @@ "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "should-type": "^1.4.0" } @@ -11620,6 +12017,7 @@ "integrity": "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "should-type": "^1.3.0", "should-type-adaptors": "^1.0.1" @@ -11630,7 +12028,8 @@ "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", "integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/should-type-adaptors": { "version": "1.1.0", @@ -11638,6 +12037,7 @@ "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "should-type": "^1.3.0", "should-util": "^1.0.0" @@ -11648,7 +12048,8 @@ "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/side-channel": { "version": "1.1.1", @@ -11656,6 +12057,7 @@ "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4", @@ -11676,6 +12078,7 @@ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" @@ -11693,6 +12096,7 @@ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -11712,6 +12116,7 @@ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -11765,7 +12170,8 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/simple-get": { "version": "4.0.1", @@ -11787,6 +12193,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", @@ -11828,7 +12235,8 @@ "resolved": "https://registry.npmjs.org/stack-chain/-/stack-chain-1.3.7.tgz", "integrity": "sha512-D8cWtWVdIe/jBA7v5p5Hwl5yOSOrmZPWDPe2KxQ5UAGD+nxbxU0lKXA4h85Ta6+qgdKVL3vUxsbIZjc1kBG7ug==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/stackback": { "version": "0.0.2", @@ -11843,6 +12251,7 @@ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } @@ -11860,6 +12269,7 @@ "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "date-format": "^4.0.14", "debug": "^4.3.4", @@ -11875,6 +12285,7 @@ "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", @@ -11890,6 +12301,7 @@ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "dev": true, "license": "MIT", + "peer": true, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -11900,6 +12312,7 @@ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 4.0.0" } @@ -11918,6 +12331,7 @@ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -11969,6 +12383,7 @@ "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -11985,6 +12400,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "anynum": "^1.0.1" } @@ -12041,6 +12457,7 @@ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -12076,6 +12493,7 @@ "integrity": "sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { "call-me-maybe": "^1.0.1", "node-fetch": "^2.6.1", @@ -12104,6 +12522,7 @@ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -12124,14 +12543,16 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/swagger2openapi/node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "dev": true, - "license": "BSD-2-Clause" + "license": "BSD-2-Clause", + "peer": true }, "node_modules/swagger2openapi/node_modules/whatwg-url": { "version": "5.0.0", @@ -12139,6 +12560,7 @@ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -12150,6 +12572,7 @@ "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "dev": true, "license": "ISC", + "peer": true, "engines": { "node": ">= 6" } @@ -12205,6 +12628,7 @@ "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", "dev": true, "license": "BlueOak-1.0.0", + "peer": true, "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", @@ -12222,6 +12646,7 @@ "integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", @@ -12234,7 +12659,8 @@ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "dev": true, - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/tar-stream": { "version": "2.2.0", @@ -12242,6 +12668,7 @@ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -12259,6 +12686,7 @@ "integrity": "sha512-UDdUF9kV2hYdlv+7AgqP2kXarVSUhjK7tg1BUflIRGEgND0/QoNpN64rcEuhEcM8AIbW65yrCopJWqRhLZ3m8w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "axios": "^1.6.1" } @@ -12269,6 +12697,7 @@ "integrity": "sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -12287,7 +12716,8 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/thenify": { "version": "3.3.1", @@ -12373,7 +12803,6 @@ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -12407,6 +12836,7 @@ "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=14.14" } @@ -12430,6 +12860,7 @@ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.6" } @@ -12466,6 +12897,7 @@ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true, "license": "MIT", + "peer": true, "bin": { "tree-kill": "cli.js" } @@ -12508,6 +12940,7 @@ "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "safe-buffer": "^5.0.1" }, @@ -12521,6 +12954,7 @@ "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=4" } @@ -12531,6 +12965,7 @@ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, "license": "(MIT OR CC0-1.0)", + "peer": true, "engines": { "node": ">=10" }, @@ -12544,6 +12979,7 @@ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -12557,7 +12993,8 @@ "resolved": "https://registry.npmjs.org/typedi/-/typedi-0.10.0.tgz", "integrity": "sha512-v3UJF8xm68BBj6AF4oQML3ikrfK2c9EmZUyLOfShpJuItAqVBHWP/KtpGinkSsIiP6EZyyb6Z3NXyW9dgS9X1w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/typescript": { "version": "5.9.3", @@ -12580,6 +13017,7 @@ "dev": true, "license": "BSD-2-Clause", "optional": true, + "peer": true, "bin": { "uglifyjs": "bin/uglifyjs" }, @@ -12592,14 +13030,16 @@ "resolved": "https://registry.npmjs.org/uhyphen/-/uhyphen-0.2.0.tgz", "integrity": "sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==", "dev": true, - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/underscore": { "version": "1.13.8", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/undici": { "version": "7.30.0", @@ -12634,6 +13074,7 @@ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } @@ -12691,6 +13132,7 @@ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.4.0" } @@ -12705,6 +13147,7 @@ "https://github.com/sponsors/ctavan" ], "license": "MIT", + "peer": true, "bin": { "uuid": "dist/esm/bin/uuid" } @@ -12715,6 +13158,7 @@ "integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.10" } @@ -12725,6 +13169,7 @@ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } @@ -12735,6 +13180,7 @@ "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", @@ -12813,6 +13259,7 @@ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -12922,6 +13369,7 @@ "integrity": "sha512-perEnXQdQOJMTDFNv+UF3h1Y0z4iSiaN9jIlb0OqIYgosPCZGYh/MCUlkFtV2668PL69lRDO32hmvL2yiidUYg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8.0.0 || >=10.0.0" } @@ -12990,6 +13438,7 @@ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "isexe": "^2.0.0" }, @@ -13029,7 +13478,8 @@ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/wrap-ansi": { "version": "6.2.0", @@ -13051,7 +13501,8 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true, - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/ws": { "version": "7.5.13", @@ -13081,6 +13532,7 @@ "integrity": "sha512-c16PKLIRxBYpR3jSWsdS7SFp0COm5SOQdiFjY818dYFHJbMGuLBszpF07LBWmOS1uzrHa9VGdynsEgoeKy0oOQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.2.0" @@ -13098,6 +13550,7 @@ "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "is-inside-container": "^1.0.0" }, @@ -13130,6 +13583,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=16.0.0" } @@ -13180,6 +13634,7 @@ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, "license": "ISC", + "peer": true, "engines": { "node": ">=10" } @@ -13190,6 +13645,7 @@ "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", "dev": true, "license": "BlueOak-1.0.0", + "peer": true, "engines": { "node": ">=18" } @@ -13200,6 +13656,7 @@ "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", "dev": true, "license": "ISC", + "peer": true, "bin": { "yaml": "bin.mjs" }, @@ -13216,6 +13673,7 @@ "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -13235,6 +13693,7 @@ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -13269,6 +13728,7 @@ "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "dev": true, "license": "ISC", + "peer": true, "peerDependencies": { "zod": "^3.25.28 || ^4" } diff --git a/public/note/swagger-ui/swagger-ui-bundle.js b/public/note/swagger-ui/swagger-ui-bundle.js index 745261d..a9fd9e5 100644 --- a/public/note/swagger-ui/swagger-ui-bundle.js +++ b/public/note/swagger-ui/swagger-ui-bundle.js @@ -1,2 +1,2 @@ /*! For license information please see swagger-ui-bundle.js.LICENSE.txt */ -!function webpackUniversalModuleDefinition(s,o){"object"==typeof exports&&"object"==typeof module?module.exports=o():"function"==typeof define&&define.amd?define([],o):"object"==typeof exports?exports.SwaggerUIBundle=o():s.SwaggerUIBundle=o()}(this,(()=>(()=>{var s={67526(s,o){"use strict";o.byteLength=function byteLength(s){var o=getLens(s),i=o[0],a=o[1];return 3*(i+a)/4-a},o.toByteArray=function toByteArray(s){var o,i,_=getLens(s),w=_[0],x=_[1],C=new u(function _byteLength(s,o,i){return 3*(o+i)/4-i}(0,w,x)),j=0,L=x>0?w-4:w;for(i=0;i>16&255,C[j++]=o>>8&255,C[j++]=255&o;2===x&&(o=a[s.charCodeAt(i)]<<2|a[s.charCodeAt(i+1)]>>4,C[j++]=255&o);1===x&&(o=a[s.charCodeAt(i)]<<10|a[s.charCodeAt(i+1)]<<4|a[s.charCodeAt(i+2)]>>2,C[j++]=o>>8&255,C[j++]=255&o);return C},o.fromByteArray=function fromByteArray(s){for(var o,a=s.length,u=a%3,_=[],w=16383,x=0,C=a-u;xC?C:x+w));1===u?(o=s[a-1],_.push(i[o>>2]+i[o<<4&63]+"==")):2===u&&(o=(s[a-2]<<8)+s[a-1],_.push(i[o>>10]+i[o>>4&63]+i[o<<2&63]+"="));return _.join("")};for(var i=[],a=[],u="undefined"!=typeof Uint8Array?Uint8Array:Array,_="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",w=0;w<64;++w)i[w]=_[w],a[_.charCodeAt(w)]=w;function getLens(s){var o=s.length;if(o%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var i=s.indexOf("=");return-1===i&&(i=o),[i,i===o?0:4-i%4]}function encodeChunk(s,o,a){for(var u,_,w=[],x=o;x>18&63]+i[_>>12&63]+i[_>>6&63]+i[63&_]);return w.join("")}a["-".charCodeAt(0)]=62,a["_".charCodeAt(0)]=63},48287(s,o,i){"use strict";const a=i(67526),u=i(251),_="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):null;o.Buffer=Buffer,o.SlowBuffer=function SlowBuffer(s){+s!=s&&(s=0);return Buffer.alloc(+s)},o.INSPECT_MAX_BYTES=50;const w=2147483647;function createBuffer(s){if(s>w)throw new RangeError('The value "'+s+'" is invalid for option "size"');const o=new Uint8Array(s);return Object.setPrototypeOf(o,Buffer.prototype),o}function Buffer(s,o,i){if("number"==typeof s){if("string"==typeof o)throw new TypeError('The "string" argument must be of type string. Received type number');return allocUnsafe(s)}return from(s,o,i)}function from(s,o,i){if("string"==typeof s)return function fromString(s,o){"string"==typeof o&&""!==o||(o="utf8");if(!Buffer.isEncoding(o))throw new TypeError("Unknown encoding: "+o);const i=0|byteLength(s,o);let a=createBuffer(i);const u=a.write(s,o);u!==i&&(a=a.slice(0,u));return a}(s,o);if(ArrayBuffer.isView(s))return function fromArrayView(s){if(isInstance(s,Uint8Array)){const o=new Uint8Array(s);return fromArrayBuffer(o.buffer,o.byteOffset,o.byteLength)}return fromArrayLike(s)}(s);if(null==s)throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof s);if(isInstance(s,ArrayBuffer)||s&&isInstance(s.buffer,ArrayBuffer))return fromArrayBuffer(s,o,i);if("undefined"!=typeof SharedArrayBuffer&&(isInstance(s,SharedArrayBuffer)||s&&isInstance(s.buffer,SharedArrayBuffer)))return fromArrayBuffer(s,o,i);if("number"==typeof s)throw new TypeError('The "value" argument must not be of type number. Received type number');const a=s.valueOf&&s.valueOf();if(null!=a&&a!==s)return Buffer.from(a,o,i);const u=function fromObject(s){if(Buffer.isBuffer(s)){const o=0|checked(s.length),i=createBuffer(o);return 0===i.length||s.copy(i,0,0,o),i}if(void 0!==s.length)return"number"!=typeof s.length||numberIsNaN(s.length)?createBuffer(0):fromArrayLike(s);if("Buffer"===s.type&&Array.isArray(s.data))return fromArrayLike(s.data)}(s);if(u)return u;if("undefined"!=typeof Symbol&&null!=Symbol.toPrimitive&&"function"==typeof s[Symbol.toPrimitive])return Buffer.from(s[Symbol.toPrimitive]("string"),o,i);throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof s)}function assertSize(s){if("number"!=typeof s)throw new TypeError('"size" argument must be of type number');if(s<0)throw new RangeError('The value "'+s+'" is invalid for option "size"')}function allocUnsafe(s){return assertSize(s),createBuffer(s<0?0:0|checked(s))}function fromArrayLike(s){const o=s.length<0?0:0|checked(s.length),i=createBuffer(o);for(let a=0;a=w)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+w.toString(16)+" bytes");return 0|s}function byteLength(s,o){if(Buffer.isBuffer(s))return s.length;if(ArrayBuffer.isView(s)||isInstance(s,ArrayBuffer))return s.byteLength;if("string"!=typeof s)throw new TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer. Received type '+typeof s);const i=s.length,a=arguments.length>2&&!0===arguments[2];if(!a&&0===i)return 0;let u=!1;for(;;)switch(o){case"ascii":case"latin1":case"binary":return i;case"utf8":case"utf-8":return utf8ToBytes(s).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*i;case"hex":return i>>>1;case"base64":return base64ToBytes(s).length;default:if(u)return a?-1:utf8ToBytes(s).length;o=(""+o).toLowerCase(),u=!0}}function slowToString(s,o,i){let a=!1;if((void 0===o||o<0)&&(o=0),o>this.length)return"";if((void 0===i||i>this.length)&&(i=this.length),i<=0)return"";if((i>>>=0)<=(o>>>=0))return"";for(s||(s="utf8");;)switch(s){case"hex":return hexSlice(this,o,i);case"utf8":case"utf-8":return utf8Slice(this,o,i);case"ascii":return asciiSlice(this,o,i);case"latin1":case"binary":return latin1Slice(this,o,i);case"base64":return base64Slice(this,o,i);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return utf16leSlice(this,o,i);default:if(a)throw new TypeError("Unknown encoding: "+s);s=(s+"").toLowerCase(),a=!0}}function swap(s,o,i){const a=s[o];s[o]=s[i],s[i]=a}function bidirectionalIndexOf(s,o,i,a,u){if(0===s.length)return-1;if("string"==typeof i?(a=i,i=0):i>2147483647?i=2147483647:i<-2147483648&&(i=-2147483648),numberIsNaN(i=+i)&&(i=u?0:s.length-1),i<0&&(i=s.length+i),i>=s.length){if(u)return-1;i=s.length-1}else if(i<0){if(!u)return-1;i=0}if("string"==typeof o&&(o=Buffer.from(o,a)),Buffer.isBuffer(o))return 0===o.length?-1:arrayIndexOf(s,o,i,a,u);if("number"==typeof o)return o&=255,"function"==typeof Uint8Array.prototype.indexOf?u?Uint8Array.prototype.indexOf.call(s,o,i):Uint8Array.prototype.lastIndexOf.call(s,o,i):arrayIndexOf(s,[o],i,a,u);throw new TypeError("val must be string, number or Buffer")}function arrayIndexOf(s,o,i,a,u){let _,w=1,x=s.length,C=o.length;if(void 0!==a&&("ucs2"===(a=String(a).toLowerCase())||"ucs-2"===a||"utf16le"===a||"utf-16le"===a)){if(s.length<2||o.length<2)return-1;w=2,x/=2,C/=2,i/=2}function read(s,o){return 1===w?s[o]:s.readUInt16BE(o*w)}if(u){let a=-1;for(_=i;_x&&(i=x-C),_=i;_>=0;_--){let i=!0;for(let a=0;au&&(a=u):a=u;const _=o.length;let w;for(a>_/2&&(a=_/2),w=0;w>8,u=i%256,_.push(u),_.push(a);return _}(o,s.length-i),s,i,a)}function base64Slice(s,o,i){return 0===o&&i===s.length?a.fromByteArray(s):a.fromByteArray(s.slice(o,i))}function utf8Slice(s,o,i){i=Math.min(s.length,i);const a=[];let u=o;for(;u239?4:o>223?3:o>191?2:1;if(u+w<=i){let i,a,x,C;switch(w){case 1:o<128&&(_=o);break;case 2:i=s[u+1],128==(192&i)&&(C=(31&o)<<6|63&i,C>127&&(_=C));break;case 3:i=s[u+1],a=s[u+2],128==(192&i)&&128==(192&a)&&(C=(15&o)<<12|(63&i)<<6|63&a,C>2047&&(C<55296||C>57343)&&(_=C));break;case 4:i=s[u+1],a=s[u+2],x=s[u+3],128==(192&i)&&128==(192&a)&&128==(192&x)&&(C=(15&o)<<18|(63&i)<<12|(63&a)<<6|63&x,C>65535&&C<1114112&&(_=C))}}null===_?(_=65533,w=1):_>65535&&(_-=65536,a.push(_>>>10&1023|55296),_=56320|1023&_),a.push(_),u+=w}return function decodeCodePointsArray(s){const o=s.length;if(o<=x)return String.fromCharCode.apply(String,s);let i="",a=0;for(;aa.length?(Buffer.isBuffer(o)||(o=Buffer.from(o)),o.copy(a,u)):Uint8Array.prototype.set.call(a,o,u);else{if(!Buffer.isBuffer(o))throw new TypeError('"list" argument must be an Array of Buffers');o.copy(a,u)}u+=o.length}return a},Buffer.byteLength=byteLength,Buffer.prototype._isBuffer=!0,Buffer.prototype.swap16=function swap16(){const s=this.length;if(s%2!=0)throw new RangeError("Buffer size must be a multiple of 16-bits");for(let o=0;oi&&(s+=" ... "),""},_&&(Buffer.prototype[_]=Buffer.prototype.inspect),Buffer.prototype.compare=function compare(s,o,i,a,u){if(isInstance(s,Uint8Array)&&(s=Buffer.from(s,s.offset,s.byteLength)),!Buffer.isBuffer(s))throw new TypeError('The "target" argument must be one of type Buffer or Uint8Array. Received type '+typeof s);if(void 0===o&&(o=0),void 0===i&&(i=s?s.length:0),void 0===a&&(a=0),void 0===u&&(u=this.length),o<0||i>s.length||a<0||u>this.length)throw new RangeError("out of range index");if(a>=u&&o>=i)return 0;if(a>=u)return-1;if(o>=i)return 1;if(this===s)return 0;let _=(u>>>=0)-(a>>>=0),w=(i>>>=0)-(o>>>=0);const x=Math.min(_,w),C=this.slice(a,u),j=s.slice(o,i);for(let s=0;s>>=0,isFinite(i)?(i>>>=0,void 0===a&&(a="utf8")):(a=i,i=void 0)}const u=this.length-o;if((void 0===i||i>u)&&(i=u),s.length>0&&(i<0||o<0)||o>this.length)throw new RangeError("Attempt to write outside buffer bounds");a||(a="utf8");let _=!1;for(;;)switch(a){case"hex":return hexWrite(this,s,o,i);case"utf8":case"utf-8":return utf8Write(this,s,o,i);case"ascii":case"latin1":case"binary":return asciiWrite(this,s,o,i);case"base64":return base64Write(this,s,o,i);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return ucs2Write(this,s,o,i);default:if(_)throw new TypeError("Unknown encoding: "+a);a=(""+a).toLowerCase(),_=!0}},Buffer.prototype.toJSON=function toJSON(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};const x=4096;function asciiSlice(s,o,i){let a="";i=Math.min(s.length,i);for(let u=o;ua)&&(i=a);let u="";for(let a=o;ai)throw new RangeError("Trying to access beyond buffer length")}function checkInt(s,o,i,a,u,_){if(!Buffer.isBuffer(s))throw new TypeError('"buffer" argument must be a Buffer instance');if(o>u||o<_)throw new RangeError('"value" argument is out of bounds');if(i+a>s.length)throw new RangeError("Index out of range")}function wrtBigUInt64LE(s,o,i,a,u){checkIntBI(o,a,u,s,i,7);let _=Number(o&BigInt(4294967295));s[i++]=_,_>>=8,s[i++]=_,_>>=8,s[i++]=_,_>>=8,s[i++]=_;let w=Number(o>>BigInt(32)&BigInt(4294967295));return s[i++]=w,w>>=8,s[i++]=w,w>>=8,s[i++]=w,w>>=8,s[i++]=w,i}function wrtBigUInt64BE(s,o,i,a,u){checkIntBI(o,a,u,s,i,7);let _=Number(o&BigInt(4294967295));s[i+7]=_,_>>=8,s[i+6]=_,_>>=8,s[i+5]=_,_>>=8,s[i+4]=_;let w=Number(o>>BigInt(32)&BigInt(4294967295));return s[i+3]=w,w>>=8,s[i+2]=w,w>>=8,s[i+1]=w,w>>=8,s[i]=w,i+8}function checkIEEE754(s,o,i,a,u,_){if(i+a>s.length)throw new RangeError("Index out of range");if(i<0)throw new RangeError("Index out of range")}function writeFloat(s,o,i,a,_){return o=+o,i>>>=0,_||checkIEEE754(s,0,i,4),u.write(s,o,i,a,23,4),i+4}function writeDouble(s,o,i,a,_){return o=+o,i>>>=0,_||checkIEEE754(s,0,i,8),u.write(s,o,i,a,52,8),i+8}Buffer.prototype.slice=function slice(s,o){const i=this.length;(s=~~s)<0?(s+=i)<0&&(s=0):s>i&&(s=i),(o=void 0===o?i:~~o)<0?(o+=i)<0&&(o=0):o>i&&(o=i),o>>=0,o>>>=0,i||checkOffset(s,o,this.length);let a=this[s],u=1,_=0;for(;++_>>=0,o>>>=0,i||checkOffset(s,o,this.length);let a=this[s+--o],u=1;for(;o>0&&(u*=256);)a+=this[s+--o]*u;return a},Buffer.prototype.readUint8=Buffer.prototype.readUInt8=function readUInt8(s,o){return s>>>=0,o||checkOffset(s,1,this.length),this[s]},Buffer.prototype.readUint16LE=Buffer.prototype.readUInt16LE=function readUInt16LE(s,o){return s>>>=0,o||checkOffset(s,2,this.length),this[s]|this[s+1]<<8},Buffer.prototype.readUint16BE=Buffer.prototype.readUInt16BE=function readUInt16BE(s,o){return s>>>=0,o||checkOffset(s,2,this.length),this[s]<<8|this[s+1]},Buffer.prototype.readUint32LE=Buffer.prototype.readUInt32LE=function readUInt32LE(s,o){return s>>>=0,o||checkOffset(s,4,this.length),(this[s]|this[s+1]<<8|this[s+2]<<16)+16777216*this[s+3]},Buffer.prototype.readUint32BE=Buffer.prototype.readUInt32BE=function readUInt32BE(s,o){return s>>>=0,o||checkOffset(s,4,this.length),16777216*this[s]+(this[s+1]<<16|this[s+2]<<8|this[s+3])},Buffer.prototype.readBigUInt64LE=defineBigIntMethod((function readBigUInt64LE(s){validateNumber(s>>>=0,"offset");const o=this[s],i=this[s+7];void 0!==o&&void 0!==i||boundsError(s,this.length-8);const a=o+256*this[++s]+65536*this[++s]+this[++s]*2**24,u=this[++s]+256*this[++s]+65536*this[++s]+i*2**24;return BigInt(a)+(BigInt(u)<>>=0,"offset");const o=this[s],i=this[s+7];void 0!==o&&void 0!==i||boundsError(s,this.length-8);const a=o*2**24+65536*this[++s]+256*this[++s]+this[++s],u=this[++s]*2**24+65536*this[++s]+256*this[++s]+i;return(BigInt(a)<>>=0,o>>>=0,i||checkOffset(s,o,this.length);let a=this[s],u=1,_=0;for(;++_=u&&(a-=Math.pow(2,8*o)),a},Buffer.prototype.readIntBE=function readIntBE(s,o,i){s>>>=0,o>>>=0,i||checkOffset(s,o,this.length);let a=o,u=1,_=this[s+--a];for(;a>0&&(u*=256);)_+=this[s+--a]*u;return u*=128,_>=u&&(_-=Math.pow(2,8*o)),_},Buffer.prototype.readInt8=function readInt8(s,o){return s>>>=0,o||checkOffset(s,1,this.length),128&this[s]?-1*(255-this[s]+1):this[s]},Buffer.prototype.readInt16LE=function readInt16LE(s,o){s>>>=0,o||checkOffset(s,2,this.length);const i=this[s]|this[s+1]<<8;return 32768&i?4294901760|i:i},Buffer.prototype.readInt16BE=function readInt16BE(s,o){s>>>=0,o||checkOffset(s,2,this.length);const i=this[s+1]|this[s]<<8;return 32768&i?4294901760|i:i},Buffer.prototype.readInt32LE=function readInt32LE(s,o){return s>>>=0,o||checkOffset(s,4,this.length),this[s]|this[s+1]<<8|this[s+2]<<16|this[s+3]<<24},Buffer.prototype.readInt32BE=function readInt32BE(s,o){return s>>>=0,o||checkOffset(s,4,this.length),this[s]<<24|this[s+1]<<16|this[s+2]<<8|this[s+3]},Buffer.prototype.readBigInt64LE=defineBigIntMethod((function readBigInt64LE(s){validateNumber(s>>>=0,"offset");const o=this[s],i=this[s+7];void 0!==o&&void 0!==i||boundsError(s,this.length-8);const a=this[s+4]+256*this[s+5]+65536*this[s+6]+(i<<24);return(BigInt(a)<>>=0,"offset");const o=this[s],i=this[s+7];void 0!==o&&void 0!==i||boundsError(s,this.length-8);const a=(o<<24)+65536*this[++s]+256*this[++s]+this[++s];return(BigInt(a)<>>=0,o||checkOffset(s,4,this.length),u.read(this,s,!0,23,4)},Buffer.prototype.readFloatBE=function readFloatBE(s,o){return s>>>=0,o||checkOffset(s,4,this.length),u.read(this,s,!1,23,4)},Buffer.prototype.readDoubleLE=function readDoubleLE(s,o){return s>>>=0,o||checkOffset(s,8,this.length),u.read(this,s,!0,52,8)},Buffer.prototype.readDoubleBE=function readDoubleBE(s,o){return s>>>=0,o||checkOffset(s,8,this.length),u.read(this,s,!1,52,8)},Buffer.prototype.writeUintLE=Buffer.prototype.writeUIntLE=function writeUIntLE(s,o,i,a){if(s=+s,o>>>=0,i>>>=0,!a){checkInt(this,s,o,i,Math.pow(2,8*i)-1,0)}let u=1,_=0;for(this[o]=255&s;++_>>=0,i>>>=0,!a){checkInt(this,s,o,i,Math.pow(2,8*i)-1,0)}let u=i-1,_=1;for(this[o+u]=255&s;--u>=0&&(_*=256);)this[o+u]=s/_&255;return o+i},Buffer.prototype.writeUint8=Buffer.prototype.writeUInt8=function writeUInt8(s,o,i){return s=+s,o>>>=0,i||checkInt(this,s,o,1,255,0),this[o]=255&s,o+1},Buffer.prototype.writeUint16LE=Buffer.prototype.writeUInt16LE=function writeUInt16LE(s,o,i){return s=+s,o>>>=0,i||checkInt(this,s,o,2,65535,0),this[o]=255&s,this[o+1]=s>>>8,o+2},Buffer.prototype.writeUint16BE=Buffer.prototype.writeUInt16BE=function writeUInt16BE(s,o,i){return s=+s,o>>>=0,i||checkInt(this,s,o,2,65535,0),this[o]=s>>>8,this[o+1]=255&s,o+2},Buffer.prototype.writeUint32LE=Buffer.prototype.writeUInt32LE=function writeUInt32LE(s,o,i){return s=+s,o>>>=0,i||checkInt(this,s,o,4,4294967295,0),this[o+3]=s>>>24,this[o+2]=s>>>16,this[o+1]=s>>>8,this[o]=255&s,o+4},Buffer.prototype.writeUint32BE=Buffer.prototype.writeUInt32BE=function writeUInt32BE(s,o,i){return s=+s,o>>>=0,i||checkInt(this,s,o,4,4294967295,0),this[o]=s>>>24,this[o+1]=s>>>16,this[o+2]=s>>>8,this[o+3]=255&s,o+4},Buffer.prototype.writeBigUInt64LE=defineBigIntMethod((function writeBigUInt64LE(s,o=0){return wrtBigUInt64LE(this,s,o,BigInt(0),BigInt("0xffffffffffffffff"))})),Buffer.prototype.writeBigUInt64BE=defineBigIntMethod((function writeBigUInt64BE(s,o=0){return wrtBigUInt64BE(this,s,o,BigInt(0),BigInt("0xffffffffffffffff"))})),Buffer.prototype.writeIntLE=function writeIntLE(s,o,i,a){if(s=+s,o>>>=0,!a){const a=Math.pow(2,8*i-1);checkInt(this,s,o,i,a-1,-a)}let u=0,_=1,w=0;for(this[o]=255&s;++u>>=0,!a){const a=Math.pow(2,8*i-1);checkInt(this,s,o,i,a-1,-a)}let u=i-1,_=1,w=0;for(this[o+u]=255&s;--u>=0&&(_*=256);)s<0&&0===w&&0!==this[o+u+1]&&(w=1),this[o+u]=(s/_|0)-w&255;return o+i},Buffer.prototype.writeInt8=function writeInt8(s,o,i){return s=+s,o>>>=0,i||checkInt(this,s,o,1,127,-128),s<0&&(s=255+s+1),this[o]=255&s,o+1},Buffer.prototype.writeInt16LE=function writeInt16LE(s,o,i){return s=+s,o>>>=0,i||checkInt(this,s,o,2,32767,-32768),this[o]=255&s,this[o+1]=s>>>8,o+2},Buffer.prototype.writeInt16BE=function writeInt16BE(s,o,i){return s=+s,o>>>=0,i||checkInt(this,s,o,2,32767,-32768),this[o]=s>>>8,this[o+1]=255&s,o+2},Buffer.prototype.writeInt32LE=function writeInt32LE(s,o,i){return s=+s,o>>>=0,i||checkInt(this,s,o,4,2147483647,-2147483648),this[o]=255&s,this[o+1]=s>>>8,this[o+2]=s>>>16,this[o+3]=s>>>24,o+4},Buffer.prototype.writeInt32BE=function writeInt32BE(s,o,i){return s=+s,o>>>=0,i||checkInt(this,s,o,4,2147483647,-2147483648),s<0&&(s=4294967295+s+1),this[o]=s>>>24,this[o+1]=s>>>16,this[o+2]=s>>>8,this[o+3]=255&s,o+4},Buffer.prototype.writeBigInt64LE=defineBigIntMethod((function writeBigInt64LE(s,o=0){return wrtBigUInt64LE(this,s,o,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))})),Buffer.prototype.writeBigInt64BE=defineBigIntMethod((function writeBigInt64BE(s,o=0){return wrtBigUInt64BE(this,s,o,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))})),Buffer.prototype.writeFloatLE=function writeFloatLE(s,o,i){return writeFloat(this,s,o,!0,i)},Buffer.prototype.writeFloatBE=function writeFloatBE(s,o,i){return writeFloat(this,s,o,!1,i)},Buffer.prototype.writeDoubleLE=function writeDoubleLE(s,o,i){return writeDouble(this,s,o,!0,i)},Buffer.prototype.writeDoubleBE=function writeDoubleBE(s,o,i){return writeDouble(this,s,o,!1,i)},Buffer.prototype.copy=function copy(s,o,i,a){if(!Buffer.isBuffer(s))throw new TypeError("argument should be a Buffer");if(i||(i=0),a||0===a||(a=this.length),o>=s.length&&(o=s.length),o||(o=0),a>0&&a=this.length)throw new RangeError("Index out of range");if(a<0)throw new RangeError("sourceEnd out of bounds");a>this.length&&(a=this.length),s.length-o>>=0,i=void 0===i?this.length:i>>>0,s||(s=0),"number"==typeof s)for(u=o;u=a+4;i-=3)o=`_${s.slice(i-3,i)}${o}`;return`${s.slice(0,i)}${o}`}function checkIntBI(s,o,i,a,u,_){if(s>i||s3?0===o||o===BigInt(0)?`>= 0${a} and < 2${a} ** ${8*(_+1)}${a}`:`>= -(2${a} ** ${8*(_+1)-1}${a}) and < 2 ** ${8*(_+1)-1}${a}`:`>= ${o}${a} and <= ${i}${a}`,new C.ERR_OUT_OF_RANGE("value",u,s)}!function checkBounds(s,o,i){validateNumber(o,"offset"),void 0!==s[o]&&void 0!==s[o+i]||boundsError(o,s.length-(i+1))}(a,u,_)}function validateNumber(s,o){if("number"!=typeof s)throw new C.ERR_INVALID_ARG_TYPE(o,"number",s)}function boundsError(s,o,i){if(Math.floor(s)!==s)throw validateNumber(s,i),new C.ERR_OUT_OF_RANGE(i||"offset","an integer",s);if(o<0)throw new C.ERR_BUFFER_OUT_OF_BOUNDS;throw new C.ERR_OUT_OF_RANGE(i||"offset",`>= ${i?1:0} and <= ${o}`,s)}E("ERR_BUFFER_OUT_OF_BOUNDS",(function(s){return s?`${s} is outside of buffer bounds`:"Attempt to access memory outside buffer bounds"}),RangeError),E("ERR_INVALID_ARG_TYPE",(function(s,o){return`The "${s}" argument must be of type number. Received type ${typeof o}`}),TypeError),E("ERR_OUT_OF_RANGE",(function(s,o,i){let a=`The value of "${s}" is out of range.`,u=i;return Number.isInteger(i)&&Math.abs(i)>2**32?u=addNumericalSeparator(String(i)):"bigint"==typeof i&&(u=String(i),(i>BigInt(2)**BigInt(32)||i<-(BigInt(2)**BigInt(32)))&&(u=addNumericalSeparator(u)),u+="n"),a+=` It must be ${o}. Received ${u}`,a}),RangeError);const j=/[^+/0-9A-Za-z-_]/g;function utf8ToBytes(s,o){let i;o=o||1/0;const a=s.length;let u=null;const _=[];for(let w=0;w55295&&i<57344){if(!u){if(i>56319){(o-=3)>-1&&_.push(239,191,189);continue}if(w+1===a){(o-=3)>-1&&_.push(239,191,189);continue}u=i;continue}if(i<56320){(o-=3)>-1&&_.push(239,191,189),u=i;continue}i=65536+(u-55296<<10|i-56320)}else u&&(o-=3)>-1&&_.push(239,191,189);if(u=null,i<128){if((o-=1)<0)break;_.push(i)}else if(i<2048){if((o-=2)<0)break;_.push(i>>6|192,63&i|128)}else if(i<65536){if((o-=3)<0)break;_.push(i>>12|224,i>>6&63|128,63&i|128)}else{if(!(i<1114112))throw new Error("Invalid code point");if((o-=4)<0)break;_.push(i>>18|240,i>>12&63|128,i>>6&63|128,63&i|128)}}return _}function base64ToBytes(s){return a.toByteArray(function base64clean(s){if((s=(s=s.split("=")[0]).trim().replace(j,"")).length<2)return"";for(;s.length%4!=0;)s+="=";return s}(s))}function blitBuffer(s,o,i,a){let u;for(u=0;u=o.length||u>=s.length);++u)o[u+i]=s[u];return u}function isInstance(s,o){return s instanceof o||null!=s&&null!=s.constructor&&null!=s.constructor.name&&s.constructor.name===o.name}function numberIsNaN(s){return s!=s}const L=function(){const s="0123456789abcdef",o=new Array(256);for(let i=0;i<16;++i){const a=16*i;for(let u=0;u<16;++u)o[a+u]=s[i]+s[u]}return o}();function defineBigIntMethod(s){return"undefined"==typeof BigInt?BufferBigIntNotDefined:s}function BufferBigIntNotDefined(){throw new Error("BigInt not supported")}},13144(s,o,i){"use strict";var a=i(66743),u=i(11002),_=i(10076),w=i(47119);s.exports=w||a.call(_,u)},12205(s,o,i){"use strict";var a=i(66743),u=i(11002),_=i(13144);s.exports=function applyBind(){return _(a,u,arguments)}},11002(s){"use strict";s.exports=Function.prototype.apply},10076(s){"use strict";s.exports=Function.prototype.call},73126(s,o,i){"use strict";var a=i(66743),u=i(69675),_=i(10076),w=i(13144);s.exports=function callBindBasic(s){if(s.length<1||"function"!=typeof s[0])throw new u("a function is required");return w(a,_,s)}},47119(s){"use strict";s.exports="undefined"!=typeof Reflect&&Reflect&&Reflect.apply},10487(s,o,i){"use strict";var a=i(96897),u=i(30655),_=i(73126),w=i(12205);s.exports=function callBind(s){var o=_(arguments),i=s.length-(arguments.length-1);return a(o,1+(i>0?i:0),!0)},u?u(s.exports,"apply",{value:w}):s.exports.apply=w},36556(s,o,i){"use strict";var a=i(70453),u=i(73126),_=u([a("%String.prototype.indexOf%")]);s.exports=function callBoundIntrinsic(s,o){var i=a(s,!!o);return"function"==typeof i&&_(s,".prototype.")>-1?u([i]):i}},17965(s,o,i){"use strict";var a=i(16426),u={"text/plain":"Text","text/html":"Url",default:"Text"};s.exports=function copy(s,o){var i,_,w,x,C,j,L=!1;o||(o={}),i=o.debug||!1;try{if(w=a(),x=document.createRange(),C=document.getSelection(),(j=document.createElement("span")).textContent=s,j.ariaHidden="true",j.style.all="unset",j.style.position="fixed",j.style.top=0,j.style.clip="rect(0, 0, 0, 0)",j.style.whiteSpace="pre",j.style.webkitUserSelect="text",j.style.MozUserSelect="text",j.style.msUserSelect="text",j.style.userSelect="text",j.addEventListener("copy",(function(a){if(a.stopPropagation(),o.format)if(a.preventDefault(),void 0===a.clipboardData){i&&console.warn("unable to use e.clipboardData"),i&&console.warn("trying IE specific stuff"),window.clipboardData.clearData();var _=u[o.format]||u.default;window.clipboardData.setData(_,s)}else a.clipboardData.clearData(),a.clipboardData.setData(o.format,s);o.onCopy&&(a.preventDefault(),o.onCopy(a.clipboardData))})),document.body.appendChild(j),x.selectNodeContents(j),C.addRange(x),!document.execCommand("copy"))throw new Error("copy command was unsuccessful");L=!0}catch(a){i&&console.error("unable to copy using execCommand: ",a),i&&console.warn("trying IE specific stuff");try{window.clipboardData.setData(o.format||"text",s),o.onCopy&&o.onCopy(window.clipboardData),L=!0}catch(a){i&&console.error("unable to copy using clipboardData: ",a),i&&console.error("falling back to prompt"),_=function format(s){var o=(/mac os x/i.test(navigator.userAgent)?"⌘":"Ctrl")+"+C";return s.replace(/#{\s*key\s*}/g,o)}("message"in o?o.message:"Copy to clipboard: #{key}, Enter"),window.prompt(_,s)}}finally{C&&("function"==typeof C.removeRange?C.removeRange(x):C.removeAllRanges()),j&&document.body.removeChild(j),w()}return L}},2205(s,o,i){var a;a=void 0!==i.g?i.g:this,s.exports=function(s){if(s.CSS&&s.CSS.escape)return s.CSS.escape;var cssEscape=function(s){if(0==arguments.length)throw new TypeError("`CSS.escape` requires an argument.");for(var o,i=String(s),a=i.length,u=-1,_="",w=i.charCodeAt(0);++u=1&&o<=31||127==o||0==u&&o>=48&&o<=57||1==u&&o>=48&&o<=57&&45==w?"\\"+o.toString(16)+" ":0==u&&1==a&&45==o||!(o>=128||45==o||95==o||o>=48&&o<=57||o>=65&&o<=90||o>=97&&o<=122)?"\\"+i.charAt(u):i.charAt(u):_+="�";return _};return s.CSS||(s.CSS={}),s.CSS.escape=cssEscape,cssEscape}(a)},81919(s,o,i){"use strict";var a=i(48287).Buffer;function isSpecificValue(s){return s instanceof a||s instanceof Date||s instanceof RegExp}function cloneSpecificValue(s){if(s instanceof a){var o=a.alloc?a.alloc(s.length):new a(s.length);return s.copy(o),o}if(s instanceof Date)return new Date(s.getTime());if(s instanceof RegExp)return new RegExp(s);throw new Error("Unexpected situation")}function deepCloneArray(s){var o=[];return s.forEach((function(s,i){"object"==typeof s&&null!==s?Array.isArray(s)?o[i]=deepCloneArray(s):isSpecificValue(s)?o[i]=cloneSpecificValue(s):o[i]=u({},s):o[i]=s})),o}function safeGetProperty(s,o){return"__proto__"===o?void 0:s[o]}var u=s.exports=function(){if(arguments.length<1||"object"!=typeof arguments[0])return!1;if(arguments.length<2)return arguments[0];var s,o,i=arguments[0];return Array.prototype.slice.call(arguments,1).forEach((function(a){"object"!=typeof a||null===a||Array.isArray(a)||Object.keys(a).forEach((function(_){return o=safeGetProperty(i,_),(s=safeGetProperty(a,_))===i?void 0:"object"!=typeof s||null===s?void(i[_]=s):Array.isArray(s)?void(i[_]=deepCloneArray(s)):isSpecificValue(s)?void(i[_]=cloneSpecificValue(s)):"object"!=typeof o||null===o||Array.isArray(o)?void(i[_]=u({},s)):void(i[_]=u(o,s))}))})),i}},14744(s){"use strict";var o=function isMergeableObject(s){return function isNonNullObject(s){return!!s&&"object"==typeof s}(s)&&!function isSpecial(s){var o=Object.prototype.toString.call(s);return"[object RegExp]"===o||"[object Date]"===o||function isReactElement(s){return s.$$typeof===i}(s)}(s)};var i="function"==typeof Symbol&&Symbol.for?Symbol.for("react.element"):60103;function cloneUnlessOtherwiseSpecified(s,o){return!1!==o.clone&&o.isMergeableObject(s)?deepmerge(function emptyTarget(s){return Array.isArray(s)?[]:{}}(s),s,o):s}function defaultArrayMerge(s,o,i){return s.concat(o).map((function(s){return cloneUnlessOtherwiseSpecified(s,i)}))}function getKeys(s){return Object.keys(s).concat(function getEnumerableOwnPropertySymbols(s){return Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(s).filter((function(o){return Object.propertyIsEnumerable.call(s,o)})):[]}(s))}function propertyIsOnObject(s,o){try{return o in s}catch(s){return!1}}function mergeObject(s,o,i){var a={};return i.isMergeableObject(s)&&getKeys(s).forEach((function(o){a[o]=cloneUnlessOtherwiseSpecified(s[o],i)})),getKeys(o).forEach((function(u){(function propertyIsUnsafe(s,o){return propertyIsOnObject(s,o)&&!(Object.hasOwnProperty.call(s,o)&&Object.propertyIsEnumerable.call(s,o))})(s,u)||(propertyIsOnObject(s,u)&&i.isMergeableObject(o[u])?a[u]=function getMergeFunction(s,o){if(!o.customMerge)return deepmerge;var i=o.customMerge(s);return"function"==typeof i?i:deepmerge}(u,i)(s[u],o[u],i):a[u]=cloneUnlessOtherwiseSpecified(o[u],i))})),a}function deepmerge(s,i,a){(a=a||{}).arrayMerge=a.arrayMerge||defaultArrayMerge,a.isMergeableObject=a.isMergeableObject||o,a.cloneUnlessOtherwiseSpecified=cloneUnlessOtherwiseSpecified;var u=Array.isArray(i);return u===Array.isArray(s)?u?a.arrayMerge(s,i,a):mergeObject(s,i,a):cloneUnlessOtherwiseSpecified(i,a)}deepmerge.all=function deepmergeAll(s,o){if(!Array.isArray(s))throw new Error("first argument should be an array");return s.reduce((function(s,i){return deepmerge(s,i,o)}),{})};var a=deepmerge;s.exports=a},30041(s,o,i){"use strict";var a=i(30655),u=i(58068),_=i(69675),w=i(75795);s.exports=function defineDataProperty(s,o,i){if(!s||"object"!=typeof s&&"function"!=typeof s)throw new _("`obj` must be an object or a function`");if("string"!=typeof o&&"symbol"!=typeof o)throw new _("`property` must be a string or a symbol`");if(arguments.length>3&&"boolean"!=typeof arguments[3]&&null!==arguments[3])throw new _("`nonEnumerable`, if provided, must be a boolean or null");if(arguments.length>4&&"boolean"!=typeof arguments[4]&&null!==arguments[4])throw new _("`nonWritable`, if provided, must be a boolean or null");if(arguments.length>5&&"boolean"!=typeof arguments[5]&&null!==arguments[5])throw new _("`nonConfigurable`, if provided, must be a boolean or null");if(arguments.length>6&&"boolean"!=typeof arguments[6])throw new _("`loose`, if provided, must be a boolean");var x=arguments.length>3?arguments[3]:null,C=arguments.length>4?arguments[4]:null,j=arguments.length>5?arguments[5]:null,L=arguments.length>6&&arguments[6],B=!!w&&w(s,o);if(a)a(s,o,{configurable:null===j&&B?B.configurable:!j,enumerable:null===x&&B?B.enumerable:!x,value:i,writable:null===C&&B?B.writable:!C});else{if(!L&&(x||C||j))throw new u("This environment does not support defining a property as non-configurable, non-writable, or non-enumerable.");s[o]=i}}},78004(s){"use strict";class SubRange{constructor(s,o){this.low=s,this.high=o,this.length=1+o-s}overlaps(s){return!(this.highs.high)}touches(s){return!(this.high+1s.high)}add(s){return new SubRange(Math.min(this.low,s.low),Math.max(this.high,s.high))}subtract(s){return s.low<=this.low&&s.high>=this.high?[]:s.low>this.low&&s.highs+o.length),0)}add(s,o){var _add=s=>{for(var o=0;o{for(var o=0;o{for(var o=0;o{for(var i=o.low;i<=o.high;)s.push(i),i++;return s}),[])}subranges(){return this.ranges.map((s=>({low:s.low,high:s.high,length:1+s.high-s.low})))}}s.exports=DRange},7176(s,o,i){"use strict";var a,u=i(73126),_=i(75795);try{a=[].__proto__===Array.prototype}catch(s){if(!s||"object"!=typeof s||!("code"in s)||"ERR_PROTO_ACCESS"!==s.code)throw s}var w=!!a&&_&&_(Object.prototype,"__proto__"),x=Object,C=x.getPrototypeOf;s.exports=w&&"function"==typeof w.get?u([w.get]):"function"==typeof C&&function getDunder(s){return C(null==s?s:x(s))}},30655(s){"use strict";var o=Object.defineProperty||!1;if(o)try{o({},"a",{value:1})}catch(s){o=!1}s.exports=o},41237(s){"use strict";s.exports=EvalError},69383(s){"use strict";s.exports=Error},79290(s){"use strict";s.exports=RangeError},79538(s){"use strict";s.exports=ReferenceError},58068(s){"use strict";s.exports=SyntaxError},69675(s){"use strict";s.exports=TypeError},35345(s){"use strict";s.exports=URIError},79612(s){"use strict";s.exports=Object},37007(s){"use strict";var o,i="object"==typeof Reflect?Reflect:null,a=i&&"function"==typeof i.apply?i.apply:function ReflectApply(s,o,i){return Function.prototype.apply.call(s,o,i)};o=i&&"function"==typeof i.ownKeys?i.ownKeys:Object.getOwnPropertySymbols?function ReflectOwnKeys(s){return Object.getOwnPropertyNames(s).concat(Object.getOwnPropertySymbols(s))}:function ReflectOwnKeys(s){return Object.getOwnPropertyNames(s)};var u=Number.isNaN||function NumberIsNaN(s){return s!=s};function EventEmitter(){EventEmitter.init.call(this)}s.exports=EventEmitter,s.exports.once=function once(s,o){return new Promise((function(i,a){function errorListener(i){s.removeListener(o,resolver),a(i)}function resolver(){"function"==typeof s.removeListener&&s.removeListener("error",errorListener),i([].slice.call(arguments))}eventTargetAgnosticAddListener(s,o,resolver,{once:!0}),"error"!==o&&function addErrorHandlerIfEventEmitter(s,o,i){"function"==typeof s.on&&eventTargetAgnosticAddListener(s,"error",o,i)}(s,errorListener,{once:!0})}))},EventEmitter.EventEmitter=EventEmitter,EventEmitter.prototype._events=void 0,EventEmitter.prototype._eventsCount=0,EventEmitter.prototype._maxListeners=void 0;var _=10;function checkListener(s){if("function"!=typeof s)throw new TypeError('The "listener" argument must be of type Function. Received type '+typeof s)}function _getMaxListeners(s){return void 0===s._maxListeners?EventEmitter.defaultMaxListeners:s._maxListeners}function _addListener(s,o,i,a){var u,_,w;if(checkListener(i),void 0===(_=s._events)?(_=s._events=Object.create(null),s._eventsCount=0):(void 0!==_.newListener&&(s.emit("newListener",o,i.listener?i.listener:i),_=s._events),w=_[o]),void 0===w)w=_[o]=i,++s._eventsCount;else if("function"==typeof w?w=_[o]=a?[i,w]:[w,i]:a?w.unshift(i):w.push(i),(u=_getMaxListeners(s))>0&&w.length>u&&!w.warned){w.warned=!0;var x=new Error("Possible EventEmitter memory leak detected. "+w.length+" "+String(o)+" listeners added. Use emitter.setMaxListeners() to increase limit");x.name="MaxListenersExceededWarning",x.emitter=s,x.type=o,x.count=w.length,function ProcessEmitWarning(s){console&&console.warn&&console.warn(s)}(x)}return s}function onceWrapper(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,0===arguments.length?this.listener.call(this.target):this.listener.apply(this.target,arguments)}function _onceWrap(s,o,i){var a={fired:!1,wrapFn:void 0,target:s,type:o,listener:i},u=onceWrapper.bind(a);return u.listener=i,a.wrapFn=u,u}function _listeners(s,o,i){var a=s._events;if(void 0===a)return[];var u=a[o];return void 0===u?[]:"function"==typeof u?i?[u.listener||u]:[u]:i?function unwrapListeners(s){for(var o=new Array(s.length),i=0;i0&&(w=o[0]),w instanceof Error)throw w;var x=new Error("Unhandled error."+(w?" ("+w.message+")":""));throw x.context=w,x}var C=_[s];if(void 0===C)return!1;if("function"==typeof C)a(C,this,o);else{var j=C.length,L=arrayClone(C,j);for(i=0;i=0;_--)if(i[_]===o||i[_].listener===o){w=i[_].listener,u=_;break}if(u<0)return this;0===u?i.shift():function spliceOne(s,o){for(;o+1=0;a--)this.removeListener(s,o[a]);return this},EventEmitter.prototype.listeners=function listeners(s){return _listeners(this,s,!0)},EventEmitter.prototype.rawListeners=function rawListeners(s){return _listeners(this,s,!1)},EventEmitter.listenerCount=function(s,o){return"function"==typeof s.listenerCount?s.listenerCount(o):listenerCount.call(s,o)},EventEmitter.prototype.listenerCount=listenerCount,EventEmitter.prototype.eventNames=function eventNames(){return this._eventsCount>0?o(this._events):[]}},85587(s,o,i){"use strict";var a=i(26311),u=create(Error);function create(s){return FormattedError.displayName=s.displayName||s.name,FormattedError;function FormattedError(o){return o&&(o=a.apply(null,arguments)),new s(o)}}s.exports=u,u.eval=create(EvalError),u.range=create(RangeError),u.reference=create(ReferenceError),u.syntax=create(SyntaxError),u.type=create(TypeError),u.uri=create(URIError),u.create=create},82682(s,o,i){"use strict";var a=i(69600),u=Object.prototype.toString,_=Object.prototype.hasOwnProperty;s.exports=function forEach(s,o,i){if(!a(o))throw new TypeError("iterator must be a function");var w;arguments.length>=3&&(w=i),function isArray(s){return"[object Array]"===u.call(s)}(s)?function forEachArray(s,o,i){for(var a=0,u=s.length;a0?parseInt(i):null};x1&&"boolean"!=typeof o)throw new L('"allowMissing" argument must be a boolean');if(null===Xe(/^%?[^%]*%?$/,s))throw new j("`%` may not be present anywhere but at the beginning and end of the intrinsic name");var i=function stringToPath(s){var o=Ye(s,0,1),i=Ye(s,-1);if("%"===o&&"%"!==i)throw new j("invalid intrinsic syntax, expected closing `%`");if("%"===i&&"%"!==o)throw new j("invalid intrinsic syntax, expected opening `%`");var a=[];return He(s,Qe,(function(s,o,i,u){a[a.length]=i?He(u,et,"$1"):o||s})),a}(s),a=i.length>0?i[0]:"",u=tt("%"+a+"%",o),_=u.name,w=u.value,x=!1,C=u.alias;C&&(a=C[0],We(i,ze([0,1],C)));for(var B=1,$=!0;B=i.length){var Y=ae(w,z);w=($=!!Y)&&"get"in Y&&!("originalValue"in Y.get)?Y.get:w[z]}else $=$e(w,z),w=w[z];$&&!x&&(xe[_]=w)}}return w}},71064(s,o,i){"use strict";var a=i(79612);s.exports=a.getPrototypeOf||null},48648(s){"use strict";s.exports="undefined"!=typeof Reflect&&Reflect.getPrototypeOf||null},93628(s,o,i){"use strict";var a=i(48648),u=i(71064),_=i(7176);s.exports=a?function getProto(s){return a(s)}:u?function getProto(s){if(!s||"object"!=typeof s&&"function"!=typeof s)throw new TypeError("getProto: not an object");return u(s)}:_?function getProto(s){return _(s)}:null},6549(s){"use strict";s.exports=Object.getOwnPropertyDescriptor},75795(s,o,i){"use strict";var a=i(6549);if(a)try{a([],"length")}catch(s){a=null}s.exports=a},30592(s,o,i){"use strict";var a=i(30655),u=function hasPropertyDescriptors(){return!!a};u.hasArrayLengthDefineBug=function hasArrayLengthDefineBug(){if(!a)return null;try{return 1!==a([],"length",{value:1}).length}catch(s){return!0}},s.exports=u},64039(s,o,i){"use strict";var a="undefined"!=typeof Symbol&&Symbol,u=i(41333);s.exports=function hasNativeSymbols(){return"function"==typeof a&&("function"==typeof Symbol&&("symbol"==typeof a("foo")&&("symbol"==typeof Symbol("bar")&&u())))}},41333(s){"use strict";s.exports=function hasSymbols(){if("function"!=typeof Symbol||"function"!=typeof Object.getOwnPropertySymbols)return!1;if("symbol"==typeof Symbol.iterator)return!0;var s={},o=Symbol("test"),i=Object(o);if("string"==typeof o)return!1;if("[object Symbol]"!==Object.prototype.toString.call(o))return!1;if("[object Symbol]"!==Object.prototype.toString.call(i))return!1;for(var a in s[o]=42,s)return!1;if("function"==typeof Object.keys&&0!==Object.keys(s).length)return!1;if("function"==typeof Object.getOwnPropertyNames&&0!==Object.getOwnPropertyNames(s).length)return!1;var u=Object.getOwnPropertySymbols(s);if(1!==u.length||u[0]!==o)return!1;if(!Object.prototype.propertyIsEnumerable.call(s,o))return!1;if("function"==typeof Object.getOwnPropertyDescriptor){var _=Object.getOwnPropertyDescriptor(s,o);if(42!==_.value||!0!==_.enumerable)return!1}return!0}},49092(s,o,i){"use strict";var a=i(41333);s.exports=function hasToStringTagShams(){return a()&&!!Symbol.toStringTag}},9957(s,o,i){"use strict";var a=Function.prototype.call,u=Object.prototype.hasOwnProperty,_=i(66743);s.exports=_.call(a,u)},45981(s){function deepFreeze(s){return s instanceof Map?s.clear=s.delete=s.set=function(){throw new Error("map is read-only")}:s instanceof Set&&(s.add=s.clear=s.delete=function(){throw new Error("set is read-only")}),Object.freeze(s),Object.getOwnPropertyNames(s).forEach((function(o){var i=s[o];"object"!=typeof i||Object.isFrozen(i)||deepFreeze(i)})),s}var o=deepFreeze,i=deepFreeze;o.default=i;class Response{constructor(s){void 0===s.data&&(s.data={}),this.data=s.data,this.isMatchIgnored=!1}ignoreMatch(){this.isMatchIgnored=!0}}function escapeHTML(s){return s.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function inherit(s,...o){const i=Object.create(null);for(const o in s)i[o]=s[o];return o.forEach((function(s){for(const o in s)i[o]=s[o]})),i}const emitsWrappingTags=s=>!!s.kind;class HTMLRenderer{constructor(s,o){this.buffer="",this.classPrefix=o.classPrefix,s.walk(this)}addText(s){this.buffer+=escapeHTML(s)}openNode(s){if(!emitsWrappingTags(s))return;let o=s.kind;s.sublanguage||(o=`${this.classPrefix}${o}`),this.span(o)}closeNode(s){emitsWrappingTags(s)&&(this.buffer+="")}value(){return this.buffer}span(s){this.buffer+=``}}class TokenTree{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(s){this.top.children.push(s)}openNode(s){const o={kind:s,children:[]};this.add(o),this.stack.push(o)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(s){return this.constructor._walk(s,this.rootNode)}static _walk(s,o){return"string"==typeof o?s.addText(o):o.children&&(s.openNode(o),o.children.forEach((o=>this._walk(s,o))),s.closeNode(o)),s}static _collapse(s){"string"!=typeof s&&s.children&&(s.children.every((s=>"string"==typeof s))?s.children=[s.children.join("")]:s.children.forEach((s=>{TokenTree._collapse(s)})))}}class TokenTreeEmitter extends TokenTree{constructor(s){super(),this.options=s}addKeyword(s,o){""!==s&&(this.openNode(o),this.addText(s),this.closeNode())}addText(s){""!==s&&this.add(s)}addSublanguage(s,o){const i=s.root;i.kind=o,i.sublanguage=!0,this.add(i)}toHTML(){return new HTMLRenderer(this,this.options).value()}finalize(){return!0}}function source(s){return s?"string"==typeof s?s:s.source:null}const a=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;const u="[a-zA-Z]\\w*",_="[a-zA-Z_]\\w*",w="\\b\\d+(\\.\\d+)?",x="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",C="\\b(0b[01]+)",j={begin:"\\\\[\\s\\S]",relevance:0},L={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[j]},B={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[j]},$={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},COMMENT=function(s,o,i={}){const a=inherit({className:"comment",begin:s,end:o,contains:[]},i);return a.contains.push($),a.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),a},z=COMMENT("//","$"),U=COMMENT("/\\*","\\*/"),V=COMMENT("#","$"),Y={className:"number",begin:w,relevance:0},Z={className:"number",begin:x,relevance:0},ee={className:"number",begin:C,relevance:0},ie={className:"number",begin:w+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},ae={begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[j,{begin:/\[/,end:/\]/,relevance:0,contains:[j]}]}]},ce={className:"title",begin:u,relevance:0},le={className:"title",begin:_,relevance:0},pe={begin:"\\.\\s*"+_,relevance:0};var de=Object.freeze({__proto__:null,MATCH_NOTHING_RE:/\b\B/,IDENT_RE:u,UNDERSCORE_IDENT_RE:_,NUMBER_RE:w,C_NUMBER_RE:x,BINARY_NUMBER_RE:C,RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(s={})=>{const o=/^#![ ]*\//;return s.binary&&(s.begin=function concat(...s){return s.map((s=>source(s))).join("")}(o,/.*\b/,s.binary,/\b.*/)),inherit({className:"meta",begin:o,end:/$/,relevance:0,"on:begin":(s,o)=>{0!==s.index&&o.ignoreMatch()}},s)},BACKSLASH_ESCAPE:j,APOS_STRING_MODE:L,QUOTE_STRING_MODE:B,PHRASAL_WORDS_MODE:$,COMMENT,C_LINE_COMMENT_MODE:z,C_BLOCK_COMMENT_MODE:U,HASH_COMMENT_MODE:V,NUMBER_MODE:Y,C_NUMBER_MODE:Z,BINARY_NUMBER_MODE:ee,CSS_NUMBER_MODE:ie,REGEXP_MODE:ae,TITLE_MODE:ce,UNDERSCORE_TITLE_MODE:le,METHOD_GUARD:pe,END_SAME_AS_BEGIN:function(s){return Object.assign(s,{"on:begin":(s,o)=>{o.data._beginMatch=s[1]},"on:end":(s,o)=>{o.data._beginMatch!==s[1]&&o.ignoreMatch()}})}});function skipIfhasPrecedingDot(s,o){"."===s.input[s.index-1]&&o.ignoreMatch()}function beginKeywords(s,o){o&&s.beginKeywords&&(s.begin="\\b("+s.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)",s.__beforeBegin=skipIfhasPrecedingDot,s.keywords=s.keywords||s.beginKeywords,delete s.beginKeywords,void 0===s.relevance&&(s.relevance=0))}function compileIllegal(s,o){Array.isArray(s.illegal)&&(s.illegal=function either(...s){return"("+s.map((s=>source(s))).join("|")+")"}(...s.illegal))}function compileMatch(s,o){if(s.match){if(s.begin||s.end)throw new Error("begin & end are not supported with match");s.begin=s.match,delete s.match}}function compileRelevance(s,o){void 0===s.relevance&&(s.relevance=1)}const fe=["of","and","for","in","not","or","if","then","parent","list","value"];function compileKeywords(s,o,i="keyword"){const a={};return"string"==typeof s?compileList(i,s.split(" ")):Array.isArray(s)?compileList(i,s):Object.keys(s).forEach((function(i){Object.assign(a,compileKeywords(s[i],o,i))})),a;function compileList(s,i){o&&(i=i.map((s=>s.toLowerCase()))),i.forEach((function(o){const i=o.split("|");a[i[0]]=[s,scoreForKeyword(i[0],i[1])]}))}}function scoreForKeyword(s,o){return o?Number(o):function commonKeyword(s){return fe.includes(s.toLowerCase())}(s)?0:1}function compileLanguage(s,{plugins:o}){function langRe(o,i){return new RegExp(source(o),"m"+(s.case_insensitive?"i":"")+(i?"g":""))}class MultiRegex{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(s,o){o.position=this.position++,this.matchIndexes[this.matchAt]=o,this.regexes.push([o,s]),this.matchAt+=function countMatchGroups(s){return new RegExp(s.toString()+"|").exec("").length-1}(s)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const s=this.regexes.map((s=>s[1]));this.matcherRe=langRe(function join(s,o="|"){let i=0;return s.map((s=>{i+=1;const o=i;let u=source(s),_="";for(;u.length>0;){const s=a.exec(u);if(!s){_+=u;break}_+=u.substring(0,s.index),u=u.substring(s.index+s[0].length),"\\"===s[0][0]&&s[1]?_+="\\"+String(Number(s[1])+o):(_+=s[0],"("===s[0]&&i++)}return _})).map((s=>`(${s})`)).join(o)}(s),!0),this.lastIndex=0}exec(s){this.matcherRe.lastIndex=this.lastIndex;const o=this.matcherRe.exec(s);if(!o)return null;const i=o.findIndex(((s,o)=>o>0&&void 0!==s)),a=this.matchIndexes[i];return o.splice(0,i),Object.assign(o,a)}}class ResumableMultiRegex{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(s){if(this.multiRegexes[s])return this.multiRegexes[s];const o=new MultiRegex;return this.rules.slice(s).forEach((([s,i])=>o.addRule(s,i))),o.compile(),this.multiRegexes[s]=o,o}resumingScanAtSamePosition(){return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(s,o){this.rules.push([s,o]),"begin"===o.type&&this.count++}exec(s){const o=this.getMatcher(this.regexIndex);o.lastIndex=this.lastIndex;let i=o.exec(s);if(this.resumingScanAtSamePosition())if(i&&i.index===this.lastIndex);else{const o=this.getMatcher(0);o.lastIndex=this.lastIndex+1,i=o.exec(s)}return i&&(this.regexIndex+=i.position+1,this.regexIndex===this.count&&this.considerAll()),i}}if(s.compilerExtensions||(s.compilerExtensions=[]),s.contains&&s.contains.includes("self"))throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return s.classNameAliases=inherit(s.classNameAliases||{}),function compileMode(o,i){const a=o;if(o.isCompiled)return a;[compileMatch].forEach((s=>s(o,i))),s.compilerExtensions.forEach((s=>s(o,i))),o.__beforeBegin=null,[beginKeywords,compileIllegal,compileRelevance].forEach((s=>s(o,i))),o.isCompiled=!0;let u=null;if("object"==typeof o.keywords&&(u=o.keywords.$pattern,delete o.keywords.$pattern),o.keywords&&(o.keywords=compileKeywords(o.keywords,s.case_insensitive)),o.lexemes&&u)throw new Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return u=u||o.lexemes||/\w+/,a.keywordPatternRe=langRe(u,!0),i&&(o.begin||(o.begin=/\B|\b/),a.beginRe=langRe(o.begin),o.endSameAsBegin&&(o.end=o.begin),o.end||o.endsWithParent||(o.end=/\B|\b/),o.end&&(a.endRe=langRe(o.end)),a.terminatorEnd=source(o.end)||"",o.endsWithParent&&i.terminatorEnd&&(a.terminatorEnd+=(o.end?"|":"")+i.terminatorEnd)),o.illegal&&(a.illegalRe=langRe(o.illegal)),o.contains||(o.contains=[]),o.contains=[].concat(...o.contains.map((function(s){return function expandOrCloneMode(s){s.variants&&!s.cachedVariants&&(s.cachedVariants=s.variants.map((function(o){return inherit(s,{variants:null},o)})));if(s.cachedVariants)return s.cachedVariants;if(dependencyOnParent(s))return inherit(s,{starts:s.starts?inherit(s.starts):null});if(Object.isFrozen(s))return inherit(s);return s}("self"===s?o:s)}))),o.contains.forEach((function(s){compileMode(s,a)})),o.starts&&compileMode(o.starts,i),a.matcher=function buildModeRegex(s){const o=new ResumableMultiRegex;return s.contains.forEach((s=>o.addRule(s.begin,{rule:s,type:"begin"}))),s.terminatorEnd&&o.addRule(s.terminatorEnd,{type:"end"}),s.illegal&&o.addRule(s.illegal,{type:"illegal"}),o}(a),a}(s)}function dependencyOnParent(s){return!!s&&(s.endsWithParent||dependencyOnParent(s.starts))}function BuildVuePlugin(s){const o={props:["language","code","autodetect"],data:function(){return{detectedLanguage:"",unknownLanguage:!1}},computed:{className(){return this.unknownLanguage?"":"hljs "+this.detectedLanguage},highlighted(){if(!this.autoDetect&&!s.getLanguage(this.language))return console.warn(`The language "${this.language}" you specified could not be found.`),this.unknownLanguage=!0,escapeHTML(this.code);let o={};return this.autoDetect?(o=s.highlightAuto(this.code),this.detectedLanguage=o.language):(o=s.highlight(this.language,this.code,this.ignoreIllegals),this.detectedLanguage=this.language),o.value},autoDetect(){return!this.language||function hasValueOrEmptyAttribute(s){return Boolean(s||""===s)}(this.autodetect)},ignoreIllegals:()=>!0},render(s){return s("pre",{},[s("code",{class:this.className,domProps:{innerHTML:this.highlighted}})])}};return{Component:o,VuePlugin:{install(s){s.component("highlightjs",o)}}}}const ye={"after:highlightElement":({el:s,result:o,text:i})=>{const a=nodeStream(s);if(!a.length)return;const u=document.createElement("div");u.innerHTML=o.value,o.value=function mergeStreams(s,o,i){let a=0,u="";const _=[];function selectStream(){return s.length&&o.length?s[0].offset!==o[0].offset?s[0].offset"}function close(s){u+=""}function render(s){("start"===s.event?open:close)(s.node)}for(;s.length||o.length;){let o=selectStream();if(u+=escapeHTML(i.substring(a,o[0].offset)),a=o[0].offset,o===s){_.reverse().forEach(close);do{render(o.splice(0,1)[0]),o=selectStream()}while(o===s&&o.length&&o[0].offset===a);_.reverse().forEach(open)}else"start"===o[0].event?_.push(o[0].node):_.pop(),render(o.splice(0,1)[0])}return u+escapeHTML(i.substr(a))}(a,nodeStream(u),i)}};function tag(s){return s.nodeName.toLowerCase()}function nodeStream(s){const o=[];return function _nodeStream(s,i){for(let a=s.firstChild;a;a=a.nextSibling)3===a.nodeType?i+=a.nodeValue.length:1===a.nodeType&&(o.push({event:"start",offset:i,node:a}),i=_nodeStream(a,i),tag(a).match(/br|hr|img|input/)||o.push({event:"stop",offset:i,node:a}));return i}(s,0),o}const be={},error=s=>{console.error(s)},warn=(s,...o)=>{console.log(`WARN: ${s}`,...o)},deprecated=(s,o)=>{be[`${s}/${o}`]||(console.log(`Deprecated as of ${s}. ${o}`),be[`${s}/${o}`]=!0)},Se=escapeHTML,_e=inherit,we=Symbol("nomatch");var xe=function(s){const i=Object.create(null),a=Object.create(null),u=[];let _=!0;const w=/(^(<[^>]+>|\t|)+|\n)/gm,x="Could not find the language '{}', did you forget to load/include a language module?",C={disableAutodetect:!0,name:"Plain text",contains:[]};let j={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:TokenTreeEmitter};function shouldNotHighlight(s){return j.noHighlightRe.test(s)}function highlight(s,o,i,a){let u="",_="";"object"==typeof o?(u=s,i=o.ignoreIllegals,_=o.language,a=void 0):(deprecated("10.7.0","highlight(lang, code, ...args) has been deprecated."),deprecated("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"),_=s,u=o);const w={code:u,language:_};fire("before:highlight",w);const x=w.result?w.result:_highlight(w.language,w.code,i,a);return x.code=w.code,fire("after:highlight",x),x}function _highlight(s,o,a,w){function keywordData(s,o){const i=L.case_insensitive?o[0].toLowerCase():o[0];return Object.prototype.hasOwnProperty.call(s.keywords,i)&&s.keywords[i]}function processBuffer(){null!=z.subLanguage?function processSubLanguage(){if(""===Y)return;let s=null;if("string"==typeof z.subLanguage){if(!i[z.subLanguage])return void V.addText(Y);s=_highlight(z.subLanguage,Y,!0,U[z.subLanguage]),U[z.subLanguage]=s.top}else s=highlightAuto(Y,z.subLanguage.length?z.subLanguage:null);z.relevance>0&&(Z+=s.relevance),V.addSublanguage(s.emitter,s.language)}():function processKeywords(){if(!z.keywords)return void V.addText(Y);let s=0;z.keywordPatternRe.lastIndex=0;let o=z.keywordPatternRe.exec(Y),i="";for(;o;){i+=Y.substring(s,o.index);const a=keywordData(z,o);if(a){const[s,u]=a;if(V.addText(i),i="",Z+=u,s.startsWith("_"))i+=o[0];else{const i=L.classNameAliases[s]||s;V.addKeyword(o[0],i)}}else i+=o[0];s=z.keywordPatternRe.lastIndex,o=z.keywordPatternRe.exec(Y)}i+=Y.substr(s),V.addText(i)}(),Y=""}function startNewMode(s){return s.className&&V.openNode(L.classNameAliases[s.className]||s.className),z=Object.create(s,{parent:{value:z}}),z}function endOfMode(s,o,i){let a=function startsWith(s,o){const i=s&&s.exec(o);return i&&0===i.index}(s.endRe,i);if(a){if(s["on:end"]){const i=new Response(s);s["on:end"](o,i),i.isMatchIgnored&&(a=!1)}if(a){for(;s.endsParent&&s.parent;)s=s.parent;return s}}if(s.endsWithParent)return endOfMode(s.parent,o,i)}function doIgnore(s){return 0===z.matcher.regexIndex?(Y+=s[0],1):(ae=!0,0)}function doBeginMatch(s){const o=s[0],i=s.rule,a=new Response(i),u=[i.__beforeBegin,i["on:begin"]];for(const i of u)if(i&&(i(s,a),a.isMatchIgnored))return doIgnore(o);return i&&i.endSameAsBegin&&(i.endRe=function escape(s){return new RegExp(s.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")}(o)),i.skip?Y+=o:(i.excludeBegin&&(Y+=o),processBuffer(),i.returnBegin||i.excludeBegin||(Y=o)),startNewMode(i),i.returnBegin?0:o.length}function doEndMatch(s){const i=s[0],a=o.substr(s.index),u=endOfMode(z,s,a);if(!u)return we;const _=z;_.skip?Y+=i:(_.returnEnd||_.excludeEnd||(Y+=i),processBuffer(),_.excludeEnd&&(Y=i));do{z.className&&V.closeNode(),z.skip||z.subLanguage||(Z+=z.relevance),z=z.parent}while(z!==u.parent);return u.starts&&(u.endSameAsBegin&&(u.starts.endRe=u.endRe),startNewMode(u.starts)),_.returnEnd?0:i.length}let C={};function processLexeme(i,u){const w=u&&u[0];if(Y+=i,null==w)return processBuffer(),0;if("begin"===C.type&&"end"===u.type&&C.index===u.index&&""===w){if(Y+=o.slice(u.index,u.index+1),!_){const o=new Error("0 width match regex");throw o.languageName=s,o.badRule=C.rule,o}return 1}if(C=u,"begin"===u.type)return doBeginMatch(u);if("illegal"===u.type&&!a){const s=new Error('Illegal lexeme "'+w+'" for mode "'+(z.className||"")+'"');throw s.mode=z,s}if("end"===u.type){const s=doEndMatch(u);if(s!==we)return s}if("illegal"===u.type&&""===w)return 1;if(ie>1e5&&ie>3*u.index){throw new Error("potential infinite loop, way more iterations than matches")}return Y+=w,w.length}const L=getLanguage(s);if(!L)throw error(x.replace("{}",s)),new Error('Unknown language: "'+s+'"');const B=compileLanguage(L,{plugins:u});let $="",z=w||B;const U={},V=new j.__emitter(j);!function processContinuations(){const s=[];for(let o=z;o!==L;o=o.parent)o.className&&s.unshift(o.className);s.forEach((s=>V.openNode(s)))}();let Y="",Z=0,ee=0,ie=0,ae=!1;try{for(z.matcher.considerAll();;){ie++,ae?ae=!1:z.matcher.considerAll(),z.matcher.lastIndex=ee;const s=z.matcher.exec(o);if(!s)break;const i=processLexeme(o.substring(ee,s.index),s);ee=s.index+i}return processLexeme(o.substr(ee)),V.closeAllNodes(),V.finalize(),$=V.toHTML(),{relevance:Math.floor(Z),value:$,language:s,illegal:!1,emitter:V,top:z}}catch(i){if(i.message&&i.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:i.message,context:o.slice(ee-100,ee+100),mode:i.mode},sofar:$,relevance:0,value:Se(o),emitter:V};if(_)return{illegal:!1,relevance:0,value:Se(o),emitter:V,language:s,top:z,errorRaised:i};throw i}}function highlightAuto(s,o){o=o||j.languages||Object.keys(i);const a=function justTextHighlightResult(s){const o={relevance:0,emitter:new j.__emitter(j),value:Se(s),illegal:!1,top:C};return o.emitter.addText(s),o}(s),u=o.filter(getLanguage).filter(autoDetection).map((o=>_highlight(o,s,!1)));u.unshift(a);const _=u.sort(((s,o)=>{if(s.relevance!==o.relevance)return o.relevance-s.relevance;if(s.language&&o.language){if(getLanguage(s.language).supersetOf===o.language)return 1;if(getLanguage(o.language).supersetOf===s.language)return-1}return 0})),[w,x]=_,L=w;return L.second_best=x,L}const L={"before:highlightElement":({el:s})=>{j.useBR&&(s.innerHTML=s.innerHTML.replace(/\n/g,"").replace(//g,"\n"))},"after:highlightElement":({result:s})=>{j.useBR&&(s.value=s.value.replace(/\n/g,"
"))}},B=/^(<[^>]+>|\t)+/gm,$={"after:highlightElement":({result:s})=>{j.tabReplace&&(s.value=s.value.replace(B,(s=>s.replace(/\t/g,j.tabReplace))))}};function highlightElement(s){let o=null;const i=function blockLanguage(s){let o=s.className+" ";o+=s.parentNode?s.parentNode.className:"";const i=j.languageDetectRe.exec(o);if(i){const o=getLanguage(i[1]);return o||(warn(x.replace("{}",i[1])),warn("Falling back to no-highlight mode for this block.",s)),o?i[1]:"no-highlight"}return o.split(/\s+/).find((s=>shouldNotHighlight(s)||getLanguage(s)))}(s);if(shouldNotHighlight(i))return;fire("before:highlightElement",{el:s,language:i}),o=s;const u=o.textContent,_=i?highlight(u,{language:i,ignoreIllegals:!0}):highlightAuto(u);fire("after:highlightElement",{el:s,result:_,text:u}),s.innerHTML=_.value,function updateClassName(s,o,i){const u=o?a[o]:i;s.classList.add("hljs"),u&&s.classList.add(u)}(s,i,_.language),s.result={language:_.language,re:_.relevance,relavance:_.relevance},_.second_best&&(s.second_best={language:_.second_best.language,re:_.second_best.relevance,relavance:_.second_best.relevance})}const initHighlighting=()=>{if(initHighlighting.called)return;initHighlighting.called=!0,deprecated("10.6.0","initHighlighting() is deprecated. Use highlightAll() instead.");document.querySelectorAll("pre code").forEach(highlightElement)};let z=!1;function highlightAll(){if("loading"===document.readyState)return void(z=!0);document.querySelectorAll("pre code").forEach(highlightElement)}function getLanguage(s){return s=(s||"").toLowerCase(),i[s]||i[a[s]]}function registerAliases(s,{languageName:o}){"string"==typeof s&&(s=[s]),s.forEach((s=>{a[s.toLowerCase()]=o}))}function autoDetection(s){const o=getLanguage(s);return o&&!o.disableAutodetect}function fire(s,o){const i=s;u.forEach((function(s){s[i]&&s[i](o)}))}"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(function boot(){z&&highlightAll()}),!1),Object.assign(s,{highlight,highlightAuto,highlightAll,fixMarkup:function deprecateFixMarkup(s){return deprecated("10.2.0","fixMarkup will be removed entirely in v11.0"),deprecated("10.2.0","Please see https://github.com/highlightjs/highlight.js/issues/2534"),function fixMarkup(s){return j.tabReplace||j.useBR?s.replace(w,(s=>"\n"===s?j.useBR?"
":s:j.tabReplace?s.replace(/\t/g,j.tabReplace):s)):s}(s)},highlightElement,highlightBlock:function deprecateHighlightBlock(s){return deprecated("10.7.0","highlightBlock will be removed entirely in v12.0"),deprecated("10.7.0","Please use highlightElement now."),highlightElement(s)},configure:function configure(s){s.useBR&&(deprecated("10.3.0","'useBR' will be removed entirely in v11.0"),deprecated("10.3.0","Please see https://github.com/highlightjs/highlight.js/issues/2559")),j=_e(j,s)},initHighlighting,initHighlightingOnLoad:function initHighlightingOnLoad(){deprecated("10.6.0","initHighlightingOnLoad() is deprecated. Use highlightAll() instead."),z=!0},registerLanguage:function registerLanguage(o,a){let u=null;try{u=a(s)}catch(s){if(error("Language definition for '{}' could not be registered.".replace("{}",o)),!_)throw s;error(s),u=C}u.name||(u.name=o),i[o]=u,u.rawDefinition=a.bind(null,s),u.aliases&®isterAliases(u.aliases,{languageName:o})},unregisterLanguage:function unregisterLanguage(s){delete i[s];for(const o of Object.keys(a))a[o]===s&&delete a[o]},listLanguages:function listLanguages(){return Object.keys(i)},getLanguage,registerAliases,requireLanguage:function requireLanguage(s){deprecated("10.4.0","requireLanguage will be removed entirely in v11."),deprecated("10.4.0","Please see https://github.com/highlightjs/highlight.js/pull/2844");const o=getLanguage(s);if(o)return o;throw new Error("The '{}' language is required, but not loaded.".replace("{}",s))},autoDetection,inherit:_e,addPlugin:function addPlugin(s){!function upgradePluginAPI(s){s["before:highlightBlock"]&&!s["before:highlightElement"]&&(s["before:highlightElement"]=o=>{s["before:highlightBlock"](Object.assign({block:o.el},o))}),s["after:highlightBlock"]&&!s["after:highlightElement"]&&(s["after:highlightElement"]=o=>{s["after:highlightBlock"](Object.assign({block:o.el},o))})}(s),u.push(s)},vuePlugin:BuildVuePlugin(s).VuePlugin}),s.debugMode=function(){_=!1},s.safeMode=function(){_=!0},s.versionString="10.7.3";for(const s in de)"object"==typeof de[s]&&o(de[s]);return Object.assign(s,de),s.addPlugin(L),s.addPlugin(ye),s.addPlugin($),s}({});s.exports=xe},35344(s){function concat(...s){return s.map((s=>function source(s){return s?"string"==typeof s?s:s.source:null}(s))).join("")}s.exports=function bash(s){const o={},i={begin:/\$\{/,end:/\}/,contains:["self",{begin:/:-/,contains:[o]}]};Object.assign(o,{className:"variable",variants:[{begin:concat(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},i]});const a={className:"subst",begin:/\$\(/,end:/\)/,contains:[s.BACKSLASH_ESCAPE]},u={begin:/<<-?\s*(?=\w+)/,starts:{contains:[s.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,className:"string"})]}},_={className:"string",begin:/"/,end:/"/,contains:[s.BACKSLASH_ESCAPE,o,a]};a.contains.push(_);const w={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},s.NUMBER_MODE,o]},x=s.SHEBANG({binary:`(${["fish","bash","zsh","sh","csh","ksh","tcsh","dash","scsh"].join("|")})`,relevance:10}),C={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[s.inherit(s.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b[a-z._-]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp"},contains:[x,s.SHEBANG(),C,w,s.HASH_COMMENT_MODE,u,_,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},o]}}},73402(s){function concat(...s){return s.map((s=>function source(s){return s?"string"==typeof s?s:s.source:null}(s))).join("")}s.exports=function http(s){const o="HTTP/(2|1\\.[01])",i={className:"attribute",begin:concat("^",/[A-Za-z][A-Za-z0-9-]*/,"(?=\\:\\s)"),starts:{contains:[{className:"punctuation",begin:/: /,relevance:0,starts:{end:"$",relevance:0}}]}},a=[i,{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}];return{name:"HTTP",aliases:["https"],illegal:/\S/,contains:[{begin:"^(?="+o+" \\d{3})",end:/$/,contains:[{className:"meta",begin:o},{className:"number",begin:"\\b\\d{3}\\b"}],starts:{end:/\b\B/,illegal:/\S/,contains:a}},{begin:"(?=^[A-Z]+ (.*?) "+o+"$)",end:/$/,contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{className:"meta",begin:o},{className:"keyword",begin:"[A-Z]+"}],starts:{end:/\b\B/,illegal:/\S/,contains:a}},s.inherit(i,{relevance:0})]}}},95089(s){const o="[A-Za-z$_][0-9A-Za-z$_]*",i=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],a=["true","false","null","undefined","NaN","Infinity"],u=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer","BigInt64Array","BigUint64Array","BigInt"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);function lookahead(s){return concat("(?=",s,")")}function concat(...s){return s.map((s=>function source(s){return s?"string"==typeof s?s:s.source:null}(s))).join("")}s.exports=function javascript(s){const _=o,w="<>",x="",C={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(s,o)=>{const i=s[0].length+s.index,a=s.input[i];"<"!==a?">"===a&&(((s,{after:o})=>{const i="",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:s.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:j,contains:ce}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:w,end:x},{begin:C.begin,"on:begin":C.isTrulyOpeningTag,end:C.end}],subLanguage:"xml",contains:[{begin:C.begin,end:C.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[{;]/,excludeEnd:!0,keywords:j,contains:["self",s.inherit(s.TITLE_MODE,{begin:_}),le],illegal:/%/},{beginKeywords:"while if switch catch for"},{className:"function",begin:s.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",returnBegin:!0,contains:[le,s.inherit(s.TITLE_MODE,{begin:_})]},{variants:[{begin:"\\."+_},{begin:"\\$"+_}],relevance:0},{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"[\]]/,contains:[{beginKeywords:"extends"},s.UNDERSCORE_TITLE_MODE]},{begin:/\b(?=constructor)/,end:/[{;]/,excludeEnd:!0,contains:[s.inherit(s.TITLE_MODE,{begin:_}),"self",le]},{begin:"(get|set)\\s+(?="+_+"\\()",end:/\{/,keywords:"get set",contains:[s.inherit(s.TITLE_MODE,{begin:_}),{begin:/\(\)/},le]},{begin:/\$[(.]/}]}}},65772(s){s.exports=function json(s){const o={literal:"true false null"},i=[s.C_LINE_COMMENT_MODE,s.C_BLOCK_COMMENT_MODE],a=[s.QUOTE_STRING_MODE,s.C_NUMBER_MODE],u={end:",",endsWithParent:!0,excludeEnd:!0,contains:a,keywords:o},_={begin:/\{/,end:/\}/,contains:[{className:"attr",begin:/"/,end:/"/,contains:[s.BACKSLASH_ESCAPE],illegal:"\\n"},s.inherit(u,{begin:/:/})].concat(i),illegal:"\\S"},w={begin:"\\[",end:"\\]",contains:[s.inherit(u)],illegal:"\\S"};return a.push(_,w),i.forEach((function(s){a.push(s)})),{name:"JSON",contains:a,keywords:o,illegal:"\\S"}}},26571(s){s.exports=function powershell(s){const o={$pattern:/-?[A-z\.\-]+\b/,keyword:"if else foreach return do while until elseif begin for trap data dynamicparam end break throw param continue finally in switch exit filter try process catch hidden static parameter",built_in:"ac asnp cat cd CFS chdir clc clear clhy cli clp cls clv cnsn compare copy cp cpi cpp curl cvpa dbp del diff dir dnsn ebp echo|0 epal epcsv epsn erase etsn exsn fc fhx fl ft fw gal gbp gc gcb gci gcm gcs gdr gerr ghy gi gin gjb gl gm gmo gp gps gpv group gsn gsnp gsv gtz gu gv gwmi h history icm iex ihy ii ipal ipcsv ipmo ipsn irm ise iwmi iwr kill lp ls man md measure mi mount move mp mv nal ndr ni nmo npssc nsn nv ogv oh popd ps pushd pwd r rbp rcjb rcsn rd rdr ren ri rjb rm rmdir rmo rni rnp rp rsn rsnp rujb rv rvpa rwmi sajb sal saps sasv sbp sc scb select set shcm si sl sleep sls sort sp spjb spps spsv start stz sujb sv swmi tee trcm type wget where wjb write"},i={begin:"`[\\s\\S]",relevance:0},a={className:"variable",variants:[{begin:/\$\B/},{className:"keyword",begin:/\$this/},{begin:/\$[\w\d][\w\d_:]*/}]},u={className:"string",variants:[{begin:/"/,end:/"/},{begin:/@"/,end:/^"@/}],contains:[i,a,{className:"variable",begin:/\$[A-z]/,end:/[^A-z]/}]},_={className:"string",variants:[{begin:/'/,end:/'/},{begin:/@'/,end:/^'@/}]},w=s.inherit(s.COMMENT(null,null),{variants:[{begin:/#/,end:/$/},{begin:/<#/,end:/#>/}],contains:[{className:"doctag",variants:[{begin:/\.(synopsis|description|example|inputs|outputs|notes|link|component|role|functionality)/},{begin:/\.(parameter|forwardhelptargetname|forwardhelpcategory|remotehelprunspace|externalhelp)\s+\S+/}]}]}),x={className:"built_in",variants:[{begin:"(".concat("Add|Clear|Close|Copy|Enter|Exit|Find|Format|Get|Hide|Join|Lock|Move|New|Open|Optimize|Pop|Push|Redo|Remove|Rename|Reset|Resize|Search|Select|Set|Show|Skip|Split|Step|Switch|Undo|Unlock|Watch|Backup|Checkpoint|Compare|Compress|Convert|ConvertFrom|ConvertTo|Dismount|Edit|Expand|Export|Group|Import|Initialize|Limit|Merge|Mount|Out|Publish|Restore|Save|Sync|Unpublish|Update|Approve|Assert|Build|Complete|Confirm|Deny|Deploy|Disable|Enable|Install|Invoke|Register|Request|Restart|Resume|Start|Stop|Submit|Suspend|Uninstall|Unregister|Wait|Debug|Measure|Ping|Repair|Resolve|Test|Trace|Connect|Disconnect|Read|Receive|Send|Write|Block|Grant|Protect|Revoke|Unblock|Unprotect|Use|ForEach|Sort|Tee|Where",")+(-)[\\w\\d]+")}]},C={className:"class",beginKeywords:"class enum",end:/\s*[{]/,excludeEnd:!0,relevance:0,contains:[s.TITLE_MODE]},j={className:"function",begin:/function\s+/,end:/\s*\{|$/,excludeEnd:!0,returnBegin:!0,relevance:0,contains:[{begin:"function",relevance:0,className:"keyword"},{className:"title",begin:/\w[\w\d]*((-)[\w\d]+)*/,relevance:0},{begin:/\(/,end:/\)/,className:"params",relevance:0,contains:[a]}]},L={begin:/using\s/,end:/$/,returnBegin:!0,contains:[u,_,{className:"keyword",begin:/(using|assembly|command|module|namespace|type)/}]},B={variants:[{className:"operator",begin:"(".concat("-and|-as|-band|-bnot|-bor|-bxor|-casesensitive|-ccontains|-ceq|-cge|-cgt|-cle|-clike|-clt|-cmatch|-cne|-cnotcontains|-cnotlike|-cnotmatch|-contains|-creplace|-csplit|-eq|-exact|-f|-file|-ge|-gt|-icontains|-ieq|-ige|-igt|-ile|-ilike|-ilt|-imatch|-in|-ine|-inotcontains|-inotlike|-inotmatch|-ireplace|-is|-isnot|-isplit|-join|-le|-like|-lt|-match|-ne|-not|-notcontains|-notin|-notlike|-notmatch|-or|-regex|-replace|-shl|-shr|-split|-wildcard|-xor",")\\b")},{className:"literal",begin:/(-)[\w\d]+/,relevance:0}]},$={className:"function",begin:/\[.*\]\s*[\w]+[ ]??\(/,end:/$/,returnBegin:!0,relevance:0,contains:[{className:"keyword",begin:"(".concat(o.keyword.toString().replace(/\s/g,"|"),")\\b"),endsParent:!0,relevance:0},s.inherit(s.TITLE_MODE,{endsParent:!0})]},z=[$,w,i,s.NUMBER_MODE,u,_,x,a,{className:"literal",begin:/\$(null|true|false)\b/},{className:"selector-tag",begin:/@\B/,relevance:0}],U={begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[].concat("self",z,{begin:"("+["string","char","byte","int","long","bool","decimal","single","double","DateTime","xml","array","hashtable","void"].join("|")+")",className:"built_in",relevance:0},{className:"type",begin:/[\.\w\d]+/,relevance:0})};return $.contains.unshift(U),{name:"PowerShell",aliases:["ps","ps1"],case_insensitive:!0,keywords:o,contains:z.concat(C,j,L,B,U)}}},17285(s){function source(s){return s?"string"==typeof s?s:s.source:null}function lookahead(s){return concat("(?=",s,")")}function concat(...s){return s.map((s=>source(s))).join("")}function either(...s){return"("+s.map((s=>source(s))).join("|")+")"}s.exports=function xml(s){const o=concat(/[A-Z_]/,function optional(s){return concat("(",s,")?")}(/[A-Z0-9_.-]*:/),/[A-Z0-9_.-]*/),i={className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},a={begin:/\s/,contains:[{className:"meta-keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}]},u=s.inherit(a,{begin:/\(/,end:/\)/}),_=s.inherit(s.APOS_STRING_MODE,{className:"meta-string"}),w=s.inherit(s.QUOTE_STRING_MODE,{className:"meta-string"}),x={endsWithParent:!0,illegal:/`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin://,relevance:10,contains:[a,w,_,u,{begin:/\[/,end:/\]/,contains:[{className:"meta",begin://,contains:[a,u,w,_]}]}]},s.COMMENT(//,{relevance:10}),{begin://,relevance:10},i,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:/)/,end:/>/,keywords:{name:"style"},contains:[x],starts:{end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:/)/,end:/>/,keywords:{name:"script"},contains:[x],starts:{end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:/<>|<\/>/},{className:"tag",begin:concat(//,/>/,/\s/)))),end:/\/?>/,contains:[{className:"name",begin:o,relevance:0,starts:x}]},{className:"tag",begin:concat(/<\//,lookahead(concat(o,/>/))),contains:[{className:"name",begin:o,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]}}},17533(s){s.exports=function yaml(s){var o="true false yes no null",i="[\\w#;/?:@&=+$,.~*'()[\\]]+",a={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[s.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]}]},u=s.inherit(a,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),_={className:"number",begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"},w={end:",",endsWithParent:!0,excludeEnd:!0,keywords:o,relevance:0},x={begin:/\{/,end:/\}/,contains:[w],illegal:"\\n",relevance:0},C={begin:"\\[",end:"\\]",contains:[w],illegal:"\\n",relevance:0},j=[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---\\s*$",relevance:10},{className:"string",begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+i},{className:"type",begin:"!<"+i+">"},{className:"type",begin:"!"+i},{className:"type",begin:"!!"+i},{className:"meta",begin:"&"+s.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+s.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)",relevance:0},s.HASH_COMMENT_MODE,{beginKeywords:o,keywords:{literal:o}},_,{className:"number",begin:s.C_NUMBER_RE+"\\b",relevance:0},x,C,a],L=[...j];return L.pop(),L.push(u),w.contains=L,{name:"YAML",case_insensitive:!0,aliases:["yml"],contains:j}}},251(s,o){o.read=function(s,o,i,a,u){var _,w,x=8*u-a-1,C=(1<>1,L=-7,B=i?u-1:0,$=i?-1:1,z=s[o+B];for(B+=$,_=z&(1<<-L)-1,z>>=-L,L+=x;L>0;_=256*_+s[o+B],B+=$,L-=8);for(w=_&(1<<-L)-1,_>>=-L,L+=a;L>0;w=256*w+s[o+B],B+=$,L-=8);if(0===_)_=1-j;else{if(_===C)return w?NaN:1/0*(z?-1:1);w+=Math.pow(2,a),_-=j}return(z?-1:1)*w*Math.pow(2,_-a)},o.write=function(s,o,i,a,u,_){var w,x,C,j=8*_-u-1,L=(1<>1,$=23===u?Math.pow(2,-24)-Math.pow(2,-77):0,z=a?0:_-1,U=a?1:-1,V=o<0||0===o&&1/o<0?1:0;for(o=Math.abs(o),isNaN(o)||o===1/0?(x=isNaN(o)?1:0,w=L):(w=Math.floor(Math.log(o)/Math.LN2),o*(C=Math.pow(2,-w))<1&&(w--,C*=2),(o+=w+B>=1?$/C:$*Math.pow(2,1-B))*C>=2&&(w++,C/=2),w+B>=L?(x=0,w=L):w+B>=1?(x=(o*C-1)*Math.pow(2,u),w+=B):(x=o*Math.pow(2,B-1)*Math.pow(2,u),w=0));u>=8;s[i+z]=255&x,z+=U,x/=256,u-=8);for(w=w<0;s[i+z]=255&w,z+=U,w/=256,j-=8);s[i+z-U]|=128*V}},49568(s,o,i){"use strict";i.r(o),i.d(o,{Collection:()=>w,Iterable:()=>Zt,List:()=>wt,Map:()=>ct,OrderedMap:()=>jt,OrderedSet:()=>Vt,PairSorting:()=>Kt,Range:()=>Rt,Record:()=>Ht,Repeat:()=>Xt,Seq:()=>le,Set:()=>Ft,Stack:()=>Tt,fromJS:()=>fromJS,get:()=>get,getIn:()=>getIn$1,has:()=>has,hasIn:()=>hasIn$1,hash:()=>hash,is:()=>is,isAssociative:()=>isAssociative,isCollection:()=>isCollection,isImmutable:()=>isImmutable,isIndexed:()=>isIndexed,isKeyed:()=>isKeyed,isList:()=>isList,isMap:()=>isMap,isOrdered:()=>isOrdered,isOrderedMap:()=>isOrderedMap,isOrderedSet:()=>isOrderedSet,isPlainObject:()=>isPlainObject,isRecord:()=>isRecord,isSeq:()=>isSeq,isSet:()=>isSet,isStack:()=>isStack,isValueObject:()=>isValueObject,merge:()=>merge,mergeDeep:()=>mergeDeep$1,mergeDeepWith:()=>mergeDeepWith$1,mergeWith:()=>mergeWith,remove:()=>remove,removeIn:()=>removeIn,set:()=>set,setIn:()=>setIn$1,update:()=>update$1,updateIn:()=>updateIn,version:()=>Qt});var a="@@__IMMUTABLE_INDEXED__@@";function isIndexed(s){return Boolean(s&&s[a])}var u="@@__IMMUTABLE_KEYED__@@";function isKeyed(s){return Boolean(s&&s[u])}function isAssociative(s){return isKeyed(s)||isIndexed(s)}var _="@@__IMMUTABLE_ITERABLE__@@";function isCollection(s){return Boolean(s&&s[_])}var w=function Collection(s){return isCollection(s)?s:le(s)},x=function(s){function KeyedCollection(s){return isKeyed(s)?s:pe(s)}return s&&(KeyedCollection.__proto__=s),KeyedCollection.prototype=Object.create(s&&s.prototype),KeyedCollection.prototype.constructor=KeyedCollection,KeyedCollection}(w),C=function(s){function IndexedCollection(s){return isIndexed(s)?s:de(s)}return s&&(IndexedCollection.__proto__=s),IndexedCollection.prototype=Object.create(s&&s.prototype),IndexedCollection.prototype.constructor=IndexedCollection,IndexedCollection}(w),j=function(s){function SetCollection(s){return isCollection(s)&&!isAssociative(s)?s:fe(s)}return s&&(SetCollection.__proto__=s),SetCollection.prototype=Object.create(s&&s.prototype),SetCollection.prototype.constructor=SetCollection,SetCollection}(w);w.Keyed=x,w.Indexed=C,w.Set=j;var L="function"==typeof Symbol&&Symbol.iterator,B="@@iterator",$=L||B,z=function Iterator(s){this.next=s};function iteratorValue(s,o,i,a){var u=0===s?o:1===s?i:[o,i];return a?a.value=u:a={value:u,done:!1},a}function iteratorDone(){return{value:void 0,done:!0}}function hasIterator(s){return!!Array.isArray(s)||!!getIteratorFn(s)}function isIterator(s){return!(!s||"function"!=typeof s.next)}function getIterator(s){var o=getIteratorFn(s);return o&&o.call(s)}function getIteratorFn(s){var o=s&&(L&&s[L]||s[B]);if("function"==typeof o)return o}z.prototype.toString=function toString(){return"[Iterator]"},z.KEYS=0,z.VALUES=1,z.ENTRIES=2,z.prototype.inspect=z.prototype.toSource=function(){return this.toString()},z.prototype[$]=function(){return this};var U="delete",V=32,Y=31,Z={};function SetRef(s){s&&(s.value=!0)}function OwnerID(){}function ensureSize(s){return void 0===s.size&&(s.size=s.__iterate(returnTrue)),s.size}function wrapIndex(s,o){if("number"!=typeof o){var i=o>>>0;if(""+i!==o||4294967295===i)return NaN;o=i}return o<0?ensureSize(s)+o:o}function returnTrue(){return!0}function wholeSlice(s,o,i){return(0===s&&!isNeg(s)||void 0!==i&&s<=-i)&&(void 0===o||void 0!==i&&o>=i)}function resolveBegin(s,o){return resolveIndex(s,o,0)}function resolveEnd(s,o){return resolveIndex(s,o,o)}function resolveIndex(s,o,i){return void 0===s?i:isNeg(s)?o===1/0?o:0|Math.max(0,o+s):void 0===o||o===s?s:0|Math.min(o,s)}function isNeg(s){return s<0||0===s&&1/s==-1/0}var ee="@@__IMMUTABLE_RECORD__@@";function isRecord(s){return Boolean(s&&s[ee])}function isImmutable(s){return isCollection(s)||isRecord(s)}var ie="@@__IMMUTABLE_ORDERED__@@";function isOrdered(s){return Boolean(s&&s[ie])}var ae="@@__IMMUTABLE_SEQ__@@";function isSeq(s){return Boolean(s&&s[ae])}var ce=Object.prototype.hasOwnProperty;function isArrayLike(s){return!(!Array.isArray(s)&&"string"!=typeof s)||s&&"object"==typeof s&&Number.isInteger(s.length)&&s.length>=0&&(0===s.length?1===Object.keys(s).length:s.hasOwnProperty(s.length-1))}var le=function(s){function Seq(s){return null==s?emptySequence():isImmutable(s)?s.toSeq():function seqFromValue(s){var o=maybeIndexedSeqFromValue(s);if(o)return function isEntriesIterable(s){var o=getIteratorFn(s);return o&&o===s.entries}(s)?o.fromEntrySeq():function isKeysIterable(s){var o=getIteratorFn(s);return o&&o===s.keys}(s)?o.toSetSeq():o;if("object"==typeof s)return new be(s);throw new TypeError("Expected Array or collection object of values, or keyed object: "+s)}(s)}return s&&(Seq.__proto__=s),Seq.prototype=Object.create(s&&s.prototype),Seq.prototype.constructor=Seq,Seq.prototype.toSeq=function toSeq(){return this},Seq.prototype.toString=function toString(){return this.__toString("Seq {","}")},Seq.prototype.cacheResult=function cacheResult(){return!this._cache&&this.__iterateUncached&&(this._cache=this.entrySeq().toArray(),this.size=this._cache.length),this},Seq.prototype.__iterate=function __iterate(s,o){var i=this._cache;if(i){for(var a=i.length,u=0;u!==a;){var _=i[o?a-++u:u++];if(!1===s(_[1],_[0],this))break}return u}return this.__iterateUncached(s,o)},Seq.prototype.__iterator=function __iterator(s,o){var i=this._cache;if(i){var a=i.length,u=0;return new z((function(){if(u===a)return{value:void 0,done:!0};var _=i[o?a-++u:u++];return iteratorValue(s,_[0],_[1])}))}return this.__iteratorUncached(s,o)},Seq}(w),pe=function(s){function KeyedSeq(s){return null==s?emptySequence().toKeyedSeq():isCollection(s)?isKeyed(s)?s.toSeq():s.fromEntrySeq():isRecord(s)?s.toSeq():keyedSeqFromValue(s)}return s&&(KeyedSeq.__proto__=s),KeyedSeq.prototype=Object.create(s&&s.prototype),KeyedSeq.prototype.constructor=KeyedSeq,KeyedSeq.prototype.toKeyedSeq=function toKeyedSeq(){return this},KeyedSeq}(le),de=function(s){function IndexedSeq(s){return null==s?emptySequence():isCollection(s)?isKeyed(s)?s.entrySeq():s.toIndexedSeq():isRecord(s)?s.toSeq().entrySeq():indexedSeqFromValue(s)}return s&&(IndexedSeq.__proto__=s),IndexedSeq.prototype=Object.create(s&&s.prototype),IndexedSeq.prototype.constructor=IndexedSeq,IndexedSeq.of=function of(){return IndexedSeq(arguments)},IndexedSeq.prototype.toIndexedSeq=function toIndexedSeq(){return this},IndexedSeq.prototype.toString=function toString(){return this.__toString("Seq [","]")},IndexedSeq}(le),fe=function(s){function SetSeq(s){return(isCollection(s)&&!isAssociative(s)?s:de(s)).toSetSeq()}return s&&(SetSeq.__proto__=s),SetSeq.prototype=Object.create(s&&s.prototype),SetSeq.prototype.constructor=SetSeq,SetSeq.of=function of(){return SetSeq(arguments)},SetSeq.prototype.toSetSeq=function toSetSeq(){return this},SetSeq}(le);le.isSeq=isSeq,le.Keyed=pe,le.Set=fe,le.Indexed=de,le.prototype[ae]=!0;var ye=function(s){function ArraySeq(s){this._array=s,this.size=s.length}return s&&(ArraySeq.__proto__=s),ArraySeq.prototype=Object.create(s&&s.prototype),ArraySeq.prototype.constructor=ArraySeq,ArraySeq.prototype.get=function get(s,o){return this.has(s)?this._array[wrapIndex(this,s)]:o},ArraySeq.prototype.__iterate=function __iterate(s,o){for(var i=this._array,a=i.length,u=0;u!==a;){var _=o?a-++u:u++;if(!1===s(i[_],_,this))break}return u},ArraySeq.prototype.__iterator=function __iterator(s,o){var i=this._array,a=i.length,u=0;return new z((function(){if(u===a)return{value:void 0,done:!0};var _=o?a-++u:u++;return iteratorValue(s,_,i[_])}))},ArraySeq}(de),be=function(s){function ObjectSeq(s){var o=Object.keys(s).concat(Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(s):[]);this._object=s,this._keys=o,this.size=o.length}return s&&(ObjectSeq.__proto__=s),ObjectSeq.prototype=Object.create(s&&s.prototype),ObjectSeq.prototype.constructor=ObjectSeq,ObjectSeq.prototype.get=function get(s,o){return void 0===o||this.has(s)?this._object[s]:o},ObjectSeq.prototype.has=function has(s){return ce.call(this._object,s)},ObjectSeq.prototype.__iterate=function __iterate(s,o){for(var i=this._object,a=this._keys,u=a.length,_=0;_!==u;){var w=a[o?u-++_:_++];if(!1===s(i[w],w,this))break}return _},ObjectSeq.prototype.__iterator=function __iterator(s,o){var i=this._object,a=this._keys,u=a.length,_=0;return new z((function(){if(_===u)return{value:void 0,done:!0};var w=a[o?u-++_:_++];return iteratorValue(s,w,i[w])}))},ObjectSeq}(pe);be.prototype[ie]=!0;var Se,_e=function(s){function CollectionSeq(s){this._collection=s,this.size=s.length||s.size}return s&&(CollectionSeq.__proto__=s),CollectionSeq.prototype=Object.create(s&&s.prototype),CollectionSeq.prototype.constructor=CollectionSeq,CollectionSeq.prototype.__iterateUncached=function __iterateUncached(s,o){if(o)return this.cacheResult().__iterate(s,o);var i=getIterator(this._collection),a=0;if(isIterator(i))for(var u;!(u=i.next()).done&&!1!==s(u.value,a++,this););return a},CollectionSeq.prototype.__iteratorUncached=function __iteratorUncached(s,o){if(o)return this.cacheResult().__iterator(s,o);var i=getIterator(this._collection);if(!isIterator(i))return new z(iteratorDone);var a=0;return new z((function(){var o=i.next();return o.done?o:iteratorValue(s,a++,o.value)}))},CollectionSeq}(de);function emptySequence(){return Se||(Se=new ye([]))}function keyedSeqFromValue(s){var o=maybeIndexedSeqFromValue(s);if(o)return o.fromEntrySeq();if("object"==typeof s)return new be(s);throw new TypeError("Expected Array or collection object of [k, v] entries, or keyed object: "+s)}function indexedSeqFromValue(s){var o=maybeIndexedSeqFromValue(s);if(o)return o;throw new TypeError("Expected Array or collection object of values: "+s)}function maybeIndexedSeqFromValue(s){return isArrayLike(s)?new ye(s):hasIterator(s)?new _e(s):void 0}function asImmutable(){return this.__ensureOwner()}function asMutable(){return this.__ownerID?this:this.__ensureOwner(new OwnerID)}var we="function"==typeof Math.imul&&-2===Math.imul(4294967295,2)?Math.imul:function imul(s,o){var i=65535&(s|=0),a=65535&(o|=0);return i*a+((s>>>16)*a+i*(o>>>16)<<16>>>0)|0};function smi(s){return s>>>1&1073741824|3221225471&s}var xe=Object.prototype.valueOf;function hash(s){if(null==s)return hashNullish(s);if("function"==typeof s.hashCode)return smi(s.hashCode(s));var o=function valueOf(s){return s.valueOf!==xe&&"function"==typeof s.valueOf?s.valueOf(s):s}(s);if(null==o)return hashNullish(o);switch(typeof o){case"boolean":return o?1108378657:1108378656;case"number":return function hashNumber(s){if(s!=s||s===1/0)return 0;var o=0|s;o!==s&&(o^=4294967295*s);for(;s>4294967295;)o^=s/=4294967295;return smi(o)}(o);case"string":return o.length>Ye?function cachedHashString(s){var o=et[s];void 0===o&&(o=hashString(s),Qe===Xe&&(Qe=0,et={}),Qe++,et[s]=o);return o}(o):hashString(o);case"object":case"function":return function hashJSObj(s){var o;if($e&&void 0!==(o=qe.get(s)))return o;if(void 0!==(o=s[He]))return o;if(!Re){if(void 0!==(o=s.propertyIsEnumerable&&s.propertyIsEnumerable[He]))return o;if(void 0!==(o=function getIENodeHash(s){if(s&&s.nodeType>0)switch(s.nodeType){case 1:return s.uniqueID;case 9:return s.documentElement&&s.documentElement.uniqueID}}(s)))return o}if(o=nextHash(),$e)qe.set(s,o);else{if(void 0!==Te&&!1===Te(s))throw new Error("Non-extensible objects are not allowed as keys.");if(Re)Object.defineProperty(s,He,{enumerable:!1,configurable:!1,writable:!1,value:o});else if(void 0!==s.propertyIsEnumerable&&s.propertyIsEnumerable===s.constructor.prototype.propertyIsEnumerable)s.propertyIsEnumerable=function(){return this.constructor.prototype.propertyIsEnumerable.apply(this,arguments)},s.propertyIsEnumerable[He]=o;else{if(void 0===s.nodeType)throw new Error("Unable to set a non-enumerable property on object.");s[He]=o}}return o}(o);case"symbol":return function hashSymbol(s){var o=ze[s];if(void 0!==o)return o;return o=nextHash(),ze[s]=o,o}(o);default:if("function"==typeof o.toString)return hashString(o.toString());throw new Error("Value type "+typeof o+" cannot be hashed.")}}function hashNullish(s){return null===s?1108378658:1108378659}function hashString(s){for(var o=0,i=0;i=0&&(C.get=function(o,i){return(o=wrapIndex(this,o))>=0&&o<_?s.get(o+w,i):i}),C.__iterateUncached=function(o,i){var u=this;if(0===_)return 0;if(i)return this.cacheResult().__iterate(o,i);var x=0,C=!0,j=0;return s.__iterate((function(s,i){if(!C||!(C=x++_)return{value:void 0,done:!0};var s=u.next();return a||1===o||s.done?s:iteratorValue(o,C-1,0===o?void 0:s.value[1],s)}))},C}function skipWhileFactory(s,o,i,a){var u=makeSequence(s);return u.__iterateUncached=function(u,_){var w=this;if(_)return this.cacheResult().__iterate(u,_);var x=!0,C=0;return s.__iterate((function(s,_,j){if(!x||!(x=o.call(i,s,_,j)))return C++,u(s,a?_:C-1,w)})),C},u.__iteratorUncached=function(u,_){var w=this;if(_)return this.cacheResult().__iterator(u,_);var x=s.__iterator(2,_),C=!0,j=0;return new z((function(){var s,_,L;do{if((s=x.next()).done)return a||1===u?s:iteratorValue(u,j++,0===u?void 0:s.value[1],s);var B=s.value;_=B[0],L=B[1],C&&(C=o.call(i,L,_,w))}while(C);return 2===u?s:iteratorValue(u,_,L,s)}))},u}rt.prototype.cacheResult=tt.prototype.cacheResult=nt.prototype.cacheResult=st.prototype.cacheResult=cacheResultThrough;var ot=function(s){function ConcatSeq(s){this._wrappedIterables=s.flatMap((function(s){return s._wrappedIterables?s._wrappedIterables:[s]})),this.size=this._wrappedIterables.reduce((function(s,o){if(void 0!==s){var i=o.size;if(void 0!==i)return s+i}}),0),this[u]=this._wrappedIterables[0][u],this[a]=this._wrappedIterables[0][a],this[ie]=this._wrappedIterables[0][ie]}return s&&(ConcatSeq.__proto__=s),ConcatSeq.prototype=Object.create(s&&s.prototype),ConcatSeq.prototype.constructor=ConcatSeq,ConcatSeq.prototype.__iterateUncached=function __iterateUncached(s,o){if(0!==this._wrappedIterables.length){if(o)return this.cacheResult().__iterate(s,o);for(var i=0,a=isKeyed(this),u=a?2:1,_=this._wrappedIterables[i].__iterator(u,o),w=!0,x=0;w;){for(var C=_.next();C.done;){if(++i===this._wrappedIterables.length)return x;C=(_=this._wrappedIterables[i].__iterator(u,o)).next()}w=!1!==(a?s(C.value[1],C.value[0],this):s(C.value,x,this)),x++}return x}},ConcatSeq.prototype.__iteratorUncached=function __iteratorUncached(s,o){var i=this;if(0===this._wrappedIterables.length)return new z(iteratorDone);if(o)return this.cacheResult().__iterator(s,o);var a=0,u=this._wrappedIterables[a].__iterator(s,o);return new z((function(){for(var _=u.next();_.done;){if(++a===i._wrappedIterables.length)return _;_=(u=i._wrappedIterables[a].__iterator(s,o)).next()}return _}))},ConcatSeq}(le);function flattenFactory(s,o,i){var a=makeSequence(s);return a.__iterateUncached=function(u,_){if(_)return this.cacheResult().__iterate(u,_);var w=0,x=!1;return function flatDeep(s,C){s.__iterate((function(s,_){return(!o||C0}function zipWithFactory(s,o,i,a){var u=makeSequence(s),_=new ye(i).map((function(s){return s.size}));return u.size=a?_.max():_.min(),u.__iterate=function(s,o){for(var i,a=this.__iterator(1,o),u=0;!(i=a.next()).done&&!1!==s(i.value,u++,this););return u},u.__iteratorUncached=function(s,u){var _=i.map((function(s){return s=w(s),getIterator(u?s.reverse():s)})),x=0,C=!1;return new z((function(){var i;return C||(i=_.map((function(s){return s.next()})),C=a?i.every((function(s){return s.done})):i.some((function(s){return s.done}))),C?{value:void 0,done:!0}:iteratorValue(s,x++,o.apply(null,i.map((function(s){return s.value}))))}))},u}function reify(s,o){return s===o?s:isSeq(s)?o:s.constructor(o)}function validateEntry(s){if(s!==Object(s))throw new TypeError("Expected [K, V] tuple: "+s)}function collectionClass(s){return isKeyed(s)?x:isIndexed(s)?C:j}function makeSequence(s){return Object.create((isKeyed(s)?pe:isIndexed(s)?de:fe).prototype)}function cacheResultThrough(){return this._iter.cacheResult?(this._iter.cacheResult(),this.size=this._iter.size,this):le.prototype.cacheResult.call(this)}function defaultComparator(s,o){return void 0===s&&void 0===o?0:void 0===s?1:void 0===o?-1:s>o?1:s0;)o[i]=arguments[i+1];if("function"!=typeof s)throw new TypeError("Invalid merger function: "+s);return mergeIntoKeyedWith(this,o,s)}function mergeIntoKeyedWith(s,o,i){for(var a=[],u=0;u0;)o[i]=arguments[i+1];return mergeWithSources(s,o)}function mergeWith(s,o){for(var i=[],a=arguments.length-2;a-- >0;)i[a]=arguments[a+2];return mergeWithSources(o,i,s)}function mergeDeep$1(s){for(var o=[],i=arguments.length-1;i-- >0;)o[i]=arguments[i+1];return mergeDeepWithSources(s,o)}function mergeDeepWith$1(s,o){for(var i=[],a=arguments.length-2;a-- >0;)i[a]=arguments[a+2];return mergeDeepWithSources(o,i,s)}function mergeDeepWithSources(s,o,i){return mergeWithSources(s,o,function deepMergerWith(s){function deepMerger(o,i,a){return isDataStructure(o)&&isDataStructure(i)&&function areMergeable(s,o){var i=le(s),a=le(o);return isIndexed(i)===isIndexed(a)&&isKeyed(i)===isKeyed(a)}(o,i)?mergeWithSources(o,[i],deepMerger):s?s(o,i,a):i}return deepMerger}(i))}function mergeWithSources(s,o,i){if(!isDataStructure(s))throw new TypeError("Cannot merge into non-data-structure value: "+s);if(isImmutable(s))return"function"==typeof i&&s.mergeWith?s.mergeWith.apply(s,[i].concat(o)):s.merge?s.merge.apply(s,o):s.concat.apply(s,o);for(var a=Array.isArray(s),u=s,_=a?C:x,w=a?function(o){u===s&&(u=shallowCopy(u)),u.push(o)}:function(o,a){if(!isProtoKey(a)){var _=ce.call(u,a),w=_&&i?i(u[a],o,a):o;_&&w===u[a]||(u===s&&(u=shallowCopy(u)),u[a]=w)}},j=0;j0;)o[i]=arguments[i+1];return mergeDeepWithSources(this,o,s)}function mergeDeepIn(s){for(var o=[],i=arguments.length-1;i-- >0;)o[i]=arguments[i+1];return updateIn(this,s,emptyMap(),(function(s){return mergeDeepWithSources(s,o)}))}function mergeIn(s){for(var o=[],i=arguments.length-1;i-- >0;)o[i]=arguments[i+1];return updateIn(this,s,emptyMap(),(function(s){return mergeWithSources(s,o)}))}function setIn$1(s,o,i){return updateIn(s,o,Z,(function(){return i}))}function setIn(s,o){return setIn$1(this,s,o)}function update(s,o,i){return 1===arguments.length?s(this):update$1(this,s,o,i)}function updateIn$1(s,o,i){return updateIn(this,s,o,i)}function wasAltered(){return this.__altered}function withMutations(s){var o=this.asMutable();return s(o),o.wasAltered()?o.__ensureOwner(this.__ownerID):this}var at="@@__IMMUTABLE_MAP__@@";function isMap(s){return Boolean(s&&s[at])}function invariant(s,o){if(!s)throw new Error(o)}function assertNotInfinite(s){invariant(s!==1/0,"Cannot perform this action with an infinite size.")}var ct=function(s){function Map(o){return null==o?emptyMap():isMap(o)&&!isOrdered(o)?o:emptyMap().withMutations((function(i){var a=s(o);assertNotInfinite(a.size),a.forEach((function(s,o){return i.set(o,s)}))}))}return s&&(Map.__proto__=s),Map.prototype=Object.create(s&&s.prototype),Map.prototype.constructor=Map,Map.prototype.toString=function toString(){return this.__toString("Map {","}")},Map.prototype.get=function get(s,o){return this._root?this._root.get(0,void 0,s,o):o},Map.prototype.set=function set(s,o){return updateMap(this,s,o)},Map.prototype.remove=function remove(s){return updateMap(this,s,Z)},Map.prototype.deleteAll=function deleteAll(s){var o=w(s);return 0===o.size?this:this.withMutations((function(s){o.forEach((function(o){return s.remove(o)}))}))},Map.prototype.clear=function clear(){return 0===this.size?this:this.__ownerID?(this.size=0,this._root=null,this.__hash=void 0,this.__altered=!0,this):emptyMap()},Map.prototype.sort=function sort(s){return jt(sortFactory(this,s))},Map.prototype.sortBy=function sortBy(s,o){return jt(sortFactory(this,o,s))},Map.prototype.map=function map(s,o){var i=this;return this.withMutations((function(a){a.forEach((function(u,_){a.set(_,s.call(o,u,_,i))}))}))},Map.prototype.__iterator=function __iterator(s,o){return new yt(this,s,o)},Map.prototype.__iterate=function __iterate(s,o){var i=this,a=0;return this._root&&this._root.iterate((function(o){return a++,s(o[1],o[0],i)}),o),a},Map.prototype.__ensureOwner=function __ensureOwner(s){return s===this.__ownerID?this:s?makeMap(this.size,this._root,s,this.__hash):0===this.size?emptyMap():(this.__ownerID=s,this.__altered=!1,this)},Map}(x);ct.isMap=isMap;var lt=ct.prototype;lt[at]=!0,lt[U]=lt.remove,lt.removeAll=lt.deleteAll,lt.setIn=setIn,lt.removeIn=lt.deleteIn=deleteIn,lt.update=update,lt.updateIn=updateIn$1,lt.merge=lt.concat=merge$1,lt.mergeWith=mergeWith$1,lt.mergeDeep=mergeDeep,lt.mergeDeepWith=mergeDeepWith,lt.mergeIn=mergeIn,lt.mergeDeepIn=mergeDeepIn,lt.withMutations=withMutations,lt.wasAltered=wasAltered,lt.asImmutable=asImmutable,lt["@@transducer/init"]=lt.asMutable=asMutable,lt["@@transducer/step"]=function(s,o){return s.set(o[0],o[1])},lt["@@transducer/result"]=function(s){return s.asImmutable()};var ut=function ArrayMapNode(s,o){this.ownerID=s,this.entries=o};ut.prototype.get=function get(s,o,i,a){for(var u=this.entries,_=0,w=u.length;_=vt)return function createNodes(s,o,i,a){s||(s=new OwnerID);for(var u=new mt(s,hash(i),[i,a]),_=0;_>>s)&Y),_=this.bitmap;return _&u?this.nodes[popCount(_&u-1)].get(s+5,o,i,a):a},pt.prototype.update=function update(s,o,i,a,u,_,w){void 0===i&&(i=hash(a));var x=(0===o?i:i>>>o)&Y,C=1<=bt)return function expandNodes(s,o,i,a,u){for(var _=0,w=new Array(V),x=0;0!==i;x++,i>>>=1)w[x]=1&i?o[_++]:void 0;return w[a]=u,new ht(s,_+1,w)}(s,$,j,x,U);if(L&&!U&&2===$.length&&isLeafNode($[1^B]))return $[1^B];if(L&&U&&1===$.length&&isLeafNode(U))return U;var ee=s&&s===this.ownerID,ie=L?U?j:j^C:j|C,ae=L?U?setAt($,B,U,ee):function spliceOut(s,o,i){var a=s.length-1;if(i&&o===a)return s.pop(),s;for(var u=new Array(a),_=0,w=0;w>>s)&Y,_=this.nodes[u];return _?_.get(s+5,o,i,a):a},ht.prototype.update=function update(s,o,i,a,u,_,w){void 0===i&&(i=hash(a));var x=(0===o?i:i>>>o)&Y,C=u===Z,j=this.nodes,L=j[x];if(C&&!L)return this;var B=updateNode(L,s,o+5,i,a,u,_,w);if(B===L)return this;var $=this.count;if(L){if(!B&&--$=_t&&(a=this._buildIndex()),void 0!==a){var u=a[hashCollisionKey(s)];if(void 0!==u)for(var _=0;_>>i)&Y,x=(0===i?a:a>>>i)&Y,C=w===x?[mergeIntoNode(s,o,i+5,a,u)]:(_=new mt(o,a,u),w>1&1431655765))+(s>>2&858993459))+(s>>4)&252645135,s+=s>>8,127&(s+=s>>16)}function setAt(s,o,i,a){var u=a?s:arrCopy(s);return u[o]=i,u}var vt=8,bt=16,St=8,_t=16;function coerceKeyPath(s){if(isArrayLike(s)&&"string"!=typeof s)return s;if(isOrdered(s))return s.toArray();throw new TypeError("Invalid keyPath: expected Ordered Collection or Array: "+s)}function quoteString(s){try{return"string"==typeof s?JSON.stringify(s):String(s)}catch(o){return JSON.stringify(s)}}function has(s,o){return isImmutable(s)?s.has(o):isDataStructure(s)&&ce.call(s,o)}function get(s,o,i){return isImmutable(s)?s.get(o,i):has(s,o)?"function"==typeof s.get?s.get(o):s[o]:i}function remove(s,o){if(!isDataStructure(s))throw new TypeError("Cannot update non-data-structure value: "+s);if(isImmutable(s)){if(!s.remove)throw new TypeError("Cannot update immutable value without .remove() method: "+s);return s.remove(o)}if(!ce.call(s,o))return s;var i=shallowCopy(s);return Array.isArray(i)?i.splice(o,1):delete i[o],i}function set(s,o,i){if(isProtoKey(o))return s;if(!isDataStructure(s))throw new TypeError("Cannot update non-data-structure value: "+s);if(isImmutable(s)){if(!s.set)throw new TypeError("Cannot update immutable value without .set() method: "+s);return s.set(o,i)}if(ce.call(s,o)&&i===s[o])return s;var a=shallowCopy(s);return a[o]=i,a}function updateIn(s,o,i,a){a||(a=i,i=void 0);var u=updateInDeeply(isImmutable(s),s,coerceKeyPath(o),0,i,a);return u===Z?i:u}function updateInDeeply(s,o,i,a,u,_){var w=o===Z;if(a===i.length){var x=w?u:o,C=_(x);return C===x?o:C}if(!w&&!isDataStructure(o))throw new TypeError("Cannot update within non-data-structure value in path ["+Array.from(i).slice(0,a).map(quoteString)+"]: "+o);var j=i[a],L=w?Z:get(o,j,Z),B=updateInDeeply(L===Z?s:isImmutable(L),L,i,a+1,u,_);return B===L?o:B===Z?remove(o,j):set(w?s?emptyMap():{}:o,j,B)}function removeIn(s,o){return updateIn(s,o,(function(){return Z}))}function deleteIn(s){return removeIn(this,s)}var Et="@@__IMMUTABLE_LIST__@@";function isList(s){return Boolean(s&&s[Et])}var wt=function(s){function List(o){var i=emptyList();if(null==o)return i;if(isList(o))return o;var a=s(o),u=a.size;return 0===u?i:(assertNotInfinite(u),u>0&&u=0&&s=s.size||o<0)return s.withMutations((function(s){o<0?setListBounds(s,o).set(0,i):setListBounds(s,0,o+1).set(o,i)}));o+=s._origin;var a=s._tail,u=s._root,_={value:!1};o>=getTailOffset(s._capacity)?a=updateVNode(a,s.__ownerID,0,o,i,_):u=updateVNode(u,s.__ownerID,s._level,o,i,_);if(!_.value)return s;if(s.__ownerID)return s._root=u,s._tail=a,s.__hash=void 0,s.__altered=!0,s;return makeList(s._origin,s._capacity,s._level,u,a)}(this,s,o)},List.prototype.remove=function remove(s){return this.has(s)?0===s?this.shift():s===this.size-1?this.pop():this.splice(s,1):this},List.prototype.insert=function insert(s,o){return this.splice(s,0,o)},List.prototype.clear=function clear(){return 0===this.size?this:this.__ownerID?(this.size=this._origin=this._capacity=0,this._level=5,this._root=this._tail=this.__hash=void 0,this.__altered=!0,this):emptyList()},List.prototype.push=function push(){var s=arguments,o=this.size;return this.withMutations((function(i){setListBounds(i,0,o+s.length);for(var a=0;a>>o&Y;if(a>=this.array.length)return new kt([],s);var u,_=0===a;if(o>0){var w=this.array[a];if((u=w&&w.removeBefore(s,o-5,i))===w&&_)return this}if(_&&!u)return this;var x=editableVNode(this,s);if(!_)for(var C=0;C>>o&Y;if(u>=this.array.length)return this;if(o>0){var _=this.array[u];if((a=_&&_.removeAfter(s,o-5,i))===_&&u===this.array.length-1)return this}var w=editableVNode(this,s);return w.array.splice(u+1),a&&(w.array[u]=a),w};var Ot={};function iterateList(s,o){var i=s._origin,a=s._capacity,u=getTailOffset(a),_=s._tail;return iterateNodeOrLeaf(s._root,s._level,0);function iterateNodeOrLeaf(s,w,x){return 0===w?function iterateLeaf(s,w){var x=w===u?_&&_.array:s&&s.array,C=w>i?0:i-w,j=a-w;j>V&&(j=V);return function(){if(C===j)return Ot;var s=o?--j:C++;return x&&x[s]}}(s,x):function iterateNode(s,u,_){var w,x=s&&s.array,C=_>i?0:i-_>>u,j=1+(a-_>>u);j>V&&(j=V);return function(){for(;;){if(w){var s=w();if(s!==Ot)return s;w=null}if(C===j)return Ot;var i=o?--j:C++;w=iterateNodeOrLeaf(x&&x[i],u-5,_+(i<>>i&Y,C=s&&x0){var j=s&&s.array[x],L=updateVNode(j,o,i-5,a,u,_);return L===j?s:((w=editableVNode(s,o)).array[x]=L,w)}return C&&s.array[x]===u?s:(_&&SetRef(_),w=editableVNode(s,o),void 0===u&&x===w.array.length-1?w.array.pop():w.array[x]=u,w)}function editableVNode(s,o){return o&&s&&o===s.ownerID?s:new kt(s?s.array.slice():[],o)}function listNodeFor(s,o){if(o>=getTailOffset(s._capacity))return s._tail;if(o<1<0;)i=i.array[o>>>a&Y],a-=5;return i}}function setListBounds(s,o,i){!function validateListBoundsRequest(s,o,i){var a=s._origin+(void 0===o?0:o),u=void 0===i?s._capacity:i<0?s._capacity+i:s._origin+i;if(Number.isFinite(u)&&u>At||Number.isFinite(a)&&a<-At||Number.isFinite(u)&&Number.isFinite(a)&&u-a>At)throw new RangeError("Invalid List size: a List cannot hold more than "+At+" (2 ** 30) values.")}(s,o,i),void 0!==o&&(o|=0),void 0!==i&&(i|=0);var a=s.__ownerID||new OwnerID,u=s._origin,_=s._capacity,w=u+o,x=void 0===i?_:i<0?_+i:u+i;if(w===u&&x===_)return s;if(w>=x)return s.clear();for(var C=s._level,j=s._root,L=0;w+L<0;)j=new kt(j&&j.array.length?[void 0,j]:[],a),L+=levelCapacity(C+=5);L&&(w+=L,u+=L,x+=L,_+=L);for(var B=getTailOffset(_),$=getTailOffset(x);$>=levelCapacity(C+5);)j=new kt(j&&j.array.length?[j]:[],a),C+=5;var z=s._tail,U=$B?new kt([],a):z;if(z&&$>B&&w<_&&z.array.length){for(var V=j=editableVNode(j,a),Z=C;Z>5;Z-=5){var ee=B>>>Z&Y;V=V.array[ee]=editableVNode(V.array[ee],a)}V.array[B>>>5&Y]=z}if(x<_&&(U=U&&U.removeAfter(a,0,x)),w>=$)w-=$,x-=$,C=5,j=void 0,U=U&&U.removeBefore(a,0,w);else if(w>u||$>>C&Y;if(ie!==$>>>C&Y)break;ie&&(L+=(1<u&&(j=j.removeBefore(a,C,w-L)),j&&$>>5<<5}var At=Math.pow(2,30);function levelCapacity(s){return s<31?1<=V&&w.size>=2*_.size?(a=(u=w.filter((function(s,o){return void 0!==s&&x!==o}))).toKeyedSeq().map((function(s){return s[0]})).flip().toMap(),s.__ownerID&&(a.__ownerID=u.__ownerID=s.__ownerID)):(a=_.remove(o),u=x===w.size-1?w.pop():w.set(x,void 0))}else if(C){if(i===w.get(x)[1])return s;a=_,u=w.set(x,[o,i])}else a=_.set(o,w.size),u=w.set(w.size,[o,i]);return s.__ownerID?(s.size=a.size,s._map=a,s._list=u,s.__hash=void 0,s.__altered=!0,s):makeOrderedMap(a,u)}jt.isOrderedMap=isOrderedMap,jt.prototype[ie]=!0,jt.prototype[U]=jt.prototype.remove;var Pt="@@__IMMUTABLE_STACK__@@";function isStack(s){return Boolean(s&&s[Pt])}var Tt=function(s){function Stack(s){return null==s?emptyStack():isStack(s)?s:emptyStack().pushAll(s)}return s&&(Stack.__proto__=s),Stack.prototype=Object.create(s&&s.prototype),Stack.prototype.constructor=Stack,Stack.of=function of(){return this(arguments)},Stack.prototype.toString=function toString(){return this.__toString("Stack [","]")},Stack.prototype.get=function get(s,o){var i=this._head;for(s=wrapIndex(this,s);i&&s--;)i=i.next;return i?i.value:o},Stack.prototype.peek=function peek(){return this._head&&this._head.value},Stack.prototype.push=function push(){var s=arguments;if(0===arguments.length)return this;for(var o=this.size+arguments.length,i=this._head,a=arguments.length-1;a>=0;a--)i={value:s[a],next:i};return this.__ownerID?(this.size=o,this._head=i,this.__hash=void 0,this.__altered=!0,this):makeStack(o,i)},Stack.prototype.pushAll=function pushAll(o){if(0===(o=s(o)).size)return this;if(0===this.size&&isStack(o))return o;assertNotInfinite(o.size);var i=this.size,a=this._head;return o.__iterate((function(s){i++,a={value:s,next:a}}),!0),this.__ownerID?(this.size=i,this._head=a,this.__hash=void 0,this.__altered=!0,this):makeStack(i,a)},Stack.prototype.pop=function pop(){return this.slice(1)},Stack.prototype.clear=function clear(){return 0===this.size?this:this.__ownerID?(this.size=0,this._head=void 0,this.__hash=void 0,this.__altered=!0,this):emptyStack()},Stack.prototype.slice=function slice(o,i){if(wholeSlice(o,i,this.size))return this;var a=resolveBegin(o,this.size);if(resolveEnd(i,this.size)!==this.size)return s.prototype.slice.call(this,o,i);for(var u=this.size-a,_=this._head;a--;)_=_.next;return this.__ownerID?(this.size=u,this._head=_,this.__hash=void 0,this.__altered=!0,this):makeStack(u,_)},Stack.prototype.__ensureOwner=function __ensureOwner(s){return s===this.__ownerID?this:s?makeStack(this.size,this._head,s,this.__hash):0===this.size?emptyStack():(this.__ownerID=s,this.__altered=!1,this)},Stack.prototype.__iterate=function __iterate(s,o){var i=this;if(o)return new ye(this.toArray()).__iterate((function(o,a){return s(o,a,i)}),o);for(var a=0,u=this._head;u&&!1!==s(u.value,a++,this);)u=u.next;return a},Stack.prototype.__iterator=function __iterator(s,o){if(o)return new ye(this.toArray()).__iterator(s,o);var i=0,a=this._head;return new z((function(){if(a){var o=a.value;return a=a.next,iteratorValue(s,i++,o)}return{value:void 0,done:!0}}))},Stack}(C);Tt.isStack=isStack;var It,Mt=Tt.prototype;function makeStack(s,o,i,a){var u=Object.create(Mt);return u.size=s,u._head=o,u.__ownerID=i,u.__hash=a,u.__altered=!1,u}function emptyStack(){return It||(It=makeStack(0))}function reduce(s,o,i,a,u,_){return assertNotInfinite(s.size),s.__iterate((function(s,_,w){u?(u=!1,i=s):i=o.call(a,i,s,_,w)}),_),i}function keyMapper(s,o){return o}function entryMapper(s,o){return[o,s]}function not(s){return function(){for(var o=[],i=arguments.length;i--;)o[i]=arguments[i];return!s.apply(this,o)}}function neg(s){return function(){for(var o=[],i=arguments.length;i--;)o[i]=arguments[i];return-s.apply(this,o)}}function defaultNegComparator(s,o){return so?-1:0}function deepEqual(s,o){if(s===o)return!0;if(!isCollection(o)||void 0!==s.size&&void 0!==o.size&&s.size!==o.size||void 0!==s.__hash&&void 0!==o.__hash&&s.__hash!==o.__hash||isKeyed(s)!==isKeyed(o)||isIndexed(s)!==isIndexed(o)||isOrdered(s)!==isOrdered(o))return!1;if(0===s.size&&0===o.size)return!0;var i=!isAssociative(s);if(isOrdered(s)){var a=s.entries();return o.every((function(s,o){var u=a.next().value;return u&&is(u[1],s)&&(i||is(u[0],o))}))&&a.next().done}var u=!1;if(void 0===s.size)if(void 0===o.size)"function"==typeof s.cacheResult&&s.cacheResult();else{u=!0;var _=s;s=o,o=_}var w=!0,x=o.__iterate((function(o,a){if(i?!s.has(o):u?!is(o,s.get(a,Z)):!is(s.get(a,Z),o))return w=!1,!1}));return w&&s.size===x}Mt[Pt]=!0,Mt.shift=Mt.pop,Mt.unshift=Mt.push,Mt.unshiftAll=Mt.pushAll,Mt.withMutations=withMutations,Mt.wasAltered=wasAltered,Mt.asImmutable=asImmutable,Mt["@@transducer/init"]=Mt.asMutable=asMutable,Mt["@@transducer/step"]=function(s,o){return s.unshift(o)},Mt["@@transducer/result"]=function(s){return s.asImmutable()};var Nt,Rt=function(s){function Range(s,o,i){if(void 0===i&&(i=1),!(this instanceof Range))return new Range(s,o,i);if(invariant(0!==i,"Cannot step a Range by 0"),invariant(void 0!==s,"You must define a start value when using Range"),invariant(void 0!==o,"You must define an end value when using Range"),i=Math.abs(i),o=0&&o=0&&i>>-15,461845907),o=we(o<<13|o>>>-13,5),o=o+3864292196^s,o=we(o^o>>>16,2246822507),o=we(o^o>>>13,3266489909),o=smi(o^o>>>16),o}(s.size,a)}function hashMerge(s,o){return s^o+2654435769+(s<<6)+(s>>2)}function mixin(s,o){var keyCopier=function(i){s.prototype[i]=o[i]};return Object.keys(o).forEach(keyCopier),Object.getOwnPropertySymbols&&Object.getOwnPropertySymbols(o).forEach(keyCopier),s}Bt[Dt]=!0,Bt[U]=Bt.remove,Bt.merge=Bt.concat=Bt.union,Bt.withMutations=withMutations,Bt.asImmutable=asImmutable,Bt["@@transducer/init"]=Bt.asMutable=asMutable,Bt["@@transducer/step"]=function(s,o){return s.add(o)},Bt["@@transducer/result"]=function(s){return s.asImmutable()},Bt.__empty=emptySet,Bt.__make=makeSet,w.Iterator=z,mixin(w,{toArray:function toArray(){assertNotInfinite(this.size);var s=new Array(this.size||0),o=isKeyed(this),i=0;return this.__iterate((function(a,u){s[i++]=o?[u,a]:a})),s},toIndexedSeq:function toIndexedSeq(){return new rt(this)},toJS:function toJS$1(){return toJS(this)},toKeyedSeq:function toKeyedSeq(){return new tt(this,!0)},toMap:function toMap(){return ct(this.toKeyedSeq())},toObject,toOrderedMap:function toOrderedMap(){return jt(this.toKeyedSeq())},toOrderedSet:function toOrderedSet(){return Vt(isKeyed(this)?this.valueSeq():this)},toSet:function toSet(){return Ft(isKeyed(this)?this.valueSeq():this)},toSetSeq:function toSetSeq(){return new nt(this)},toSeq:function toSeq(){return isIndexed(this)?this.toIndexedSeq():isKeyed(this)?this.toKeyedSeq():this.toSetSeq()},toStack:function toStack(){return Tt(isKeyed(this)?this.valueSeq():this)},toList:function toList(){return wt(isKeyed(this)?this.valueSeq():this)},toString:function toString(){return"[Collection]"},__toString:function __toString(s,o){return 0===this.size?s+o:s+" "+this.toSeq().map(this.__toStringMapper).join(", ")+" "+o},concat:function concat(){for(var s=[],o=arguments.length;o--;)s[o]=arguments[o];return reify(this,function concatFactory(s,o){var i=isKeyed(s),a=[s].concat(o).map((function(s){return isCollection(s)?i&&(s=x(s)):s=i?keyedSeqFromValue(s):indexedSeqFromValue(Array.isArray(s)?s:[s]),s})).filter((function(s){return 0!==s.size}));if(0===a.length)return s;if(1===a.length){var u=a[0];if(u===s||i&&isKeyed(u)||isIndexed(s)&&isIndexed(u))return u}return new ot(a)}(this,s))},includes:function includes(s){return this.some((function(o){return is(o,s)}))},entries:function entries(){return this.__iterator(2)},every:function every(s,o){assertNotInfinite(this.size);var i=!0;return this.__iterate((function(a,u,_){if(!s.call(o,a,u,_))return i=!1,!1})),i},filter:function filter(s,o){return reify(this,filterFactory(this,s,o,!0))},partition:function partition(s,o){return function partitionFactory(s,o,i){var a=isKeyed(s),u=[[],[]];s.__iterate((function(_,w){u[o.call(i,_,w,s)?1:0].push(a?[w,_]:_)}));var _=collectionClass(s);return u.map((function(o){return reify(s,_(o))}))}(this,s,o)},find:function find(s,o,i){var a=this.findEntry(s,o);return a?a[1]:i},forEach:function forEach(s,o){return assertNotInfinite(this.size),this.__iterate(o?s.bind(o):s)},join:function join(s){assertNotInfinite(this.size),s=void 0!==s?""+s:",";var o="",i=!0;return this.__iterate((function(a){i?i=!1:o+=s,o+=null!=a?a.toString():""})),o},keys:function keys(){return this.__iterator(0)},map:function map(s,o){return reify(this,mapFactory(this,s,o))},reduce:function reduce$1(s,o,i){return reduce(this,s,o,i,arguments.length<2,!1)},reduceRight:function reduceRight(s,o,i){return reduce(this,s,o,i,arguments.length<2,!0)},reverse:function reverse(){return reify(this,reverseFactory(this,!0))},slice:function slice(s,o){return reify(this,sliceFactory(this,s,o,!0))},some:function some(s,o){assertNotInfinite(this.size);var i=!1;return this.__iterate((function(a,u,_){if(s.call(o,a,u,_))return i=!0,!1})),i},sort:function sort(s){return reify(this,sortFactory(this,s))},values:function values(){return this.__iterator(1)},butLast:function butLast(){return this.slice(0,-1)},isEmpty:function isEmpty(){return void 0!==this.size?0===this.size:!this.some((function(){return!0}))},count:function count(s,o){return ensureSize(s?this.toSeq().filter(s,o):this)},countBy:function countBy(s,o){return function countByFactory(s,o,i){var a=ct().asMutable();return s.__iterate((function(u,_){a.update(o.call(i,u,_,s),0,(function(s){return s+1}))})),a.asImmutable()}(this,s,o)},equals:function equals(s){return deepEqual(this,s)},entrySeq:function entrySeq(){var s=this;if(s._cache)return new ye(s._cache);var o=s.toSeq().map(entryMapper).toIndexedSeq();return o.fromEntrySeq=function(){return s.toSeq()},o},filterNot:function filterNot(s,o){return this.filter(not(s),o)},findEntry:function findEntry(s,o,i){var a=i;return this.__iterate((function(i,u,_){if(s.call(o,i,u,_))return a=[u,i],!1})),a},findKey:function findKey(s,o){var i=this.findEntry(s,o);return i&&i[0]},findLast:function findLast(s,o,i){return this.toKeyedSeq().reverse().find(s,o,i)},findLastEntry:function findLastEntry(s,o,i){return this.toKeyedSeq().reverse().findEntry(s,o,i)},findLastKey:function findLastKey(s,o){return this.toKeyedSeq().reverse().findKey(s,o)},first:function first(s){return this.find(returnTrue,null,s)},flatMap:function flatMap(s,o){return reify(this,function flatMapFactory(s,o,i){var a=collectionClass(s);return s.toSeq().map((function(u,_){return a(o.call(i,u,_,s))})).flatten(!0)}(this,s,o))},flatten:function flatten(s){return reify(this,flattenFactory(this,s,!0))},fromEntrySeq:function fromEntrySeq(){return new st(this)},get:function get(s,o){return this.find((function(o,i){return is(i,s)}),void 0,o)},getIn,groupBy:function groupBy(s,o){return function groupByFactory(s,o,i){var a=isKeyed(s),u=(isOrdered(s)?jt():ct()).asMutable();s.__iterate((function(_,w){u.update(o.call(i,_,w,s),(function(s){return(s=s||[]).push(a?[w,_]:_),s}))}));var _=collectionClass(s);return u.map((function(o){return reify(s,_(o))})).asImmutable()}(this,s,o)},has:function has(s){return this.get(s,Z)!==Z},hasIn:function hasIn(s){return hasIn$1(this,s)},isSubset:function isSubset(s){return s="function"==typeof s.includes?s:w(s),this.every((function(o){return s.includes(o)}))},isSuperset:function isSuperset(s){return(s="function"==typeof s.isSubset?s:w(s)).isSubset(this)},keyOf:function keyOf(s){return this.findKey((function(o){return is(o,s)}))},keySeq:function keySeq(){return this.toSeq().map(keyMapper).toIndexedSeq()},last:function last(s){return this.toSeq().reverse().first(s)},lastKeyOf:function lastKeyOf(s){return this.toKeyedSeq().reverse().keyOf(s)},max:function max(s){return maxFactory(this,s)},maxBy:function maxBy(s,o){return maxFactory(this,o,s)},min:function min(s){return maxFactory(this,s?neg(s):defaultNegComparator)},minBy:function minBy(s,o){return maxFactory(this,o?neg(o):defaultNegComparator,s)},rest:function rest(){return this.slice(1)},skip:function skip(s){return 0===s?this:this.slice(Math.max(0,s))},skipLast:function skipLast(s){return 0===s?this:this.slice(0,-Math.max(0,s))},skipWhile:function skipWhile(s,o){return reify(this,skipWhileFactory(this,s,o,!0))},skipUntil:function skipUntil(s,o){return this.skipWhile(not(s),o)},sortBy:function sortBy(s,o){return reify(this,sortFactory(this,o,s))},take:function take(s){return this.slice(0,Math.max(0,s))},takeLast:function takeLast(s){return this.slice(-Math.max(0,s))},takeWhile:function takeWhile(s,o){return reify(this,function takeWhileFactory(s,o,i){var a=makeSequence(s);return a.__iterateUncached=function(a,u){var _=this;if(u)return this.cacheResult().__iterate(a,u);var w=0;return s.__iterate((function(s,u,x){return o.call(i,s,u,x)&&++w&&a(s,u,_)})),w},a.__iteratorUncached=function(a,u){var _=this;if(u)return this.cacheResult().__iterator(a,u);var w=s.__iterator(2,u),x=!0;return new z((function(){if(!x)return{value:void 0,done:!0};var s=w.next();if(s.done)return s;var u=s.value,C=u[0],j=u[1];return o.call(i,j,C,_)?2===a?s:iteratorValue(a,C,j,s):(x=!1,{value:void 0,done:!0})}))},a}(this,s,o))},takeUntil:function takeUntil(s,o){return this.takeWhile(not(s),o)},update:function update(s){return s(this)},valueSeq:function valueSeq(){return this.toIndexedSeq()},hashCode:function hashCode(){return this.__hash||(this.__hash=hashCollection(this))}});var qt=w.prototype;qt[_]=!0,qt[$]=qt.values,qt.toJSON=qt.toArray,qt.__toStringMapper=quoteString,qt.inspect=qt.toSource=function(){return this.toString()},qt.chain=qt.flatMap,qt.contains=qt.includes,mixin(x,{flip:function flip(){return reify(this,flipFactory(this))},mapEntries:function mapEntries(s,o){var i=this,a=0;return reify(this,this.toSeq().map((function(u,_){return s.call(o,[_,u],a++,i)})).fromEntrySeq())},mapKeys:function mapKeys(s,o){var i=this;return reify(this,this.toSeq().flip().map((function(a,u){return s.call(o,a,u,i)})).flip())}});var $t=x.prototype;$t[u]=!0,$t[$]=qt.entries,$t.toJSON=toObject,$t.__toStringMapper=function(s,o){return quoteString(o)+": "+quoteString(s)},mixin(C,{toKeyedSeq:function toKeyedSeq(){return new tt(this,!1)},filter:function filter(s,o){return reify(this,filterFactory(this,s,o,!1))},findIndex:function findIndex(s,o){var i=this.findEntry(s,o);return i?i[0]:-1},indexOf:function indexOf(s){var o=this.keyOf(s);return void 0===o?-1:o},lastIndexOf:function lastIndexOf(s){var o=this.lastKeyOf(s);return void 0===o?-1:o},reverse:function reverse(){return reify(this,reverseFactory(this,!1))},slice:function slice(s,o){return reify(this,sliceFactory(this,s,o,!1))},splice:function splice(s,o){var i=arguments.length;if(o=Math.max(o||0,0),0===i||2===i&&!o)return this;s=resolveBegin(s,s<0?this.count():this.size);var a=this.slice(0,s);return reify(this,1===i?a:a.concat(arrCopy(arguments,2),this.slice(s+o)))},findLastIndex:function findLastIndex(s,o){var i=this.findLastEntry(s,o);return i?i[0]:-1},first:function first(s){return this.get(0,s)},flatten:function flatten(s){return reify(this,flattenFactory(this,s,!1))},get:function get(s,o){return(s=wrapIndex(this,s))<0||this.size===1/0||void 0!==this.size&&s>this.size?o:this.find((function(o,i){return i===s}),void 0,o)},has:function has(s){return(s=wrapIndex(this,s))>=0&&(void 0!==this.size?this.size===1/0||s2?[]:void 0,{"":s})}function fromJSWith(s,o,i,a,u,_){if("string"!=typeof i&&!isImmutable(i)&&(isArrayLike(i)||hasIterator(i)||isPlainObject(i))){if(~s.indexOf(i))throw new TypeError("Cannot convert circular structure to Immutable");s.push(i),u&&""!==a&&u.push(a);var w=o.call(_,a,le(i).map((function(a,_){return fromJSWith(s,o,a,_,u,i)})),u&&u.slice());return s.pop(),u&&u.pop(),w}return i}function defaultConverter(s,o){return isIndexed(o)?o.toList():isKeyed(o)?o.toMap():o.toSet()}var Qt="5.1.9",Zt=w},56698(s){"function"==typeof Object.create?s.exports=function inherits(s,o){o&&(s.super_=o,s.prototype=Object.create(o.prototype,{constructor:{value:s,enumerable:!1,writable:!0,configurable:!0}}))}:s.exports=function inherits(s,o){if(o){s.super_=o;var TempCtor=function(){};TempCtor.prototype=o.prototype,s.prototype=new TempCtor,s.prototype.constructor=s}}},69600(s){"use strict";var o,i,a=Function.prototype.toString,u="object"==typeof Reflect&&null!==Reflect&&Reflect.apply;if("function"==typeof u&&"function"==typeof Object.defineProperty)try{o=Object.defineProperty({},"length",{get:function(){throw i}}),i={},u((function(){throw 42}),null,o)}catch(s){s!==i&&(u=null)}else u=null;var _=/^\s*class\b/,w=function isES6ClassFunction(s){try{var o=a.call(s);return _.test(o)}catch(s){return!1}},x=function tryFunctionToStr(s){try{return!w(s)&&(a.call(s),!0)}catch(s){return!1}},C=Object.prototype.toString,j="function"==typeof Symbol&&!!Symbol.toStringTag,L=!(0 in[,]),B=function isDocumentDotAll(){return!1};if("object"==typeof document){var $=document.all;C.call($)===C.call(document.all)&&(B=function isDocumentDotAll(s){if((L||!s)&&(void 0===s||"object"==typeof s))try{var o=C.call(s);return("[object HTMLAllCollection]"===o||"[object HTML document.all class]"===o||"[object HTMLCollection]"===o||"[object Object]"===o)&&null==s("")}catch(s){}return!1})}s.exports=u?function isCallable(s){if(B(s))return!0;if(!s)return!1;if("function"!=typeof s&&"object"!=typeof s)return!1;try{u(s,null,o)}catch(s){if(s!==i)return!1}return!w(s)&&x(s)}:function isCallable(s){if(B(s))return!0;if(!s)return!1;if("function"!=typeof s&&"object"!=typeof s)return!1;if(j)return x(s);if(w(s))return!1;var o=C.call(s);return!("[object Function]"!==o&&"[object GeneratorFunction]"!==o&&!/^\[object HTML/.test(o))&&x(s)}},35680(s,o,i){"use strict";var a=i(25767);s.exports=function isTypedArray(s){return!!a(s)}},64634(s){var o={}.toString;s.exports=Array.isArray||function(s){return"[object Array]"==o.call(s)}},5419(s){s.exports=function(s,o,i,a){var u=new Blob(void 0!==a?[a,s]:[s],{type:i||"application/octet-stream"});if(void 0!==window.navigator.msSaveBlob)window.navigator.msSaveBlob(u,o);else{var _=window.URL&&window.URL.createObjectURL?window.URL.createObjectURL(u):window.webkitURL.createObjectURL(u),w=document.createElement("a");w.style.display="none",w.href=_,w.setAttribute("download",o),void 0===w.download&&w.setAttribute("target","_blank"),document.body.appendChild(w),w.click(),setTimeout((function(){document.body.removeChild(w),window.URL.revokeObjectURL(_)}),200)}}},20181(s,o,i){var a=/^\s+|\s+$/g,u=/^[-+]0x[0-9a-f]+$/i,_=/^0b[01]+$/i,w=/^0o[0-7]+$/i,x=parseInt,C="object"==typeof i.g&&i.g&&i.g.Object===Object&&i.g,j="object"==typeof self&&self&&self.Object===Object&&self,L=C||j||Function("return this")(),B=Object.prototype.toString,$=Math.max,z=Math.min,now=function(){return L.Date.now()};function isObject(s){var o=typeof s;return!!s&&("object"==o||"function"==o)}function toNumber(s){if("number"==typeof s)return s;if(function isSymbol(s){return"symbol"==typeof s||function isObjectLike(s){return!!s&&"object"==typeof s}(s)&&"[object Symbol]"==B.call(s)}(s))return NaN;if(isObject(s)){var o="function"==typeof s.valueOf?s.valueOf():s;s=isObject(o)?o+"":o}if("string"!=typeof s)return 0===s?s:+s;s=s.replace(a,"");var i=_.test(s);return i||w.test(s)?x(s.slice(2),i?2:8):u.test(s)?NaN:+s}s.exports=function debounce(s,o,i){var a,u,_,w,x,C,j=0,L=!1,B=!1,U=!0;if("function"!=typeof s)throw new TypeError("Expected a function");function invokeFunc(o){var i=a,_=u;return a=u=void 0,j=o,w=s.apply(_,i)}function shouldInvoke(s){var i=s-C;return void 0===C||i>=o||i<0||B&&s-j>=_}function timerExpired(){var s=now();if(shouldInvoke(s))return trailingEdge(s);x=setTimeout(timerExpired,function remainingWait(s){var i=o-(s-C);return B?z(i,_-(s-j)):i}(s))}function trailingEdge(s){return x=void 0,U&&a?invokeFunc(s):(a=u=void 0,w)}function debounced(){var s=now(),i=shouldInvoke(s);if(a=arguments,u=this,C=s,i){if(void 0===x)return function leadingEdge(s){return j=s,x=setTimeout(timerExpired,o),L?invokeFunc(s):w}(C);if(B)return x=setTimeout(timerExpired,o),invokeFunc(C)}return void 0===x&&(x=setTimeout(timerExpired,o)),w}return o=toNumber(o)||0,isObject(i)&&(L=!!i.leading,_=(B="maxWait"in i)?$(toNumber(i.maxWait)||0,o):_,U="trailing"in i?!!i.trailing:U),debounced.cancel=function cancel(){void 0!==x&&clearTimeout(x),j=0,a=C=u=x=void 0},debounced.flush=function flush(){return void 0===x?w:trailingEdge(now())},debounced}},55580(s,o,i){var a=i(56110)(i(9325),"DataView");s.exports=a},21549(s,o,i){var a=i(22032),u=i(63862),_=i(66721),w=i(12749),x=i(35749);function Hash(s){var o=-1,i=null==s?0:s.length;for(this.clear();++o-1}},70695(s,o,i){var a=i(78096),u=i(72428),_=i(56449),w=i(3656),x=i(30361),C=i(37167),j=Object.prototype.hasOwnProperty;s.exports=function arrayLikeKeys(s,o){var i=_(s),L=!i&&u(s),B=!i&&!L&&w(s),$=!i&&!L&&!B&&C(s),z=i||L||B||$,U=z?a(s.length,String):[],V=U.length;for(var Y in s)!o&&!j.call(s,Y)||z&&("length"==Y||B&&("offset"==Y||"parent"==Y)||$&&("buffer"==Y||"byteLength"==Y||"byteOffset"==Y)||x(Y,V))||U.push(Y);return U}},34932(s){s.exports=function arrayMap(s,o){for(var i=-1,a=null==s?0:s.length,u=Array(a);++i0&&i(j)?o>1?baseFlatten(j,o-1,i,_,w):a(w,j):_||(w[w.length]=j)}return w}},86649(s,o,i){var a=i(83221)();s.exports=a},30641(s,o,i){var a=i(86649),u=i(95950);s.exports=function baseForOwn(s,o){return s&&a(s,o,u)}},47422(s,o,i){var a=i(31769),u=i(77797);s.exports=function baseGet(s,o){for(var i=0,_=(o=a(o,s)).length;null!=s&&i<_;)s=s[u(o[i++])];return i&&i==_?s:void 0}},82199(s,o,i){var a=i(14528),u=i(56449);s.exports=function baseGetAllKeys(s,o,i){var _=o(s);return u(s)?_:a(_,i(s))}},72552(s,o,i){var a=i(51873),u=i(659),_=i(59350),w=a?a.toStringTag:void 0;s.exports=function baseGetTag(s){return null==s?void 0===s?"[object Undefined]":"[object Null]":w&&w in Object(s)?u(s):_(s)}},20426(s){var o=Object.prototype.hasOwnProperty;s.exports=function baseHas(s,i){return null!=s&&o.call(s,i)}},28077(s){s.exports=function baseHasIn(s,o){return null!=s&&o in Object(s)}},96131(s,o,i){var a=i(2523),u=i(85463),_=i(76959);s.exports=function baseIndexOf(s,o,i){return o==o?_(s,o,i):a(s,u,i)}},27534(s,o,i){var a=i(72552),u=i(40346);s.exports=function baseIsArguments(s){return u(s)&&"[object Arguments]"==a(s)}},60270(s,o,i){var a=i(87068),u=i(40346);s.exports=function baseIsEqual(s,o,i,_,w){return s===o||(null==s||null==o||!u(s)&&!u(o)?s!=s&&o!=o:a(s,o,i,_,baseIsEqual,w))}},87068(s,o,i){var a=i(37217),u=i(25911),_=i(21986),w=i(50689),x=i(5861),C=i(56449),j=i(3656),L=i(37167),B="[object Arguments]",$="[object Array]",z="[object Object]",U=Object.prototype.hasOwnProperty;s.exports=function baseIsEqualDeep(s,o,i,V,Y,Z){var ee=C(s),ie=C(o),ae=ee?$:x(s),ce=ie?$:x(o),le=(ae=ae==B?z:ae)==z,pe=(ce=ce==B?z:ce)==z,de=ae==ce;if(de&&j(s)){if(!j(o))return!1;ee=!0,le=!1}if(de&&!le)return Z||(Z=new a),ee||L(s)?u(s,o,i,V,Y,Z):_(s,o,ae,i,V,Y,Z);if(!(1&i)){var fe=le&&U.call(s,"__wrapped__"),ye=pe&&U.call(o,"__wrapped__");if(fe||ye){var be=fe?s.value():s,Se=ye?o.value():o;return Z||(Z=new a),Y(be,Se,i,V,Z)}}return!!de&&(Z||(Z=new a),w(s,o,i,V,Y,Z))}},29172(s,o,i){var a=i(5861),u=i(40346);s.exports=function baseIsMap(s){return u(s)&&"[object Map]"==a(s)}},41799(s,o,i){var a=i(37217),u=i(60270);s.exports=function baseIsMatch(s,o,i,_){var w=i.length,x=w,C=!_;if(null==s)return!x;for(s=Object(s);w--;){var j=i[w];if(C&&j[2]?j[1]!==s[j[0]]:!(j[0]in s))return!1}for(;++wu?0:u+o),(i=i>u?u:i)<0&&(i+=u),u=o>i?0:i-o>>>0,o>>>=0;for(var _=Array(u);++a=u?s:a(s,o,i)}},49653(s,o,i){var a=i(37828);s.exports=function cloneArrayBuffer(s){var o=new s.constructor(s.byteLength);return new a(o).set(new a(s)),o}},93290(s,o,i){s=i.nmd(s);var a=i(9325),u=o&&!o.nodeType&&o,_=u&&s&&!s.nodeType&&s,w=_&&_.exports===u?a.Buffer:void 0,x=w?w.allocUnsafe:void 0;s.exports=function cloneBuffer(s,o){if(o)return s.slice();var i=s.length,a=x?x(i):new s.constructor(i);return s.copy(a),a}},76169(s,o,i){var a=i(49653);s.exports=function cloneDataView(s,o){var i=o?a(s.buffer):s.buffer;return new s.constructor(i,s.byteOffset,s.byteLength)}},73201(s){var o=/\w*$/;s.exports=function cloneRegExp(s){var i=new s.constructor(s.source,o.exec(s));return i.lastIndex=s.lastIndex,i}},93736(s,o,i){var a=i(51873),u=a?a.prototype:void 0,_=u?u.valueOf:void 0;s.exports=function cloneSymbol(s){return _?Object(_.call(s)):{}}},71961(s,o,i){var a=i(49653);s.exports=function cloneTypedArray(s,o){var i=o?a(s.buffer):s.buffer;return new s.constructor(i,s.byteOffset,s.length)}},91596(s){var o=Math.max;s.exports=function composeArgs(s,i,a,u){for(var _=-1,w=s.length,x=a.length,C=-1,j=i.length,L=o(w-x,0),B=Array(j+L),$=!u;++C1?i[_-1]:void 0,x=_>2?i[2]:void 0;for(w=s.length>3&&"function"==typeof w?(_--,w):void 0,x&&u(i[0],i[1],x)&&(w=_<3?void 0:w,_=1),o=Object(o);++a<_;){var C=i[a];C&&s(o,C,a,w)}return o}))}},38329(s,o,i){var a=i(64894);s.exports=function createBaseEach(s,o){return function(i,u){if(null==i)return i;if(!a(i))return s(i,u);for(var _=i.length,w=o?_:-1,x=Object(i);(o?w--:++w<_)&&!1!==u(x[w],w,x););return i}}},83221(s){s.exports=function createBaseFor(s){return function(o,i,a){for(var u=-1,_=Object(o),w=a(o),x=w.length;x--;){var C=w[s?x:++u];if(!1===i(_[C],C,_))break}return o}}},11842(s,o,i){var a=i(82819),u=i(9325);s.exports=function createBind(s,o,i){var _=1&o,w=a(s);return function wrapper(){return(this&&this!==u&&this instanceof wrapper?w:s).apply(_?i:this,arguments)}}},12507(s,o,i){var a=i(28754),u=i(49698),_=i(63912),w=i(13222);s.exports=function createCaseFirst(s){return function(o){o=w(o);var i=u(o)?_(o):void 0,x=i?i[0]:o.charAt(0),C=i?a(i,1).join(""):o.slice(1);return x[s]()+C}}},45539(s,o,i){var a=i(40882),u=i(50828),_=i(66645),w=RegExp("['’]","g");s.exports=function createCompounder(s){return function(o){return a(_(u(o).replace(w,"")),s,"")}}},82819(s,o,i){var a=i(39344),u=i(23805);s.exports=function createCtor(s){return function(){var o=arguments;switch(o.length){case 0:return new s;case 1:return new s(o[0]);case 2:return new s(o[0],o[1]);case 3:return new s(o[0],o[1],o[2]);case 4:return new s(o[0],o[1],o[2],o[3]);case 5:return new s(o[0],o[1],o[2],o[3],o[4]);case 6:return new s(o[0],o[1],o[2],o[3],o[4],o[5]);case 7:return new s(o[0],o[1],o[2],o[3],o[4],o[5],o[6])}var i=a(s.prototype),_=s.apply(i,o);return u(_)?_:i}}},77078(s,o,i){var a=i(91033),u=i(82819),_=i(37471),w=i(18073),x=i(11287),C=i(36306),j=i(9325);s.exports=function createCurry(s,o,i){var L=u(s);return function wrapper(){for(var u=arguments.length,B=Array(u),$=u,z=x(wrapper);$--;)B[$]=arguments[$];var U=u<3&&B[0]!==z&&B[u-1]!==z?[]:C(B,z);return(u-=U.length)-1?x[C?o[j]:j]:void 0}}},37471(s,o,i){var a=i(91596),u=i(53320),_=i(58523),w=i(82819),x=i(18073),C=i(11287),j=i(68294),L=i(36306),B=i(9325);s.exports=function createHybrid(s,o,i,$,z,U,V,Y,Z,ee){var ie=128&o,ae=1&o,ce=2&o,le=24&o,pe=512&o,de=ce?void 0:w(s);return function wrapper(){for(var fe=arguments.length,ye=Array(fe),be=fe;be--;)ye[be]=arguments[be];if(le)var Se=C(wrapper),_e=_(ye,Se);if($&&(ye=a(ye,$,z,le)),U&&(ye=u(ye,U,V,le)),fe-=_e,le&&fe1&&ye.reverse(),ie&&ZL))return!1;var $=C.get(s),z=C.get(o);if($&&z)return $==o&&z==s;var U=-1,V=!0,Y=2&i?new a:void 0;for(C.set(s,o),C.set(o,s);++U1?"& ":"")+i[u],i=i.join(a>2?", ":" "),s.replace(o,"{\n/* [wrapped with "+i+"] */\n")}},45891(s,o,i){var a=i(51873),u=i(72428),_=i(56449),w=a?a.isConcatSpreadable:void 0;s.exports=function isFlattenable(s){return _(s)||u(s)||!!(w&&s&&s[w])}},30361(s){var o=/^(?:0|[1-9]\d*)$/;s.exports=function isIndex(s,i){var a=typeof s;return!!(i=null==i?9007199254740991:i)&&("number"==a||"symbol"!=a&&o.test(s))&&s>-1&&s%1==0&&s-1}},31175(s,o,i){var a=i(26025);s.exports=function listCacheSet(s,o){var i=this.__data__,u=a(i,s);return u<0?(++this.size,i.push([s,o])):i[u][1]=o,this}},63040(s,o,i){var a=i(21549),u=i(80079),_=i(68223);s.exports=function mapCacheClear(){this.size=0,this.__data__={hash:new a,map:new(_||u),string:new a}}},17670(s,o,i){var a=i(12651);s.exports=function mapCacheDelete(s){var o=a(this,s).delete(s);return this.size-=o?1:0,o}},90289(s,o,i){var a=i(12651);s.exports=function mapCacheGet(s){return a(this,s).get(s)}},4509(s,o,i){var a=i(12651);s.exports=function mapCacheHas(s){return a(this,s).has(s)}},72949(s,o,i){var a=i(12651);s.exports=function mapCacheSet(s,o){var i=a(this,s),u=i.size;return i.set(s,o),this.size+=i.size==u?0:1,this}},20317(s){s.exports=function mapToArray(s){var o=-1,i=Array(s.size);return s.forEach((function(s,a){i[++o]=[a,s]})),i}},67197(s){s.exports=function matchesStrictComparable(s,o){return function(i){return null!=i&&(i[s]===o&&(void 0!==o||s in Object(i)))}}},62224(s,o,i){var a=i(50104);s.exports=function memoizeCapped(s){var o=a(s,(function(s){return 500===i.size&&i.clear(),s})),i=o.cache;return o}},3209(s,o,i){var a=i(91596),u=i(53320),_=i(36306),w="__lodash_placeholder__",x=128,C=Math.min;s.exports=function mergeData(s,o){var i=s[1],j=o[1],L=i|j,B=L<131,$=j==x&&8==i||j==x&&256==i&&s[7].length<=o[8]||384==j&&o[7].length<=o[8]&&8==i;if(!B&&!$)return s;1&j&&(s[2]=o[2],L|=1&i?0:4);var z=o[3];if(z){var U=s[3];s[3]=U?a(U,z,o[4]):z,s[4]=U?_(s[3],w):o[4]}return(z=o[5])&&(U=s[5],s[5]=U?u(U,z,o[6]):z,s[6]=U?_(s[5],w):o[6]),(z=o[7])&&(s[7]=z),j&x&&(s[8]=null==s[8]?o[8]:C(s[8],o[8])),null==s[9]&&(s[9]=o[9]),s[0]=o[0],s[1]=L,s}},48152(s,o,i){var a=i(28303),u=a&&new a;s.exports=u},81042(s,o,i){var a=i(56110)(Object,"create");s.exports=a},3650(s,o,i){var a=i(74335)(Object.keys,Object);s.exports=a},90181(s){s.exports=function nativeKeysIn(s){var o=[];if(null!=s)for(var i in Object(s))o.push(i);return o}},86009(s,o,i){s=i.nmd(s);var a=i(34840),u=o&&!o.nodeType&&o,_=u&&s&&!s.nodeType&&s,w=_&&_.exports===u&&a.process,x=function(){try{var s=_&&_.require&&_.require("util").types;return s||w&&w.binding&&w.binding("util")}catch(s){}}();s.exports=x},59350(s){var o=Object.prototype.toString;s.exports=function objectToString(s){return o.call(s)}},74335(s){s.exports=function overArg(s,o){return function(i){return s(o(i))}}},56757(s,o,i){var a=i(91033),u=Math.max;s.exports=function overRest(s,o,i){return o=u(void 0===o?s.length-1:o,0),function(){for(var _=arguments,w=-1,x=u(_.length-o,0),C=Array(x);++w0){if(++i>=800)return arguments[0]}else i=0;return s.apply(void 0,arguments)}}},51420(s,o,i){var a=i(80079);s.exports=function stackClear(){this.__data__=new a,this.size=0}},90938(s){s.exports=function stackDelete(s){var o=this.__data__,i=o.delete(s);return this.size=o.size,i}},63605(s){s.exports=function stackGet(s){return this.__data__.get(s)}},29817(s){s.exports=function stackHas(s){return this.__data__.has(s)}},80945(s,o,i){var a=i(80079),u=i(68223),_=i(53661);s.exports=function stackSet(s,o){var i=this.__data__;if(i instanceof a){var w=i.__data__;if(!u||w.length<199)return w.push([s,o]),this.size=++i.size,this;i=this.__data__=new _(w)}return i.set(s,o),this.size=i.size,this}},76959(s){s.exports=function strictIndexOf(s,o,i){for(var a=i-1,u=s.length;++a=o||i<0||Y&&s-U>=L}function timerExpired(){var s=u();if(shouldInvoke(s))return trailingEdge(s);$=setTimeout(timerExpired,function remainingWait(s){var i=o-(s-z);return Y?x(i,L-(s-U)):i}(s))}function trailingEdge(s){return $=void 0,Z&&C?invokeFunc(s):(C=j=void 0,B)}function debounced(){var s=u(),i=shouldInvoke(s);if(C=arguments,j=this,z=s,i){if(void 0===$)return function leadingEdge(s){return U=s,$=setTimeout(timerExpired,o),V?invokeFunc(s):B}(z);if(Y)return clearTimeout($),$=setTimeout(timerExpired,o),invokeFunc(z)}return void 0===$&&($=setTimeout(timerExpired,o)),B}return o=_(o)||0,a(i)&&(V=!!i.leading,L=(Y="maxWait"in i)?w(_(i.maxWait)||0,o):L,Z="trailing"in i?!!i.trailing:Z),debounced.cancel=function cancel(){void 0!==$&&clearTimeout($),U=0,C=z=j=$=void 0},debounced.flush=function flush(){return void 0===$?B:trailingEdge(u())},debounced}},50828(s,o,i){var a=i(24647),u=i(13222),_=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,w=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]","g");s.exports=function deburr(s){return(s=u(s))&&s.replace(_,a).replace(w,"")}},75288(s){s.exports=function eq(s,o){return s===o||s!=s&&o!=o}},60680(s,o,i){var a=i(13222),u=/[\\^$.*+?()[\]{}|]/g,_=RegExp(u.source);s.exports=function escapeRegExp(s){return(s=a(s))&&_.test(s)?s.replace(u,"\\$&"):s}},7309(s,o,i){var a=i(62006)(i(24713));s.exports=a},24713(s,o,i){var a=i(2523),u=i(15389),_=i(61489),w=Math.max;s.exports=function findIndex(s,o,i){var x=null==s?0:s.length;if(!x)return-1;var C=null==i?0:_(i);return C<0&&(C=w(x+C,0)),a(s,u(o,3),C)}},35970(s,o,i){var a=i(83120);s.exports=function flatten(s){return(null==s?0:s.length)?a(s,1):[]}},73424(s,o,i){var a=i(16962),u=i(2874),_=Array.prototype.push;function baseAry(s,o){return 2==o?function(o,i){return s(o,i)}:function(o){return s(o)}}function cloneArray(s){for(var o=s?s.length:0,i=Array(o);o--;)i[o]=s[o];return i}function wrapImmutable(s,o){return function(){var i=arguments.length;if(i){for(var a=Array(i);i--;)a[i]=arguments[i];var u=a[0]=o.apply(void 0,a);return s.apply(void 0,a),u}}}s.exports=function baseConvert(s,o,i,w){var x="function"==typeof o,C=o===Object(o);if(C&&(w=i,i=o,o=void 0),null==i)throw new TypeError;w||(w={});var j=!("cap"in w)||w.cap,L=!("curry"in w)||w.curry,B=!("fixed"in w)||w.fixed,$=!("immutable"in w)||w.immutable,z=!("rearg"in w)||w.rearg,U=x?i:u,V="curry"in w&&w.curry,Y="fixed"in w&&w.fixed,Z="rearg"in w&&w.rearg,ee=x?i.runInContext():void 0,ie=x?i:{ary:s.ary,assign:s.assign,clone:s.clone,curry:s.curry,forEach:s.forEach,isArray:s.isArray,isError:s.isError,isFunction:s.isFunction,isWeakMap:s.isWeakMap,iteratee:s.iteratee,keys:s.keys,rearg:s.rearg,toInteger:s.toInteger,toPath:s.toPath},ae=ie.ary,ce=ie.assign,le=ie.clone,pe=ie.curry,de=ie.forEach,fe=ie.isArray,ye=ie.isError,be=ie.isFunction,Se=ie.isWeakMap,_e=ie.keys,we=ie.rearg,xe=ie.toInteger,Pe=ie.toPath,Te=_e(a.aryMethod),Re={castArray:function(s){return function(){var o=arguments[0];return fe(o)?s(cloneArray(o)):s.apply(void 0,arguments)}},iteratee:function(s){return function(){var o=arguments[1],i=s(arguments[0],o),a=i.length;return j&&"number"==typeof o?(o=o>2?o-2:1,a&&a<=o?i:baseAry(i,o)):i}},mixin:function(s){return function(o){var i=this;if(!be(i))return s(i,Object(o));var a=[];return de(_e(o),(function(s){be(o[s])&&a.push([s,i.prototype[s]])})),s(i,Object(o)),de(a,(function(s){var o=s[1];be(o)?i.prototype[s[0]]=o:delete i.prototype[s[0]]})),i}},nthArg:function(s){return function(o){var i=o<0?1:xe(o)+1;return pe(s(o),i)}},rearg:function(s){return function(o,i){var a=i?i.length:0;return pe(s(o,i),a)}},runInContext:function(o){return function(i){return baseConvert(s,o(i),w)}}};function castCap(s,o){if(j){var i=a.iterateeRearg[s];if(i)return function iterateeRearg(s,o){return overArg(s,(function(s){var i=o.length;return function baseArity(s,o){return 2==o?function(o,i){return s.apply(void 0,arguments)}:function(o){return s.apply(void 0,arguments)}}(we(baseAry(s,i),o),i)}))}(o,i);var u=!x&&a.iterateeAry[s];if(u)return function iterateeAry(s,o){return overArg(s,(function(s){return"function"==typeof s?baseAry(s,o):s}))}(o,u)}return o}function castFixed(s,o,i){if(B&&(Y||!a.skipFixed[s])){var u=a.methodSpread[s],w=u&&u.start;return void 0===w?ae(o,i):function flatSpread(s,o){return function(){for(var i=arguments.length,a=i-1,u=Array(i);i--;)u[i]=arguments[i];var w=u[o],x=u.slice(0,o);return w&&_.apply(x,w),o!=a&&_.apply(x,u.slice(o+1)),s.apply(this,x)}}(o,w)}return o}function castRearg(s,o,i){return z&&i>1&&(Z||!a.skipRearg[s])?we(o,a.methodRearg[s]||a.aryRearg[i]):o}function cloneByPath(s,o){for(var i=-1,a=(o=Pe(o)).length,u=a-1,_=le(Object(s)),w=_;null!=w&&++i1?pe(o,i):o}(0,u=castCap(_,u),s),!1}})),!u})),u||(u=w),u==o&&(u=V?pe(u,1):function(){return o.apply(this,arguments)}),u.convert=createConverter(_,o),u.placeholder=o.placeholder=i,u}if(!C)return wrap(o,i,U);var qe=i,$e=[];return de(Te,(function(s){de(a.aryMethod[s],(function(s){var o=qe[a.remap[s]||s];o&&$e.push([s,wrap(s,o,qe)])}))})),de(_e(qe),(function(s){var o=qe[s];if("function"==typeof o){for(var i=$e.length;i--;)if($e[i][0]==s)return;o.convert=createConverter(s,o),$e.push([s,o])}})),de($e,(function(s){qe[s[0]]=s[1]})),qe.convert=function convertLib(s){return qe.runInContext.convert(s)(void 0)},qe.placeholder=qe,de(_e(qe),(function(s){de(a.realToAlias[s]||[],(function(o){qe[o]=qe[s]}))})),qe}},16962(s,o){o.aliasToReal={each:"forEach",eachRight:"forEachRight",entries:"toPairs",entriesIn:"toPairsIn",extend:"assignIn",extendAll:"assignInAll",extendAllWith:"assignInAllWith",extendWith:"assignInWith",first:"head",conforms:"conformsTo",matches:"isMatch",property:"get",__:"placeholder",F:"stubFalse",T:"stubTrue",all:"every",allPass:"overEvery",always:"constant",any:"some",anyPass:"overSome",apply:"spread",assoc:"set",assocPath:"set",complement:"negate",compose:"flowRight",contains:"includes",dissoc:"unset",dissocPath:"unset",dropLast:"dropRight",dropLastWhile:"dropRightWhile",equals:"isEqual",identical:"eq",indexBy:"keyBy",init:"initial",invertObj:"invert",juxt:"over",omitAll:"omit",nAry:"ary",path:"get",pathEq:"matchesProperty",pathOr:"getOr",paths:"at",pickAll:"pick",pipe:"flow",pluck:"map",prop:"get",propEq:"matchesProperty",propOr:"getOr",props:"at",symmetricDifference:"xor",symmetricDifferenceBy:"xorBy",symmetricDifferenceWith:"xorWith",takeLast:"takeRight",takeLastWhile:"takeRightWhile",unapply:"rest",unnest:"flatten",useWith:"overArgs",where:"conformsTo",whereEq:"isMatch",zipObj:"zipObject"},o.aryMethod={1:["assignAll","assignInAll","attempt","castArray","ceil","create","curry","curryRight","defaultsAll","defaultsDeepAll","floor","flow","flowRight","fromPairs","invert","iteratee","memoize","method","mergeAll","methodOf","mixin","nthArg","over","overEvery","overSome","rest","reverse","round","runInContext","spread","template","trim","trimEnd","trimStart","uniqueId","words","zipAll"],2:["add","after","ary","assign","assignAllWith","assignIn","assignInAllWith","at","before","bind","bindAll","bindKey","chunk","cloneDeepWith","cloneWith","concat","conformsTo","countBy","curryN","curryRightN","debounce","defaults","defaultsDeep","defaultTo","delay","difference","divide","drop","dropRight","dropRightWhile","dropWhile","endsWith","eq","every","filter","find","findIndex","findKey","findLast","findLastIndex","findLastKey","flatMap","flatMapDeep","flattenDepth","forEach","forEachRight","forIn","forInRight","forOwn","forOwnRight","get","groupBy","gt","gte","has","hasIn","includes","indexOf","intersection","invertBy","invoke","invokeMap","isEqual","isMatch","join","keyBy","lastIndexOf","lt","lte","map","mapKeys","mapValues","matchesProperty","maxBy","meanBy","merge","mergeAllWith","minBy","multiply","nth","omit","omitBy","overArgs","pad","padEnd","padStart","parseInt","partial","partialRight","partition","pick","pickBy","propertyOf","pull","pullAll","pullAt","random","range","rangeRight","rearg","reject","remove","repeat","restFrom","result","sampleSize","some","sortBy","sortedIndex","sortedIndexOf","sortedLastIndex","sortedLastIndexOf","sortedUniqBy","split","spreadFrom","startsWith","subtract","sumBy","take","takeRight","takeRightWhile","takeWhile","tap","throttle","thru","times","trimChars","trimCharsEnd","trimCharsStart","truncate","union","uniqBy","uniqWith","unset","unzipWith","without","wrap","xor","zip","zipObject","zipObjectDeep"],3:["assignInWith","assignWith","clamp","differenceBy","differenceWith","findFrom","findIndexFrom","findLastFrom","findLastIndexFrom","getOr","includesFrom","indexOfFrom","inRange","intersectionBy","intersectionWith","invokeArgs","invokeArgsMap","isEqualWith","isMatchWith","flatMapDepth","lastIndexOfFrom","mergeWith","orderBy","padChars","padCharsEnd","padCharsStart","pullAllBy","pullAllWith","rangeStep","rangeStepRight","reduce","reduceRight","replace","set","slice","sortedIndexBy","sortedLastIndexBy","transform","unionBy","unionWith","update","xorBy","xorWith","zipWith"],4:["fill","setWith","updateWith"]},o.aryRearg={2:[1,0],3:[2,0,1],4:[3,2,0,1]},o.iterateeAry={dropRightWhile:1,dropWhile:1,every:1,filter:1,find:1,findFrom:1,findIndex:1,findIndexFrom:1,findKey:1,findLast:1,findLastFrom:1,findLastIndex:1,findLastIndexFrom:1,findLastKey:1,flatMap:1,flatMapDeep:1,flatMapDepth:1,forEach:1,forEachRight:1,forIn:1,forInRight:1,forOwn:1,forOwnRight:1,map:1,mapKeys:1,mapValues:1,partition:1,reduce:2,reduceRight:2,reject:1,remove:1,some:1,takeRightWhile:1,takeWhile:1,times:1,transform:2},o.iterateeRearg={mapKeys:[1],reduceRight:[1,0]},o.methodRearg={assignInAllWith:[1,0],assignInWith:[1,2,0],assignAllWith:[1,0],assignWith:[1,2,0],differenceBy:[1,2,0],differenceWith:[1,2,0],getOr:[2,1,0],intersectionBy:[1,2,0],intersectionWith:[1,2,0],isEqualWith:[1,2,0],isMatchWith:[2,1,0],mergeAllWith:[1,0],mergeWith:[1,2,0],padChars:[2,1,0],padCharsEnd:[2,1,0],padCharsStart:[2,1,0],pullAllBy:[2,1,0],pullAllWith:[2,1,0],rangeStep:[1,2,0],rangeStepRight:[1,2,0],setWith:[3,1,2,0],sortedIndexBy:[2,1,0],sortedLastIndexBy:[2,1,0],unionBy:[1,2,0],unionWith:[1,2,0],updateWith:[3,1,2,0],xorBy:[1,2,0],xorWith:[1,2,0],zipWith:[1,2,0]},o.methodSpread={assignAll:{start:0},assignAllWith:{start:0},assignInAll:{start:0},assignInAllWith:{start:0},defaultsAll:{start:0},defaultsDeepAll:{start:0},invokeArgs:{start:2},invokeArgsMap:{start:2},mergeAll:{start:0},mergeAllWith:{start:0},partial:{start:1},partialRight:{start:1},without:{start:1},zipAll:{start:0}},o.mutate={array:{fill:!0,pull:!0,pullAll:!0,pullAllBy:!0,pullAllWith:!0,pullAt:!0,remove:!0,reverse:!0},object:{assign:!0,assignAll:!0,assignAllWith:!0,assignIn:!0,assignInAll:!0,assignInAllWith:!0,assignInWith:!0,assignWith:!0,defaults:!0,defaultsAll:!0,defaultsDeep:!0,defaultsDeepAll:!0,merge:!0,mergeAll:!0,mergeAllWith:!0,mergeWith:!0},set:{set:!0,setWith:!0,unset:!0,update:!0,updateWith:!0}},o.realToAlias=function(){var s=Object.prototype.hasOwnProperty,i=o.aliasToReal,a={};for(var u in i){var _=i[u];s.call(a,_)?a[_].push(u):a[_]=[u]}return a}(),o.remap={assignAll:"assign",assignAllWith:"assignWith",assignInAll:"assignIn",assignInAllWith:"assignInWith",curryN:"curry",curryRightN:"curryRight",defaultsAll:"defaults",defaultsDeepAll:"defaultsDeep",findFrom:"find",findIndexFrom:"findIndex",findLastFrom:"findLast",findLastIndexFrom:"findLastIndex",getOr:"get",includesFrom:"includes",indexOfFrom:"indexOf",invokeArgs:"invoke",invokeArgsMap:"invokeMap",lastIndexOfFrom:"lastIndexOf",mergeAll:"merge",mergeAllWith:"mergeWith",padChars:"pad",padCharsEnd:"padEnd",padCharsStart:"padStart",propertyOf:"get",rangeStep:"range",rangeStepRight:"rangeRight",restFrom:"rest",spreadFrom:"spread",trimChars:"trim",trimCharsEnd:"trimEnd",trimCharsStart:"trimStart",zipAll:"zip"},o.skipFixed={castArray:!0,flow:!0,flowRight:!0,iteratee:!0,mixin:!0,rearg:!0,runInContext:!0},o.skipRearg={add:!0,assign:!0,assignIn:!0,bind:!0,bindKey:!0,concat:!0,difference:!0,divide:!0,eq:!0,gt:!0,gte:!0,isEqual:!0,lt:!0,lte:!0,matchesProperty:!0,merge:!0,multiply:!0,overArgs:!0,partial:!0,partialRight:!0,propertyOf:!0,random:!0,range:!0,rangeRight:!0,subtract:!0,zip:!0,zipObject:!0,zipObjectDeep:!0}},47934(s,o,i){s.exports={ary:i(64626),assign:i(74733),clone:i(32629),curry:i(49747),forEach:i(83729),isArray:i(56449),isError:i(23546),isFunction:i(1882),isWeakMap:i(47886),iteratee:i(33855),keys:i(88984),rearg:i(84195),toInteger:i(61489),toPath:i(42072)}},56367(s,o,i){s.exports=i(77731)},79920(s,o,i){var a=i(73424),u=i(47934);s.exports=function convert(s,o,i){return a(u,s,o,i)}},2874(s){s.exports={}},77731(s,o,i){var a=i(79920)("set",i(63560));a.placeholder=i(2874),s.exports=a},58156(s,o,i){var a=i(47422);s.exports=function get(s,o,i){var u=null==s?void 0:a(s,o);return void 0===u?i:u}},61448(s,o,i){var a=i(20426),u=i(49326);s.exports=function has(s,o){return null!=s&&u(s,o,a)}},80631(s,o,i){var a=i(28077),u=i(49326);s.exports=function hasIn(s,o){return null!=s&&u(s,o,a)}},83488(s){s.exports=function identity(s){return s}},72428(s,o,i){var a=i(27534),u=i(40346),_=Object.prototype,w=_.hasOwnProperty,x=_.propertyIsEnumerable,C=a(function(){return arguments}())?a:function(s){return u(s)&&w.call(s,"callee")&&!x.call(s,"callee")};s.exports=C},56449(s){var o=Array.isArray;s.exports=o},64894(s,o,i){var a=i(1882),u=i(30294);s.exports=function isArrayLike(s){return null!=s&&u(s.length)&&!a(s)}},83693(s,o,i){var a=i(64894),u=i(40346);s.exports=function isArrayLikeObject(s){return u(s)&&a(s)}},53812(s,o,i){var a=i(72552),u=i(40346);s.exports=function isBoolean(s){return!0===s||!1===s||u(s)&&"[object Boolean]"==a(s)}},3656(s,o,i){s=i.nmd(s);var a=i(9325),u=i(89935),_=o&&!o.nodeType&&o,w=_&&s&&!s.nodeType&&s,x=w&&w.exports===_?a.Buffer:void 0,C=(x?x.isBuffer:void 0)||u;s.exports=C},62193(s,o,i){var a=i(88984),u=i(5861),_=i(72428),w=i(56449),x=i(64894),C=i(3656),j=i(55527),L=i(37167),B=Object.prototype.hasOwnProperty;s.exports=function isEmpty(s){if(null==s)return!0;if(x(s)&&(w(s)||"string"==typeof s||"function"==typeof s.splice||C(s)||L(s)||_(s)))return!s.length;var o=u(s);if("[object Map]"==o||"[object Set]"==o)return!s.size;if(j(s))return!a(s).length;for(var i in s)if(B.call(s,i))return!1;return!0}},2404(s,o,i){var a=i(60270);s.exports=function isEqual(s,o){return a(s,o)}},23546(s,o,i){var a=i(72552),u=i(40346),_=i(11331);s.exports=function isError(s){if(!u(s))return!1;var o=a(s);return"[object Error]"==o||"[object DOMException]"==o||"string"==typeof s.message&&"string"==typeof s.name&&!_(s)}},1882(s,o,i){var a=i(72552),u=i(23805);s.exports=function isFunction(s){if(!u(s))return!1;var o=a(s);return"[object Function]"==o||"[object GeneratorFunction]"==o||"[object AsyncFunction]"==o||"[object Proxy]"==o}},30294(s){s.exports=function isLength(s){return"number"==typeof s&&s>-1&&s%1==0&&s<=9007199254740991}},87730(s,o,i){var a=i(29172),u=i(27301),_=i(86009),w=_&&_.isMap,x=w?u(w):a;s.exports=x},5187(s){s.exports=function isNull(s){return null===s}},98023(s,o,i){var a=i(72552),u=i(40346);s.exports=function isNumber(s){return"number"==typeof s||u(s)&&"[object Number]"==a(s)}},23805(s){s.exports=function isObject(s){var o=typeof s;return null!=s&&("object"==o||"function"==o)}},40346(s){s.exports=function isObjectLike(s){return null!=s&&"object"==typeof s}},11331(s,o,i){var a=i(72552),u=i(28879),_=i(40346),w=Function.prototype,x=Object.prototype,C=w.toString,j=x.hasOwnProperty,L=C.call(Object);s.exports=function isPlainObject(s){if(!_(s)||"[object Object]"!=a(s))return!1;var o=u(s);if(null===o)return!0;var i=j.call(o,"constructor")&&o.constructor;return"function"==typeof i&&i instanceof i&&C.call(i)==L}},38440(s,o,i){var a=i(16038),u=i(27301),_=i(86009),w=_&&_.isSet,x=w?u(w):a;s.exports=x},85015(s,o,i){var a=i(72552),u=i(56449),_=i(40346);s.exports=function isString(s){return"string"==typeof s||!u(s)&&_(s)&&"[object String]"==a(s)}},44394(s,o,i){var a=i(72552),u=i(40346);s.exports=function isSymbol(s){return"symbol"==typeof s||u(s)&&"[object Symbol]"==a(s)}},37167(s,o,i){var a=i(4901),u=i(27301),_=i(86009),w=_&&_.isTypedArray,x=w?u(w):a;s.exports=x},47886(s,o,i){var a=i(5861),u=i(40346);s.exports=function isWeakMap(s){return u(s)&&"[object WeakMap]"==a(s)}},33855(s,o,i){var a=i(9999),u=i(15389);s.exports=function iteratee(s){return u("function"==typeof s?s:a(s,1))}},95950(s,o,i){var a=i(70695),u=i(88984),_=i(64894);s.exports=function keys(s){return _(s)?a(s):u(s)}},37241(s,o,i){var a=i(70695),u=i(72903),_=i(64894);s.exports=function keysIn(s){return _(s)?a(s,!0):u(s)}},68090(s){s.exports=function last(s){var o=null==s?0:s.length;return o?s[o-1]:void 0}},50104(s,o,i){var a=i(53661);function memoize(s,o){if("function"!=typeof s||null!=o&&"function"!=typeof o)throw new TypeError("Expected a function");var memoized=function(){var i=arguments,a=o?o.apply(this,i):i[0],u=memoized.cache;if(u.has(a))return u.get(a);var _=s.apply(this,i);return memoized.cache=u.set(a,_)||u,_};return memoized.cache=new(memoize.Cache||a),memoized}memoize.Cache=a,s.exports=memoize},55364(s,o,i){var a=i(85250),u=i(20999)((function(s,o,i){a(s,o,i)}));s.exports=u},6048(s){s.exports=function negate(s){if("function"!=typeof s)throw new TypeError("Expected a function");return function(){var o=arguments;switch(o.length){case 0:return!s.call(this);case 1:return!s.call(this,o[0]);case 2:return!s.call(this,o[0],o[1]);case 3:return!s.call(this,o[0],o[1],o[2])}return!s.apply(this,o)}}},63950(s){s.exports=function noop(){}},10124(s,o,i){var a=i(9325);s.exports=function(){return a.Date.now()}},90179(s,o,i){var a=i(34932),u=i(9999),_=i(19931),w=i(31769),x=i(21791),C=i(53138),j=i(38816),L=i(83349),B=j((function(s,o){var i={};if(null==s)return i;var j=!1;o=a(o,(function(o){return o=w(o,s),j||(j=o.length>1),o})),x(s,L(s),i),j&&(i=u(i,7,C));for(var B=o.length;B--;)_(i,o[B]);return i}));s.exports=B},50583(s,o,i){var a=i(47237),u=i(17255),_=i(28586),w=i(77797);s.exports=function property(s){return _(s)?a(w(s)):u(s)}},84195(s,o,i){var a=i(66977),u=i(38816),_=u((function(s,o){return a(s,256,void 0,void 0,void 0,o)}));s.exports=_},40860(s,o,i){var a=i(40882),u=i(80909),_=i(15389),w=i(85558),x=i(56449);s.exports=function reduce(s,o,i){var C=x(s)?a:w,j=arguments.length<3;return C(s,_(o,4),i,j,u)}},63560(s,o,i){var a=i(73170);s.exports=function set(s,o,i){return null==s?s:a(s,o,i)}},42426(s,o,i){var a=i(14248),u=i(15389),_=i(90916),w=i(56449),x=i(36800);s.exports=function some(s,o,i){var C=w(s)?a:_;return i&&x(s,o,i)&&(o=void 0),C(s,u(o,3))}},63345(s){s.exports=function stubArray(){return[]}},89935(s){s.exports=function stubFalse(){return!1}},17400(s,o,i){var a=i(99374),u=1/0;s.exports=function toFinite(s){return s?(s=a(s))===u||s===-1/0?17976931348623157e292*(s<0?-1:1):s==s?s:0:0===s?s:0}},61489(s,o,i){var a=i(17400);s.exports=function toInteger(s){var o=a(s),i=o%1;return o==o?i?o-i:o:0}},80218(s,o,i){var a=i(13222);s.exports=function toLower(s){return a(s).toLowerCase()}},99374(s,o,i){var a=i(54128),u=i(23805),_=i(44394),w=/^[-+]0x[0-9a-f]+$/i,x=/^0b[01]+$/i,C=/^0o[0-7]+$/i,j=parseInt;s.exports=function toNumber(s){if("number"==typeof s)return s;if(_(s))return NaN;if(u(s)){var o="function"==typeof s.valueOf?s.valueOf():s;s=u(o)?o+"":o}if("string"!=typeof s)return 0===s?s:+s;s=a(s);var i=x.test(s);return i||C.test(s)?j(s.slice(2),i?2:8):w.test(s)?NaN:+s}},42072(s,o,i){var a=i(34932),u=i(23007),_=i(56449),w=i(44394),x=i(61802),C=i(77797),j=i(13222);s.exports=function toPath(s){return _(s)?a(s,C):w(s)?[s]:u(x(j(s)))}},69884(s,o,i){var a=i(21791),u=i(37241);s.exports=function toPlainObject(s){return a(s,u(s))}},13222(s,o,i){var a=i(77556);s.exports=function toString(s){return null==s?"":a(s)}},55808(s,o,i){var a=i(12507)("toUpperCase");s.exports=a},66645(s,o,i){var a=i(1733),u=i(45434),_=i(13222),w=i(22225);s.exports=function words(s,o,i){return s=_(s),void 0===(o=i?void 0:o)?u(s)?w(s):a(s):s.match(o)||[]}},53758(s,o,i){var a=i(30980),u=i(56017),_=i(94033),w=i(56449),x=i(40346),C=i(80257),j=Object.prototype.hasOwnProperty;function lodash(s){if(x(s)&&!w(s)&&!(s instanceof a)){if(s instanceof u)return s;if(j.call(s,"__wrapped__"))return C(s)}return new u(s)}lodash.prototype=_.prototype,lodash.prototype.constructor=lodash,s.exports=lodash},47248(s,o,i){var a=i(16547),u=i(51234);s.exports=function zipObject(s,o){return u(s||[],o||[],a)}},43768(s,o,i){"use strict";var a=i(45981),u=i(85587);o.highlight=highlight,o.highlightAuto=function highlightAuto(s,o){var i,w,x,C,j=o||{},L=j.subset||a.listLanguages(),B=j.prefix,$=L.length,z=-1;null==B&&(B=_);if("string"!=typeof s)throw u("Expected `string` for value, got `%s`",s);w={relevance:0,language:null,value:[]},i={relevance:0,language:null,value:[]};for(;++z<$;)C=L[z],a.getLanguage(C)&&((x=highlight(C,s,o)).language=C,x.relevance>w.relevance&&(w=x),x.relevance>i.relevance&&(w=i,i=x));w.language&&(i.secondBest=w);return i},o.registerLanguage=function registerLanguage(s,o){a.registerLanguage(s,o)},o.listLanguages=function listLanguages(){return a.listLanguages()},o.registerAlias=function registerAlias(s,o){var i,u=s;o&&((u={})[s]=o);for(i in u)a.registerAliases(u[i],{languageName:i})},Emitter.prototype.addText=function text(s){var o,i,a=this.stack;if(""===s)return;o=a[a.length-1],(i=o.children[o.children.length-1])&&"text"===i.type?i.value+=s:o.children.push({type:"text",value:s})},Emitter.prototype.addKeyword=function addKeyword(s,o){this.openNode(o),this.addText(s),this.closeNode()},Emitter.prototype.addSublanguage=function addSublanguage(s,o){var i=this.stack,a=i[i.length-1],u=s.rootNode.children,_=o?{type:"element",tagName:"span",properties:{className:[o]},children:u}:u;a.children=a.children.concat(_)},Emitter.prototype.openNode=function open(s){var o=this.stack,i=this.options.classPrefix+s,a=o[o.length-1],u={type:"element",tagName:"span",properties:{className:[i]},children:[]};a.children.push(u),o.push(u)},Emitter.prototype.closeNode=function close(){this.stack.pop()},Emitter.prototype.closeAllNodes=noop,Emitter.prototype.finalize=noop,Emitter.prototype.toHTML=function toHtmlNoop(){return""};var _="hljs-";function highlight(s,o,i){var w,x=a.configure({}),C=(i||{}).prefix;if("string"!=typeof s)throw u("Expected `string` for name, got `%s`",s);if(!a.getLanguage(s))throw u("Unknown language: `%s` is not registered",s);if("string"!=typeof o)throw u("Expected `string` for value, got `%s`",o);if(null==C&&(C=_),a.configure({__emitter:Emitter,classPrefix:C}),w=a.highlight(o,{language:s,ignoreIllegals:!0}),a.configure(x||{}),w.errorRaised)throw w.errorRaised;return{relevance:w.relevance,language:w.language,value:w.emitter.rootNode.children}}function Emitter(s){this.options=s,this.rootNode={children:[]},this.stack=[this.rootNode]}function noop(){}},71514(s){"use strict";s.exports=Math.abs},58968(s){"use strict";s.exports=Math.floor},94459(s){"use strict";s.exports=Number.isNaN||function isNaN(s){return s!=s}},6188(s){"use strict";s.exports=Math.max},68002(s){"use strict";s.exports=Math.min},75880(s){"use strict";s.exports=Math.pow},70414(s){"use strict";s.exports=Math.round},73093(s,o,i){"use strict";var a=i(94459);s.exports=function sign(s){return a(s)||0===s?s:s<0?-1:1}},92340(s,o,i){const a=i(6048);function coerceElementMatchingCallback(s){return"string"==typeof s?o=>o.element===s:s.constructor&&s.extend?o=>o instanceof s:s}class ArraySlice{constructor(s){this.elements=s||[]}toValue(){return this.elements.map((s=>s.toValue()))}map(s,o){return this.elements.map(s,o)}flatMap(s,o){return this.map(s,o).reduce(((s,o)=>s.concat(o)),[])}compactMap(s,o){const i=[];return this.forEach((a=>{const u=s.bind(o)(a);u&&i.push(u)})),i}filter(s,o){return s=coerceElementMatchingCallback(s),new ArraySlice(this.elements.filter(s,o))}reject(s,o){return s=coerceElementMatchingCallback(s),new ArraySlice(this.elements.filter(a(s),o))}find(s,o){return s=coerceElementMatchingCallback(s),this.elements.find(s,o)}forEach(s,o){this.elements.forEach(s,o)}reduce(s,o){return this.elements.reduce(s,o)}includes(s){return this.elements.some((o=>o.equals(s)))}shift(){return this.elements.shift()}unshift(s){this.elements.unshift(this.refract(s))}push(s){return this.elements.push(this.refract(s)),this}add(s){this.push(s)}get(s){return this.elements[s]}getValue(s){const o=this.elements[s];if(o)return o.toValue()}get length(){return this.elements.length}get isEmpty(){return 0===this.elements.length}get first(){return this.elements[0]}}"undefined"!=typeof Symbol&&(ArraySlice.prototype[Symbol.iterator]=function symbol(){return this.elements[Symbol.iterator]()}),s.exports=ArraySlice},55973(s){class KeyValuePair{constructor(s,o){this.key=s,this.value=o}clone(){const s=new KeyValuePair;return this.key&&(s.key=this.key.clone()),this.value&&(s.value=this.value.clone()),s}}s.exports=KeyValuePair},3110(s,o,i){const a=i(5187),u=i(85015),_=i(98023),w=i(53812),x=i(23805),C=i(85105),j=i(86804);class Namespace{constructor(s){this.elementMap={},this.elementDetection=[],this.Element=j.Element,this.KeyValuePair=j.KeyValuePair,s&&s.noDefault||this.useDefault(),this._attributeElementKeys=[],this._attributeElementArrayKeys=[]}use(s){return s.namespace&&s.namespace({base:this}),s.load&&s.load({base:this}),this}useDefault(){return this.register("null",j.NullElement).register("string",j.StringElement).register("number",j.NumberElement).register("boolean",j.BooleanElement).register("array",j.ArrayElement).register("object",j.ObjectElement).register("member",j.MemberElement).register("ref",j.RefElement).register("link",j.LinkElement),this.detect(a,j.NullElement,!1).detect(u,j.StringElement,!1).detect(_,j.NumberElement,!1).detect(w,j.BooleanElement,!1).detect(Array.isArray,j.ArrayElement,!1).detect(x,j.ObjectElement,!1),this}register(s,o){return this._elements=void 0,this.elementMap[s]=o,this}unregister(s){return this._elements=void 0,delete this.elementMap[s],this}detect(s,o,i){return void 0===i||i?this.elementDetection.unshift([s,o]):this.elementDetection.push([s,o]),this}toElement(s){if(s instanceof this.Element)return s;let o;for(let i=0;i{const o=s[0].toUpperCase()+s.substr(1);this._elements[o]=this.elementMap[s]}))),this._elements}get serialiser(){return new C(this)}}C.prototype.Namespace=Namespace,s.exports=Namespace},10866(s,o,i){const a=i(6048),u=i(92340);class ObjectSlice extends u{map(s,o){return this.elements.map((i=>s.bind(o)(i.value,i.key,i)))}filter(s,o){return new ObjectSlice(this.elements.filter((i=>s.bind(o)(i.value,i.key,i))))}reject(s,o){return this.filter(a(s.bind(o)))}forEach(s,o){return this.elements.forEach(((i,a)=>{s.bind(o)(i.value,i.key,i,a)}))}keys(){return this.map(((s,o)=>o.toValue()))}values(){return this.map((s=>s.toValue()))}}s.exports=ObjectSlice},86804(s,o,i){const a=i(10316),u=i(41067),_=i(71167),w=i(40239),x=i(12242),C=i(6233),j=i(87726),L=i(61045),B=i(86303),$=i(14540),z=i(92340),U=i(10866),V=i(55973);function refract(s){if(s instanceof a)return s;if("string"==typeof s)return new _(s);if("number"==typeof s)return new w(s);if("boolean"==typeof s)return new x(s);if(null===s)return new u;if(Array.isArray(s))return new C(s.map(refract));if("object"==typeof s){return new L(s)}return s}a.prototype.ObjectElement=L,a.prototype.RefElement=$,a.prototype.MemberElement=j,a.prototype.refract=refract,z.prototype.refract=refract,s.exports={Element:a,NullElement:u,StringElement:_,NumberElement:w,BooleanElement:x,ArrayElement:C,MemberElement:j,ObjectElement:L,LinkElement:B,RefElement:$,refract,ArraySlice:z,ObjectSlice:U,KeyValuePair:V}},86303(s,o,i){const a=i(10316);s.exports=class LinkElement extends a{constructor(s,o,i){super(s||[],o,i),this.element="link"}get relation(){return this.attributes.get("relation")}set relation(s){this.attributes.set("relation",s)}get href(){return this.attributes.get("href")}set href(s){this.attributes.set("href",s)}}},14540(s,o,i){const a=i(10316);s.exports=class RefElement extends a{constructor(s,o,i){super(s||[],o,i),this.element="ref",this.path||(this.path="element")}get path(){return this.attributes.get("path")}set path(s){this.attributes.set("path",s)}}},34035(s,o,i){const a=i(3110),u=i(86804);o.g$=a,o.KeyValuePair=i(55973),o.G6=u.ArraySlice,o.ot=u.ObjectSlice,o.Hg=u.Element,o.Om=u.StringElement,o.kT=u.NumberElement,o.bd=u.BooleanElement,o.Os=u.NullElement,o.wE=u.ArrayElement,o.Sh=u.ObjectElement,o.Pr=u.MemberElement,o.sI=u.RefElement,o.Ft=u.LinkElement,o.e=u.refract,i(85105),i(75147)},6233(s,o,i){const a=i(6048),u=i(10316),_=i(92340);class ArrayElement extends u{constructor(s,o,i){super(s||[],o,i),this.element="array"}primitive(){return"array"}get(s){return this.content[s]}getValue(s){const o=this.get(s);if(o)return o.toValue()}getIndex(s){return this.content[s]}set(s,o){return this.content[s]=this.refract(o),this}remove(s){const o=this.content.splice(s,1);return o.length?o[0]:null}map(s,o){return this.content.map(s,o)}flatMap(s,o){return this.map(s,o).reduce(((s,o)=>s.concat(o)),[])}compactMap(s,o){const i=[];return this.forEach((a=>{const u=s.bind(o)(a);u&&i.push(u)})),i}filter(s,o){return new _(this.content.filter(s,o))}reject(s,o){return this.filter(a(s),o)}reduce(s,o){let i,a;void 0!==o?(i=0,a=this.refract(o)):(i=1,a="object"===this.primitive()?this.first.value:this.first);for(let o=i;o{s.bind(o)(i,this.refract(a))}))}shift(){return this.content.shift()}unshift(s){this.content.unshift(this.refract(s))}push(s){return this.content.push(this.refract(s)),this}add(s){this.push(s)}findElements(s,o){const i=o||{},a=!!i.recursive,u=void 0===i.results?[]:i.results;return this.forEach(((o,i,_)=>{a&&void 0!==o.findElements&&o.findElements(s,{results:u,recursive:a}),s(o,i,_)&&u.push(o)})),u}find(s){return new _(this.findElements(s,{recursive:!0}))}findByElement(s){return this.find((o=>o.element===s))}findByClass(s){return this.find((o=>o.classes.includes(s)))}getById(s){return this.find((o=>o.id.toValue()===s)).first}includes(s){return this.content.some((o=>o.equals(s)))}contains(s){return this.includes(s)}empty(){return new this.constructor([])}"fantasy-land/empty"(){return this.empty()}concat(s){return new this.constructor(this.content.concat(s.content))}"fantasy-land/concat"(s){return this.concat(s)}"fantasy-land/map"(s){return new this.constructor(this.map(s))}"fantasy-land/chain"(s){return this.map((o=>s(o)),this).reduce(((s,o)=>s.concat(o)),this.empty())}"fantasy-land/filter"(s){return new this.constructor(this.content.filter(s))}"fantasy-land/reduce"(s,o){return this.content.reduce(s,o)}get length(){return this.content.length}get isEmpty(){return 0===this.content.length}get first(){return this.getIndex(0)}get second(){return this.getIndex(1)}get last(){return this.getIndex(this.length-1)}}ArrayElement.empty=function empty(){return new this},ArrayElement["fantasy-land/empty"]=ArrayElement.empty,"undefined"!=typeof Symbol&&(ArrayElement.prototype[Symbol.iterator]=function symbol(){return this.content[Symbol.iterator]()}),s.exports=ArrayElement},12242(s,o,i){const a=i(10316);s.exports=class BooleanElement extends a{constructor(s,o,i){super(s,o,i),this.element="boolean"}primitive(){return"boolean"}}},10316(s,o,i){const a=i(2404),u=i(55973),_=i(92340);class Element{constructor(s,o,i){o&&(this.meta=o),i&&(this.attributes=i),this.content=s}freeze(){Object.isFrozen(this)||(this._meta&&(this.meta.parent=this,this.meta.freeze()),this._attributes&&(this.attributes.parent=this,this.attributes.freeze()),this.children.forEach((s=>{s.parent=this,s.freeze()}),this),this.content&&Array.isArray(this.content)&&Object.freeze(this.content),Object.freeze(this))}primitive(){}clone(){const s=new this.constructor;return s.element=this.element,this.meta.length&&(s._meta=this.meta.clone()),this.attributes.length&&(s._attributes=this.attributes.clone()),this.content?this.content.clone?s.content=this.content.clone():Array.isArray(this.content)?s.content=this.content.map((s=>s.clone())):s.content=this.content:s.content=this.content,s}toValue(){return this.content instanceof Element?this.content.toValue():this.content instanceof u?{key:this.content.key.toValue(),value:this.content.value?this.content.value.toValue():void 0}:this.content&&this.content.map?this.content.map((s=>s.toValue()),this):this.content}toRef(s){if(""===this.id.toValue())throw Error("Cannot create reference to an element that does not contain an ID");const o=new this.RefElement(this.id.toValue());return s&&(o.path=s),o}findRecursive(...s){if(arguments.length>1&&!this.isFrozen)throw new Error("Cannot find recursive with multiple element names without first freezing the element. Call `element.freeze()`");const o=s.pop();let i=new _;const append=(s,o)=>(s.push(o),s),checkElement=(s,i)=>{i.element===o&&s.push(i);const a=i.findRecursive(o);return a&&a.reduce(append,s),i.content instanceof u&&(i.content.key&&checkElement(s,i.content.key),i.content.value&&checkElement(s,i.content.value)),s};return this.content&&(this.content.element&&checkElement(i,this.content),Array.isArray(this.content)&&this.content.reduce(checkElement,i)),s.isEmpty||(i=i.filter((o=>{let i=o.parents.map((s=>s.element));for(const o in s){const a=s[o],u=i.indexOf(a);if(-1===u)return!1;i=i.splice(0,u)}return!0}))),i}set(s){return this.content=s,this}equals(s){return a(this.toValue(),s)}getMetaProperty(s,o){if(!this.meta.hasKey(s)){if(this.isFrozen){const s=this.refract(o);return s.freeze(),s}this.meta.set(s,o)}return this.meta.get(s)}setMetaProperty(s,o){this.meta.set(s,o)}get element(){return this._storedElement||"element"}set element(s){this._storedElement=s}get content(){return this._content}set content(s){if(s instanceof Element)this._content=s;else if(s instanceof _)this.content=s.elements;else if("string"==typeof s||"number"==typeof s||"boolean"==typeof s||"null"===s||null==s)this._content=s;else if(s instanceof u)this._content=s;else if(Array.isArray(s))this._content=s.map(this.refract);else{if("object"!=typeof s)throw new Error("Cannot set content to given value");this._content=Object.keys(s).map((o=>new this.MemberElement(o,s[o])))}}get meta(){if(!this._meta){if(this.isFrozen){const s=new this.ObjectElement;return s.freeze(),s}this._meta=new this.ObjectElement}return this._meta}set meta(s){s instanceof this.ObjectElement?this._meta=s:this.meta.set(s||{})}get attributes(){if(!this._attributes){if(this.isFrozen){const s=new this.ObjectElement;return s.freeze(),s}this._attributes=new this.ObjectElement}return this._attributes}set attributes(s){s instanceof this.ObjectElement?this._attributes=s:this.attributes.set(s||{})}get id(){return this.getMetaProperty("id","")}set id(s){this.setMetaProperty("id",s)}get classes(){return this.getMetaProperty("classes",[])}set classes(s){this.setMetaProperty("classes",s)}get title(){return this.getMetaProperty("title","")}set title(s){this.setMetaProperty("title",s)}get description(){return this.getMetaProperty("description","")}set description(s){this.setMetaProperty("description",s)}get links(){return this.getMetaProperty("links",[])}set links(s){this.setMetaProperty("links",s)}get isFrozen(){return Object.isFrozen(this)}get parents(){let{parent:s}=this;const o=new _;for(;s;)o.push(s),s=s.parent;return o}get children(){if(Array.isArray(this.content))return new _(this.content);if(this.content instanceof u){const s=new _([this.content.key]);return this.content.value&&s.push(this.content.value),s}return this.content instanceof Element?new _([this.content]):new _}get recursiveChildren(){const s=new _;return this.children.forEach((o=>{s.push(o),o.recursiveChildren.forEach((o=>{s.push(o)}))})),s}}s.exports=Element},87726(s,o,i){const a=i(55973),u=i(10316);s.exports=class MemberElement extends u{constructor(s,o,i,u){super(new a,i,u),this.element="member",this.key=s,this.value=o}get key(){return this.content.key}set key(s){this.content.key=this.refract(s)}get value(){return this.content.value}set value(s){this.content.value=this.refract(s)}}},41067(s,o,i){const a=i(10316);s.exports=class NullElement extends a{constructor(s,o,i){super(s||null,o,i),this.element="null"}primitive(){return"null"}set(){return new Error("Cannot set the value of null")}}},40239(s,o,i){const a=i(10316);s.exports=class NumberElement extends a{constructor(s,o,i){super(s,o,i),this.element="number"}primitive(){return"number"}}},61045(s,o,i){const a=i(6048),u=i(23805),_=i(6233),w=i(87726),x=i(10866);s.exports=class ObjectElement extends _{constructor(s,o,i){super(s||[],o,i),this.element="object"}primitive(){return"object"}toValue(){return this.content.reduce(((s,o)=>(s[o.key.toValue()]=o.value?o.value.toValue():void 0,s)),{})}get(s){const o=this.getMember(s);if(o)return o.value}getMember(s){if(void 0!==s)return this.content.find((o=>o.key.toValue()===s))}remove(s){let o=null;return this.content=this.content.filter((i=>i.key.toValue()!==s||(o=i,!1))),o}getKey(s){const o=this.getMember(s);if(o)return o.key}set(s,o){if(u(s))return Object.keys(s).forEach((o=>{this.set(o,s[o])})),this;const i=s,a=this.getMember(i);return a?a.value=o:this.content.push(new w(i,o)),this}keys(){return this.content.map((s=>s.key.toValue()))}values(){return this.content.map((s=>s.value.toValue()))}hasKey(s){return this.content.some((o=>o.key.equals(s)))}items(){return this.content.map((s=>[s.key.toValue(),s.value.toValue()]))}map(s,o){return this.content.map((i=>s.bind(o)(i.value,i.key,i)))}compactMap(s,o){const i=[];return this.forEach(((a,u,_)=>{const w=s.bind(o)(a,u,_);w&&i.push(w)})),i}filter(s,o){return new x(this.content).filter(s,o)}reject(s,o){return this.filter(a(s),o)}forEach(s,o){return this.content.forEach((i=>s.bind(o)(i.value,i.key,i)))}}},71167(s,o,i){const a=i(10316);s.exports=class StringElement extends a{constructor(s,o,i){super(s,o,i),this.element="string"}primitive(){return"string"}get length(){return this.content.length}}},75147(s,o,i){const a=i(85105);s.exports=class JSON06Serialiser extends a{serialise(s){if(!(s instanceof this.namespace.elements.Element))throw new TypeError(`Given element \`${s}\` is not an Element instance`);let o;s._attributes&&s.attributes.get("variable")&&(o=s.attributes.get("variable"));const i={element:s.element};s._meta&&s._meta.length>0&&(i.meta=this.serialiseObject(s.meta));const a="enum"===s.element||-1!==s.attributes.keys().indexOf("enumerations");if(a){const o=this.enumSerialiseAttributes(s);o&&(i.attributes=o)}else if(s._attributes&&s._attributes.length>0){let{attributes:a}=s;a.get("metadata")&&(a=a.clone(),a.set("meta",a.get("metadata")),a.remove("metadata")),"member"===s.element&&o&&(a=a.clone(),a.remove("variable")),a.length>0&&(i.attributes=this.serialiseObject(a))}if(a)i.content=this.enumSerialiseContent(s,i);else if(this[`${s.element}SerialiseContent`])i.content=this[`${s.element}SerialiseContent`](s,i);else if(void 0!==s.content){let a;o&&s.content.key?(a=s.content.clone(),a.key.attributes.set("variable",o),a=this.serialiseContent(a)):a=this.serialiseContent(s.content),this.shouldSerialiseContent(s,a)&&(i.content=a)}else this.shouldSerialiseContent(s,s.content)&&s instanceof this.namespace.elements.Array&&(i.content=[]);return i}shouldSerialiseContent(s,o){return"parseResult"===s.element||"httpRequest"===s.element||"httpResponse"===s.element||"category"===s.element||"link"===s.element||void 0!==o&&(!Array.isArray(o)||0!==o.length)}refSerialiseContent(s,o){return delete o.attributes,{href:s.toValue(),path:s.path.toValue()}}sourceMapSerialiseContent(s){return s.toValue()}dataStructureSerialiseContent(s){return[this.serialiseContent(s.content)]}enumSerialiseAttributes(s){const o=s.attributes.clone(),i=o.remove("enumerations")||new this.namespace.elements.Array([]),a=o.get("default");let u=o.get("samples")||new this.namespace.elements.Array([]);if(a&&a.content&&(a.content.attributes&&a.content.attributes.remove("typeAttributes"),o.set("default",new this.namespace.elements.Array([a.content]))),u.forEach((s=>{s.content&&s.content.element&&s.content.attributes.remove("typeAttributes")})),s.content&&0!==i.length&&u.unshift(s.content),u=u.map((s=>s instanceof this.namespace.elements.Array?[s]:new this.namespace.elements.Array([s.content]))),u.length&&o.set("samples",u),o.length>0)return this.serialiseObject(o)}enumSerialiseContent(s){if(s._attributes){const o=s.attributes.get("enumerations");if(o&&o.length>0)return o.content.map((s=>{const o=s.clone();return o.attributes.remove("typeAttributes"),this.serialise(o)}))}if(s.content){const o=s.content.clone();return o.attributes.remove("typeAttributes"),[this.serialise(o)]}return[]}deserialise(s){if("string"==typeof s)return new this.namespace.elements.String(s);if("number"==typeof s)return new this.namespace.elements.Number(s);if("boolean"==typeof s)return new this.namespace.elements.Boolean(s);if(null===s)return new this.namespace.elements.Null;if(Array.isArray(s))return new this.namespace.elements.Array(s.map(this.deserialise,this));const o=this.namespace.getElementClass(s.element),i=new o;i.element!==s.element&&(i.element=s.element),s.meta&&this.deserialiseObject(s.meta,i.meta),s.attributes&&this.deserialiseObject(s.attributes,i.attributes);const a=this.deserialiseContent(s.content);if(void 0===a&&null!==i.content||(i.content=a),"enum"===i.element){i.content&&i.attributes.set("enumerations",i.content);let s=i.attributes.get("samples");if(i.attributes.remove("samples"),s){const a=s;s=new this.namespace.elements.Array,a.forEach((a=>{a.forEach((a=>{const u=new o(a);u.element=i.element,s.push(u)}))}));const u=s.shift();i.content=u?u.content:void 0,i.attributes.set("samples",s)}else i.content=void 0;let a=i.attributes.get("default");if(a&&a.length>0){a=a.get(0);const s=new o(a);s.element=i.element,i.attributes.set("default",s)}}else if("dataStructure"===i.element&&Array.isArray(i.content))[i.content]=i.content;else if("category"===i.element){const s=i.attributes.get("meta");s&&(i.attributes.set("metadata",s),i.attributes.remove("meta"))}else"member"===i.element&&i.key&&i.key._attributes&&i.key._attributes.getValue("variable")&&(i.attributes.set("variable",i.key.attributes.get("variable")),i.key.attributes.remove("variable"));return i}serialiseContent(s){if(s instanceof this.namespace.elements.Element)return this.serialise(s);if(s instanceof this.namespace.KeyValuePair){const o={key:this.serialise(s.key)};return s.value&&(o.value=this.serialise(s.value)),o}return s&&s.map?s.map(this.serialise,this):s}deserialiseContent(s){if(s){if(s.element)return this.deserialise(s);if(s.key){const o=new this.namespace.KeyValuePair(this.deserialise(s.key));return s.value&&(o.value=this.deserialise(s.value)),o}if(s.map)return s.map(this.deserialise,this)}return s}shouldRefract(s){return!!(s._attributes&&s.attributes.keys().length||s._meta&&s.meta.keys().length)||"enum"!==s.element&&(s.element!==s.primitive()||"member"===s.element)}convertKeyToRefract(s,o){return this.shouldRefract(o)?this.serialise(o):"enum"===o.element?this.serialiseEnum(o):"array"===o.element?o.map((o=>this.shouldRefract(o)||"default"===s?this.serialise(o):"array"===o.element||"object"===o.element||"enum"===o.element?o.children.map((s=>this.serialise(s))):o.toValue())):"object"===o.element?(o.content||[]).map(this.serialise,this):o.toValue()}serialiseEnum(s){return s.children.map((s=>this.serialise(s)))}serialiseObject(s){const o={};return s.forEach(((s,i)=>{if(s){const a=i.toValue();o[a]=this.convertKeyToRefract(a,s)}})),o}deserialiseObject(s,o){Object.keys(s).forEach((i=>{o.set(i,this.deserialise(s[i]))}))}}},85105(s){s.exports=class JSONSerialiser{constructor(s){this.namespace=s||new this.Namespace}serialise(s){if(!(s instanceof this.namespace.elements.Element))throw new TypeError(`Given element \`${s}\` is not an Element instance`);const o={element:s.element};s._meta&&s._meta.length>0&&(o.meta=this.serialiseObject(s.meta)),s._attributes&&s._attributes.length>0&&(o.attributes=this.serialiseObject(s.attributes));const i=this.serialiseContent(s.content);return void 0!==i&&(o.content=i),o}deserialise(s){if(!s.element)throw new Error("Given value is not an object containing an element name");const o=new(this.namespace.getElementClass(s.element));o.element!==s.element&&(o.element=s.element),s.meta&&this.deserialiseObject(s.meta,o.meta),s.attributes&&this.deserialiseObject(s.attributes,o.attributes);const i=this.deserialiseContent(s.content);return void 0===i&&null!==o.content||(o.content=i),o}serialiseContent(s){if(s instanceof this.namespace.elements.Element)return this.serialise(s);if(s instanceof this.namespace.KeyValuePair){const o={key:this.serialise(s.key)};return s.value&&(o.value=this.serialise(s.value)),o}if(s&&s.map){if(0===s.length)return;return s.map(this.serialise,this)}return s}deserialiseContent(s){if(s){if(s.element)return this.deserialise(s);if(s.key){const o=new this.namespace.KeyValuePair(this.deserialise(s.key));return s.value&&(o.value=this.deserialise(s.value)),o}if(s.map)return s.map(this.deserialise,this)}return s}serialiseObject(s){const o={};if(s.forEach(((s,i)=>{s&&(o[i.toValue()]=this.serialise(s))})),0!==Object.keys(o).length)return o}deserialiseObject(s,o){Object.keys(s).forEach((i=>{o.set(i,this.deserialise(s[i]))}))}}},76578(s){"use strict";s.exports=["Float16Array","Float32Array","Float64Array","Int8Array","Int16Array","Int32Array","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array"]},65606(s){var o,i,a=s.exports={};function defaultSetTimout(){throw new Error("setTimeout has not been defined")}function defaultClearTimeout(){throw new Error("clearTimeout has not been defined")}function runTimeout(s){if(o===setTimeout)return setTimeout(s,0);if((o===defaultSetTimout||!o)&&setTimeout)return o=setTimeout,setTimeout(s,0);try{return o(s,0)}catch(i){try{return o.call(null,s,0)}catch(i){return o.call(this,s,0)}}}!function(){try{o="function"==typeof setTimeout?setTimeout:defaultSetTimout}catch(s){o=defaultSetTimout}try{i="function"==typeof clearTimeout?clearTimeout:defaultClearTimeout}catch(s){i=defaultClearTimeout}}();var u,_=[],w=!1,x=-1;function cleanUpNextTick(){w&&u&&(w=!1,u.length?_=u.concat(_):x=-1,_.length&&drainQueue())}function drainQueue(){if(!w){var s=runTimeout(cleanUpNextTick);w=!0;for(var o=_.length;o;){for(u=_,_=[];++x1)for(var i=1;i_)throw new RangeError("requested too many random bytes");var i=w.allocUnsafe(s);if(s>0)if(s>u)for(var C=0;C=0||(u[i]=s[i]);return u}(s,o);if(Object.getOwnPropertySymbols){var _=Object.getOwnPropertySymbols(s);for(a=0;a<_.length;a++)i=_[a],o.indexOf(i)>=0||Object.prototype.propertyIsEnumerable.call(s,i)&&(u[i]=s[i])}return u}function ownKeys(s,o){var i=Object.keys(s);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(s);o&&(a=a.filter((function(o){return Object.getOwnPropertyDescriptor(s,o).enumerable}))),i.push.apply(i,a)}return i}function _objectSpread(s){for(var o=1;o=a?i.notify(s):o.length>u.length&&i.notify(_objectSpread(_objectSpread({},s),{},{target:_objectSpread(_objectSpread({},s.target),{},{value:""})}))}))})),_defineProperty(_assertThisInitialized(i),"onKeyDown",(function(s){"Enter"===s.key&&i.forceNotify(s);var o=i.props.onKeyDown;o&&(s.persist(),o(s))})),_defineProperty(_assertThisInitialized(i),"onBlur",(function(s){i.forceNotify(s);var o=i.props.onBlur;o&&(s.persist(),o(s))})),_defineProperty(_assertThisInitialized(i),"createNotifier",(function(s){if(s<0)i.notify=function(){return null};else if(0===s)i.notify=i.doNotify;else{var o=(0,u.default)((function(s){i.isDebouncing=!1,i.doNotify(s)}),s);i.notify=function(s){i.isDebouncing=!0,o(s)},i.flush=function(){return o.flush()},i.cancel=function(){i.isDebouncing=!1,o.cancel()}}})),_defineProperty(_assertThisInitialized(i),"doNotify",(function(){i.props.onChange.apply(void 0,arguments)})),_defineProperty(_assertThisInitialized(i),"forceNotify",(function(s){var o=i.props.debounceTimeout;if(i.isDebouncing||!(o>0)){i.cancel&&i.cancel();var a=i.state.value,u=i.props.minLength;a.length>=u?i.doNotify(s):i.doNotify(_objectSpread(_objectSpread({},s),{},{target:_objectSpread(_objectSpread({},s.target),{},{value:a})}))}})),i.isDebouncing=!1,i.state={value:void 0===s.value||null===s.value?"":s.value};var a=i.props.debounceTimeout;return i.createNotifier(a),i}return function _createClass(s,o,i){return o&&_defineProperties(s.prototype,o),i&&_defineProperties(s,i),Object.defineProperty(s,"prototype",{writable:!1}),s}(DebounceInput,[{key:"componentDidUpdate",value:function componentDidUpdate(s){if(!this.isDebouncing){var o=this.props,i=o.value,a=o.debounceTimeout,u=s.debounceTimeout,_=s.value,w=this.state.value;void 0!==i&&_!==i&&w!==i&&this.setState({value:i}),a!==u&&this.createNotifier(a)}}},{key:"componentWillUnmount",value:function componentWillUnmount(){this.flush&&this.flush()}},{key:"render",value:function render(){var s,o,i=this.props,u=i.element,w=(i.onChange,i.value,i.minLength,i.debounceTimeout,i.forceNotifyByEnter),x=i.forceNotifyOnBlur,C=i.onKeyDown,j=i.onBlur,L=i.inputRef,B=_objectWithoutProperties(i,_),$=this.state.value;s=w?{onKeyDown:this.onKeyDown}:C?{onKeyDown:C}:{},o=x?{onBlur:this.onBlur}:j?{onBlur:j}:{};var z=L?{ref:L}:{};return a.default.createElement(u,_objectSpread(_objectSpread(_objectSpread(_objectSpread({},B),{},{onChange:this.onChange,value:$},s),o),z))}}]),DebounceInput}(a.default.PureComponent);o.DebounceInput=w,_defineProperty(w,"defaultProps",{element:"input",type:"text",onKeyDown:void 0,onBlur:void 0,value:void 0,minLength:0,debounceTimeout:100,forceNotifyByEnter:!0,forceNotifyOnBlur:!0,inputRef:void 0})},24677(s,o,i){"use strict";var a=i(81214).DebounceInput;a.DebounceInput=a,s.exports=a},22551(s,o,i){"use strict";var a=i(96540),u=i(69982);function p(s){for(var o="https://reactjs.org/docs/error-decoder.html?invariant="+s,i=1;i
")}value(){return this.buffer}span(s){this.buffer+=``}}class TokenTree{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(s){this.top.children.push(s)}openNode(s){const o={kind:s,children:[]};this.add(o),this.stack.push(o)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(s){return this.constructor._walk(s,this.rootNode)}static _walk(s,o){return"string"==typeof o?s.addText(o):o.children&&(s.openNode(o),o.children.forEach((o=>this._walk(s,o))),s.closeNode(o)),s}static _collapse(s){"string"!=typeof s&&s.children&&(s.children.every((s=>"string"==typeof s))?s.children=[s.children.join("")]:s.children.forEach((s=>{TokenTree._collapse(s)})))}}class TokenTreeEmitter extends TokenTree{constructor(s){super(),this.options=s}addKeyword(s,o){""!==s&&(this.openNode(o),this.addText(s),this.closeNode())}addText(s){""!==s&&this.add(s)}addSublanguage(s,o){const i=s.root;i.kind=o,i.sublanguage=!0,this.add(i)}toHTML(){return new HTMLRenderer(this,this.options).value()}finalize(){return!0}}function source(s){return s?"string"==typeof s?s:s.source:null}const a=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;const u="[a-zA-Z]\\w*",_="[a-zA-Z_]\\w*",w="\\b\\d+(\\.\\d+)?",x="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",C="\\b(0b[01]+)",j={begin:"\\\\[\\s\\S]",relevance:0},L={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[j]},B={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[j]},$={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},COMMENT=function(s,o,i={}){const a=inherit({className:"comment",begin:s,end:o,contains:[]},i);return a.contains.push($),a.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),a},z=COMMENT("//","$"),U=COMMENT("/\\*","\\*/"),V=COMMENT("#","$"),Y={className:"number",begin:w,relevance:0},Z={className:"number",begin:x,relevance:0},ee={className:"number",begin:C,relevance:0},ie={className:"number",begin:w+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},ae={begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[j,{begin:/\[/,end:/\]/,relevance:0,contains:[j]}]}]},ce={className:"title",begin:u,relevance:0},le={className:"title",begin:_,relevance:0},pe={begin:"\\.\\s*"+_,relevance:0};var de=Object.freeze({__proto__:null,MATCH_NOTHING_RE:/\b\B/,IDENT_RE:u,UNDERSCORE_IDENT_RE:_,NUMBER_RE:w,C_NUMBER_RE:x,BINARY_NUMBER_RE:C,RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(s={})=>{const o=/^#![ ]*\//;return s.binary&&(s.begin=function concat(...s){return s.map((s=>source(s))).join("")}(o,/.*\b/,s.binary,/\b.*/)),inherit({className:"meta",begin:o,end:/$/,relevance:0,"on:begin":(s,o)=>{0!==s.index&&o.ignoreMatch()}},s)},BACKSLASH_ESCAPE:j,APOS_STRING_MODE:L,QUOTE_STRING_MODE:B,PHRASAL_WORDS_MODE:$,COMMENT,C_LINE_COMMENT_MODE:z,C_BLOCK_COMMENT_MODE:U,HASH_COMMENT_MODE:V,NUMBER_MODE:Y,C_NUMBER_MODE:Z,BINARY_NUMBER_MODE:ee,CSS_NUMBER_MODE:ie,REGEXP_MODE:ae,TITLE_MODE:ce,UNDERSCORE_TITLE_MODE:le,METHOD_GUARD:pe,END_SAME_AS_BEGIN:function(s){return Object.assign(s,{"on:begin":(s,o)=>{o.data._beginMatch=s[1]},"on:end":(s,o)=>{o.data._beginMatch!==s[1]&&o.ignoreMatch()}})}});function skipIfhasPrecedingDot(s,o){"."===s.input[s.index-1]&&o.ignoreMatch()}function beginKeywords(s,o){o&&s.beginKeywords&&(s.begin="\\b("+s.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)",s.__beforeBegin=skipIfhasPrecedingDot,s.keywords=s.keywords||s.beginKeywords,delete s.beginKeywords,void 0===s.relevance&&(s.relevance=0))}function compileIllegal(s,o){Array.isArray(s.illegal)&&(s.illegal=function either(...s){return"("+s.map((s=>source(s))).join("|")+")"}(...s.illegal))}function compileMatch(s,o){if(s.match){if(s.begin||s.end)throw new Error("begin & end are not supported with match");s.begin=s.match,delete s.match}}function compileRelevance(s,o){void 0===s.relevance&&(s.relevance=1)}const fe=["of","and","for","in","not","or","if","then","parent","list","value"];function compileKeywords(s,o,i="keyword"){const a={};return"string"==typeof s?compileList(i,s.split(" ")):Array.isArray(s)?compileList(i,s):Object.keys(s).forEach((function(i){Object.assign(a,compileKeywords(s[i],o,i))})),a;function compileList(s,i){o&&(i=i.map((s=>s.toLowerCase()))),i.forEach((function(o){const i=o.split("|");a[i[0]]=[s,scoreForKeyword(i[0],i[1])]}))}}function scoreForKeyword(s,o){return o?Number(o):function commonKeyword(s){return fe.includes(s.toLowerCase())}(s)?0:1}function compileLanguage(s,{plugins:o}){function langRe(o,i){return new RegExp(source(o),"m"+(s.case_insensitive?"i":"")+(i?"g":""))}class MultiRegex{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(s,o){o.position=this.position++,this.matchIndexes[this.matchAt]=o,this.regexes.push([o,s]),this.matchAt+=function countMatchGroups(s){return new RegExp(s.toString()+"|").exec("").length-1}(s)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const s=this.regexes.map((s=>s[1]));this.matcherRe=langRe(function join(s,o="|"){let i=0;return s.map((s=>{i+=1;const o=i;let u=source(s),_="";for(;u.length>0;){const s=a.exec(u);if(!s){_+=u;break}_+=u.substring(0,s.index),u=u.substring(s.index+s[0].length),"\\"===s[0][0]&&s[1]?_+="\\"+String(Number(s[1])+o):(_+=s[0],"("===s[0]&&i++)}return _})).map((s=>`(${s})`)).join(o)}(s),!0),this.lastIndex=0}exec(s){this.matcherRe.lastIndex=this.lastIndex;const o=this.matcherRe.exec(s);if(!o)return null;const i=o.findIndex(((s,o)=>o>0&&void 0!==s)),a=this.matchIndexes[i];return o.splice(0,i),Object.assign(o,a)}}class ResumableMultiRegex{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(s){if(this.multiRegexes[s])return this.multiRegexes[s];const o=new MultiRegex;return this.rules.slice(s).forEach((([s,i])=>o.addRule(s,i))),o.compile(),this.multiRegexes[s]=o,o}resumingScanAtSamePosition(){return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(s,o){this.rules.push([s,o]),"begin"===o.type&&this.count++}exec(s){const o=this.getMatcher(this.regexIndex);o.lastIndex=this.lastIndex;let i=o.exec(s);if(this.resumingScanAtSamePosition())if(i&&i.index===this.lastIndex);else{const o=this.getMatcher(0);o.lastIndex=this.lastIndex+1,i=o.exec(s)}return i&&(this.regexIndex+=i.position+1,this.regexIndex===this.count&&this.considerAll()),i}}if(s.compilerExtensions||(s.compilerExtensions=[]),s.contains&&s.contains.includes("self"))throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return s.classNameAliases=inherit(s.classNameAliases||{}),function compileMode(o,i){const a=o;if(o.isCompiled)return a;[compileMatch].forEach((s=>s(o,i))),s.compilerExtensions.forEach((s=>s(o,i))),o.__beforeBegin=null,[beginKeywords,compileIllegal,compileRelevance].forEach((s=>s(o,i))),o.isCompiled=!0;let u=null;if("object"==typeof o.keywords&&(u=o.keywords.$pattern,delete o.keywords.$pattern),o.keywords&&(o.keywords=compileKeywords(o.keywords,s.case_insensitive)),o.lexemes&&u)throw new Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return u=u||o.lexemes||/\w+/,a.keywordPatternRe=langRe(u,!0),i&&(o.begin||(o.begin=/\B|\b/),a.beginRe=langRe(o.begin),o.endSameAsBegin&&(o.end=o.begin),o.end||o.endsWithParent||(o.end=/\B|\b/),o.end&&(a.endRe=langRe(o.end)),a.terminatorEnd=source(o.end)||"",o.endsWithParent&&i.terminatorEnd&&(a.terminatorEnd+=(o.end?"|":"")+i.terminatorEnd)),o.illegal&&(a.illegalRe=langRe(o.illegal)),o.contains||(o.contains=[]),o.contains=[].concat(...o.contains.map((function(s){return function expandOrCloneMode(s){s.variants&&!s.cachedVariants&&(s.cachedVariants=s.variants.map((function(o){return inherit(s,{variants:null},o)})));if(s.cachedVariants)return s.cachedVariants;if(dependencyOnParent(s))return inherit(s,{starts:s.starts?inherit(s.starts):null});if(Object.isFrozen(s))return inherit(s);return s}("self"===s?o:s)}))),o.contains.forEach((function(s){compileMode(s,a)})),o.starts&&compileMode(o.starts,i),a.matcher=function buildModeRegex(s){const o=new ResumableMultiRegex;return s.contains.forEach((s=>o.addRule(s.begin,{rule:s,type:"begin"}))),s.terminatorEnd&&o.addRule(s.terminatorEnd,{type:"end"}),s.illegal&&o.addRule(s.illegal,{type:"illegal"}),o}(a),a}(s)}function dependencyOnParent(s){return!!s&&(s.endsWithParent||dependencyOnParent(s.starts))}function BuildVuePlugin(s){const o={props:["language","code","autodetect"],data:function(){return{detectedLanguage:"",unknownLanguage:!1}},computed:{className(){return this.unknownLanguage?"":"hljs "+this.detectedLanguage},highlighted(){if(!this.autoDetect&&!s.getLanguage(this.language))return console.warn(`The language "${this.language}" you specified could not be found.`),this.unknownLanguage=!0,escapeHTML(this.code);let o={};return this.autoDetect?(o=s.highlightAuto(this.code),this.detectedLanguage=o.language):(o=s.highlight(this.language,this.code,this.ignoreIllegals),this.detectedLanguage=this.language),o.value},autoDetect(){return!this.language||function hasValueOrEmptyAttribute(s){return Boolean(s||""===s)}(this.autodetect)},ignoreIllegals:()=>!0},render(s){return s("pre",{},[s("code",{class:this.className,domProps:{innerHTML:this.highlighted}})])}};return{Component:o,VuePlugin:{install(s){s.component("highlightjs",o)}}}}const ye={"after:highlightElement":({el:s,result:o,text:i})=>{const a=nodeStream(s);if(!a.length)return;const u=document.createElement("div");u.innerHTML=o.value,o.value=function mergeStreams(s,o,i){let a=0,u="";const _=[];function selectStream(){return s.length&&o.length?s[0].offset!==o[0].offset?s[0].offset"}function close(s){u+=""}function render(s){("start"===s.event?open:close)(s.node)}for(;s.length||o.length;){let o=selectStream();if(u+=escapeHTML(i.substring(a,o[0].offset)),a=o[0].offset,o===s){_.reverse().forEach(close);do{render(o.splice(0,1)[0]),o=selectStream()}while(o===s&&o.length&&o[0].offset===a);_.reverse().forEach(open)}else"start"===o[0].event?_.push(o[0].node):_.pop(),render(o.splice(0,1)[0])}return u+escapeHTML(i.substr(a))}(a,nodeStream(u),i)}};function tag(s){return s.nodeName.toLowerCase()}function nodeStream(s){const o=[];return function _nodeStream(s,i){for(let a=s.firstChild;a;a=a.nextSibling)3===a.nodeType?i+=a.nodeValue.length:1===a.nodeType&&(o.push({event:"start",offset:i,node:a}),i=_nodeStream(a,i),tag(a).match(/br|hr|img|input/)||o.push({event:"stop",offset:i,node:a}));return i}(s,0),o}const be={},error=s=>{console.error(s)},warn=(s,...o)=>{console.log(`WARN: ${s}`,...o)},deprecated=(s,o)=>{be[`${s}/${o}`]||(console.log(`Deprecated as of ${s}. ${o}`),be[`${s}/${o}`]=!0)},Se=escapeHTML,_e=inherit,we=Symbol("nomatch");var xe=function(s){const i=Object.create(null),a=Object.create(null),u=[];let _=!0;const w=/(^(<[^>]+>|\t|)+|\n)/gm,x="Could not find the language '{}', did you forget to load/include a language module?",C={disableAutodetect:!0,name:"Plain text",contains:[]};let j={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:TokenTreeEmitter};function shouldNotHighlight(s){return j.noHighlightRe.test(s)}function highlight(s,o,i,a){let u="",_="";"object"==typeof o?(u=s,i=o.ignoreIllegals,_=o.language,a=void 0):(deprecated("10.7.0","highlight(lang, code, ...args) has been deprecated."),deprecated("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"),_=s,u=o);const w={code:u,language:_};fire("before:highlight",w);const x=w.result?w.result:_highlight(w.language,w.code,i,a);return x.code=w.code,fire("after:highlight",x),x}function _highlight(s,o,a,w){function keywordData(s,o){const i=L.case_insensitive?o[0].toLowerCase():o[0];return Object.prototype.hasOwnProperty.call(s.keywords,i)&&s.keywords[i]}function processBuffer(){null!=z.subLanguage?function processSubLanguage(){if(""===Y)return;let s=null;if("string"==typeof z.subLanguage){if(!i[z.subLanguage])return void V.addText(Y);s=_highlight(z.subLanguage,Y,!0,U[z.subLanguage]),U[z.subLanguage]=s.top}else s=highlightAuto(Y,z.subLanguage.length?z.subLanguage:null);z.relevance>0&&(Z+=s.relevance),V.addSublanguage(s.emitter,s.language)}():function processKeywords(){if(!z.keywords)return void V.addText(Y);let s=0;z.keywordPatternRe.lastIndex=0;let o=z.keywordPatternRe.exec(Y),i="";for(;o;){i+=Y.substring(s,o.index);const a=keywordData(z,o);if(a){const[s,u]=a;if(V.addText(i),i="",Z+=u,s.startsWith("_"))i+=o[0];else{const i=L.classNameAliases[s]||s;V.addKeyword(o[0],i)}}else i+=o[0];s=z.keywordPatternRe.lastIndex,o=z.keywordPatternRe.exec(Y)}i+=Y.substr(s),V.addText(i)}(),Y=""}function startNewMode(s){return s.className&&V.openNode(L.classNameAliases[s.className]||s.className),z=Object.create(s,{parent:{value:z}}),z}function endOfMode(s,o,i){let a=function startsWith(s,o){const i=s&&s.exec(o);return i&&0===i.index}(s.endRe,i);if(a){if(s["on:end"]){const i=new Response(s);s["on:end"](o,i),i.isMatchIgnored&&(a=!1)}if(a){for(;s.endsParent&&s.parent;)s=s.parent;return s}}if(s.endsWithParent)return endOfMode(s.parent,o,i)}function doIgnore(s){return 0===z.matcher.regexIndex?(Y+=s[0],1):(ae=!0,0)}function doBeginMatch(s){const o=s[0],i=s.rule,a=new Response(i),u=[i.__beforeBegin,i["on:begin"]];for(const i of u)if(i&&(i(s,a),a.isMatchIgnored))return doIgnore(o);return i&&i.endSameAsBegin&&(i.endRe=function escape(s){return new RegExp(s.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")}(o)),i.skip?Y+=o:(i.excludeBegin&&(Y+=o),processBuffer(),i.returnBegin||i.excludeBegin||(Y=o)),startNewMode(i),i.returnBegin?0:o.length}function doEndMatch(s){const i=s[0],a=o.substr(s.index),u=endOfMode(z,s,a);if(!u)return we;const _=z;_.skip?Y+=i:(_.returnEnd||_.excludeEnd||(Y+=i),processBuffer(),_.excludeEnd&&(Y=i));do{z.className&&V.closeNode(),z.skip||z.subLanguage||(Z+=z.relevance),z=z.parent}while(z!==u.parent);return u.starts&&(u.endSameAsBegin&&(u.starts.endRe=u.endRe),startNewMode(u.starts)),_.returnEnd?0:i.length}let C={};function processLexeme(i,u){const w=u&&u[0];if(Y+=i,null==w)return processBuffer(),0;if("begin"===C.type&&"end"===u.type&&C.index===u.index&&""===w){if(Y+=o.slice(u.index,u.index+1),!_){const o=new Error("0 width match regex");throw o.languageName=s,o.badRule=C.rule,o}return 1}if(C=u,"begin"===u.type)return doBeginMatch(u);if("illegal"===u.type&&!a){const s=new Error('Illegal lexeme "'+w+'" for mode "'+(z.className||"")+'"');throw s.mode=z,s}if("end"===u.type){const s=doEndMatch(u);if(s!==we)return s}if("illegal"===u.type&&""===w)return 1;if(ie>1e5&&ie>3*u.index){throw new Error("potential infinite loop, way more iterations than matches")}return Y+=w,w.length}const L=getLanguage(s);if(!L)throw error(x.replace("{}",s)),new Error('Unknown language: "'+s+'"');const B=compileLanguage(L,{plugins:u});let $="",z=w||B;const U={},V=new j.__emitter(j);!function processContinuations(){const s=[];for(let o=z;o!==L;o=o.parent)o.className&&s.unshift(o.className);s.forEach((s=>V.openNode(s)))}();let Y="",Z=0,ee=0,ie=0,ae=!1;try{for(z.matcher.considerAll();;){ie++,ae?ae=!1:z.matcher.considerAll(),z.matcher.lastIndex=ee;const s=z.matcher.exec(o);if(!s)break;const i=processLexeme(o.substring(ee,s.index),s);ee=s.index+i}return processLexeme(o.substr(ee)),V.closeAllNodes(),V.finalize(),$=V.toHTML(),{relevance:Math.floor(Z),value:$,language:s,illegal:!1,emitter:V,top:z}}catch(i){if(i.message&&i.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:i.message,context:o.slice(ee-100,ee+100),mode:i.mode},sofar:$,relevance:0,value:Se(o),emitter:V};if(_)return{illegal:!1,relevance:0,value:Se(o),emitter:V,language:s,top:z,errorRaised:i};throw i}}function highlightAuto(s,o){o=o||j.languages||Object.keys(i);const a=function justTextHighlightResult(s){const o={relevance:0,emitter:new j.__emitter(j),value:Se(s),illegal:!1,top:C};return o.emitter.addText(s),o}(s),u=o.filter(getLanguage).filter(autoDetection).map((o=>_highlight(o,s,!1)));u.unshift(a);const _=u.sort(((s,o)=>{if(s.relevance!==o.relevance)return o.relevance-s.relevance;if(s.language&&o.language){if(getLanguage(s.language).supersetOf===o.language)return 1;if(getLanguage(o.language).supersetOf===s.language)return-1}return 0})),[w,x]=_,L=w;return L.second_best=x,L}const L={"before:highlightElement":({el:s})=>{j.useBR&&(s.innerHTML=s.innerHTML.replace(/\n/g,"").replace(//g,"\n"))},"after:highlightElement":({result:s})=>{j.useBR&&(s.value=s.value.replace(/\n/g,"
"))}},B=/^(<[^>]+>|\t)+/gm,$={"after:highlightElement":({result:s})=>{j.tabReplace&&(s.value=s.value.replace(B,(s=>s.replace(/\t/g,j.tabReplace))))}};function highlightElement(s){let o=null;const i=function blockLanguage(s){let o=s.className+" ";o+=s.parentNode?s.parentNode.className:"";const i=j.languageDetectRe.exec(o);if(i){const o=getLanguage(i[1]);return o||(warn(x.replace("{}",i[1])),warn("Falling back to no-highlight mode for this block.",s)),o?i[1]:"no-highlight"}return o.split(/\s+/).find((s=>shouldNotHighlight(s)||getLanguage(s)))}(s);if(shouldNotHighlight(i))return;fire("before:highlightElement",{el:s,language:i}),o=s;const u=o.textContent,_=i?highlight(u,{language:i,ignoreIllegals:!0}):highlightAuto(u);fire("after:highlightElement",{el:s,result:_,text:u}),s.innerHTML=_.value,function updateClassName(s,o,i){const u=o?a[o]:i;s.classList.add("hljs"),u&&s.classList.add(u)}(s,i,_.language),s.result={language:_.language,re:_.relevance,relavance:_.relevance},_.second_best&&(s.second_best={language:_.second_best.language,re:_.second_best.relevance,relavance:_.second_best.relevance})}const initHighlighting=()=>{if(initHighlighting.called)return;initHighlighting.called=!0,deprecated("10.6.0","initHighlighting() is deprecated. Use highlightAll() instead.");document.querySelectorAll("pre code").forEach(highlightElement)};let z=!1;function highlightAll(){if("loading"===document.readyState)return void(z=!0);document.querySelectorAll("pre code").forEach(highlightElement)}function getLanguage(s){return s=(s||"").toLowerCase(),i[s]||i[a[s]]}function registerAliases(s,{languageName:o}){"string"==typeof s&&(s=[s]),s.forEach((s=>{a[s.toLowerCase()]=o}))}function autoDetection(s){const o=getLanguage(s);return o&&!o.disableAutodetect}function fire(s,o){const i=s;u.forEach((function(s){s[i]&&s[i](o)}))}"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(function boot(){z&&highlightAll()}),!1),Object.assign(s,{highlight,highlightAuto,highlightAll,fixMarkup:function deprecateFixMarkup(s){return deprecated("10.2.0","fixMarkup will be removed entirely in v11.0"),deprecated("10.2.0","Please see https://github.com/highlightjs/highlight.js/issues/2534"),function fixMarkup(s){return j.tabReplace||j.useBR?s.replace(w,(s=>"\n"===s?j.useBR?"
":s:j.tabReplace?s.replace(/\t/g,j.tabReplace):s)):s}(s)},highlightElement,highlightBlock:function deprecateHighlightBlock(s){return deprecated("10.7.0","highlightBlock will be removed entirely in v12.0"),deprecated("10.7.0","Please use highlightElement now."),highlightElement(s)},configure:function configure(s){s.useBR&&(deprecated("10.3.0","'useBR' will be removed entirely in v11.0"),deprecated("10.3.0","Please see https://github.com/highlightjs/highlight.js/issues/2559")),j=_e(j,s)},initHighlighting,initHighlightingOnLoad:function initHighlightingOnLoad(){deprecated("10.6.0","initHighlightingOnLoad() is deprecated. Use highlightAll() instead."),z=!0},registerLanguage:function registerLanguage(o,a){let u=null;try{u=a(s)}catch(s){if(error("Language definition for '{}' could not be registered.".replace("{}",o)),!_)throw s;error(s),u=C}u.name||(u.name=o),i[o]=u,u.rawDefinition=a.bind(null,s),u.aliases&®isterAliases(u.aliases,{languageName:o})},unregisterLanguage:function unregisterLanguage(s){delete i[s];for(const o of Object.keys(a))a[o]===s&&delete a[o]},listLanguages:function listLanguages(){return Object.keys(i)},getLanguage,registerAliases,requireLanguage:function requireLanguage(s){deprecated("10.4.0","requireLanguage will be removed entirely in v11."),deprecated("10.4.0","Please see https://github.com/highlightjs/highlight.js/pull/2844");const o=getLanguage(s);if(o)return o;throw new Error("The '{}' language is required, but not loaded.".replace("{}",s))},autoDetection,inherit:_e,addPlugin:function addPlugin(s){!function upgradePluginAPI(s){s["before:highlightBlock"]&&!s["before:highlightElement"]&&(s["before:highlightElement"]=o=>{s["before:highlightBlock"](Object.assign({block:o.el},o))}),s["after:highlightBlock"]&&!s["after:highlightElement"]&&(s["after:highlightElement"]=o=>{s["after:highlightBlock"](Object.assign({block:o.el},o))})}(s),u.push(s)},vuePlugin:BuildVuePlugin(s).VuePlugin}),s.debugMode=function(){_=!1},s.safeMode=function(){_=!0},s.versionString="10.7.3";for(const s in de)"object"==typeof de[s]&&o(de[s]);return Object.assign(s,de),s.addPlugin(L),s.addPlugin(ye),s.addPlugin($),s}({});s.exports=xe},35344(s){function concat(...s){return s.map((s=>function source(s){return s?"string"==typeof s?s:s.source:null}(s))).join("")}s.exports=function bash(s){const o={},i={begin:/\$\{/,end:/\}/,contains:["self",{begin:/:-/,contains:[o]}]};Object.assign(o,{className:"variable",variants:[{begin:concat(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},i]});const a={className:"subst",begin:/\$\(/,end:/\)/,contains:[s.BACKSLASH_ESCAPE]},u={begin:/<<-?\s*(?=\w+)/,starts:{contains:[s.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,className:"string"})]}},_={className:"string",begin:/"/,end:/"/,contains:[s.BACKSLASH_ESCAPE,o,a]};a.contains.push(_);const w={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},s.NUMBER_MODE,o]},x=s.SHEBANG({binary:`(${["fish","bash","zsh","sh","csh","ksh","tcsh","dash","scsh"].join("|")})`,relevance:10}),C={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[s.inherit(s.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b[a-z._-]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp"},contains:[x,s.SHEBANG(),C,w,s.HASH_COMMENT_MODE,u,_,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},o]}}},73402(s){function concat(...s){return s.map((s=>function source(s){return s?"string"==typeof s?s:s.source:null}(s))).join("")}s.exports=function http(s){const o="HTTP/(2|1\\.[01])",i={className:"attribute",begin:concat("^",/[A-Za-z][A-Za-z0-9-]*/,"(?=\\:\\s)"),starts:{contains:[{className:"punctuation",begin:/: /,relevance:0,starts:{end:"$",relevance:0}}]}},a=[i,{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}];return{name:"HTTP",aliases:["https"],illegal:/\S/,contains:[{begin:"^(?="+o+" \\d{3})",end:/$/,contains:[{className:"meta",begin:o},{className:"number",begin:"\\b\\d{3}\\b"}],starts:{end:/\b\B/,illegal:/\S/,contains:a}},{begin:"(?=^[A-Z]+ (.*?) "+o+"$)",end:/$/,contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{className:"meta",begin:o},{className:"keyword",begin:"[A-Z]+"}],starts:{end:/\b\B/,illegal:/\S/,contains:a}},s.inherit(i,{relevance:0})]}}},95089(s){const o="[A-Za-z$_][0-9A-Za-z$_]*",i=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],a=["true","false","null","undefined","NaN","Infinity"],u=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer","BigInt64Array","BigUint64Array","BigInt"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);function lookahead(s){return concat("(?=",s,")")}function concat(...s){return s.map((s=>function source(s){return s?"string"==typeof s?s:s.source:null}(s))).join("")}s.exports=function javascript(s){const _=o,w="<>",x="",C={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(s,o)=>{const i=s[0].length+s.index,a=s.input[i];"<"!==a?">"===a&&(((s,{after:o})=>{const i="",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:s.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:j,contains:ce}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:w,end:x},{begin:C.begin,"on:begin":C.isTrulyOpeningTag,end:C.end}],subLanguage:"xml",contains:[{begin:C.begin,end:C.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[{;]/,excludeEnd:!0,keywords:j,contains:["self",s.inherit(s.TITLE_MODE,{begin:_}),le],illegal:/%/},{beginKeywords:"while if switch catch for"},{className:"function",begin:s.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",returnBegin:!0,contains:[le,s.inherit(s.TITLE_MODE,{begin:_})]},{variants:[{begin:"\\."+_},{begin:"\\$"+_}],relevance:0},{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"[\]]/,contains:[{beginKeywords:"extends"},s.UNDERSCORE_TITLE_MODE]},{begin:/\b(?=constructor)/,end:/[{;]/,excludeEnd:!0,contains:[s.inherit(s.TITLE_MODE,{begin:_}),"self",le]},{begin:"(get|set)\\s+(?="+_+"\\()",end:/\{/,keywords:"get set",contains:[s.inherit(s.TITLE_MODE,{begin:_}),{begin:/\(\)/},le]},{begin:/\$[(.]/}]}}},65772(s){s.exports=function json(s){const o={literal:"true false null"},i=[s.C_LINE_COMMENT_MODE,s.C_BLOCK_COMMENT_MODE],a=[s.QUOTE_STRING_MODE,s.C_NUMBER_MODE],u={end:",",endsWithParent:!0,excludeEnd:!0,contains:a,keywords:o},_={begin:/\{/,end:/\}/,contains:[{className:"attr",begin:/"/,end:/"/,contains:[s.BACKSLASH_ESCAPE],illegal:"\\n"},s.inherit(u,{begin:/:/})].concat(i),illegal:"\\S"},w={begin:"\\[",end:"\\]",contains:[s.inherit(u)],illegal:"\\S"};return a.push(_,w),i.forEach((function(s){a.push(s)})),{name:"JSON",contains:a,keywords:o,illegal:"\\S"}}},26571(s){s.exports=function powershell(s){const o={$pattern:/-?[A-z\.\-]+\b/,keyword:"if else foreach return do while until elseif begin for trap data dynamicparam end break throw param continue finally in switch exit filter try process catch hidden static parameter",built_in:"ac asnp cat cd CFS chdir clc clear clhy cli clp cls clv cnsn compare copy cp cpi cpp curl cvpa dbp del diff dir dnsn ebp echo|0 epal epcsv epsn erase etsn exsn fc fhx fl ft fw gal gbp gc gcb gci gcm gcs gdr gerr ghy gi gin gjb gl gm gmo gp gps gpv group gsn gsnp gsv gtz gu gv gwmi h history icm iex ihy ii ipal ipcsv ipmo ipsn irm ise iwmi iwr kill lp ls man md measure mi mount move mp mv nal ndr ni nmo npssc nsn nv ogv oh popd ps pushd pwd r rbp rcjb rcsn rd rdr ren ri rjb rm rmdir rmo rni rnp rp rsn rsnp rujb rv rvpa rwmi sajb sal saps sasv sbp sc scb select set shcm si sl sleep sls sort sp spjb spps spsv start stz sujb sv swmi tee trcm type wget where wjb write"},i={begin:"`[\\s\\S]",relevance:0},a={className:"variable",variants:[{begin:/\$\B/},{className:"keyword",begin:/\$this/},{begin:/\$[\w\d][\w\d_:]*/}]},u={className:"string",variants:[{begin:/"/,end:/"/},{begin:/@"/,end:/^"@/}],contains:[i,a,{className:"variable",begin:/\$[A-z]/,end:/[^A-z]/}]},_={className:"string",variants:[{begin:/'/,end:/'/},{begin:/@'/,end:/^'@/}]},w=s.inherit(s.COMMENT(null,null),{variants:[{begin:/#/,end:/$/},{begin:/<#/,end:/#>/}],contains:[{className:"doctag",variants:[{begin:/\.(synopsis|description|example|inputs|outputs|notes|link|component|role|functionality)/},{begin:/\.(parameter|forwardhelptargetname|forwardhelpcategory|remotehelprunspace|externalhelp)\s+\S+/}]}]}),x={className:"built_in",variants:[{begin:"(".concat("Add|Clear|Close|Copy|Enter|Exit|Find|Format|Get|Hide|Join|Lock|Move|New|Open|Optimize|Pop|Push|Redo|Remove|Rename|Reset|Resize|Search|Select|Set|Show|Skip|Split|Step|Switch|Undo|Unlock|Watch|Backup|Checkpoint|Compare|Compress|Convert|ConvertFrom|ConvertTo|Dismount|Edit|Expand|Export|Group|Import|Initialize|Limit|Merge|Mount|Out|Publish|Restore|Save|Sync|Unpublish|Update|Approve|Assert|Build|Complete|Confirm|Deny|Deploy|Disable|Enable|Install|Invoke|Register|Request|Restart|Resume|Start|Stop|Submit|Suspend|Uninstall|Unregister|Wait|Debug|Measure|Ping|Repair|Resolve|Test|Trace|Connect|Disconnect|Read|Receive|Send|Write|Block|Grant|Protect|Revoke|Unblock|Unprotect|Use|ForEach|Sort|Tee|Where",")+(-)[\\w\\d]+")}]},C={className:"class",beginKeywords:"class enum",end:/\s*[{]/,excludeEnd:!0,relevance:0,contains:[s.TITLE_MODE]},j={className:"function",begin:/function\s+/,end:/\s*\{|$/,excludeEnd:!0,returnBegin:!0,relevance:0,contains:[{begin:"function",relevance:0,className:"keyword"},{className:"title",begin:/\w[\w\d]*((-)[\w\d]+)*/,relevance:0},{begin:/\(/,end:/\)/,className:"params",relevance:0,contains:[a]}]},L={begin:/using\s/,end:/$/,returnBegin:!0,contains:[u,_,{className:"keyword",begin:/(using|assembly|command|module|namespace|type)/}]},B={variants:[{className:"operator",begin:"(".concat("-and|-as|-band|-bnot|-bor|-bxor|-casesensitive|-ccontains|-ceq|-cge|-cgt|-cle|-clike|-clt|-cmatch|-cne|-cnotcontains|-cnotlike|-cnotmatch|-contains|-creplace|-csplit|-eq|-exact|-f|-file|-ge|-gt|-icontains|-ieq|-ige|-igt|-ile|-ilike|-ilt|-imatch|-in|-ine|-inotcontains|-inotlike|-inotmatch|-ireplace|-is|-isnot|-isplit|-join|-le|-like|-lt|-match|-ne|-not|-notcontains|-notin|-notlike|-notmatch|-or|-regex|-replace|-shl|-shr|-split|-wildcard|-xor",")\\b")},{className:"literal",begin:/(-)[\w\d]+/,relevance:0}]},$={className:"function",begin:/\[.*\]\s*[\w]+[ ]??\(/,end:/$/,returnBegin:!0,relevance:0,contains:[{className:"keyword",begin:"(".concat(o.keyword.toString().replace(/\s/g,"|"),")\\b"),endsParent:!0,relevance:0},s.inherit(s.TITLE_MODE,{endsParent:!0})]},z=[$,w,i,s.NUMBER_MODE,u,_,x,a,{className:"literal",begin:/\$(null|true|false)\b/},{className:"selector-tag",begin:/@\B/,relevance:0}],U={begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[].concat("self",z,{begin:"("+["string","char","byte","int","long","bool","decimal","single","double","DateTime","xml","array","hashtable","void"].join("|")+")",className:"built_in",relevance:0},{className:"type",begin:/[\.\w\d]+/,relevance:0})};return $.contains.unshift(U),{name:"PowerShell",aliases:["ps","ps1"],case_insensitive:!0,keywords:o,contains:z.concat(C,j,L,B,U)}}},17285(s){function source(s){return s?"string"==typeof s?s:s.source:null}function lookahead(s){return concat("(?=",s,")")}function concat(...s){return s.map((s=>source(s))).join("")}function either(...s){return"("+s.map((s=>source(s))).join("|")+")"}s.exports=function xml(s){const o=concat(/[A-Z_]/,function optional(s){return concat("(",s,")?")}(/[A-Z0-9_.-]*:/),/[A-Z0-9_.-]*/),i={className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},a={begin:/\s/,contains:[{className:"meta-keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}]},u=s.inherit(a,{begin:/\(/,end:/\)/}),_=s.inherit(s.APOS_STRING_MODE,{className:"meta-string"}),w=s.inherit(s.QUOTE_STRING_MODE,{className:"meta-string"}),x={endsWithParent:!0,illegal:/`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin://,relevance:10,contains:[a,w,_,u,{begin:/\[/,end:/\]/,contains:[{className:"meta",begin://,contains:[a,u,w,_]}]}]},s.COMMENT(//,{relevance:10}),{begin://,relevance:10},i,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:/)/,end:/>/,keywords:{name:"style"},contains:[x],starts:{end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:/)/,end:/>/,keywords:{name:"script"},contains:[x],starts:{end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:/<>|<\/>/},{className:"tag",begin:concat(//,/>/,/\s/)))),end:/\/?>/,contains:[{className:"name",begin:o,relevance:0,starts:x}]},{className:"tag",begin:concat(/<\//,lookahead(concat(o,/>/))),contains:[{className:"name",begin:o,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]}}},17533(s){s.exports=function yaml(s){var o="true false yes no null",i="[\\w#;/?:@&=+$,.~*'()[\\]]+",a={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[s.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]}]},u=s.inherit(a,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),_={className:"number",begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"},w={end:",",endsWithParent:!0,excludeEnd:!0,keywords:o,relevance:0},x={begin:/\{/,end:/\}/,contains:[w],illegal:"\\n",relevance:0},C={begin:"\\[",end:"\\]",contains:[w],illegal:"\\n",relevance:0},j=[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---\\s*$",relevance:10},{className:"string",begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+i},{className:"type",begin:"!<"+i+">"},{className:"type",begin:"!"+i},{className:"type",begin:"!!"+i},{className:"meta",begin:"&"+s.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+s.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)",relevance:0},s.HASH_COMMENT_MODE,{beginKeywords:o,keywords:{literal:o}},_,{className:"number",begin:s.C_NUMBER_RE+"\\b",relevance:0},x,C,a],L=[...j];return L.pop(),L.push(u),w.contains=L,{name:"YAML",case_insensitive:!0,aliases:["yml"],contains:j}}},251(s,o){o.read=function(s,o,i,a,u){var _,w,x=8*u-a-1,C=(1<>1,L=-7,B=i?u-1:0,$=i?-1:1,z=s[o+B];for(B+=$,_=z&(1<<-L)-1,z>>=-L,L+=x;L>0;_=256*_+s[o+B],B+=$,L-=8);for(w=_&(1<<-L)-1,_>>=-L,L+=a;L>0;w=256*w+s[o+B],B+=$,L-=8);if(0===_)_=1-j;else{if(_===C)return w?NaN:1/0*(z?-1:1);w+=Math.pow(2,a),_-=j}return(z?-1:1)*w*Math.pow(2,_-a)},o.write=function(s,o,i,a,u,_){var w,x,C,j=8*_-u-1,L=(1<>1,$=23===u?Math.pow(2,-24)-Math.pow(2,-77):0,z=a?0:_-1,U=a?1:-1,V=o<0||0===o&&1/o<0?1:0;for(o=Math.abs(o),isNaN(o)||o===1/0?(x=isNaN(o)?1:0,w=L):(w=Math.floor(Math.log(o)/Math.LN2),o*(C=Math.pow(2,-w))<1&&(w--,C*=2),(o+=w+B>=1?$/C:$*Math.pow(2,1-B))*C>=2&&(w++,C/=2),w+B>=L?(x=0,w=L):w+B>=1?(x=(o*C-1)*Math.pow(2,u),w+=B):(x=o*Math.pow(2,B-1)*Math.pow(2,u),w=0));u>=8;s[i+z]=255&x,z+=U,x/=256,u-=8);for(w=w<0;s[i+z]=255&w,z+=U,w/=256,j-=8);s[i+z-U]|=128*V}},49568(s,o,i){"use strict";i.r(o),i.d(o,{Collection:()=>w,Iterable:()=>Zt,List:()=>wt,Map:()=>ct,OrderedMap:()=>jt,OrderedSet:()=>Vt,PairSorting:()=>Ht,Range:()=>Rt,Record:()=>Kt,Repeat:()=>Xt,Seq:()=>le,Set:()=>Ft,Stack:()=>Tt,fromJS:()=>fromJS,get:()=>get,getIn:()=>getIn$1,has:()=>has,hasIn:()=>hasIn$1,hash:()=>hash,is:()=>is,isAssociative:()=>isAssociative,isCollection:()=>isCollection,isImmutable:()=>isImmutable,isIndexed:()=>isIndexed,isKeyed:()=>isKeyed,isList:()=>isList,isMap:()=>isMap,isOrdered:()=>isOrdered,isOrderedMap:()=>isOrderedMap,isOrderedSet:()=>isOrderedSet,isPlainObject:()=>isPlainObject,isRecord:()=>isRecord,isSeq:()=>isSeq,isSet:()=>isSet,isStack:()=>isStack,isValueObject:()=>isValueObject,merge:()=>merge,mergeDeep:()=>mergeDeep$1,mergeDeepWith:()=>mergeDeepWith$1,mergeWith:()=>mergeWith,remove:()=>remove,removeIn:()=>removeIn,set:()=>set,setIn:()=>setIn$1,update:()=>update$1,updateIn:()=>updateIn,version:()=>Qt});var a="@@__IMMUTABLE_INDEXED__@@";function isIndexed(s){return Boolean(s&&s[a])}var u="@@__IMMUTABLE_KEYED__@@";function isKeyed(s){return Boolean(s&&s[u])}function isAssociative(s){return isKeyed(s)||isIndexed(s)}var _="@@__IMMUTABLE_ITERABLE__@@";function isCollection(s){return Boolean(s&&s[_])}var w=function Collection(s){return isCollection(s)?s:le(s)},x=function(s){function KeyedCollection(s){return isKeyed(s)?s:pe(s)}return s&&(KeyedCollection.__proto__=s),KeyedCollection.prototype=Object.create(s&&s.prototype),KeyedCollection.prototype.constructor=KeyedCollection,KeyedCollection}(w),C=function(s){function IndexedCollection(s){return isIndexed(s)?s:de(s)}return s&&(IndexedCollection.__proto__=s),IndexedCollection.prototype=Object.create(s&&s.prototype),IndexedCollection.prototype.constructor=IndexedCollection,IndexedCollection}(w),j=function(s){function SetCollection(s){return isCollection(s)&&!isAssociative(s)?s:fe(s)}return s&&(SetCollection.__proto__=s),SetCollection.prototype=Object.create(s&&s.prototype),SetCollection.prototype.constructor=SetCollection,SetCollection}(w);w.Keyed=x,w.Indexed=C,w.Set=j;var L="function"==typeof Symbol&&Symbol.iterator,B="@@iterator",$=L||B,z=function Iterator(s){this.next=s};function iteratorValue(s,o,i,a){var u=0===s?o:1===s?i:[o,i];return a?a.value=u:a={value:u,done:!1},a}function iteratorDone(){return{value:void 0,done:!0}}function hasIterator(s){return!!Array.isArray(s)||!!getIteratorFn(s)}function isIterator(s){return!(!s||"function"!=typeof s.next)}function getIterator(s){var o=getIteratorFn(s);return o&&o.call(s)}function getIteratorFn(s){var o=s&&(L&&s[L]||s[B]);if("function"==typeof o)return o}z.prototype.toString=function toString(){return"[Iterator]"},z.KEYS=0,z.VALUES=1,z.ENTRIES=2,z.prototype.inspect=z.prototype.toSource=function(){return this.toString()},z.prototype[$]=function(){return this};var U="delete",V=32,Y=31,Z={};function SetRef(s){s&&(s.value=!0)}function OwnerID(){}function ensureSize(s){return void 0===s.size&&(s.size=s.__iterate(returnTrue)),s.size}function wrapIndex(s,o){if("number"!=typeof o){var i=o>>>0;if(""+i!==o||4294967295===i)return NaN;o=i}return o<0?ensureSize(s)+o:o}function returnTrue(){return!0}function wholeSlice(s,o,i){return(0===s&&!isNeg(s)||void 0!==i&&s<=-i)&&(void 0===o||void 0!==i&&o>=i)}function resolveBegin(s,o){return resolveIndex(s,o,0)}function resolveEnd(s,o){return resolveIndex(s,o,o)}function resolveIndex(s,o,i){return void 0===s?i:isNeg(s)?o===1/0?o:0|Math.max(0,o+s):void 0===o||o===s?s:0|Math.min(o,s)}function isNeg(s){return s<0||0===s&&1/s==-1/0}var ee="@@__IMMUTABLE_RECORD__@@";function isRecord(s){return Boolean(s&&s[ee])}function isImmutable(s){return isCollection(s)||isRecord(s)}var ie="@@__IMMUTABLE_ORDERED__@@";function isOrdered(s){return Boolean(s&&s[ie])}var ae="@@__IMMUTABLE_SEQ__@@";function isSeq(s){return Boolean(s&&s[ae])}var ce=Object.prototype.hasOwnProperty;function isArrayLike(s){return!(!Array.isArray(s)&&"string"!=typeof s)||s&&"object"==typeof s&&Number.isInteger(s.length)&&s.length>=0&&(0===s.length?1===Object.keys(s).length:s.hasOwnProperty(s.length-1))}var le=function(s){function Seq(s){return null==s?emptySequence():isImmutable(s)?s.toSeq():function seqFromValue(s){var o=maybeIndexedSeqFromValue(s);if(o)return function isEntriesIterable(s){var o=getIteratorFn(s);return o&&o===s.entries}(s)?o.fromEntrySeq():function isKeysIterable(s){var o=getIteratorFn(s);return o&&o===s.keys}(s)?o.toSetSeq():o;if("object"==typeof s)return new be(s);throw new TypeError("Expected Array or collection object of values, or keyed object: "+s)}(s)}return s&&(Seq.__proto__=s),Seq.prototype=Object.create(s&&s.prototype),Seq.prototype.constructor=Seq,Seq.prototype.toSeq=function toSeq(){return this},Seq.prototype.toString=function toString(){return this.__toString("Seq {","}")},Seq.prototype.cacheResult=function cacheResult(){return!this._cache&&this.__iterateUncached&&(this._cache=this.entrySeq().toArray(),this.size=this._cache.length),this},Seq.prototype.__iterate=function __iterate(s,o){var i=this._cache;if(i){for(var a=i.length,u=0;u!==a;){var _=i[o?a-++u:u++];if(!1===s(_[1],_[0],this))break}return u}return this.__iterateUncached(s,o)},Seq.prototype.__iterator=function __iterator(s,o){var i=this._cache;if(i){var a=i.length,u=0;return new z((function(){if(u===a)return{value:void 0,done:!0};var _=i[o?a-++u:u++];return iteratorValue(s,_[0],_[1])}))}return this.__iteratorUncached(s,o)},Seq}(w),pe=function(s){function KeyedSeq(s){return null==s?emptySequence().toKeyedSeq():isCollection(s)?isKeyed(s)?s.toSeq():s.fromEntrySeq():isRecord(s)?s.toSeq():keyedSeqFromValue(s)}return s&&(KeyedSeq.__proto__=s),KeyedSeq.prototype=Object.create(s&&s.prototype),KeyedSeq.prototype.constructor=KeyedSeq,KeyedSeq.prototype.toKeyedSeq=function toKeyedSeq(){return this},KeyedSeq}(le),de=function(s){function IndexedSeq(s){return null==s?emptySequence():isCollection(s)?isKeyed(s)?s.entrySeq():s.toIndexedSeq():isRecord(s)?s.toSeq().entrySeq():indexedSeqFromValue(s)}return s&&(IndexedSeq.__proto__=s),IndexedSeq.prototype=Object.create(s&&s.prototype),IndexedSeq.prototype.constructor=IndexedSeq,IndexedSeq.of=function of(){return IndexedSeq(arguments)},IndexedSeq.prototype.toIndexedSeq=function toIndexedSeq(){return this},IndexedSeq.prototype.toString=function toString(){return this.__toString("Seq [","]")},IndexedSeq}(le),fe=function(s){function SetSeq(s){return(isCollection(s)&&!isAssociative(s)?s:de(s)).toSetSeq()}return s&&(SetSeq.__proto__=s),SetSeq.prototype=Object.create(s&&s.prototype),SetSeq.prototype.constructor=SetSeq,SetSeq.of=function of(){return SetSeq(arguments)},SetSeq.prototype.toSetSeq=function toSetSeq(){return this},SetSeq}(le);le.isSeq=isSeq,le.Keyed=pe,le.Set=fe,le.Indexed=de,le.prototype[ae]=!0;var ye=function(s){function ArraySeq(s){this._array=s,this.size=s.length}return s&&(ArraySeq.__proto__=s),ArraySeq.prototype=Object.create(s&&s.prototype),ArraySeq.prototype.constructor=ArraySeq,ArraySeq.prototype.get=function get(s,o){return this.has(s)?this._array[wrapIndex(this,s)]:o},ArraySeq.prototype.__iterate=function __iterate(s,o){for(var i=this._array,a=i.length,u=0;u!==a;){var _=o?a-++u:u++;if(!1===s(i[_],_,this))break}return u},ArraySeq.prototype.__iterator=function __iterator(s,o){var i=this._array,a=i.length,u=0;return new z((function(){if(u===a)return{value:void 0,done:!0};var _=o?a-++u:u++;return iteratorValue(s,_,i[_])}))},ArraySeq}(de),be=function(s){function ObjectSeq(s){var o=Object.keys(s).concat(Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(s):[]);this._object=s,this._keys=o,this.size=o.length}return s&&(ObjectSeq.__proto__=s),ObjectSeq.prototype=Object.create(s&&s.prototype),ObjectSeq.prototype.constructor=ObjectSeq,ObjectSeq.prototype.get=function get(s,o){return void 0===o||this.has(s)?this._object[s]:o},ObjectSeq.prototype.has=function has(s){return ce.call(this._object,s)},ObjectSeq.prototype.__iterate=function __iterate(s,o){for(var i=this._object,a=this._keys,u=a.length,_=0;_!==u;){var w=a[o?u-++_:_++];if(!1===s(i[w],w,this))break}return _},ObjectSeq.prototype.__iterator=function __iterator(s,o){var i=this._object,a=this._keys,u=a.length,_=0;return new z((function(){if(_===u)return{value:void 0,done:!0};var w=a[o?u-++_:_++];return iteratorValue(s,w,i[w])}))},ObjectSeq}(pe);be.prototype[ie]=!0;var Se,_e=function(s){function CollectionSeq(s){this._collection=s,this.size=s.length||s.size}return s&&(CollectionSeq.__proto__=s),CollectionSeq.prototype=Object.create(s&&s.prototype),CollectionSeq.prototype.constructor=CollectionSeq,CollectionSeq.prototype.__iterateUncached=function __iterateUncached(s,o){if(o)return this.cacheResult().__iterate(s,o);var i=getIterator(this._collection),a=0;if(isIterator(i))for(var u;!(u=i.next()).done&&!1!==s(u.value,a++,this););return a},CollectionSeq.prototype.__iteratorUncached=function __iteratorUncached(s,o){if(o)return this.cacheResult().__iterator(s,o);var i=getIterator(this._collection);if(!isIterator(i))return new z(iteratorDone);var a=0;return new z((function(){var o=i.next();return o.done?o:iteratorValue(s,a++,o.value)}))},CollectionSeq}(de);function emptySequence(){return Se||(Se=new ye([]))}function keyedSeqFromValue(s){var o=maybeIndexedSeqFromValue(s);if(o)return o.fromEntrySeq();if("object"==typeof s)return new be(s);throw new TypeError("Expected Array or collection object of [k, v] entries, or keyed object: "+s)}function indexedSeqFromValue(s){var o=maybeIndexedSeqFromValue(s);if(o)return o;throw new TypeError("Expected Array or collection object of values: "+s)}function maybeIndexedSeqFromValue(s){return isArrayLike(s)?new ye(s):hasIterator(s)?new _e(s):void 0}function asImmutable(){return this.__ensureOwner()}function asMutable(){return this.__ownerID?this:this.__ensureOwner(new OwnerID)}var we="function"==typeof Math.imul&&-2===Math.imul(4294967295,2)?Math.imul:function imul(s,o){var i=65535&(s|=0),a=65535&(o|=0);return i*a+((s>>>16)*a+i*(o>>>16)<<16>>>0)|0};function smi(s){return s>>>1&1073741824|3221225471&s}var xe=Object.prototype.valueOf;function hash(s){if(null==s)return hashNullish(s);if("function"==typeof s.hashCode)return smi(s.hashCode(s));var o=function valueOf(s){return s.valueOf!==xe&&"function"==typeof s.valueOf?s.valueOf(s):s}(s);if(null==o)return hashNullish(o);switch(typeof o){case"boolean":return o?1108378657:1108378656;case"number":return function hashNumber(s){if(s!=s||s===1/0)return 0;var o=0|s;o!==s&&(o^=4294967295*s);for(;s>4294967295;)o^=s/=4294967295;return smi(o)}(o);case"string":return o.length>Ye?function cachedHashString(s){var o=et[s];void 0===o&&(o=hashString(s),Qe===Xe&&(Qe=0,et={}),Qe++,et[s]=o);return o}(o):hashString(o);case"object":case"function":return function hashJSObj(s){var o;if($e&&void 0!==(o=qe.get(s)))return o;if(void 0!==(o=s[He]))return o;if(!Re){if(void 0!==(o=s.propertyIsEnumerable&&s.propertyIsEnumerable[He]))return o;if(void 0!==(o=function getIENodeHash(s){if(s&&s.nodeType>0)switch(s.nodeType){case 1:return s.uniqueID;case 9:return s.documentElement&&s.documentElement.uniqueID}}(s)))return o}if(o=nextHash(),$e)qe.set(s,o);else{if(void 0!==Te&&!1===Te(s))throw new Error("Non-extensible objects are not allowed as keys.");if(Re)Object.defineProperty(s,He,{enumerable:!1,configurable:!1,writable:!1,value:o});else if(void 0!==s.propertyIsEnumerable&&s.propertyIsEnumerable===s.constructor.prototype.propertyIsEnumerable)s.propertyIsEnumerable=function(){return this.constructor.prototype.propertyIsEnumerable.apply(this,arguments)},s.propertyIsEnumerable[He]=o;else{if(void 0===s.nodeType)throw new Error("Unable to set a non-enumerable property on object.");s[He]=o}}return o}(o);case"symbol":return function hashSymbol(s){var o=ze[s];if(void 0!==o)return o;return o=nextHash(),ze[s]=o,o}(o);default:if("function"==typeof o.toString)return hashString(o.toString());throw new Error("Value type "+typeof o+" cannot be hashed.")}}function hashNullish(s){return null===s?1108378658:1108378659}function hashString(s){for(var o=0,i=0;i=0&&(C.get=function(o,i){return(o=wrapIndex(this,o))>=0&&o<_?s.get(o+w,i):i}),C.__iterateUncached=function(o,i){var u=this;if(0===_)return 0;if(i)return this.cacheResult().__iterate(o,i);var x=0,C=!0,j=0;return s.__iterate((function(s,i){if(!C||!(C=x++_)return{value:void 0,done:!0};var s=u.next();return a||1===o||s.done?s:iteratorValue(o,C-1,0===o?void 0:s.value[1],s)}))},C}function skipWhileFactory(s,o,i,a){var u=makeSequence(s);return u.__iterateUncached=function(u,_){var w=this;if(_)return this.cacheResult().__iterate(u,_);var x=!0,C=0;return s.__iterate((function(s,_,j){if(!x||!(x=o.call(i,s,_,j)))return C++,u(s,a?_:C-1,w)})),C},u.__iteratorUncached=function(u,_){var w=this;if(_)return this.cacheResult().__iterator(u,_);var x=s.__iterator(2,_),C=!0,j=0;return new z((function(){var s,_,L;do{if((s=x.next()).done)return a||1===u?s:iteratorValue(u,j++,0===u?void 0:s.value[1],s);var B=s.value;_=B[0],L=B[1],C&&(C=o.call(i,L,_,w))}while(C);return 2===u?s:iteratorValue(u,_,L,s)}))},u}rt.prototype.cacheResult=tt.prototype.cacheResult=nt.prototype.cacheResult=st.prototype.cacheResult=cacheResultThrough;var ot=function(s){function ConcatSeq(s){this._wrappedIterables=s.flatMap((function(s){return s._wrappedIterables?s._wrappedIterables:[s]})),this.size=this._wrappedIterables.reduce((function(s,o){if(void 0!==s){var i=o.size;if(void 0!==i)return s+i}}),0),this[u]=this._wrappedIterables[0][u],this[a]=this._wrappedIterables[0][a],this[ie]=this._wrappedIterables[0][ie]}return s&&(ConcatSeq.__proto__=s),ConcatSeq.prototype=Object.create(s&&s.prototype),ConcatSeq.prototype.constructor=ConcatSeq,ConcatSeq.prototype.__iterateUncached=function __iterateUncached(s,o){if(0!==this._wrappedIterables.length){if(o)return this.cacheResult().__iterate(s,o);for(var i=0,a=isKeyed(this),u=a?2:1,_=this._wrappedIterables[i].__iterator(u,o),w=!0,x=0;w;){for(var C=_.next();C.done;){if(++i===this._wrappedIterables.length)return x;C=(_=this._wrappedIterables[i].__iterator(u,o)).next()}w=!1!==(a?s(C.value[1],C.value[0],this):s(C.value,x,this)),x++}return x}},ConcatSeq.prototype.__iteratorUncached=function __iteratorUncached(s,o){var i=this;if(0===this._wrappedIterables.length)return new z(iteratorDone);if(o)return this.cacheResult().__iterator(s,o);var a=0,u=this._wrappedIterables[a].__iterator(s,o);return new z((function(){for(var _=u.next();_.done;){if(++a===i._wrappedIterables.length)return _;_=(u=i._wrappedIterables[a].__iterator(s,o)).next()}return _}))},ConcatSeq}(le);function flattenFactory(s,o,i){var a=makeSequence(s);return a.__iterateUncached=function(u,_){if(_)return this.cacheResult().__iterate(u,_);var w=0,x=!1;return function flatDeep(s,C){s.__iterate((function(s,_){return(!o||C0}function zipWithFactory(s,o,i,a){var u=makeSequence(s),_=new ye(i).map((function(s){return s.size}));return u.size=a?_.max():_.min(),u.__iterate=function(s,o){for(var i,a=this.__iterator(1,o),u=0;!(i=a.next()).done&&!1!==s(i.value,u++,this););return u},u.__iteratorUncached=function(s,u){var _=i.map((function(s){return s=w(s),getIterator(u?s.reverse():s)})),x=0,C=!1;return new z((function(){var i;return C||(i=_.map((function(s){return s.next()})),C=a?i.every((function(s){return s.done})):i.some((function(s){return s.done}))),C?{value:void 0,done:!0}:iteratorValue(s,x++,o.apply(null,i.map((function(s){return s.value}))))}))},u}function reify(s,o){return s===o?s:isSeq(s)?o:s.constructor(o)}function validateEntry(s){if(s!==Object(s))throw new TypeError("Expected [K, V] tuple: "+s)}function collectionClass(s){return isKeyed(s)?x:isIndexed(s)?C:j}function makeSequence(s){return Object.create((isKeyed(s)?pe:isIndexed(s)?de:fe).prototype)}function cacheResultThrough(){return this._iter.cacheResult?(this._iter.cacheResult(),this.size=this._iter.size,this):le.prototype.cacheResult.call(this)}function defaultComparator(s,o){return void 0===s&&void 0===o?0:void 0===s?1:void 0===o?-1:s>o?1:s0;)o[i]=arguments[i+1];if("function"!=typeof s)throw new TypeError("Invalid merger function: "+s);return mergeIntoKeyedWith(this,o,s)}function mergeIntoKeyedWith(s,o,i){for(var a=[],u=0;u0;)o[i]=arguments[i+1];return mergeWithSources(s,o)}function mergeWith(s,o){for(var i=[],a=arguments.length-2;a-- >0;)i[a]=arguments[a+2];return mergeWithSources(o,i,s)}function mergeDeep$1(s){for(var o=[],i=arguments.length-1;i-- >0;)o[i]=arguments[i+1];return mergeDeepWithSources(s,o)}function mergeDeepWith$1(s,o){for(var i=[],a=arguments.length-2;a-- >0;)i[a]=arguments[a+2];return mergeDeepWithSources(o,i,s)}function mergeDeepWithSources(s,o,i){return mergeWithSources(s,o,function deepMergerWith(s){function deepMerger(o,i,a){return isDataStructure(o)&&isDataStructure(i)&&function areMergeable(s,o){var i=le(s),a=le(o);return isIndexed(i)===isIndexed(a)&&isKeyed(i)===isKeyed(a)}(o,i)?mergeWithSources(o,[i],deepMerger):s?s(o,i,a):i}return deepMerger}(i))}function mergeWithSources(s,o,i){if(!isDataStructure(s))throw new TypeError("Cannot merge into non-data-structure value: "+s);if(isImmutable(s))return"function"==typeof i&&s.mergeWith?s.mergeWith.apply(s,[i].concat(o)):s.merge?s.merge.apply(s,o):s.concat.apply(s,o);for(var a=Array.isArray(s),u=s,_=a?C:x,w=a?function(o){u===s&&(u=shallowCopy(u)),u.push(o)}:function(o,a){if(!isProtoKey(a)){var _=ce.call(u,a),w=_&&i?i(u[a],o,a):o;_&&w===u[a]||(u===s&&(u=shallowCopy(u)),u[a]=w)}},j=0;j0;)o[i]=arguments[i+1];return mergeDeepWithSources(this,o,s)}function mergeDeepIn(s){for(var o=[],i=arguments.length-1;i-- >0;)o[i]=arguments[i+1];return updateIn(this,s,emptyMap(),(function(s){return mergeDeepWithSources(s,o)}))}function mergeIn(s){for(var o=[],i=arguments.length-1;i-- >0;)o[i]=arguments[i+1];return updateIn(this,s,emptyMap(),(function(s){return mergeWithSources(s,o)}))}function setIn$1(s,o,i){return updateIn(s,o,Z,(function(){return i}))}function setIn(s,o){return setIn$1(this,s,o)}function update(s,o,i){return 1===arguments.length?s(this):update$1(this,s,o,i)}function updateIn$1(s,o,i){return updateIn(this,s,o,i)}function wasAltered(){return this.__altered}function withMutations(s){var o=this.asMutable();return s(o),o.wasAltered()?o.__ensureOwner(this.__ownerID):this}var at="@@__IMMUTABLE_MAP__@@";function isMap(s){return Boolean(s&&s[at])}function invariant(s,o){if(!s)throw new Error(o)}function assertNotInfinite(s){invariant(s!==1/0,"Cannot perform this action with an infinite size.")}var ct=function(s){function Map(o){return null==o?emptyMap():isMap(o)&&!isOrdered(o)?o:emptyMap().withMutations((function(i){var a=s(o);assertNotInfinite(a.size),a.forEach((function(s,o){return i.set(o,s)}))}))}return s&&(Map.__proto__=s),Map.prototype=Object.create(s&&s.prototype),Map.prototype.constructor=Map,Map.prototype.toString=function toString(){return this.__toString("Map {","}")},Map.prototype.get=function get(s,o){return this._root?this._root.get(0,void 0,s,o):o},Map.prototype.set=function set(s,o){return updateMap(this,s,o)},Map.prototype.remove=function remove(s){return updateMap(this,s,Z)},Map.prototype.deleteAll=function deleteAll(s){var o=w(s);return 0===o.size?this:this.withMutations((function(s){o.forEach((function(o){return s.remove(o)}))}))},Map.prototype.clear=function clear(){return 0===this.size?this:this.__ownerID?(this.size=0,this._root=null,this.__hash=void 0,this.__altered=!0,this):emptyMap()},Map.prototype.sort=function sort(s){return jt(sortFactory(this,s))},Map.prototype.sortBy=function sortBy(s,o){return jt(sortFactory(this,o,s))},Map.prototype.map=function map(s,o){var i=this;return this.withMutations((function(a){a.forEach((function(u,_){a.set(_,s.call(o,u,_,i))}))}))},Map.prototype.__iterator=function __iterator(s,o){return new yt(this,s,o)},Map.prototype.__iterate=function __iterate(s,o){var i=this,a=0;return this._root&&this._root.iterate((function(o){return a++,s(o[1],o[0],i)}),o),a},Map.prototype.__ensureOwner=function __ensureOwner(s){return s===this.__ownerID?this:s?makeMap(this.size,this._root,s,this.__hash):0===this.size?emptyMap():(this.__ownerID=s,this.__altered=!1,this)},Map}(x);ct.isMap=isMap;var lt=ct.prototype;lt[at]=!0,lt[U]=lt.remove,lt.removeAll=lt.deleteAll,lt.setIn=setIn,lt.removeIn=lt.deleteIn=deleteIn,lt.update=update,lt.updateIn=updateIn$1,lt.merge=lt.concat=merge$1,lt.mergeWith=mergeWith$1,lt.mergeDeep=mergeDeep,lt.mergeDeepWith=mergeDeepWith,lt.mergeIn=mergeIn,lt.mergeDeepIn=mergeDeepIn,lt.withMutations=withMutations,lt.wasAltered=wasAltered,lt.asImmutable=asImmutable,lt["@@transducer/init"]=lt.asMutable=asMutable,lt["@@transducer/step"]=function(s,o){return s.set(o[0],o[1])},lt["@@transducer/result"]=function(s){return s.asImmutable()};var ut=function ArrayMapNode(s,o){this.ownerID=s,this.entries=o};ut.prototype.get=function get(s,o,i,a){for(var u=this.entries,_=0,w=u.length;_=vt)return function createNodes(s,o,i,a){s||(s=new OwnerID);for(var u=new mt(s,hash(i),[i,a]),_=0;_>>s)&Y),_=this.bitmap;return _&u?this.nodes[popCount(_&u-1)].get(s+5,o,i,a):a},pt.prototype.update=function update(s,o,i,a,u,_,w){void 0===i&&(i=hash(a));var x=(0===o?i:i>>>o)&Y,C=1<=bt)return function expandNodes(s,o,i,a,u){for(var _=0,w=new Array(V),x=0;0!==i;x++,i>>>=1)w[x]=1&i?o[_++]:void 0;return w[a]=u,new ht(s,_+1,w)}(s,$,j,x,U);if(L&&!U&&2===$.length&&isLeafNode($[1^B]))return $[1^B];if(L&&U&&1===$.length&&isLeafNode(U))return U;var ee=s&&s===this.ownerID,ie=L?U?j:j^C:j|C,ae=L?U?setAt($,B,U,ee):function spliceOut(s,o,i){var a=s.length-1;if(i&&o===a)return s.pop(),s;for(var u=new Array(a),_=0,w=0;w>>s)&Y,_=this.nodes[u];return _?_.get(s+5,o,i,a):a},ht.prototype.update=function update(s,o,i,a,u,_,w){void 0===i&&(i=hash(a));var x=(0===o?i:i>>>o)&Y,C=u===Z,j=this.nodes,L=j[x];if(C&&!L)return this;var B=updateNode(L,s,o+5,i,a,u,_,w);if(B===L)return this;var $=this.count;if(L){if(!B&&--$=_t&&(a=this._buildIndex()),void 0!==a){var u=a[hashCollisionKey(s)];if(void 0!==u)for(var _=0;_>>i)&Y,x=(0===i?a:a>>>i)&Y,C=w===x?[mergeIntoNode(s,o,i+5,a,u)]:(_=new mt(o,a,u),w>1&1431655765))+(s>>2&858993459))+(s>>4)&252645135,s+=s>>8,127&(s+=s>>16)}function setAt(s,o,i,a){var u=a?s:arrCopy(s);return u[o]=i,u}var vt=8,bt=16,St=8,_t=16;function coerceKeyPath(s){if(isArrayLike(s)&&"string"!=typeof s)return s;if(isOrdered(s))return s.toArray();throw new TypeError("Invalid keyPath: expected Ordered Collection or Array: "+s)}function quoteString(s){try{return"string"==typeof s?JSON.stringify(s):String(s)}catch(o){return JSON.stringify(s)}}function has(s,o){return isImmutable(s)?s.has(o):isDataStructure(s)&&ce.call(s,o)}function get(s,o,i){return isImmutable(s)?s.get(o,i):has(s,o)?"function"==typeof s.get?s.get(o):s[o]:i}function remove(s,o){if(!isDataStructure(s))throw new TypeError("Cannot update non-data-structure value: "+s);if(isImmutable(s)){if(!s.remove)throw new TypeError("Cannot update immutable value without .remove() method: "+s);return s.remove(o)}if(!ce.call(s,o))return s;var i=shallowCopy(s);return Array.isArray(i)?i.splice(o,1):delete i[o],i}function set(s,o,i){if(isProtoKey(o))return s;if(!isDataStructure(s))throw new TypeError("Cannot update non-data-structure value: "+s);if(isImmutable(s)){if(!s.set)throw new TypeError("Cannot update immutable value without .set() method: "+s);return s.set(o,i)}if(ce.call(s,o)&&i===s[o])return s;var a=shallowCopy(s);return a[o]=i,a}function updateIn(s,o,i,a){a||(a=i,i=void 0);var u=updateInDeeply(isImmutable(s),s,coerceKeyPath(o),0,i,a);return u===Z?i:u}function updateInDeeply(s,o,i,a,u,_){var w=o===Z;if(a===i.length){var x=w?u:o,C=_(x);return C===x?o:C}if(!w&&!isDataStructure(o))throw new TypeError("Cannot update within non-data-structure value in path ["+Array.from(i).slice(0,a).map(quoteString)+"]: "+o);var j=i[a],L=w?Z:get(o,j,Z),B=updateInDeeply(L===Z?s:isImmutable(L),L,i,a+1,u,_);return B===L?o:B===Z?remove(o,j):set(w?s?emptyMap():{}:o,j,B)}function removeIn(s,o){return updateIn(s,o,(function(){return Z}))}function deleteIn(s){return removeIn(this,s)}var Et="@@__IMMUTABLE_LIST__@@";function isList(s){return Boolean(s&&s[Et])}var wt=function(s){function List(o){var i=emptyList();if(null==o)return i;if(isList(o))return o;var a=s(o),u=a.size;return 0===u?i:(assertNotInfinite(u),u>0&&u=0&&s=s.size||o<0)return s.withMutations((function(s){o<0?setListBounds(s,o).set(0,i):setListBounds(s,0,o+1).set(o,i)}));o+=s._origin;var a=s._tail,u=s._root,_={value:!1};o>=getTailOffset(s._capacity)?a=updateVNode(a,s.__ownerID,0,o,i,_):u=updateVNode(u,s.__ownerID,s._level,o,i,_);if(!_.value)return s;if(s.__ownerID)return s._root=u,s._tail=a,s.__hash=void 0,s.__altered=!0,s;return makeList(s._origin,s._capacity,s._level,u,a)}(this,s,o)},List.prototype.remove=function remove(s){return this.has(s)?0===s?this.shift():s===this.size-1?this.pop():this.splice(s,1):this},List.prototype.insert=function insert(s,o){return this.splice(s,0,o)},List.prototype.clear=function clear(){return 0===this.size?this:this.__ownerID?(this.size=this._origin=this._capacity=0,this._level=5,this._root=this._tail=this.__hash=void 0,this.__altered=!0,this):emptyList()},List.prototype.push=function push(){var s=arguments,o=this.size;return this.withMutations((function(i){setListBounds(i,0,o+s.length);for(var a=0;a>>o&Y;if(a>=this.array.length)return new kt([],s);var u,_=0===a;if(o>0){var w=this.array[a];if((u=w&&w.removeBefore(s,o-5,i))===w&&_)return this}if(_&&!u)return this;var x=editableVNode(this,s);if(!_)for(var C=0;C>>o&Y;if(u>=this.array.length)return this;if(o>0){var _=this.array[u];if((a=_&&_.removeAfter(s,o-5,i))===_&&u===this.array.length-1)return this}var w=editableVNode(this,s);return w.array.splice(u+1),a&&(w.array[u]=a),w};var Ot={};function iterateList(s,o){var i=s._origin,a=s._capacity,u=getTailOffset(a),_=s._tail;return iterateNodeOrLeaf(s._root,s._level,0);function iterateNodeOrLeaf(s,w,x){return 0===w?function iterateLeaf(s,w){var x=w===u?_&&_.array:s&&s.array,C=w>i?0:i-w,j=a-w;j>V&&(j=V);return function(){if(C===j)return Ot;var s=o?--j:C++;return x&&x[s]}}(s,x):function iterateNode(s,u,_){var w,x=s&&s.array,C=_>i?0:i-_>>u,j=1+(a-_>>u);j>V&&(j=V);return function(){for(;;){if(w){var s=w();if(s!==Ot)return s;w=null}if(C===j)return Ot;var i=o?--j:C++;w=iterateNodeOrLeaf(x&&x[i],u-5,_+(i<>>i&Y,C=s&&x0){var j=s&&s.array[x],L=updateVNode(j,o,i-5,a,u,_);return L===j?s:((w=editableVNode(s,o)).array[x]=L,w)}return C&&s.array[x]===u?s:(_&&SetRef(_),w=editableVNode(s,o),void 0===u&&x===w.array.length-1?w.array.pop():w.array[x]=u,w)}function editableVNode(s,o){return o&&s&&o===s.ownerID?s:new kt(s?s.array.slice():[],o)}function listNodeFor(s,o){if(o>=getTailOffset(s._capacity))return s._tail;if(o<1<0;)i=i.array[o>>>a&Y],a-=5;return i}}function setListBounds(s,o,i){!function validateListBoundsRequest(s,o,i){var a=s._origin+(void 0===o?0:o),u=void 0===i?s._capacity:i<0?s._capacity+i:s._origin+i;if(Number.isFinite(u)&&u>At||Number.isFinite(a)&&a<-At||Number.isFinite(u)&&Number.isFinite(a)&&u-a>At)throw new RangeError("Invalid List size: a List cannot hold more than "+At+" (2 ** 30) values.")}(s,o,i),void 0!==o&&(o|=0),void 0!==i&&(i|=0);var a=s.__ownerID||new OwnerID,u=s._origin,_=s._capacity,w=u+o,x=void 0===i?_:i<0?_+i:u+i;if(w===u&&x===_)return s;if(w>=x)return s.clear();for(var C=s._level,j=s._root,L=0;w+L<0;)j=new kt(j&&j.array.length?[void 0,j]:[],a),L+=levelCapacity(C+=5);L&&(w+=L,u+=L,x+=L,_+=L);for(var B=getTailOffset(_),$=getTailOffset(x);$>=levelCapacity(C+5);)j=new kt(j&&j.array.length?[j]:[],a),C+=5;var z=s._tail,U=$B?new kt([],a):z;if(z&&$>B&&w<_&&z.array.length){for(var V=j=editableVNode(j,a),Z=C;Z>5;Z-=5){var ee=B>>>Z&Y;V=V.array[ee]=editableVNode(V.array[ee],a)}V.array[B>>>5&Y]=z}if(x<_&&(U=U&&U.removeAfter(a,0,x)),w>=$)w-=$,x-=$,C=5,j=void 0,U=U&&U.removeBefore(a,0,w);else if(w>u||$>>C&Y;if(ie!==$>>>C&Y)break;ie&&(L+=(1<u&&(j=j.removeBefore(a,C,w-L)),j&&$>>5<<5}var At=Math.pow(2,30);function levelCapacity(s){return s<31?1<=V&&w.size>=2*_.size?(a=(u=w.filter((function(s,o){return void 0!==s&&x!==o}))).toKeyedSeq().map((function(s){return s[0]})).flip().toMap(),s.__ownerID&&(a.__ownerID=u.__ownerID=s.__ownerID)):(a=_.remove(o),u=x===w.size-1?w.pop():w.set(x,void 0))}else if(C){if(i===w.get(x)[1])return s;a=_,u=w.set(x,[o,i])}else a=_.set(o,w.size),u=w.set(w.size,[o,i]);return s.__ownerID?(s.size=a.size,s._map=a,s._list=u,s.__hash=void 0,s.__altered=!0,s):makeOrderedMap(a,u)}jt.isOrderedMap=isOrderedMap,jt.prototype[ie]=!0,jt.prototype[U]=jt.prototype.remove;var Pt="@@__IMMUTABLE_STACK__@@";function isStack(s){return Boolean(s&&s[Pt])}var Tt=function(s){function Stack(s){return null==s?emptyStack():isStack(s)?s:emptyStack().pushAll(s)}return s&&(Stack.__proto__=s),Stack.prototype=Object.create(s&&s.prototype),Stack.prototype.constructor=Stack,Stack.of=function of(){return this(arguments)},Stack.prototype.toString=function toString(){return this.__toString("Stack [","]")},Stack.prototype.get=function get(s,o){var i=this._head;for(s=wrapIndex(this,s);i&&s--;)i=i.next;return i?i.value:o},Stack.prototype.peek=function peek(){return this._head&&this._head.value},Stack.prototype.push=function push(){var s=arguments;if(0===arguments.length)return this;for(var o=this.size+arguments.length,i=this._head,a=arguments.length-1;a>=0;a--)i={value:s[a],next:i};return this.__ownerID?(this.size=o,this._head=i,this.__hash=void 0,this.__altered=!0,this):makeStack(o,i)},Stack.prototype.pushAll=function pushAll(o){if(0===(o=s(o)).size)return this;if(0===this.size&&isStack(o))return o;assertNotInfinite(o.size);var i=this.size,a=this._head;return o.__iterate((function(s){i++,a={value:s,next:a}}),!0),this.__ownerID?(this.size=i,this._head=a,this.__hash=void 0,this.__altered=!0,this):makeStack(i,a)},Stack.prototype.pop=function pop(){return this.slice(1)},Stack.prototype.clear=function clear(){return 0===this.size?this:this.__ownerID?(this.size=0,this._head=void 0,this.__hash=void 0,this.__altered=!0,this):emptyStack()},Stack.prototype.slice=function slice(o,i){if(wholeSlice(o,i,this.size))return this;var a=resolveBegin(o,this.size);if(resolveEnd(i,this.size)!==this.size)return s.prototype.slice.call(this,o,i);for(var u=this.size-a,_=this._head;a--;)_=_.next;return this.__ownerID?(this.size=u,this._head=_,this.__hash=void 0,this.__altered=!0,this):makeStack(u,_)},Stack.prototype.__ensureOwner=function __ensureOwner(s){return s===this.__ownerID?this:s?makeStack(this.size,this._head,s,this.__hash):0===this.size?emptyStack():(this.__ownerID=s,this.__altered=!1,this)},Stack.prototype.__iterate=function __iterate(s,o){var i=this;if(o)return new ye(this.toArray()).__iterate((function(o,a){return s(o,a,i)}),o);for(var a=0,u=this._head;u&&!1!==s(u.value,a++,this);)u=u.next;return a},Stack.prototype.__iterator=function __iterator(s,o){if(o)return new ye(this.toArray()).__iterator(s,o);var i=0,a=this._head;return new z((function(){if(a){var o=a.value;return a=a.next,iteratorValue(s,i++,o)}return{value:void 0,done:!0}}))},Stack}(C);Tt.isStack=isStack;var It,Nt=Tt.prototype;function makeStack(s,o,i,a){var u=Object.create(Nt);return u.size=s,u._head=o,u.__ownerID=i,u.__hash=a,u.__altered=!1,u}function emptyStack(){return It||(It=makeStack(0))}function reduce(s,o,i,a,u,_){return assertNotInfinite(s.size),s.__iterate((function(s,_,w){u?(u=!1,i=s):i=o.call(a,i,s,_,w)}),_),i}function keyMapper(s,o){return o}function entryMapper(s,o){return[o,s]}function not(s){return function(){for(var o=[],i=arguments.length;i--;)o[i]=arguments[i];return!s.apply(this,o)}}function neg(s){return function(){for(var o=[],i=arguments.length;i--;)o[i]=arguments[i];return-s.apply(this,o)}}function defaultNegComparator(s,o){return so?-1:0}function deepEqual(s,o){if(s===o)return!0;if(!isCollection(o)||void 0!==s.size&&void 0!==o.size&&s.size!==o.size||void 0!==s.__hash&&void 0!==o.__hash&&s.__hash!==o.__hash||isKeyed(s)!==isKeyed(o)||isIndexed(s)!==isIndexed(o)||isOrdered(s)!==isOrdered(o))return!1;if(0===s.size&&0===o.size)return!0;var i=!isAssociative(s);if(isOrdered(s)){var a=s.entries();return o.every((function(s,o){var u=a.next().value;return u&&is(u[1],s)&&(i||is(u[0],o))}))&&a.next().done}var u=!1;if(void 0===s.size)if(void 0===o.size)"function"==typeof s.cacheResult&&s.cacheResult();else{u=!0;var _=s;s=o,o=_}var w=!0,x=o.__iterate((function(o,a){if(i?!s.has(o):u?!is(o,s.get(a,Z)):!is(s.get(a,Z),o))return w=!1,!1}));return w&&s.size===x}Nt[Pt]=!0,Nt.shift=Nt.pop,Nt.unshift=Nt.push,Nt.unshiftAll=Nt.pushAll,Nt.withMutations=withMutations,Nt.wasAltered=wasAltered,Nt.asImmutable=asImmutable,Nt["@@transducer/init"]=Nt.asMutable=asMutable,Nt["@@transducer/step"]=function(s,o){return s.unshift(o)},Nt["@@transducer/result"]=function(s){return s.asImmutable()};var Mt,Rt=function(s){function Range(s,o,i){if(void 0===i&&(i=1),!(this instanceof Range))return new Range(s,o,i);if(invariant(0!==i,"Cannot step a Range by 0"),invariant(void 0!==s,"You must define a start value when using Range"),invariant(void 0!==o,"You must define an end value when using Range"),i=Math.abs(i),o=0&&o=0&&i>>-15,461845907),o=we(o<<13|o>>>-13,5),o=o+3864292196^s,o=we(o^o>>>16,2246822507),o=we(o^o>>>13,3266489909),o=smi(o^o>>>16),o}(s.size,a)}function hashMerge(s,o){return s^o+2654435769+(s<<6)+(s>>2)}function mixin(s,o){var keyCopier=function(i){s.prototype[i]=o[i]};return Object.keys(o).forEach(keyCopier),Object.getOwnPropertySymbols&&Object.getOwnPropertySymbols(o).forEach(keyCopier),s}Bt[Dt]=!0,Bt[U]=Bt.remove,Bt.merge=Bt.concat=Bt.union,Bt.withMutations=withMutations,Bt.asImmutable=asImmutable,Bt["@@transducer/init"]=Bt.asMutable=asMutable,Bt["@@transducer/step"]=function(s,o){return s.add(o)},Bt["@@transducer/result"]=function(s){return s.asImmutable()},Bt.__empty=emptySet,Bt.__make=makeSet,w.Iterator=z,mixin(w,{toArray:function toArray(){assertNotInfinite(this.size);var s=new Array(this.size||0),o=isKeyed(this),i=0;return this.__iterate((function(a,u){s[i++]=o?[u,a]:a})),s},toIndexedSeq:function toIndexedSeq(){return new rt(this)},toJS:function toJS$1(){return toJS(this)},toKeyedSeq:function toKeyedSeq(){return new tt(this,!0)},toMap:function toMap(){return ct(this.toKeyedSeq())},toObject,toOrderedMap:function toOrderedMap(){return jt(this.toKeyedSeq())},toOrderedSet:function toOrderedSet(){return Vt(isKeyed(this)?this.valueSeq():this)},toSet:function toSet(){return Ft(isKeyed(this)?this.valueSeq():this)},toSetSeq:function toSetSeq(){return new nt(this)},toSeq:function toSeq(){return isIndexed(this)?this.toIndexedSeq():isKeyed(this)?this.toKeyedSeq():this.toSetSeq()},toStack:function toStack(){return Tt(isKeyed(this)?this.valueSeq():this)},toList:function toList(){return wt(isKeyed(this)?this.valueSeq():this)},toString:function toString(){return"[Collection]"},__toString:function __toString(s,o){return 0===this.size?s+o:s+" "+this.toSeq().map(this.__toStringMapper).join(", ")+" "+o},concat:function concat(){for(var s=[],o=arguments.length;o--;)s[o]=arguments[o];return reify(this,function concatFactory(s,o){var i=isKeyed(s),a=[s].concat(o).map((function(s){return isCollection(s)?i&&(s=x(s)):s=i?keyedSeqFromValue(s):indexedSeqFromValue(Array.isArray(s)?s:[s]),s})).filter((function(s){return 0!==s.size}));if(0===a.length)return s;if(1===a.length){var u=a[0];if(u===s||i&&isKeyed(u)||isIndexed(s)&&isIndexed(u))return u}return new ot(a)}(this,s))},includes:function includes(s){return this.some((function(o){return is(o,s)}))},entries:function entries(){return this.__iterator(2)},every:function every(s,o){assertNotInfinite(this.size);var i=!0;return this.__iterate((function(a,u,_){if(!s.call(o,a,u,_))return i=!1,!1})),i},filter:function filter(s,o){return reify(this,filterFactory(this,s,o,!0))},partition:function partition(s,o){return function partitionFactory(s,o,i){var a=isKeyed(s),u=[[],[]];s.__iterate((function(_,w){u[o.call(i,_,w,s)?1:0].push(a?[w,_]:_)}));var _=collectionClass(s);return u.map((function(o){return reify(s,_(o))}))}(this,s,o)},find:function find(s,o,i){var a=this.findEntry(s,o);return a?a[1]:i},forEach:function forEach(s,o){return assertNotInfinite(this.size),this.__iterate(o?s.bind(o):s)},join:function join(s){assertNotInfinite(this.size),s=void 0!==s?""+s:",";var o="",i=!0;return this.__iterate((function(a){i?i=!1:o+=s,o+=null!=a?a.toString():""})),o},keys:function keys(){return this.__iterator(0)},map:function map(s,o){return reify(this,mapFactory(this,s,o))},reduce:function reduce$1(s,o,i){return reduce(this,s,o,i,arguments.length<2,!1)},reduceRight:function reduceRight(s,o,i){return reduce(this,s,o,i,arguments.length<2,!0)},reverse:function reverse(){return reify(this,reverseFactory(this,!0))},slice:function slice(s,o){return reify(this,sliceFactory(this,s,o,!0))},some:function some(s,o){assertNotInfinite(this.size);var i=!1;return this.__iterate((function(a,u,_){if(s.call(o,a,u,_))return i=!0,!1})),i},sort:function sort(s){return reify(this,sortFactory(this,s))},values:function values(){return this.__iterator(1)},butLast:function butLast(){return this.slice(0,-1)},isEmpty:function isEmpty(){return void 0!==this.size?0===this.size:!this.some((function(){return!0}))},count:function count(s,o){return ensureSize(s?this.toSeq().filter(s,o):this)},countBy:function countBy(s,o){return function countByFactory(s,o,i){var a=ct().asMutable();return s.__iterate((function(u,_){a.update(o.call(i,u,_,s),0,(function(s){return s+1}))})),a.asImmutable()}(this,s,o)},equals:function equals(s){return deepEqual(this,s)},entrySeq:function entrySeq(){var s=this;if(s._cache)return new ye(s._cache);var o=s.toSeq().map(entryMapper).toIndexedSeq();return o.fromEntrySeq=function(){return s.toSeq()},o},filterNot:function filterNot(s,o){return this.filter(not(s),o)},findEntry:function findEntry(s,o,i){var a=i;return this.__iterate((function(i,u,_){if(s.call(o,i,u,_))return a=[u,i],!1})),a},findKey:function findKey(s,o){var i=this.findEntry(s,o);return i&&i[0]},findLast:function findLast(s,o,i){return this.toKeyedSeq().reverse().find(s,o,i)},findLastEntry:function findLastEntry(s,o,i){return this.toKeyedSeq().reverse().findEntry(s,o,i)},findLastKey:function findLastKey(s,o){return this.toKeyedSeq().reverse().findKey(s,o)},first:function first(s){return this.find(returnTrue,null,s)},flatMap:function flatMap(s,o){return reify(this,function flatMapFactory(s,o,i){var a=collectionClass(s);return s.toSeq().map((function(u,_){return a(o.call(i,u,_,s))})).flatten(!0)}(this,s,o))},flatten:function flatten(s){return reify(this,flattenFactory(this,s,!0))},fromEntrySeq:function fromEntrySeq(){return new st(this)},get:function get(s,o){return this.find((function(o,i){return is(i,s)}),void 0,o)},getIn,groupBy:function groupBy(s,o){return function groupByFactory(s,o,i){var a=isKeyed(s),u=(isOrdered(s)?jt():ct()).asMutable();s.__iterate((function(_,w){u.update(o.call(i,_,w,s),(function(s){return(s=s||[]).push(a?[w,_]:_),s}))}));var _=collectionClass(s);return u.map((function(o){return reify(s,_(o))})).asImmutable()}(this,s,o)},has:function has(s){return this.get(s,Z)!==Z},hasIn:function hasIn(s){return hasIn$1(this,s)},isSubset:function isSubset(s){return s="function"==typeof s.includes?s:w(s),this.every((function(o){return s.includes(o)}))},isSuperset:function isSuperset(s){return(s="function"==typeof s.isSubset?s:w(s)).isSubset(this)},keyOf:function keyOf(s){return this.findKey((function(o){return is(o,s)}))},keySeq:function keySeq(){return this.toSeq().map(keyMapper).toIndexedSeq()},last:function last(s){return this.toSeq().reverse().first(s)},lastKeyOf:function lastKeyOf(s){return this.toKeyedSeq().reverse().keyOf(s)},max:function max(s){return maxFactory(this,s)},maxBy:function maxBy(s,o){return maxFactory(this,o,s)},min:function min(s){return maxFactory(this,s?neg(s):defaultNegComparator)},minBy:function minBy(s,o){return maxFactory(this,o?neg(o):defaultNegComparator,s)},rest:function rest(){return this.slice(1)},skip:function skip(s){return 0===s?this:this.slice(Math.max(0,s))},skipLast:function skipLast(s){return 0===s?this:this.slice(0,-Math.max(0,s))},skipWhile:function skipWhile(s,o){return reify(this,skipWhileFactory(this,s,o,!0))},skipUntil:function skipUntil(s,o){return this.skipWhile(not(s),o)},sortBy:function sortBy(s,o){return reify(this,sortFactory(this,o,s))},take:function take(s){return this.slice(0,Math.max(0,s))},takeLast:function takeLast(s){return this.slice(-Math.max(0,s))},takeWhile:function takeWhile(s,o){return reify(this,function takeWhileFactory(s,o,i){var a=makeSequence(s);return a.__iterateUncached=function(a,u){var _=this;if(u)return this.cacheResult().__iterate(a,u);var w=0;return s.__iterate((function(s,u,x){return o.call(i,s,u,x)&&++w&&a(s,u,_)})),w},a.__iteratorUncached=function(a,u){var _=this;if(u)return this.cacheResult().__iterator(a,u);var w=s.__iterator(2,u),x=!0;return new z((function(){if(!x)return{value:void 0,done:!0};var s=w.next();if(s.done)return s;var u=s.value,C=u[0],j=u[1];return o.call(i,j,C,_)?2===a?s:iteratorValue(a,C,j,s):(x=!1,{value:void 0,done:!0})}))},a}(this,s,o))},takeUntil:function takeUntil(s,o){return this.takeWhile(not(s),o)},update:function update(s){return s(this)},valueSeq:function valueSeq(){return this.toIndexedSeq()},hashCode:function hashCode(){return this.__hash||(this.__hash=hashCollection(this))}});var qt=w.prototype;qt[_]=!0,qt[$]=qt.values,qt.toJSON=qt.toArray,qt.__toStringMapper=quoteString,qt.inspect=qt.toSource=function(){return this.toString()},qt.chain=qt.flatMap,qt.contains=qt.includes,mixin(x,{flip:function flip(){return reify(this,flipFactory(this))},mapEntries:function mapEntries(s,o){var i=this,a=0;return reify(this,this.toSeq().map((function(u,_){return s.call(o,[_,u],a++,i)})).fromEntrySeq())},mapKeys:function mapKeys(s,o){var i=this;return reify(this,this.toSeq().flip().map((function(a,u){return s.call(o,a,u,i)})).flip())}});var $t=x.prototype;$t[u]=!0,$t[$]=qt.entries,$t.toJSON=toObject,$t.__toStringMapper=function(s,o){return quoteString(o)+": "+quoteString(s)},mixin(C,{toKeyedSeq:function toKeyedSeq(){return new tt(this,!1)},filter:function filter(s,o){return reify(this,filterFactory(this,s,o,!1))},findIndex:function findIndex(s,o){var i=this.findEntry(s,o);return i?i[0]:-1},indexOf:function indexOf(s){var o=this.keyOf(s);return void 0===o?-1:o},lastIndexOf:function lastIndexOf(s){var o=this.lastKeyOf(s);return void 0===o?-1:o},reverse:function reverse(){return reify(this,reverseFactory(this,!1))},slice:function slice(s,o){return reify(this,sliceFactory(this,s,o,!1))},splice:function splice(s,o){var i=arguments.length;if(o=Math.max(o||0,0),0===i||2===i&&!o)return this;s=resolveBegin(s,s<0?this.count():this.size);var a=this.slice(0,s);return reify(this,1===i?a:a.concat(arrCopy(arguments,2),this.slice(s+o)))},findLastIndex:function findLastIndex(s,o){var i=this.findLastEntry(s,o);return i?i[0]:-1},first:function first(s){return this.get(0,s)},flatten:function flatten(s){return reify(this,flattenFactory(this,s,!1))},get:function get(s,o){return(s=wrapIndex(this,s))<0||this.size===1/0||void 0!==this.size&&s>this.size?o:this.find((function(o,i){return i===s}),void 0,o)},has:function has(s){return(s=wrapIndex(this,s))>=0&&(void 0!==this.size?this.size===1/0||s2?[]:void 0,{"":s})}function fromJSWith(s,o,i,a,u,_){if("string"!=typeof i&&!isImmutable(i)&&(isArrayLike(i)||hasIterator(i)||isPlainObject(i))){if(~s.indexOf(i))throw new TypeError("Cannot convert circular structure to Immutable");s.push(i),u&&""!==a&&u.push(a);var w=o.call(_,a,le(i).map((function(a,_){return fromJSWith(s,o,a,_,u,i)})),u&&u.slice());return s.pop(),u&&u.pop(),w}return i}function defaultConverter(s,o){return isIndexed(o)?o.toList():isKeyed(o)?o.toMap():o.toSet()}var Qt="5.1.9",Zt=w},56698(s){"function"==typeof Object.create?s.exports=function inherits(s,o){o&&(s.super_=o,s.prototype=Object.create(o.prototype,{constructor:{value:s,enumerable:!1,writable:!0,configurable:!0}}))}:s.exports=function inherits(s,o){if(o){s.super_=o;var TempCtor=function(){};TempCtor.prototype=o.prototype,s.prototype=new TempCtor,s.prototype.constructor=s}}},69600(s){"use strict";var o,i,a=Function.prototype.toString,u="object"==typeof Reflect&&null!==Reflect&&Reflect.apply;if("function"==typeof u&&"function"==typeof Object.defineProperty)try{o=Object.defineProperty({},"length",{get:function(){throw i}}),i={},u((function(){throw 42}),null,o)}catch(s){s!==i&&(u=null)}else u=null;var _=/^\s*class\b/,w=function isES6ClassFunction(s){try{var o=a.call(s);return _.test(o)}catch(s){return!1}},x=function tryFunctionToStr(s){try{return!w(s)&&(a.call(s),!0)}catch(s){return!1}},C=Object.prototype.toString,j="function"==typeof Symbol&&!!Symbol.toStringTag,L=!(0 in[,]),B=function isDocumentDotAll(){return!1};if("object"==typeof document){var $=document.all;C.call($)===C.call(document.all)&&(B=function isDocumentDotAll(s){if((L||!s)&&(void 0===s||"object"==typeof s))try{var o=C.call(s);return("[object HTMLAllCollection]"===o||"[object HTML document.all class]"===o||"[object HTMLCollection]"===o||"[object Object]"===o)&&null==s("")}catch(s){}return!1})}s.exports=u?function isCallable(s){if(B(s))return!0;if(!s)return!1;if("function"!=typeof s&&"object"!=typeof s)return!1;try{u(s,null,o)}catch(s){if(s!==i)return!1}return!w(s)&&x(s)}:function isCallable(s){if(B(s))return!0;if(!s)return!1;if("function"!=typeof s&&"object"!=typeof s)return!1;if(j)return x(s);if(w(s))return!1;var o=C.call(s);return!("[object Function]"!==o&&"[object GeneratorFunction]"!==o&&!/^\[object HTML/.test(o))&&x(s)}},35680(s,o,i){"use strict";var a=i(25767);s.exports=function isTypedArray(s){return!!a(s)}},64634(s){var o={}.toString;s.exports=Array.isArray||function(s){return"[object Array]"==o.call(s)}},5419(s){s.exports=function(s,o,i,a){var u=new Blob(void 0!==a?[a,s]:[s],{type:i||"application/octet-stream"});if(void 0!==window.navigator.msSaveBlob)window.navigator.msSaveBlob(u,o);else{var _=window.URL&&window.URL.createObjectURL?window.URL.createObjectURL(u):window.webkitURL.createObjectURL(u),w=document.createElement("a");w.style.display="none",w.href=_,w.setAttribute("download",o),void 0===w.download&&w.setAttribute("target","_blank"),document.body.appendChild(w),w.click(),setTimeout((function(){document.body.removeChild(w),window.URL.revokeObjectURL(_)}),200)}}},20181(s,o,i){var a=/^\s+|\s+$/g,u=/^[-+]0x[0-9a-f]+$/i,_=/^0b[01]+$/i,w=/^0o[0-7]+$/i,x=parseInt,C="object"==typeof i.g&&i.g&&i.g.Object===Object&&i.g,j="object"==typeof self&&self&&self.Object===Object&&self,L=C||j||Function("return this")(),B=Object.prototype.toString,$=Math.max,z=Math.min,now=function(){return L.Date.now()};function isObject(s){var o=typeof s;return!!s&&("object"==o||"function"==o)}function toNumber(s){if("number"==typeof s)return s;if(function isSymbol(s){return"symbol"==typeof s||function isObjectLike(s){return!!s&&"object"==typeof s}(s)&&"[object Symbol]"==B.call(s)}(s))return NaN;if(isObject(s)){var o="function"==typeof s.valueOf?s.valueOf():s;s=isObject(o)?o+"":o}if("string"!=typeof s)return 0===s?s:+s;s=s.replace(a,"");var i=_.test(s);return i||w.test(s)?x(s.slice(2),i?2:8):u.test(s)?NaN:+s}s.exports=function debounce(s,o,i){var a,u,_,w,x,C,j=0,L=!1,B=!1,U=!0;if("function"!=typeof s)throw new TypeError("Expected a function");function invokeFunc(o){var i=a,_=u;return a=u=void 0,j=o,w=s.apply(_,i)}function shouldInvoke(s){var i=s-C;return void 0===C||i>=o||i<0||B&&s-j>=_}function timerExpired(){var s=now();if(shouldInvoke(s))return trailingEdge(s);x=setTimeout(timerExpired,function remainingWait(s){var i=o-(s-C);return B?z(i,_-(s-j)):i}(s))}function trailingEdge(s){return x=void 0,U&&a?invokeFunc(s):(a=u=void 0,w)}function debounced(){var s=now(),i=shouldInvoke(s);if(a=arguments,u=this,C=s,i){if(void 0===x)return function leadingEdge(s){return j=s,x=setTimeout(timerExpired,o),L?invokeFunc(s):w}(C);if(B)return x=setTimeout(timerExpired,o),invokeFunc(C)}return void 0===x&&(x=setTimeout(timerExpired,o)),w}return o=toNumber(o)||0,isObject(i)&&(L=!!i.leading,_=(B="maxWait"in i)?$(toNumber(i.maxWait)||0,o):_,U="trailing"in i?!!i.trailing:U),debounced.cancel=function cancel(){void 0!==x&&clearTimeout(x),j=0,a=C=u=x=void 0},debounced.flush=function flush(){return void 0===x?w:trailingEdge(now())},debounced}},55580(s,o,i){var a=i(56110)(i(9325),"DataView");s.exports=a},21549(s,o,i){var a=i(22032),u=i(63862),_=i(66721),w=i(12749),x=i(35749);function Hash(s){var o=-1,i=null==s?0:s.length;for(this.clear();++o-1}},70695(s,o,i){var a=i(78096),u=i(72428),_=i(56449),w=i(3656),x=i(30361),C=i(37167),j=Object.prototype.hasOwnProperty;s.exports=function arrayLikeKeys(s,o){var i=_(s),L=!i&&u(s),B=!i&&!L&&w(s),$=!i&&!L&&!B&&C(s),z=i||L||B||$,U=z?a(s.length,String):[],V=U.length;for(var Y in s)!o&&!j.call(s,Y)||z&&("length"==Y||B&&("offset"==Y||"parent"==Y)||$&&("buffer"==Y||"byteLength"==Y||"byteOffset"==Y)||x(Y,V))||U.push(Y);return U}},34932(s){s.exports=function arrayMap(s,o){for(var i=-1,a=null==s?0:s.length,u=Array(a);++i0&&i(j)?o>1?baseFlatten(j,o-1,i,_,w):a(w,j):_||(w[w.length]=j)}return w}},86649(s,o,i){var a=i(83221)();s.exports=a},30641(s,o,i){var a=i(86649),u=i(95950);s.exports=function baseForOwn(s,o){return s&&a(s,o,u)}},47422(s,o,i){var a=i(31769),u=i(77797);s.exports=function baseGet(s,o){for(var i=0,_=(o=a(o,s)).length;null!=s&&i<_;)s=s[u(o[i++])];return i&&i==_?s:void 0}},82199(s,o,i){var a=i(14528),u=i(56449);s.exports=function baseGetAllKeys(s,o,i){var _=o(s);return u(s)?_:a(_,i(s))}},72552(s,o,i){var a=i(51873),u=i(659),_=i(59350),w=a?a.toStringTag:void 0;s.exports=function baseGetTag(s){return null==s?void 0===s?"[object Undefined]":"[object Null]":w&&w in Object(s)?u(s):_(s)}},20426(s){var o=Object.prototype.hasOwnProperty;s.exports=function baseHas(s,i){return null!=s&&o.call(s,i)}},28077(s){s.exports=function baseHasIn(s,o){return null!=s&&o in Object(s)}},96131(s,o,i){var a=i(2523),u=i(85463),_=i(76959);s.exports=function baseIndexOf(s,o,i){return o==o?_(s,o,i):a(s,u,i)}},27534(s,o,i){var a=i(72552),u=i(40346);s.exports=function baseIsArguments(s){return u(s)&&"[object Arguments]"==a(s)}},60270(s,o,i){var a=i(87068),u=i(40346);s.exports=function baseIsEqual(s,o,i,_,w){return s===o||(null==s||null==o||!u(s)&&!u(o)?s!=s&&o!=o:a(s,o,i,_,baseIsEqual,w))}},87068(s,o,i){var a=i(37217),u=i(25911),_=i(21986),w=i(50689),x=i(5861),C=i(56449),j=i(3656),L=i(37167),B="[object Arguments]",$="[object Array]",z="[object Object]",U=Object.prototype.hasOwnProperty;s.exports=function baseIsEqualDeep(s,o,i,V,Y,Z){var ee=C(s),ie=C(o),ae=ee?$:x(s),ce=ie?$:x(o),le=(ae=ae==B?z:ae)==z,pe=(ce=ce==B?z:ce)==z,de=ae==ce;if(de&&j(s)){if(!j(o))return!1;ee=!0,le=!1}if(de&&!le)return Z||(Z=new a),ee||L(s)?u(s,o,i,V,Y,Z):_(s,o,ae,i,V,Y,Z);if(!(1&i)){var fe=le&&U.call(s,"__wrapped__"),ye=pe&&U.call(o,"__wrapped__");if(fe||ye){var be=fe?s.value():s,Se=ye?o.value():o;return Z||(Z=new a),Y(be,Se,i,V,Z)}}return!!de&&(Z||(Z=new a),w(s,o,i,V,Y,Z))}},29172(s,o,i){var a=i(5861),u=i(40346);s.exports=function baseIsMap(s){return u(s)&&"[object Map]"==a(s)}},41799(s,o,i){var a=i(37217),u=i(60270);s.exports=function baseIsMatch(s,o,i,_){var w=i.length,x=w,C=!_;if(null==s)return!x;for(s=Object(s);w--;){var j=i[w];if(C&&j[2]?j[1]!==s[j[0]]:!(j[0]in s))return!1}for(;++wu?0:u+o),(i=i>u?u:i)<0&&(i+=u),u=o>i?0:i-o>>>0,o>>>=0;for(var _=Array(u);++a=u?s:a(s,o,i)}},49653(s,o,i){var a=i(37828);s.exports=function cloneArrayBuffer(s){var o=new s.constructor(s.byteLength);return new a(o).set(new a(s)),o}},93290(s,o,i){s=i.nmd(s);var a=i(9325),u=o&&!o.nodeType&&o,_=u&&s&&!s.nodeType&&s,w=_&&_.exports===u?a.Buffer:void 0,x=w?w.allocUnsafe:void 0;s.exports=function cloneBuffer(s,o){if(o)return s.slice();var i=s.length,a=x?x(i):new s.constructor(i);return s.copy(a),a}},76169(s,o,i){var a=i(49653);s.exports=function cloneDataView(s,o){var i=o?a(s.buffer):s.buffer;return new s.constructor(i,s.byteOffset,s.byteLength)}},73201(s){var o=/\w*$/;s.exports=function cloneRegExp(s){var i=new s.constructor(s.source,o.exec(s));return i.lastIndex=s.lastIndex,i}},93736(s,o,i){var a=i(51873),u=a?a.prototype:void 0,_=u?u.valueOf:void 0;s.exports=function cloneSymbol(s){return _?Object(_.call(s)):{}}},71961(s,o,i){var a=i(49653);s.exports=function cloneTypedArray(s,o){var i=o?a(s.buffer):s.buffer;return new s.constructor(i,s.byteOffset,s.length)}},91596(s){var o=Math.max;s.exports=function composeArgs(s,i,a,u){for(var _=-1,w=s.length,x=a.length,C=-1,j=i.length,L=o(w-x,0),B=Array(j+L),$=!u;++C1?i[_-1]:void 0,x=_>2?i[2]:void 0;for(w=s.length>3&&"function"==typeof w?(_--,w):void 0,x&&u(i[0],i[1],x)&&(w=_<3?void 0:w,_=1),o=Object(o);++a<_;){var C=i[a];C&&s(o,C,a,w)}return o}))}},38329(s,o,i){var a=i(64894);s.exports=function createBaseEach(s,o){return function(i,u){if(null==i)return i;if(!a(i))return s(i,u);for(var _=i.length,w=o?_:-1,x=Object(i);(o?w--:++w<_)&&!1!==u(x[w],w,x););return i}}},83221(s){s.exports=function createBaseFor(s){return function(o,i,a){for(var u=-1,_=Object(o),w=a(o),x=w.length;x--;){var C=w[s?x:++u];if(!1===i(_[C],C,_))break}return o}}},11842(s,o,i){var a=i(82819),u=i(9325);s.exports=function createBind(s,o,i){var _=1&o,w=a(s);return function wrapper(){return(this&&this!==u&&this instanceof wrapper?w:s).apply(_?i:this,arguments)}}},12507(s,o,i){var a=i(28754),u=i(49698),_=i(63912),w=i(13222);s.exports=function createCaseFirst(s){return function(o){o=w(o);var i=u(o)?_(o):void 0,x=i?i[0]:o.charAt(0),C=i?a(i,1).join(""):o.slice(1);return x[s]()+C}}},45539(s,o,i){var a=i(40882),u=i(50828),_=i(66645),w=RegExp("['’]","g");s.exports=function createCompounder(s){return function(o){return a(_(u(o).replace(w,"")),s,"")}}},82819(s,o,i){var a=i(39344),u=i(23805);s.exports=function createCtor(s){return function(){var o=arguments;switch(o.length){case 0:return new s;case 1:return new s(o[0]);case 2:return new s(o[0],o[1]);case 3:return new s(o[0],o[1],o[2]);case 4:return new s(o[0],o[1],o[2],o[3]);case 5:return new s(o[0],o[1],o[2],o[3],o[4]);case 6:return new s(o[0],o[1],o[2],o[3],o[4],o[5]);case 7:return new s(o[0],o[1],o[2],o[3],o[4],o[5],o[6])}var i=a(s.prototype),_=s.apply(i,o);return u(_)?_:i}}},77078(s,o,i){var a=i(91033),u=i(82819),_=i(37471),w=i(18073),x=i(11287),C=i(36306),j=i(9325);s.exports=function createCurry(s,o,i){var L=u(s);return function wrapper(){for(var u=arguments.length,B=Array(u),$=u,z=x(wrapper);$--;)B[$]=arguments[$];var U=u<3&&B[0]!==z&&B[u-1]!==z?[]:C(B,z);return(u-=U.length)-1?x[C?o[j]:j]:void 0}}},37471(s,o,i){var a=i(91596),u=i(53320),_=i(58523),w=i(82819),x=i(18073),C=i(11287),j=i(68294),L=i(36306),B=i(9325);s.exports=function createHybrid(s,o,i,$,z,U,V,Y,Z,ee){var ie=128&o,ae=1&o,ce=2&o,le=24&o,pe=512&o,de=ce?void 0:w(s);return function wrapper(){for(var fe=arguments.length,ye=Array(fe),be=fe;be--;)ye[be]=arguments[be];if(le)var Se=C(wrapper),_e=_(ye,Se);if($&&(ye=a(ye,$,z,le)),U&&(ye=u(ye,U,V,le)),fe-=_e,le&&fe1&&ye.reverse(),ie&&ZL))return!1;var $=C.get(s),z=C.get(o);if($&&z)return $==o&&z==s;var U=-1,V=!0,Y=2&i?new a:void 0;for(C.set(s,o),C.set(o,s);++U1?"& ":"")+i[u],i=i.join(a>2?", ":" "),s.replace(o,"{\n/* [wrapped with "+i+"] */\n")}},45891(s,o,i){var a=i(51873),u=i(72428),_=i(56449),w=a?a.isConcatSpreadable:void 0;s.exports=function isFlattenable(s){return _(s)||u(s)||!!(w&&s&&s[w])}},30361(s){var o=/^(?:0|[1-9]\d*)$/;s.exports=function isIndex(s,i){var a=typeof s;return!!(i=null==i?9007199254740991:i)&&("number"==a||"symbol"!=a&&o.test(s))&&s>-1&&s%1==0&&s-1}},31175(s,o,i){var a=i(26025);s.exports=function listCacheSet(s,o){var i=this.__data__,u=a(i,s);return u<0?(++this.size,i.push([s,o])):i[u][1]=o,this}},63040(s,o,i){var a=i(21549),u=i(80079),_=i(68223);s.exports=function mapCacheClear(){this.size=0,this.__data__={hash:new a,map:new(_||u),string:new a}}},17670(s,o,i){var a=i(12651);s.exports=function mapCacheDelete(s){var o=a(this,s).delete(s);return this.size-=o?1:0,o}},90289(s,o,i){var a=i(12651);s.exports=function mapCacheGet(s){return a(this,s).get(s)}},4509(s,o,i){var a=i(12651);s.exports=function mapCacheHas(s){return a(this,s).has(s)}},72949(s,o,i){var a=i(12651);s.exports=function mapCacheSet(s,o){var i=a(this,s),u=i.size;return i.set(s,o),this.size+=i.size==u?0:1,this}},20317(s){s.exports=function mapToArray(s){var o=-1,i=Array(s.size);return s.forEach((function(s,a){i[++o]=[a,s]})),i}},67197(s){s.exports=function matchesStrictComparable(s,o){return function(i){return null!=i&&(i[s]===o&&(void 0!==o||s in Object(i)))}}},62224(s,o,i){var a=i(50104);s.exports=function memoizeCapped(s){var o=a(s,(function(s){return 500===i.size&&i.clear(),s})),i=o.cache;return o}},3209(s,o,i){var a=i(91596),u=i(53320),_=i(36306),w="__lodash_placeholder__",x=128,C=Math.min;s.exports=function mergeData(s,o){var i=s[1],j=o[1],L=i|j,B=L<131,$=j==x&&8==i||j==x&&256==i&&s[7].length<=o[8]||384==j&&o[7].length<=o[8]&&8==i;if(!B&&!$)return s;1&j&&(s[2]=o[2],L|=1&i?0:4);var z=o[3];if(z){var U=s[3];s[3]=U?a(U,z,o[4]):z,s[4]=U?_(s[3],w):o[4]}return(z=o[5])&&(U=s[5],s[5]=U?u(U,z,o[6]):z,s[6]=U?_(s[5],w):o[6]),(z=o[7])&&(s[7]=z),j&x&&(s[8]=null==s[8]?o[8]:C(s[8],o[8])),null==s[9]&&(s[9]=o[9]),s[0]=o[0],s[1]=L,s}},48152(s,o,i){var a=i(28303),u=a&&new a;s.exports=u},81042(s,o,i){var a=i(56110)(Object,"create");s.exports=a},3650(s,o,i){var a=i(74335)(Object.keys,Object);s.exports=a},90181(s){s.exports=function nativeKeysIn(s){var o=[];if(null!=s)for(var i in Object(s))o.push(i);return o}},86009(s,o,i){s=i.nmd(s);var a=i(34840),u=o&&!o.nodeType&&o,_=u&&s&&!s.nodeType&&s,w=_&&_.exports===u&&a.process,x=function(){try{var s=_&&_.require&&_.require("util").types;return s||w&&w.binding&&w.binding("util")}catch(s){}}();s.exports=x},59350(s){var o=Object.prototype.toString;s.exports=function objectToString(s){return o.call(s)}},74335(s){s.exports=function overArg(s,o){return function(i){return s(o(i))}}},56757(s,o,i){var a=i(91033),u=Math.max;s.exports=function overRest(s,o,i){return o=u(void 0===o?s.length-1:o,0),function(){for(var _=arguments,w=-1,x=u(_.length-o,0),C=Array(x);++w0){if(++i>=800)return arguments[0]}else i=0;return s.apply(void 0,arguments)}}},51420(s,o,i){var a=i(80079);s.exports=function stackClear(){this.__data__=new a,this.size=0}},90938(s){s.exports=function stackDelete(s){var o=this.__data__,i=o.delete(s);return this.size=o.size,i}},63605(s){s.exports=function stackGet(s){return this.__data__.get(s)}},29817(s){s.exports=function stackHas(s){return this.__data__.has(s)}},80945(s,o,i){var a=i(80079),u=i(68223),_=i(53661);s.exports=function stackSet(s,o){var i=this.__data__;if(i instanceof a){var w=i.__data__;if(!u||w.length<199)return w.push([s,o]),this.size=++i.size,this;i=this.__data__=new _(w)}return i.set(s,o),this.size=i.size,this}},76959(s){s.exports=function strictIndexOf(s,o,i){for(var a=i-1,u=s.length;++a=o||i<0||Y&&s-U>=L}function timerExpired(){var s=u();if(shouldInvoke(s))return trailingEdge(s);$=setTimeout(timerExpired,function remainingWait(s){var i=o-(s-z);return Y?x(i,L-(s-U)):i}(s))}function trailingEdge(s){return $=void 0,Z&&C?invokeFunc(s):(C=j=void 0,B)}function debounced(){var s=u(),i=shouldInvoke(s);if(C=arguments,j=this,z=s,i){if(void 0===$)return function leadingEdge(s){return U=s,$=setTimeout(timerExpired,o),V?invokeFunc(s):B}(z);if(Y)return clearTimeout($),$=setTimeout(timerExpired,o),invokeFunc(z)}return void 0===$&&($=setTimeout(timerExpired,o)),B}return o=_(o)||0,a(i)&&(V=!!i.leading,L=(Y="maxWait"in i)?w(_(i.maxWait)||0,o):L,Z="trailing"in i?!!i.trailing:Z),debounced.cancel=function cancel(){void 0!==$&&clearTimeout($),U=0,C=z=j=$=void 0},debounced.flush=function flush(){return void 0===$?B:trailingEdge(u())},debounced}},50828(s,o,i){var a=i(24647),u=i(13222),_=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,w=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]","g");s.exports=function deburr(s){return(s=u(s))&&s.replace(_,a).replace(w,"")}},75288(s){s.exports=function eq(s,o){return s===o||s!=s&&o!=o}},60680(s,o,i){var a=i(13222),u=/[\\^$.*+?()[\]{}|]/g,_=RegExp(u.source);s.exports=function escapeRegExp(s){return(s=a(s))&&_.test(s)?s.replace(u,"\\$&"):s}},7309(s,o,i){var a=i(62006)(i(24713));s.exports=a},24713(s,o,i){var a=i(2523),u=i(15389),_=i(61489),w=Math.max;s.exports=function findIndex(s,o,i){var x=null==s?0:s.length;if(!x)return-1;var C=null==i?0:_(i);return C<0&&(C=w(x+C,0)),a(s,u(o,3),C)}},35970(s,o,i){var a=i(83120);s.exports=function flatten(s){return(null==s?0:s.length)?a(s,1):[]}},73424(s,o,i){var a=i(16962),u=i(2874),_=Array.prototype.push;function baseAry(s,o){return 2==o?function(o,i){return s(o,i)}:function(o){return s(o)}}function cloneArray(s){for(var o=s?s.length:0,i=Array(o);o--;)i[o]=s[o];return i}function wrapImmutable(s,o){return function(){var i=arguments.length;if(i){for(var a=Array(i);i--;)a[i]=arguments[i];var u=a[0]=o.apply(void 0,a);return s.apply(void 0,a),u}}}s.exports=function baseConvert(s,o,i,w){var x="function"==typeof o,C=o===Object(o);if(C&&(w=i,i=o,o=void 0),null==i)throw new TypeError;w||(w={});var j=!("cap"in w)||w.cap,L=!("curry"in w)||w.curry,B=!("fixed"in w)||w.fixed,$=!("immutable"in w)||w.immutable,z=!("rearg"in w)||w.rearg,U=x?i:u,V="curry"in w&&w.curry,Y="fixed"in w&&w.fixed,Z="rearg"in w&&w.rearg,ee=x?i.runInContext():void 0,ie=x?i:{ary:s.ary,assign:s.assign,clone:s.clone,curry:s.curry,forEach:s.forEach,isArray:s.isArray,isError:s.isError,isFunction:s.isFunction,isWeakMap:s.isWeakMap,iteratee:s.iteratee,keys:s.keys,rearg:s.rearg,toInteger:s.toInteger,toPath:s.toPath},ae=ie.ary,ce=ie.assign,le=ie.clone,pe=ie.curry,de=ie.forEach,fe=ie.isArray,ye=ie.isError,be=ie.isFunction,Se=ie.isWeakMap,_e=ie.keys,we=ie.rearg,xe=ie.toInteger,Pe=ie.toPath,Te=_e(a.aryMethod),Re={castArray:function(s){return function(){var o=arguments[0];return fe(o)?s(cloneArray(o)):s.apply(void 0,arguments)}},iteratee:function(s){return function(){var o=arguments[1],i=s(arguments[0],o),a=i.length;return j&&"number"==typeof o?(o=o>2?o-2:1,a&&a<=o?i:baseAry(i,o)):i}},mixin:function(s){return function(o){var i=this;if(!be(i))return s(i,Object(o));var a=[];return de(_e(o),(function(s){be(o[s])&&a.push([s,i.prototype[s]])})),s(i,Object(o)),de(a,(function(s){var o=s[1];be(o)?i.prototype[s[0]]=o:delete i.prototype[s[0]]})),i}},nthArg:function(s){return function(o){var i=o<0?1:xe(o)+1;return pe(s(o),i)}},rearg:function(s){return function(o,i){var a=i?i.length:0;return pe(s(o,i),a)}},runInContext:function(o){return function(i){return baseConvert(s,o(i),w)}}};function castCap(s,o){if(j){var i=a.iterateeRearg[s];if(i)return function iterateeRearg(s,o){return overArg(s,(function(s){var i=o.length;return function baseArity(s,o){return 2==o?function(o,i){return s.apply(void 0,arguments)}:function(o){return s.apply(void 0,arguments)}}(we(baseAry(s,i),o),i)}))}(o,i);var u=!x&&a.iterateeAry[s];if(u)return function iterateeAry(s,o){return overArg(s,(function(s){return"function"==typeof s?baseAry(s,o):s}))}(o,u)}return o}function castFixed(s,o,i){if(B&&(Y||!a.skipFixed[s])){var u=a.methodSpread[s],w=u&&u.start;return void 0===w?ae(o,i):function flatSpread(s,o){return function(){for(var i=arguments.length,a=i-1,u=Array(i);i--;)u[i]=arguments[i];var w=u[o],x=u.slice(0,o);return w&&_.apply(x,w),o!=a&&_.apply(x,u.slice(o+1)),s.apply(this,x)}}(o,w)}return o}function castRearg(s,o,i){return z&&i>1&&(Z||!a.skipRearg[s])?we(o,a.methodRearg[s]||a.aryRearg[i]):o}function cloneByPath(s,o){for(var i=-1,a=(o=Pe(o)).length,u=a-1,_=le(Object(s)),w=_;null!=w&&++i1?pe(o,i):o}(0,u=castCap(_,u),s),!1}})),!u})),u||(u=w),u==o&&(u=V?pe(u,1):function(){return o.apply(this,arguments)}),u.convert=createConverter(_,o),u.placeholder=o.placeholder=i,u}if(!C)return wrap(o,i,U);var qe=i,$e=[];return de(Te,(function(s){de(a.aryMethod[s],(function(s){var o=qe[a.remap[s]||s];o&&$e.push([s,wrap(s,o,qe)])}))})),de(_e(qe),(function(s){var o=qe[s];if("function"==typeof o){for(var i=$e.length;i--;)if($e[i][0]==s)return;o.convert=createConverter(s,o),$e.push([s,o])}})),de($e,(function(s){qe[s[0]]=s[1]})),qe.convert=function convertLib(s){return qe.runInContext.convert(s)(void 0)},qe.placeholder=qe,de(_e(qe),(function(s){de(a.realToAlias[s]||[],(function(o){qe[o]=qe[s]}))})),qe}},16962(s,o){o.aliasToReal={each:"forEach",eachRight:"forEachRight",entries:"toPairs",entriesIn:"toPairsIn",extend:"assignIn",extendAll:"assignInAll",extendAllWith:"assignInAllWith",extendWith:"assignInWith",first:"head",conforms:"conformsTo",matches:"isMatch",property:"get",__:"placeholder",F:"stubFalse",T:"stubTrue",all:"every",allPass:"overEvery",always:"constant",any:"some",anyPass:"overSome",apply:"spread",assoc:"set",assocPath:"set",complement:"negate",compose:"flowRight",contains:"includes",dissoc:"unset",dissocPath:"unset",dropLast:"dropRight",dropLastWhile:"dropRightWhile",equals:"isEqual",identical:"eq",indexBy:"keyBy",init:"initial",invertObj:"invert",juxt:"over",omitAll:"omit",nAry:"ary",path:"get",pathEq:"matchesProperty",pathOr:"getOr",paths:"at",pickAll:"pick",pipe:"flow",pluck:"map",prop:"get",propEq:"matchesProperty",propOr:"getOr",props:"at",symmetricDifference:"xor",symmetricDifferenceBy:"xorBy",symmetricDifferenceWith:"xorWith",takeLast:"takeRight",takeLastWhile:"takeRightWhile",unapply:"rest",unnest:"flatten",useWith:"overArgs",where:"conformsTo",whereEq:"isMatch",zipObj:"zipObject"},o.aryMethod={1:["assignAll","assignInAll","attempt","castArray","ceil","create","curry","curryRight","defaultsAll","defaultsDeepAll","floor","flow","flowRight","fromPairs","invert","iteratee","memoize","method","mergeAll","methodOf","mixin","nthArg","over","overEvery","overSome","rest","reverse","round","runInContext","spread","template","trim","trimEnd","trimStart","uniqueId","words","zipAll"],2:["add","after","ary","assign","assignAllWith","assignIn","assignInAllWith","at","before","bind","bindAll","bindKey","chunk","cloneDeepWith","cloneWith","concat","conformsTo","countBy","curryN","curryRightN","debounce","defaults","defaultsDeep","defaultTo","delay","difference","divide","drop","dropRight","dropRightWhile","dropWhile","endsWith","eq","every","filter","find","findIndex","findKey","findLast","findLastIndex","findLastKey","flatMap","flatMapDeep","flattenDepth","forEach","forEachRight","forIn","forInRight","forOwn","forOwnRight","get","groupBy","gt","gte","has","hasIn","includes","indexOf","intersection","invertBy","invoke","invokeMap","isEqual","isMatch","join","keyBy","lastIndexOf","lt","lte","map","mapKeys","mapValues","matchesProperty","maxBy","meanBy","merge","mergeAllWith","minBy","multiply","nth","omit","omitBy","overArgs","pad","padEnd","padStart","parseInt","partial","partialRight","partition","pick","pickBy","propertyOf","pull","pullAll","pullAt","random","range","rangeRight","rearg","reject","remove","repeat","restFrom","result","sampleSize","some","sortBy","sortedIndex","sortedIndexOf","sortedLastIndex","sortedLastIndexOf","sortedUniqBy","split","spreadFrom","startsWith","subtract","sumBy","take","takeRight","takeRightWhile","takeWhile","tap","throttle","thru","times","trimChars","trimCharsEnd","trimCharsStart","truncate","union","uniqBy","uniqWith","unset","unzipWith","without","wrap","xor","zip","zipObject","zipObjectDeep"],3:["assignInWith","assignWith","clamp","differenceBy","differenceWith","findFrom","findIndexFrom","findLastFrom","findLastIndexFrom","getOr","includesFrom","indexOfFrom","inRange","intersectionBy","intersectionWith","invokeArgs","invokeArgsMap","isEqualWith","isMatchWith","flatMapDepth","lastIndexOfFrom","mergeWith","orderBy","padChars","padCharsEnd","padCharsStart","pullAllBy","pullAllWith","rangeStep","rangeStepRight","reduce","reduceRight","replace","set","slice","sortedIndexBy","sortedLastIndexBy","transform","unionBy","unionWith","update","xorBy","xorWith","zipWith"],4:["fill","setWith","updateWith"]},o.aryRearg={2:[1,0],3:[2,0,1],4:[3,2,0,1]},o.iterateeAry={dropRightWhile:1,dropWhile:1,every:1,filter:1,find:1,findFrom:1,findIndex:1,findIndexFrom:1,findKey:1,findLast:1,findLastFrom:1,findLastIndex:1,findLastIndexFrom:1,findLastKey:1,flatMap:1,flatMapDeep:1,flatMapDepth:1,forEach:1,forEachRight:1,forIn:1,forInRight:1,forOwn:1,forOwnRight:1,map:1,mapKeys:1,mapValues:1,partition:1,reduce:2,reduceRight:2,reject:1,remove:1,some:1,takeRightWhile:1,takeWhile:1,times:1,transform:2},o.iterateeRearg={mapKeys:[1],reduceRight:[1,0]},o.methodRearg={assignInAllWith:[1,0],assignInWith:[1,2,0],assignAllWith:[1,0],assignWith:[1,2,0],differenceBy:[1,2,0],differenceWith:[1,2,0],getOr:[2,1,0],intersectionBy:[1,2,0],intersectionWith:[1,2,0],isEqualWith:[1,2,0],isMatchWith:[2,1,0],mergeAllWith:[1,0],mergeWith:[1,2,0],padChars:[2,1,0],padCharsEnd:[2,1,0],padCharsStart:[2,1,0],pullAllBy:[2,1,0],pullAllWith:[2,1,0],rangeStep:[1,2,0],rangeStepRight:[1,2,0],setWith:[3,1,2,0],sortedIndexBy:[2,1,0],sortedLastIndexBy:[2,1,0],unionBy:[1,2,0],unionWith:[1,2,0],updateWith:[3,1,2,0],xorBy:[1,2,0],xorWith:[1,2,0],zipWith:[1,2,0]},o.methodSpread={assignAll:{start:0},assignAllWith:{start:0},assignInAll:{start:0},assignInAllWith:{start:0},defaultsAll:{start:0},defaultsDeepAll:{start:0},invokeArgs:{start:2},invokeArgsMap:{start:2},mergeAll:{start:0},mergeAllWith:{start:0},partial:{start:1},partialRight:{start:1},without:{start:1},zipAll:{start:0}},o.mutate={array:{fill:!0,pull:!0,pullAll:!0,pullAllBy:!0,pullAllWith:!0,pullAt:!0,remove:!0,reverse:!0},object:{assign:!0,assignAll:!0,assignAllWith:!0,assignIn:!0,assignInAll:!0,assignInAllWith:!0,assignInWith:!0,assignWith:!0,defaults:!0,defaultsAll:!0,defaultsDeep:!0,defaultsDeepAll:!0,merge:!0,mergeAll:!0,mergeAllWith:!0,mergeWith:!0},set:{set:!0,setWith:!0,unset:!0,update:!0,updateWith:!0}},o.realToAlias=function(){var s=Object.prototype.hasOwnProperty,i=o.aliasToReal,a={};for(var u in i){var _=i[u];s.call(a,_)?a[_].push(u):a[_]=[u]}return a}(),o.remap={assignAll:"assign",assignAllWith:"assignWith",assignInAll:"assignIn",assignInAllWith:"assignInWith",curryN:"curry",curryRightN:"curryRight",defaultsAll:"defaults",defaultsDeepAll:"defaultsDeep",findFrom:"find",findIndexFrom:"findIndex",findLastFrom:"findLast",findLastIndexFrom:"findLastIndex",getOr:"get",includesFrom:"includes",indexOfFrom:"indexOf",invokeArgs:"invoke",invokeArgsMap:"invokeMap",lastIndexOfFrom:"lastIndexOf",mergeAll:"merge",mergeAllWith:"mergeWith",padChars:"pad",padCharsEnd:"padEnd",padCharsStart:"padStart",propertyOf:"get",rangeStep:"range",rangeStepRight:"rangeRight",restFrom:"rest",spreadFrom:"spread",trimChars:"trim",trimCharsEnd:"trimEnd",trimCharsStart:"trimStart",zipAll:"zip"},o.skipFixed={castArray:!0,flow:!0,flowRight:!0,iteratee:!0,mixin:!0,rearg:!0,runInContext:!0},o.skipRearg={add:!0,assign:!0,assignIn:!0,bind:!0,bindKey:!0,concat:!0,difference:!0,divide:!0,eq:!0,gt:!0,gte:!0,isEqual:!0,lt:!0,lte:!0,matchesProperty:!0,merge:!0,multiply:!0,overArgs:!0,partial:!0,partialRight:!0,propertyOf:!0,random:!0,range:!0,rangeRight:!0,subtract:!0,zip:!0,zipObject:!0,zipObjectDeep:!0}},47934(s,o,i){s.exports={ary:i(64626),assign:i(74733),clone:i(32629),curry:i(49747),forEach:i(83729),isArray:i(56449),isError:i(23546),isFunction:i(1882),isWeakMap:i(47886),iteratee:i(33855),keys:i(88984),rearg:i(84195),toInteger:i(61489),toPath:i(42072)}},56367(s,o,i){s.exports=i(77731)},79920(s,o,i){var a=i(73424),u=i(47934);s.exports=function convert(s,o,i){return a(u,s,o,i)}},2874(s){s.exports={}},77731(s,o,i){var a=i(79920)("set",i(63560));a.placeholder=i(2874),s.exports=a},58156(s,o,i){var a=i(47422);s.exports=function get(s,o,i){var u=null==s?void 0:a(s,o);return void 0===u?i:u}},61448(s,o,i){var a=i(20426),u=i(49326);s.exports=function has(s,o){return null!=s&&u(s,o,a)}},80631(s,o,i){var a=i(28077),u=i(49326);s.exports=function hasIn(s,o){return null!=s&&u(s,o,a)}},83488(s){s.exports=function identity(s){return s}},72428(s,o,i){var a=i(27534),u=i(40346),_=Object.prototype,w=_.hasOwnProperty,x=_.propertyIsEnumerable,C=a(function(){return arguments}())?a:function(s){return u(s)&&w.call(s,"callee")&&!x.call(s,"callee")};s.exports=C},56449(s){var o=Array.isArray;s.exports=o},64894(s,o,i){var a=i(1882),u=i(30294);s.exports=function isArrayLike(s){return null!=s&&u(s.length)&&!a(s)}},83693(s,o,i){var a=i(64894),u=i(40346);s.exports=function isArrayLikeObject(s){return u(s)&&a(s)}},53812(s,o,i){var a=i(72552),u=i(40346);s.exports=function isBoolean(s){return!0===s||!1===s||u(s)&&"[object Boolean]"==a(s)}},3656(s,o,i){s=i.nmd(s);var a=i(9325),u=i(89935),_=o&&!o.nodeType&&o,w=_&&s&&!s.nodeType&&s,x=w&&w.exports===_?a.Buffer:void 0,C=(x?x.isBuffer:void 0)||u;s.exports=C},62193(s,o,i){var a=i(88984),u=i(5861),_=i(72428),w=i(56449),x=i(64894),C=i(3656),j=i(55527),L=i(37167),B=Object.prototype.hasOwnProperty;s.exports=function isEmpty(s){if(null==s)return!0;if(x(s)&&(w(s)||"string"==typeof s||"function"==typeof s.splice||C(s)||L(s)||_(s)))return!s.length;var o=u(s);if("[object Map]"==o||"[object Set]"==o)return!s.size;if(j(s))return!a(s).length;for(var i in s)if(B.call(s,i))return!1;return!0}},2404(s,o,i){var a=i(60270);s.exports=function isEqual(s,o){return a(s,o)}},23546(s,o,i){var a=i(72552),u=i(40346),_=i(11331);s.exports=function isError(s){if(!u(s))return!1;var o=a(s);return"[object Error]"==o||"[object DOMException]"==o||"string"==typeof s.message&&"string"==typeof s.name&&!_(s)}},1882(s,o,i){var a=i(72552),u=i(23805);s.exports=function isFunction(s){if(!u(s))return!1;var o=a(s);return"[object Function]"==o||"[object GeneratorFunction]"==o||"[object AsyncFunction]"==o||"[object Proxy]"==o}},30294(s){s.exports=function isLength(s){return"number"==typeof s&&s>-1&&s%1==0&&s<=9007199254740991}},87730(s,o,i){var a=i(29172),u=i(27301),_=i(86009),w=_&&_.isMap,x=w?u(w):a;s.exports=x},5187(s){s.exports=function isNull(s){return null===s}},98023(s,o,i){var a=i(72552),u=i(40346);s.exports=function isNumber(s){return"number"==typeof s||u(s)&&"[object Number]"==a(s)}},23805(s){s.exports=function isObject(s){var o=typeof s;return null!=s&&("object"==o||"function"==o)}},40346(s){s.exports=function isObjectLike(s){return null!=s&&"object"==typeof s}},11331(s,o,i){var a=i(72552),u=i(28879),_=i(40346),w=Function.prototype,x=Object.prototype,C=w.toString,j=x.hasOwnProperty,L=C.call(Object);s.exports=function isPlainObject(s){if(!_(s)||"[object Object]"!=a(s))return!1;var o=u(s);if(null===o)return!0;var i=j.call(o,"constructor")&&o.constructor;return"function"==typeof i&&i instanceof i&&C.call(i)==L}},38440(s,o,i){var a=i(16038),u=i(27301),_=i(86009),w=_&&_.isSet,x=w?u(w):a;s.exports=x},85015(s,o,i){var a=i(72552),u=i(56449),_=i(40346);s.exports=function isString(s){return"string"==typeof s||!u(s)&&_(s)&&"[object String]"==a(s)}},44394(s,o,i){var a=i(72552),u=i(40346);s.exports=function isSymbol(s){return"symbol"==typeof s||u(s)&&"[object Symbol]"==a(s)}},37167(s,o,i){var a=i(4901),u=i(27301),_=i(86009),w=_&&_.isTypedArray,x=w?u(w):a;s.exports=x},47886(s,o,i){var a=i(5861),u=i(40346);s.exports=function isWeakMap(s){return u(s)&&"[object WeakMap]"==a(s)}},33855(s,o,i){var a=i(9999),u=i(15389);s.exports=function iteratee(s){return u("function"==typeof s?s:a(s,1))}},95950(s,o,i){var a=i(70695),u=i(88984),_=i(64894);s.exports=function keys(s){return _(s)?a(s):u(s)}},37241(s,o,i){var a=i(70695),u=i(72903),_=i(64894);s.exports=function keysIn(s){return _(s)?a(s,!0):u(s)}},68090(s){s.exports=function last(s){var o=null==s?0:s.length;return o?s[o-1]:void 0}},50104(s,o,i){var a=i(53661);function memoize(s,o){if("function"!=typeof s||null!=o&&"function"!=typeof o)throw new TypeError("Expected a function");var memoized=function(){var i=arguments,a=o?o.apply(this,i):i[0],u=memoized.cache;if(u.has(a))return u.get(a);var _=s.apply(this,i);return memoized.cache=u.set(a,_)||u,_};return memoized.cache=new(memoize.Cache||a),memoized}memoize.Cache=a,s.exports=memoize},55364(s,o,i){var a=i(85250),u=i(20999)((function(s,o,i){a(s,o,i)}));s.exports=u},6048(s){s.exports=function negate(s){if("function"!=typeof s)throw new TypeError("Expected a function");return function(){var o=arguments;switch(o.length){case 0:return!s.call(this);case 1:return!s.call(this,o[0]);case 2:return!s.call(this,o[0],o[1]);case 3:return!s.call(this,o[0],o[1],o[2])}return!s.apply(this,o)}}},63950(s){s.exports=function noop(){}},10124(s,o,i){var a=i(9325);s.exports=function(){return a.Date.now()}},90179(s,o,i){var a=i(34932),u=i(9999),_=i(19931),w=i(31769),x=i(21791),C=i(53138),j=i(38816),L=i(83349),B=j((function(s,o){var i={};if(null==s)return i;var j=!1;o=a(o,(function(o){return o=w(o,s),j||(j=o.length>1),o})),x(s,L(s),i),j&&(i=u(i,7,C));for(var B=o.length;B--;)_(i,o[B]);return i}));s.exports=B},50583(s,o,i){var a=i(47237),u=i(17255),_=i(28586),w=i(77797);s.exports=function property(s){return _(s)?a(w(s)):u(s)}},84195(s,o,i){var a=i(66977),u=i(38816),_=u((function(s,o){return a(s,256,void 0,void 0,void 0,o)}));s.exports=_},40860(s,o,i){var a=i(40882),u=i(80909),_=i(15389),w=i(85558),x=i(56449);s.exports=function reduce(s,o,i){var C=x(s)?a:w,j=arguments.length<3;return C(s,_(o,4),i,j,u)}},63560(s,o,i){var a=i(73170);s.exports=function set(s,o,i){return null==s?s:a(s,o,i)}},42426(s,o,i){var a=i(14248),u=i(15389),_=i(90916),w=i(56449),x=i(36800);s.exports=function some(s,o,i){var C=w(s)?a:_;return i&&x(s,o,i)&&(o=void 0),C(s,u(o,3))}},63345(s){s.exports=function stubArray(){return[]}},89935(s){s.exports=function stubFalse(){return!1}},17400(s,o,i){var a=i(99374),u=1/0;s.exports=function toFinite(s){return s?(s=a(s))===u||s===-1/0?17976931348623157e292*(s<0?-1:1):s==s?s:0:0===s?s:0}},61489(s,o,i){var a=i(17400);s.exports=function toInteger(s){var o=a(s),i=o%1;return o==o?i?o-i:o:0}},80218(s,o,i){var a=i(13222);s.exports=function toLower(s){return a(s).toLowerCase()}},99374(s,o,i){var a=i(54128),u=i(23805),_=i(44394),w=/^[-+]0x[0-9a-f]+$/i,x=/^0b[01]+$/i,C=/^0o[0-7]+$/i,j=parseInt;s.exports=function toNumber(s){if("number"==typeof s)return s;if(_(s))return NaN;if(u(s)){var o="function"==typeof s.valueOf?s.valueOf():s;s=u(o)?o+"":o}if("string"!=typeof s)return 0===s?s:+s;s=a(s);var i=x.test(s);return i||C.test(s)?j(s.slice(2),i?2:8):w.test(s)?NaN:+s}},42072(s,o,i){var a=i(34932),u=i(23007),_=i(56449),w=i(44394),x=i(61802),C=i(77797),j=i(13222);s.exports=function toPath(s){return _(s)?a(s,C):w(s)?[s]:u(x(j(s)))}},69884(s,o,i){var a=i(21791),u=i(37241);s.exports=function toPlainObject(s){return a(s,u(s))}},13222(s,o,i){var a=i(77556);s.exports=function toString(s){return null==s?"":a(s)}},55808(s,o,i){var a=i(12507)("toUpperCase");s.exports=a},66645(s,o,i){var a=i(1733),u=i(45434),_=i(13222),w=i(22225);s.exports=function words(s,o,i){return s=_(s),void 0===(o=i?void 0:o)?u(s)?w(s):a(s):s.match(o)||[]}},53758(s,o,i){var a=i(30980),u=i(56017),_=i(94033),w=i(56449),x=i(40346),C=i(80257),j=Object.prototype.hasOwnProperty;function lodash(s){if(x(s)&&!w(s)&&!(s instanceof a)){if(s instanceof u)return s;if(j.call(s,"__wrapped__"))return C(s)}return new u(s)}lodash.prototype=_.prototype,lodash.prototype.constructor=lodash,s.exports=lodash},47248(s,o,i){var a=i(16547),u=i(51234);s.exports=function zipObject(s,o){return u(s||[],o||[],a)}},43768(s,o,i){"use strict";var a=i(45981),u=i(85587);o.highlight=highlight,o.highlightAuto=function highlightAuto(s,o){var i,w,x,C,j=o||{},L=j.subset||a.listLanguages(),B=j.prefix,$=L.length,z=-1;null==B&&(B=_);if("string"!=typeof s)throw u("Expected `string` for value, got `%s`",s);w={relevance:0,language:null,value:[]},i={relevance:0,language:null,value:[]};for(;++z<$;)C=L[z],a.getLanguage(C)&&((x=highlight(C,s,o)).language=C,x.relevance>w.relevance&&(w=x),x.relevance>i.relevance&&(w=i,i=x));w.language&&(i.secondBest=w);return i},o.registerLanguage=function registerLanguage(s,o){a.registerLanguage(s,o)},o.listLanguages=function listLanguages(){return a.listLanguages()},o.registerAlias=function registerAlias(s,o){var i,u=s;o&&((u={})[s]=o);for(i in u)a.registerAliases(u[i],{languageName:i})},Emitter.prototype.addText=function text(s){var o,i,a=this.stack;if(""===s)return;o=a[a.length-1],(i=o.children[o.children.length-1])&&"text"===i.type?i.value+=s:o.children.push({type:"text",value:s})},Emitter.prototype.addKeyword=function addKeyword(s,o){this.openNode(o),this.addText(s),this.closeNode()},Emitter.prototype.addSublanguage=function addSublanguage(s,o){var i=this.stack,a=i[i.length-1],u=s.rootNode.children,_=o?{type:"element",tagName:"span",properties:{className:[o]},children:u}:u;a.children=a.children.concat(_)},Emitter.prototype.openNode=function open(s){var o=this.stack,i=this.options.classPrefix+s,a=o[o.length-1],u={type:"element",tagName:"span",properties:{className:[i]},children:[]};a.children.push(u),o.push(u)},Emitter.prototype.closeNode=function close(){this.stack.pop()},Emitter.prototype.closeAllNodes=noop,Emitter.prototype.finalize=noop,Emitter.prototype.toHTML=function toHtmlNoop(){return""};var _="hljs-";function highlight(s,o,i){var w,x=a.configure({}),C=(i||{}).prefix;if("string"!=typeof s)throw u("Expected `string` for name, got `%s`",s);if(!a.getLanguage(s))throw u("Unknown language: `%s` is not registered",s);if("string"!=typeof o)throw u("Expected `string` for value, got `%s`",o);if(null==C&&(C=_),a.configure({__emitter:Emitter,classPrefix:C}),w=a.highlight(o,{language:s,ignoreIllegals:!0}),a.configure(x||{}),w.errorRaised)throw w.errorRaised;return{relevance:w.relevance,language:w.language,value:w.emitter.rootNode.children}}function Emitter(s){this.options=s,this.rootNode={children:[]},this.stack=[this.rootNode]}function noop(){}},71514(s){"use strict";s.exports=Math.abs},58968(s){"use strict";s.exports=Math.floor},94459(s){"use strict";s.exports=Number.isNaN||function isNaN(s){return s!=s}},6188(s){"use strict";s.exports=Math.max},68002(s){"use strict";s.exports=Math.min},75880(s){"use strict";s.exports=Math.pow},70414(s){"use strict";s.exports=Math.round},73093(s,o,i){"use strict";var a=i(94459);s.exports=function sign(s){return a(s)||0===s?s:s<0?-1:1}},92340(s,o,i){const a=i(6048);function coerceElementMatchingCallback(s){return"string"==typeof s?o=>o.element===s:s.constructor&&s.extend?o=>o instanceof s:s}class ArraySlice{constructor(s){this.elements=s||[]}toValue(){return this.elements.map((s=>s.toValue()))}map(s,o){return this.elements.map(s,o)}flatMap(s,o){return this.map(s,o).reduce(((s,o)=>s.concat(o)),[])}compactMap(s,o){const i=[];return this.forEach((a=>{const u=s.bind(o)(a);u&&i.push(u)})),i}filter(s,o){return s=coerceElementMatchingCallback(s),new ArraySlice(this.elements.filter(s,o))}reject(s,o){return s=coerceElementMatchingCallback(s),new ArraySlice(this.elements.filter(a(s),o))}find(s,o){return s=coerceElementMatchingCallback(s),this.elements.find(s,o)}forEach(s,o){this.elements.forEach(s,o)}reduce(s,o){return this.elements.reduce(s,o)}includes(s){return this.elements.some((o=>o.equals(s)))}shift(){return this.elements.shift()}unshift(s){this.elements.unshift(this.refract(s))}push(s){return this.elements.push(this.refract(s)),this}add(s){this.push(s)}get(s){return this.elements[s]}getValue(s){const o=this.elements[s];if(o)return o.toValue()}get length(){return this.elements.length}get isEmpty(){return 0===this.elements.length}get first(){return this.elements[0]}}"undefined"!=typeof Symbol&&(ArraySlice.prototype[Symbol.iterator]=function symbol(){return this.elements[Symbol.iterator]()}),s.exports=ArraySlice},55973(s){class KeyValuePair{constructor(s,o){this.key=s,this.value=o}clone(){const s=new KeyValuePair;return this.key&&(s.key=this.key.clone()),this.value&&(s.value=this.value.clone()),s}}s.exports=KeyValuePair},3110(s,o,i){const a=i(5187),u=i(85015),_=i(98023),w=i(53812),x=i(23805),C=i(85105),j=i(86804);class Namespace{constructor(s){this.elementMap={},this.elementDetection=[],this.Element=j.Element,this.KeyValuePair=j.KeyValuePair,s&&s.noDefault||this.useDefault(),this._attributeElementKeys=[],this._attributeElementArrayKeys=[]}use(s){return s.namespace&&s.namespace({base:this}),s.load&&s.load({base:this}),this}useDefault(){return this.register("null",j.NullElement).register("string",j.StringElement).register("number",j.NumberElement).register("boolean",j.BooleanElement).register("array",j.ArrayElement).register("object",j.ObjectElement).register("member",j.MemberElement).register("ref",j.RefElement).register("link",j.LinkElement),this.detect(a,j.NullElement,!1).detect(u,j.StringElement,!1).detect(_,j.NumberElement,!1).detect(w,j.BooleanElement,!1).detect(Array.isArray,j.ArrayElement,!1).detect(x,j.ObjectElement,!1),this}register(s,o){return this._elements=void 0,this.elementMap[s]=o,this}unregister(s){return this._elements=void 0,delete this.elementMap[s],this}detect(s,o,i){return void 0===i||i?this.elementDetection.unshift([s,o]):this.elementDetection.push([s,o]),this}toElement(s){if(s instanceof this.Element)return s;let o;for(let i=0;i{const o=s[0].toUpperCase()+s.substr(1);this._elements[o]=this.elementMap[s]}))),this._elements}get serialiser(){return new C(this)}}C.prototype.Namespace=Namespace,s.exports=Namespace},10866(s,o,i){const a=i(6048),u=i(92340);class ObjectSlice extends u{map(s,o){return this.elements.map((i=>s.bind(o)(i.value,i.key,i)))}filter(s,o){return new ObjectSlice(this.elements.filter((i=>s.bind(o)(i.value,i.key,i))))}reject(s,o){return this.filter(a(s.bind(o)))}forEach(s,o){return this.elements.forEach(((i,a)=>{s.bind(o)(i.value,i.key,i,a)}))}keys(){return this.map(((s,o)=>o.toValue()))}values(){return this.map((s=>s.toValue()))}}s.exports=ObjectSlice},86804(s,o,i){const a=i(10316),u=i(41067),_=i(71167),w=i(40239),x=i(12242),C=i(6233),j=i(87726),L=i(61045),B=i(86303),$=i(14540),z=i(92340),U=i(10866),V=i(55973);function refract(s){if(s instanceof a)return s;if("string"==typeof s)return new _(s);if("number"==typeof s)return new w(s);if("boolean"==typeof s)return new x(s);if(null===s)return new u;if(Array.isArray(s))return new C(s.map(refract));if("object"==typeof s){return new L(s)}return s}a.prototype.ObjectElement=L,a.prototype.RefElement=$,a.prototype.MemberElement=j,a.prototype.refract=refract,z.prototype.refract=refract,s.exports={Element:a,NullElement:u,StringElement:_,NumberElement:w,BooleanElement:x,ArrayElement:C,MemberElement:j,ObjectElement:L,LinkElement:B,RefElement:$,refract,ArraySlice:z,ObjectSlice:U,KeyValuePair:V}},86303(s,o,i){const a=i(10316);s.exports=class LinkElement extends a{constructor(s,o,i){super(s||[],o,i),this.element="link"}get relation(){return this.attributes.get("relation")}set relation(s){this.attributes.set("relation",s)}get href(){return this.attributes.get("href")}set href(s){this.attributes.set("href",s)}}},14540(s,o,i){const a=i(10316);s.exports=class RefElement extends a{constructor(s,o,i){super(s||[],o,i),this.element="ref",this.path||(this.path="element")}get path(){return this.attributes.get("path")}set path(s){this.attributes.set("path",s)}}},34035(s,o,i){const a=i(3110),u=i(86804);o.g$=a,o.KeyValuePair=i(55973),o.G6=u.ArraySlice,o.ot=u.ObjectSlice,o.Hg=u.Element,o.Om=u.StringElement,o.kT=u.NumberElement,o.bd=u.BooleanElement,o.Os=u.NullElement,o.wE=u.ArrayElement,o.Sh=u.ObjectElement,o.Pr=u.MemberElement,o.sI=u.RefElement,o.Ft=u.LinkElement,o.e=u.refract,i(85105),i(75147)},6233(s,o,i){const a=i(6048),u=i(10316),_=i(92340);class ArrayElement extends u{constructor(s,o,i){super(s||[],o,i),this.element="array"}primitive(){return"array"}get(s){return this.content[s]}getValue(s){const o=this.get(s);if(o)return o.toValue()}getIndex(s){return this.content[s]}set(s,o){return this.content[s]=this.refract(o),this}remove(s){const o=this.content.splice(s,1);return o.length?o[0]:null}map(s,o){return this.content.map(s,o)}flatMap(s,o){return this.map(s,o).reduce(((s,o)=>s.concat(o)),[])}compactMap(s,o){const i=[];return this.forEach((a=>{const u=s.bind(o)(a);u&&i.push(u)})),i}filter(s,o){return new _(this.content.filter(s,o))}reject(s,o){return this.filter(a(s),o)}reduce(s,o){let i,a;void 0!==o?(i=0,a=this.refract(o)):(i=1,a="object"===this.primitive()?this.first.value:this.first);for(let o=i;o{s.bind(o)(i,this.refract(a))}))}shift(){return this.content.shift()}unshift(s){this.content.unshift(this.refract(s))}push(s){return this.content.push(this.refract(s)),this}add(s){this.push(s)}findElements(s,o){const i=o||{},a=!!i.recursive,u=void 0===i.results?[]:i.results;return this.forEach(((o,i,_)=>{a&&void 0!==o.findElements&&o.findElements(s,{results:u,recursive:a}),s(o,i,_)&&u.push(o)})),u}find(s){return new _(this.findElements(s,{recursive:!0}))}findByElement(s){return this.find((o=>o.element===s))}findByClass(s){return this.find((o=>o.classes.includes(s)))}getById(s){return this.find((o=>o.id.toValue()===s)).first}includes(s){return this.content.some((o=>o.equals(s)))}contains(s){return this.includes(s)}empty(){return new this.constructor([])}"fantasy-land/empty"(){return this.empty()}concat(s){return new this.constructor(this.content.concat(s.content))}"fantasy-land/concat"(s){return this.concat(s)}"fantasy-land/map"(s){return new this.constructor(this.map(s))}"fantasy-land/chain"(s){return this.map((o=>s(o)),this).reduce(((s,o)=>s.concat(o)),this.empty())}"fantasy-land/filter"(s){return new this.constructor(this.content.filter(s))}"fantasy-land/reduce"(s,o){return this.content.reduce(s,o)}get length(){return this.content.length}get isEmpty(){return 0===this.content.length}get first(){return this.getIndex(0)}get second(){return this.getIndex(1)}get last(){return this.getIndex(this.length-1)}}ArrayElement.empty=function empty(){return new this},ArrayElement["fantasy-land/empty"]=ArrayElement.empty,"undefined"!=typeof Symbol&&(ArrayElement.prototype[Symbol.iterator]=function symbol(){return this.content[Symbol.iterator]()}),s.exports=ArrayElement},12242(s,o,i){const a=i(10316);s.exports=class BooleanElement extends a{constructor(s,o,i){super(s,o,i),this.element="boolean"}primitive(){return"boolean"}}},10316(s,o,i){const a=i(2404),u=i(55973),_=i(92340);class Element{constructor(s,o,i){o&&(this.meta=o),i&&(this.attributes=i),this.content=s}freeze(){Object.isFrozen(this)||(this._meta&&(this.meta.parent=this,this.meta.freeze()),this._attributes&&(this.attributes.parent=this,this.attributes.freeze()),this.children.forEach((s=>{s.parent=this,s.freeze()}),this),this.content&&Array.isArray(this.content)&&Object.freeze(this.content),Object.freeze(this))}primitive(){}clone(){const s=new this.constructor;return s.element=this.element,this.meta.length&&(s._meta=this.meta.clone()),this.attributes.length&&(s._attributes=this.attributes.clone()),this.content?this.content.clone?s.content=this.content.clone():Array.isArray(this.content)?s.content=this.content.map((s=>s.clone())):s.content=this.content:s.content=this.content,s}toValue(){return this.content instanceof Element?this.content.toValue():this.content instanceof u?{key:this.content.key.toValue(),value:this.content.value?this.content.value.toValue():void 0}:this.content&&this.content.map?this.content.map((s=>s.toValue()),this):this.content}toRef(s){if(""===this.id.toValue())throw Error("Cannot create reference to an element that does not contain an ID");const o=new this.RefElement(this.id.toValue());return s&&(o.path=s),o}findRecursive(...s){if(arguments.length>1&&!this.isFrozen)throw new Error("Cannot find recursive with multiple element names without first freezing the element. Call `element.freeze()`");const o=s.pop();let i=new _;const append=(s,o)=>(s.push(o),s),checkElement=(s,i)=>{i.element===o&&s.push(i);const a=i.findRecursive(o);return a&&a.reduce(append,s),i.content instanceof u&&(i.content.key&&checkElement(s,i.content.key),i.content.value&&checkElement(s,i.content.value)),s};return this.content&&(this.content.element&&checkElement(i,this.content),Array.isArray(this.content)&&this.content.reduce(checkElement,i)),s.isEmpty||(i=i.filter((o=>{let i=o.parents.map((s=>s.element));for(const o in s){const a=s[o],u=i.indexOf(a);if(-1===u)return!1;i=i.splice(0,u)}return!0}))),i}set(s){return this.content=s,this}equals(s){return a(this.toValue(),s)}getMetaProperty(s,o){if(!this.meta.hasKey(s)){if(this.isFrozen){const s=this.refract(o);return s.freeze(),s}this.meta.set(s,o)}return this.meta.get(s)}setMetaProperty(s,o){this.meta.set(s,o)}get element(){return this._storedElement||"element"}set element(s){this._storedElement=s}get content(){return this._content}set content(s){if(s instanceof Element)this._content=s;else if(s instanceof _)this.content=s.elements;else if("string"==typeof s||"number"==typeof s||"boolean"==typeof s||"null"===s||null==s)this._content=s;else if(s instanceof u)this._content=s;else if(Array.isArray(s))this._content=s.map(this.refract);else{if("object"!=typeof s)throw new Error("Cannot set content to given value");this._content=Object.keys(s).map((o=>new this.MemberElement(o,s[o])))}}get meta(){if(!this._meta){if(this.isFrozen){const s=new this.ObjectElement;return s.freeze(),s}this._meta=new this.ObjectElement}return this._meta}set meta(s){s instanceof this.ObjectElement?this._meta=s:this.meta.set(s||{})}get attributes(){if(!this._attributes){if(this.isFrozen){const s=new this.ObjectElement;return s.freeze(),s}this._attributes=new this.ObjectElement}return this._attributes}set attributes(s){s instanceof this.ObjectElement?this._attributes=s:this.attributes.set(s||{})}get id(){return this.getMetaProperty("id","")}set id(s){this.setMetaProperty("id",s)}get classes(){return this.getMetaProperty("classes",[])}set classes(s){this.setMetaProperty("classes",s)}get title(){return this.getMetaProperty("title","")}set title(s){this.setMetaProperty("title",s)}get description(){return this.getMetaProperty("description","")}set description(s){this.setMetaProperty("description",s)}get links(){return this.getMetaProperty("links",[])}set links(s){this.setMetaProperty("links",s)}get isFrozen(){return Object.isFrozen(this)}get parents(){let{parent:s}=this;const o=new _;for(;s;)o.push(s),s=s.parent;return o}get children(){if(Array.isArray(this.content))return new _(this.content);if(this.content instanceof u){const s=new _([this.content.key]);return this.content.value&&s.push(this.content.value),s}return this.content instanceof Element?new _([this.content]):new _}get recursiveChildren(){const s=new _;return this.children.forEach((o=>{s.push(o),o.recursiveChildren.forEach((o=>{s.push(o)}))})),s}}s.exports=Element},87726(s,o,i){const a=i(55973),u=i(10316);s.exports=class MemberElement extends u{constructor(s,o,i,u){super(new a,i,u),this.element="member",this.key=s,this.value=o}get key(){return this.content.key}set key(s){this.content.key=this.refract(s)}get value(){return this.content.value}set value(s){this.content.value=this.refract(s)}}},41067(s,o,i){const a=i(10316);s.exports=class NullElement extends a{constructor(s,o,i){super(s||null,o,i),this.element="null"}primitive(){return"null"}set(){return new Error("Cannot set the value of null")}}},40239(s,o,i){const a=i(10316);s.exports=class NumberElement extends a{constructor(s,o,i){super(s,o,i),this.element="number"}primitive(){return"number"}}},61045(s,o,i){const a=i(6048),u=i(23805),_=i(6233),w=i(87726),x=i(10866);s.exports=class ObjectElement extends _{constructor(s,o,i){super(s||[],o,i),this.element="object"}primitive(){return"object"}toValue(){return this.content.reduce(((s,o)=>(s[o.key.toValue()]=o.value?o.value.toValue():void 0,s)),{})}get(s){const o=this.getMember(s);if(o)return o.value}getMember(s){if(void 0!==s)return this.content.find((o=>o.key.toValue()===s))}remove(s){let o=null;return this.content=this.content.filter((i=>i.key.toValue()!==s||(o=i,!1))),o}getKey(s){const o=this.getMember(s);if(o)return o.key}set(s,o){if(u(s))return Object.keys(s).forEach((o=>{this.set(o,s[o])})),this;const i=s,a=this.getMember(i);return a?a.value=o:this.content.push(new w(i,o)),this}keys(){return this.content.map((s=>s.key.toValue()))}values(){return this.content.map((s=>s.value.toValue()))}hasKey(s){return this.content.some((o=>o.key.equals(s)))}items(){return this.content.map((s=>[s.key.toValue(),s.value.toValue()]))}map(s,o){return this.content.map((i=>s.bind(o)(i.value,i.key,i)))}compactMap(s,o){const i=[];return this.forEach(((a,u,_)=>{const w=s.bind(o)(a,u,_);w&&i.push(w)})),i}filter(s,o){return new x(this.content).filter(s,o)}reject(s,o){return this.filter(a(s),o)}forEach(s,o){return this.content.forEach((i=>s.bind(o)(i.value,i.key,i)))}}},71167(s,o,i){const a=i(10316);s.exports=class StringElement extends a{constructor(s,o,i){super(s,o,i),this.element="string"}primitive(){return"string"}get length(){return this.content.length}}},75147(s,o,i){const a=i(85105);s.exports=class JSON06Serialiser extends a{serialise(s){if(!(s instanceof this.namespace.elements.Element))throw new TypeError(`Given element \`${s}\` is not an Element instance`);let o;s._attributes&&s.attributes.get("variable")&&(o=s.attributes.get("variable"));const i={element:s.element};s._meta&&s._meta.length>0&&(i.meta=this.serialiseObject(s.meta));const a="enum"===s.element||-1!==s.attributes.keys().indexOf("enumerations");if(a){const o=this.enumSerialiseAttributes(s);o&&(i.attributes=o)}else if(s._attributes&&s._attributes.length>0){let{attributes:a}=s;a.get("metadata")&&(a=a.clone(),a.set("meta",a.get("metadata")),a.remove("metadata")),"member"===s.element&&o&&(a=a.clone(),a.remove("variable")),a.length>0&&(i.attributes=this.serialiseObject(a))}if(a)i.content=this.enumSerialiseContent(s,i);else if(this[`${s.element}SerialiseContent`])i.content=this[`${s.element}SerialiseContent`](s,i);else if(void 0!==s.content){let a;o&&s.content.key?(a=s.content.clone(),a.key.attributes.set("variable",o),a=this.serialiseContent(a)):a=this.serialiseContent(s.content),this.shouldSerialiseContent(s,a)&&(i.content=a)}else this.shouldSerialiseContent(s,s.content)&&s instanceof this.namespace.elements.Array&&(i.content=[]);return i}shouldSerialiseContent(s,o){return"parseResult"===s.element||"httpRequest"===s.element||"httpResponse"===s.element||"category"===s.element||"link"===s.element||void 0!==o&&(!Array.isArray(o)||0!==o.length)}refSerialiseContent(s,o){return delete o.attributes,{href:s.toValue(),path:s.path.toValue()}}sourceMapSerialiseContent(s){return s.toValue()}dataStructureSerialiseContent(s){return[this.serialiseContent(s.content)]}enumSerialiseAttributes(s){const o=s.attributes.clone(),i=o.remove("enumerations")||new this.namespace.elements.Array([]),a=o.get("default");let u=o.get("samples")||new this.namespace.elements.Array([]);if(a&&a.content&&(a.content.attributes&&a.content.attributes.remove("typeAttributes"),o.set("default",new this.namespace.elements.Array([a.content]))),u.forEach((s=>{s.content&&s.content.element&&s.content.attributes.remove("typeAttributes")})),s.content&&0!==i.length&&u.unshift(s.content),u=u.map((s=>s instanceof this.namespace.elements.Array?[s]:new this.namespace.elements.Array([s.content]))),u.length&&o.set("samples",u),o.length>0)return this.serialiseObject(o)}enumSerialiseContent(s){if(s._attributes){const o=s.attributes.get("enumerations");if(o&&o.length>0)return o.content.map((s=>{const o=s.clone();return o.attributes.remove("typeAttributes"),this.serialise(o)}))}if(s.content){const o=s.content.clone();return o.attributes.remove("typeAttributes"),[this.serialise(o)]}return[]}deserialise(s){if("string"==typeof s)return new this.namespace.elements.String(s);if("number"==typeof s)return new this.namespace.elements.Number(s);if("boolean"==typeof s)return new this.namespace.elements.Boolean(s);if(null===s)return new this.namespace.elements.Null;if(Array.isArray(s))return new this.namespace.elements.Array(s.map(this.deserialise,this));const o=this.namespace.getElementClass(s.element),i=new o;i.element!==s.element&&(i.element=s.element),s.meta&&this.deserialiseObject(s.meta,i.meta),s.attributes&&this.deserialiseObject(s.attributes,i.attributes);const a=this.deserialiseContent(s.content);if(void 0===a&&null!==i.content||(i.content=a),"enum"===i.element){i.content&&i.attributes.set("enumerations",i.content);let s=i.attributes.get("samples");if(i.attributes.remove("samples"),s){const a=s;s=new this.namespace.elements.Array,a.forEach((a=>{a.forEach((a=>{const u=new o(a);u.element=i.element,s.push(u)}))}));const u=s.shift();i.content=u?u.content:void 0,i.attributes.set("samples",s)}else i.content=void 0;let a=i.attributes.get("default");if(a&&a.length>0){a=a.get(0);const s=new o(a);s.element=i.element,i.attributes.set("default",s)}}else if("dataStructure"===i.element&&Array.isArray(i.content))[i.content]=i.content;else if("category"===i.element){const s=i.attributes.get("meta");s&&(i.attributes.set("metadata",s),i.attributes.remove("meta"))}else"member"===i.element&&i.key&&i.key._attributes&&i.key._attributes.getValue("variable")&&(i.attributes.set("variable",i.key.attributes.get("variable")),i.key.attributes.remove("variable"));return i}serialiseContent(s){if(s instanceof this.namespace.elements.Element)return this.serialise(s);if(s instanceof this.namespace.KeyValuePair){const o={key:this.serialise(s.key)};return s.value&&(o.value=this.serialise(s.value)),o}return s&&s.map?s.map(this.serialise,this):s}deserialiseContent(s){if(s){if(s.element)return this.deserialise(s);if(s.key){const o=new this.namespace.KeyValuePair(this.deserialise(s.key));return s.value&&(o.value=this.deserialise(s.value)),o}if(s.map)return s.map(this.deserialise,this)}return s}shouldRefract(s){return!!(s._attributes&&s.attributes.keys().length||s._meta&&s.meta.keys().length)||"enum"!==s.element&&(s.element!==s.primitive()||"member"===s.element)}convertKeyToRefract(s,o){return this.shouldRefract(o)?this.serialise(o):"enum"===o.element?this.serialiseEnum(o):"array"===o.element?o.map((o=>this.shouldRefract(o)||"default"===s?this.serialise(o):"array"===o.element||"object"===o.element||"enum"===o.element?o.children.map((s=>this.serialise(s))):o.toValue())):"object"===o.element?(o.content||[]).map(this.serialise,this):o.toValue()}serialiseEnum(s){return s.children.map((s=>this.serialise(s)))}serialiseObject(s){const o={};return s.forEach(((s,i)=>{if(s){const a=i.toValue();o[a]=this.convertKeyToRefract(a,s)}})),o}deserialiseObject(s,o){Object.keys(s).forEach((i=>{o.set(i,this.deserialise(s[i]))}))}}},85105(s){s.exports=class JSONSerialiser{constructor(s){this.namespace=s||new this.Namespace}serialise(s){if(!(s instanceof this.namespace.elements.Element))throw new TypeError(`Given element \`${s}\` is not an Element instance`);const o={element:s.element};s._meta&&s._meta.length>0&&(o.meta=this.serialiseObject(s.meta)),s._attributes&&s._attributes.length>0&&(o.attributes=this.serialiseObject(s.attributes));const i=this.serialiseContent(s.content);return void 0!==i&&(o.content=i),o}deserialise(s){if(!s.element)throw new Error("Given value is not an object containing an element name");const o=new(this.namespace.getElementClass(s.element));o.element!==s.element&&(o.element=s.element),s.meta&&this.deserialiseObject(s.meta,o.meta),s.attributes&&this.deserialiseObject(s.attributes,o.attributes);const i=this.deserialiseContent(s.content);return void 0===i&&null!==o.content||(o.content=i),o}serialiseContent(s){if(s instanceof this.namespace.elements.Element)return this.serialise(s);if(s instanceof this.namespace.KeyValuePair){const o={key:this.serialise(s.key)};return s.value&&(o.value=this.serialise(s.value)),o}if(s&&s.map){if(0===s.length)return;return s.map(this.serialise,this)}return s}deserialiseContent(s){if(s){if(s.element)return this.deserialise(s);if(s.key){const o=new this.namespace.KeyValuePair(this.deserialise(s.key));return s.value&&(o.value=this.deserialise(s.value)),o}if(s.map)return s.map(this.deserialise,this)}return s}serialiseObject(s){const o={};if(s.forEach(((s,i)=>{s&&(o[i.toValue()]=this.serialise(s))})),0!==Object.keys(o).length)return o}deserialiseObject(s,o){Object.keys(s).forEach((i=>{o.set(i,this.deserialise(s[i]))}))}}},76578(s){"use strict";s.exports=["Float16Array","Float32Array","Float64Array","Int8Array","Int16Array","Int32Array","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array"]},65606(s){var o,i,a=s.exports={};function defaultSetTimout(){throw new Error("setTimeout has not been defined")}function defaultClearTimeout(){throw new Error("clearTimeout has not been defined")}function runTimeout(s){if(o===setTimeout)return setTimeout(s,0);if((o===defaultSetTimout||!o)&&setTimeout)return o=setTimeout,setTimeout(s,0);try{return o(s,0)}catch(i){try{return o.call(null,s,0)}catch(i){return o.call(this,s,0)}}}!function(){try{o="function"==typeof setTimeout?setTimeout:defaultSetTimout}catch(s){o=defaultSetTimout}try{i="function"==typeof clearTimeout?clearTimeout:defaultClearTimeout}catch(s){i=defaultClearTimeout}}();var u,_=[],w=!1,x=-1;function cleanUpNextTick(){w&&u&&(w=!1,u.length?_=u.concat(_):x=-1,_.length&&drainQueue())}function drainQueue(){if(!w){var s=runTimeout(cleanUpNextTick);w=!0;for(var o=_.length;o;){for(u=_,_=[];++x1)for(var i=1;i_)throw new RangeError("requested too many random bytes");var i=w.allocUnsafe(s);if(s>0)if(s>u)for(var C=0;C=0||(u[i]=s[i]);return u}(s,o);if(Object.getOwnPropertySymbols){var _=Object.getOwnPropertySymbols(s);for(a=0;a<_.length;a++)i=_[a],o.indexOf(i)>=0||Object.prototype.propertyIsEnumerable.call(s,i)&&(u[i]=s[i])}return u}function ownKeys(s,o){var i=Object.keys(s);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(s);o&&(a=a.filter((function(o){return Object.getOwnPropertyDescriptor(s,o).enumerable}))),i.push.apply(i,a)}return i}function _objectSpread(s){for(var o=1;o=a?i.notify(s):o.length>u.length&&i.notify(_objectSpread(_objectSpread({},s),{},{target:_objectSpread(_objectSpread({},s.target),{},{value:""})}))}))})),_defineProperty(_assertThisInitialized(i),"onKeyDown",(function(s){"Enter"===s.key&&i.forceNotify(s);var o=i.props.onKeyDown;o&&(s.persist(),o(s))})),_defineProperty(_assertThisInitialized(i),"onBlur",(function(s){i.forceNotify(s);var o=i.props.onBlur;o&&(s.persist(),o(s))})),_defineProperty(_assertThisInitialized(i),"createNotifier",(function(s){if(s<0)i.notify=function(){return null};else if(0===s)i.notify=i.doNotify;else{var o=(0,u.default)((function(s){i.isDebouncing=!1,i.doNotify(s)}),s);i.notify=function(s){i.isDebouncing=!0,o(s)},i.flush=function(){return o.flush()},i.cancel=function(){i.isDebouncing=!1,o.cancel()}}})),_defineProperty(_assertThisInitialized(i),"doNotify",(function(){i.props.onChange.apply(void 0,arguments)})),_defineProperty(_assertThisInitialized(i),"forceNotify",(function(s){var o=i.props.debounceTimeout;if(i.isDebouncing||!(o>0)){i.cancel&&i.cancel();var a=i.state.value,u=i.props.minLength;a.length>=u?i.doNotify(s):i.doNotify(_objectSpread(_objectSpread({},s),{},{target:_objectSpread(_objectSpread({},s.target),{},{value:a})}))}})),i.isDebouncing=!1,i.state={value:void 0===s.value||null===s.value?"":s.value};var a=i.props.debounceTimeout;return i.createNotifier(a),i}return function _createClass(s,o,i){return o&&_defineProperties(s.prototype,o),i&&_defineProperties(s,i),Object.defineProperty(s,"prototype",{writable:!1}),s}(DebounceInput,[{key:"componentDidUpdate",value:function componentDidUpdate(s){if(!this.isDebouncing){var o=this.props,i=o.value,a=o.debounceTimeout,u=s.debounceTimeout,_=s.value,w=this.state.value;void 0!==i&&_!==i&&w!==i&&this.setState({value:i}),a!==u&&this.createNotifier(a)}}},{key:"componentWillUnmount",value:function componentWillUnmount(){this.flush&&this.flush()}},{key:"render",value:function render(){var s,o,i=this.props,u=i.element,w=(i.onChange,i.value,i.minLength,i.debounceTimeout,i.forceNotifyByEnter),x=i.forceNotifyOnBlur,C=i.onKeyDown,j=i.onBlur,L=i.inputRef,B=_objectWithoutProperties(i,_),$=this.state.value;s=w?{onKeyDown:this.onKeyDown}:C?{onKeyDown:C}:{},o=x?{onBlur:this.onBlur}:j?{onBlur:j}:{};var z=L?{ref:L}:{};return a.default.createElement(u,_objectSpread(_objectSpread(_objectSpread(_objectSpread({},B),{},{onChange:this.onChange,value:$},s),o),z))}}]),DebounceInput}(a.default.PureComponent);o.DebounceInput=w,_defineProperty(w,"defaultProps",{element:"input",type:"text",onKeyDown:void 0,onBlur:void 0,value:void 0,minLength:0,debounceTimeout:100,forceNotifyByEnter:!0,forceNotifyOnBlur:!0,inputRef:void 0})},24677(s,o,i){"use strict";var a=i(81214).DebounceInput;a.DebounceInput=a,s.exports=a},22551(s,o,i){"use strict";var a=i(96540),u=i(69982);function p(s){for(var o="https://reactjs.org/docs/error-decoder.html?invariant="+s,i=1;i
+ {/* FR-17 — banner auto-save error ยกไว้เหนือ toggle (เดิมซ่อนอยู่ใน SaveControls ของแท็บ + versioning): auto-save รันทุกแท็บ การเขียนค่าทดสอบของแท็บ template ก็ทำให้ dirty ได้ + จึงต้องเห็น error แม้อยู่อีกแท็บ */} + {(phase === "untracked" || phase === "tracking") && autoSaveError ? ( + {autoSaveError} + ) : null} + + {/* toggle แท็บ — แสดงเฉพาะ phase ที่เข้าถึงเอกสารแล้ว (checking/signed-out ยังไม่มีอะไรให้จัดการ) + ปิดทั้งสองปุ่มระหว่าง flow ที่เขียนเอกสาร: saving/starting ของ versioning (กัน progress หาย + กลาง flow — handler อยู่ที่ component นี้ flow ยังรันจนจบ แค่หน้าตา), saveBusy ของบันทึก + local/ปิดเอกสาร และ templateBusy ที่ panel รายงานผ่าน onBusyChange — กันเครื่องมือ template + แข่งแก้เอกสารกลาง stamp/capture ของ versionFlow หรือกลับกัน (panel unmount แล้ว promise + ของ service ก็ยังเขียนเอกสารต่อ ต้องปิดทางสลับตั้งแต่ต้น) */} + {(phase === "untracked" || phase === "tracking") && ( +
+ + +
+ )} + {phase === "checking" ? (
@@ -443,7 +499,7 @@ const TaskpaneApp: React.FC<{ demo?: "untracked" | "tracking" }> = ({ demo }) =>
) : null} - {phase === "untracked" ? ( + {phase === "untracked" && activeTab === "versioning" ? (

เอกสารนี้ยังไม่ได้ติดตาม version

@@ -470,7 +526,6 @@ const TaskpaneApp: React.FC<{ demo?: "untracked" | "tracking" }> = ({ demo }) => busy={saveBusy} status={saveStatus} error={saveError} - autoSaveError={autoSaveError} closeSupported={closeSupported} onSave={() => void handleSaveOnly()} onSaveAndClose={() => void handleSaveAndClose()} @@ -480,7 +535,7 @@ const TaskpaneApp: React.FC<{ demo?: "untracked" | "tracking" }> = ({ demo }) =>

) : null} - {phase === "tracking" ? ( + {phase === "tracking" && activeTab === "versioning" ? ( /* ห้ามใส่ items-start — ลูกทุกตัวจะหดตามเนื้อหา (MessageBar/history list ไม่เต็มความกว้าง pane) */
{/* กลุ่มข้อความสถานะ */} @@ -512,7 +567,6 @@ const TaskpaneApp: React.FC<{ demo?: "untracked" | "tracking" }> = ({ demo }) => busy={saveBusy} status={saveStatus} error={saveError} - autoSaveError={autoSaveError} closeSupported={closeSupported} onSave={() => void handleSaveOnly()} onSaveAndClose={() => void handleSaveAndClose()} @@ -535,43 +589,56 @@ const TaskpaneApp: React.FC<{ demo?: "untracked" | "tracking" }> = ({ demo }) =>
) : null} - {/* กดยืนยันแล้วปิด dialog ทันที — progress/ผลลัพธ์แสดงที่ pane (dialog ค้าง = บังผลลัพธ์ - + เสี่ยงกดยืนยันซ้ำสร้าง version ซ้ำ; busy ใน dialog เหลือเป็น defense ชั่วขณะก่อน re-render) */} - { - setConfirmOpen(false); - void handleSaveAndVersion(message); - }} - {...(closeSupported - ? { - onConfirmClose: (message: string) => { - setConfirmOpen(false); - void handleSaveAndVersion(message, true); - }, - } - : {})} - onDismiss={() => setConfirmOpen(false)} - /> + {/* แท็บเครื่องมือ Template — state ทั้งหมดอยู่ใน panel เอง (snapshot ชั่วคราวของเอกสาร + สลับแท็บแล้ว unmount = ทิ้งค่าทิ้งโดยตั้งใจ) */} + {(phase === "untracked" || phase === "tracking") && activeTab === "template" ? ( + + ) : null} - {/* FR-13 — Start Versioning ใช้ dialog เดียวกัน: ยืนยัน + พิมพ์ commit message ของ v1 ก่อนเริ่ม track */} - { - setStartConfirmOpen(false); - void handleStartVersioning(message); - }} - onDismiss={() => setStartConfirmOpen(false)} - /> + {/* dialog ทั้งสองเป็นของ flow versioning — render เฉพาะแท็บ versioning (ตอน dialog เปิด + overlay fixed inset-0 + Tab trap อยู่แล้ว ปุ่ม toggle แตะไม่ได้ จึงไม่มี path ที่ dialog + เปิดค้างขณะสลับแท็บ) */} + {(phase === "untracked" || phase === "tracking") && activeTab === "versioning" ? ( + <> + {/* กดยืนยันแล้วปิด dialog ทันที — progress/ผลลัพธ์แสดงที่ pane (dialog ค้าง = บังผลลัพธ์ + + เสี่ยงกดยืนยันซ้ำสร้าง version ซ้ำ; busy ใน dialog เหลือเป็น defense ชั่วขณะก่อน re-render) */} + { + setConfirmOpen(false); + void handleSaveAndVersion(message); + }} + {...(closeSupported + ? { + onConfirmClose: (message: string) => { + setConfirmOpen(false); + void handleSaveAndVersion(message, true); + }, + } + : {})} + onDismiss={() => setConfirmOpen(false)} + /> + + {/* FR-13 — Start Versioning ใช้ dialog เดียวกัน: ยืนยัน + พิมพ์ commit message ของ v1 ก่อนเริ่ม track */} + { + setStartConfirmOpen(false); + void handleStartVersioning(message); + }} + onDismiss={() => setStartConfirmOpen(false)} + /> + + ) : null} ); }; diff --git a/components/wordaddin/TemplateToolsPanel.tsx b/components/wordaddin/TemplateToolsPanel.tsx new file mode 100644 index 0000000..3267edf --- /dev/null +++ b/components/wordaddin/TemplateToolsPanel.tsx @@ -0,0 +1,411 @@ +"use client"; + +import { useEffect, useMemo, useState } from "react"; +import { CornerDownLeft, Crosshair, FilePlus, LoaderCircle, PenLine, ScanSearch, WrapText } from "lucide-react"; + +import { + applyTestValues, + insertBreakAtSelection, + insertParagraphAtSelection, + listContentControls, + selectContentControl, + supportsAllContentControlTypes, + type ScannedField, +} from "@/wordaddin/services/templateService"; +import { getErrorMessage } from "@/wordaddin/services/errors"; + +import { MessageBar } from "./MessageBar"; + +/** + * แท็บ "เครื่องมือ Template" (แท็บที่สองของ taskpane) — เครื่องมือช่วยทำ template + * สำหรับคนออกแบบเอกสาร ใช้ได้ทั้ง phase untracked และ tracking เพราะไม่แตะ backend/tracking: + * 1. สแกน Content Controls ทั้งเอกสาร + เตือน tag ว่าง/ซ้ำ + กระโดดไปเลือกช่อง + * 2. กรอกค่าทดสอบทุกช่องแล้วเขียนกลับเข้าเอกสารจริง (ดูผลจัดหน้า/ตัดบรรทัดทันที + * โดยไม่ต้อง render ผ่าน /api/template/render) + * 3. แทรกย่อหน้า/ตัวแบ่งบรรทัด/ตัวแบ่งหน้าที่ cursor ตอนจัดโครงสร้าง + * + * state ทั้งหมดถือภายใน — เป็น snapshot ชั่วคราวของเอกสาร: สลับแท็บแล้ว unmount = ทิ้ง + * ค่าทิ้งโดยตั้งใจ (เก็บไว้ก็ stale เสียเองเมื่อผู้ใช้แก้เอกสารระหว่างนั้น) + */ + +const BTN_PRIMARY = + "focus-ring inline-flex items-center gap-1.5 rounded-md bg-primary px-4 py-2.5 text-sm font-medium text-white transition hover:-translate-y-px hover:bg-primary-hover hover:shadow-btn-glow disabled:pointer-events-none disabled:opacity-50"; +const BTN_SECONDARY = + "focus-ring inline-flex items-center gap-1.5 rounded-md border border-line bg-white px-3 py-1.5 text-sm text-ink transition hover:border-primary hover:text-primary disabled:pointer-events-none disabled:opacity-50"; +/** px-2 py-1.5 + text-xs ≈ สูง 30px — พ้นเกณฑ์ touch target 24px (WCAG 2.5.8) เหมือน VersionHistoryPanel */ +const BTN_ACTION = + "focus-ring inline-flex items-center gap-1 rounded-md px-2 py-1.5 text-xs text-ink-soft transition hover:bg-primary/[0.06] hover:text-primary disabled:pointer-events-none disabled:opacity-50"; + +/** ปุ่มแทรกองค์ประกอบ (ย่อหน้า/ตัวแบ่ง) — icon บน label ล่าง 3 ปุ่มต่อแถว */ +const BTN_INSERT = + "focus-ring flex flex-col items-center gap-1 rounded-md border border-line bg-white px-1 py-2 text-xs text-ink transition hover:border-primary hover:text-primary disabled:pointer-events-none disabled:opacity-50"; + +function TemplateToolsPanel({ onBusyChange }: { onBusyChange?: (busy: boolean) => void }) { + /** null = ยังไม่เคยสแกน (empty state ต่างจาก [] = สแกนแล้วไม่มีช่อง) */ + const [fields, setFields] = useState(null); + const [scanning, setScanning] = useState(false); + const [applying, setApplying] = useState(false); + const [insertingParagraph, setInsertingParagraph] = useState(false); + const [insertingBreak, setInsertingBreak] = useState<"line" | "page" | null>(null); + /** ค่าฟอร์มต่อ tag — key คือ tag ที่ trim แล้วไม่ว่างและไม่ซ้ำ */ + const [values, setValues] = useState>({}); + const [error, setError] = useState(null); + const [warning, setWarning] = useState(null); + const [success, setSuccess] = useState(null); + /** คำเตือนความสามารถของ host (WordApi <1.5 เห็นเฉพาะ rich text CC) — แยก state จาก warning + * ผลลัพธ์ของ operation เพราะเป็นข้อจำกัดของ host ตลอด session ห้ามโดน banner อื่นล้าง/ทับ */ + const [capabilityWarning, setCapabilityWarning] = useState(null); + + const busy = scanning || applying || insertingParagraph || insertingBreak !== null; + + /** รายงาน busy ให้ TaskpaneApp ปิด toggle แท็บระหว่าง operation ที่เขียนเอกสาร — กันสลับไปแท็บ + * Versioning กด Save & Version แล้ว stamp/capture ซ้อน operation ที่ยังรันอยู่ (panel unmount + * แล้ว promise ของ service ก็ยังเขียนเอกสารต่อ) · cleanup ส่ง false ตอน unmount กันปุ่มค้าง disabled */ + useEffect(() => { + onBusyChange?.(busy); + return () => onBusyChange?.(false); + }, [busy, onBusyChange]); + + /** tag ที่ไม่ว่างไม่ซ้ำ (เอาตัวแรกของ tag ซ้ำ) — เป็น key ของฟอร์มค่าทดสอบ */ + const uniqueTags = useMemo(() => { + if (!fields) return []; + const seen = new Set(); + const tags: string[] = []; + for (const field of fields) { + const tag = field.tag.trim(); + if (tag === "" || seen.has(tag)) continue; + seen.add(tag); + tags.push(tag); + } + return tags; + }, [fields]); + + const emptyTagCount = useMemo( + () => (fields ? fields.filter((field) => field.tag.trim() === "").length : 0), + [fields], + ); + + /** tag ที่มีมากกว่า 1 ช่อง (เทียบหลัง trim) — render pipeline อ่านค่าเฉพาะช่องแรกของ tag จึงต้องเตือน */ + const duplicateTags = useMemo(() => { + if (!fields) return []; + const counts = new Map(); + for (const field of fields) { + const tag = field.tag.trim(); + if (tag === "") continue; + counts.set(tag, (counts.get(tag) ?? 0) + 1); + } + return Array.from(counts.entries()) + .filter(([, count]) => count > 1) + .map(([tag]) => tag); + }, [fields]); + + /** มีอย่างน้อยหนึ่งช่องที่กรอกค่า — กันกดเขียนตอนฟอร์มว่างทั้งหมด (service จะไม่แตะอะไรเลย + * แล้ว UI ไปตีความ appliedCount 0 ว่า "ช่องถูกลบไป" ซึ่งเข้าใจผิด) */ + const hasFilledValue = useMemo( + () => uniqueTags.some((tag) => (values[tag] ?? "").trim() !== ""), + [uniqueTags, values], + ); + + /** + * สแกนเอกสาร + เติมค่าฟอร์ม + * - merge=false (กดปุ่มสแกนเอง): seed ฟอร์มใหม่ทั้งหมดจากค่าปัจจุบันของช่องแรกของแต่ละ tag + * พร้อมรีเซ็ต success/warning เดิม (ผลลัพธ์รอบก่อนไม่เกี่ยวกับ list รอบใหม่แล้ว) + * - merge=true (หลังเขียนค่า): คงค่าที่ผู้ใช้พิมพ์ค้างไว้ เติมเฉพาะ tag ใหม่ที่ยังไม่มีในฟอร์ม + * และตัด tag ที่หายไปจากเอกสารทิ้ง — ทั้งสองอยู่บนผลสแกนรอบใหม่เสมอ + * **ไม่แตะ success/warning** เพราะ handler ที่เรียกจัด banner ผลลัพธ์ของตัวเองอยู่ + * (คำเตือนความสามารถของ host แยก state ต่างหาก จึงไม่ทับกัน) + */ + const runScan = async (merge: boolean) => { + setScanning(true); + setError(null); + if (!merge) { + setSuccess(null); + setWarning(null); + } + try { + const scanned = await listContentControls(); + setFields(scanned); + // Word ต่ำกว่า WordApi 1.5 (เช่น Office 2019) มองเห็นเฉพาะ rich text CC — template ของศาล + // ส่วนใหญ่เป็น plain text CC; เป็นข้อจำกัดของ host ตลอด session จึงเช็คให้ทุกรอบสแกน + setCapabilityWarning( + supportsAllContentControlTypes() + ? null + : "Word รุ่นนี้มองเห็นเฉพาะช่องแบบ rich text — ช่องแบบ plain text ต้องใช้ Word ที่รองรับ WordApi 1.5 (เช่น Microsoft 365) จึงแสดงและเขียนค่าได้", + ); + setValues((previous) => { + // สร้างใหม่จาก tag ของผลสแกนรอบนี้เท่านั้น — merge เอาแค่ค่าเดิมของ tag ที่ยังอยู่ + // (tag ที่ถูกลบจากเอกสารตัดทิ้ง ไม่ให้ค้างไปโดน missingTags รอบเขียนถัด ๆ ไป) + const next: Record = {}; + const seen = new Set(); + for (const field of scanned) { + const tag = field.tag.trim(); + if (tag === "" || seen.has(tag)) continue; + seen.add(tag); + next[tag] = merge && tag in previous ? previous[tag] : field.text; + } + return next; + }); + } catch (scanError) { + setError(getErrorMessage(scanError, "สแกนช่องในเอกสารไม่สำเร็จ")); + } finally { + setScanning(false); + } + }; + + const handleView = async (id: number) => { + setError(null); + try { + await selectContentControl(id); + } catch (viewError) { + setError(getErrorMessage(viewError, "เลือกช่องไม่สำเร็จ")); + } + }; + + /** แทรกย่อหน้าว่างที่ cursor — เห็นผลในเอกสารทันทีจึงไม่ต้องแจ้ง success รบกวน */ + const handleInsertParagraph = async () => { + setInsertingParagraph(true); + setError(null); + try { + await insertParagraphAtSelection(); + } catch (insertError) { + setError(getErrorMessage(insertError, "แทรกย่อหน้าไม่สำเร็จ")); + } finally { + setInsertingParagraph(false); + } + }; + + const handleInsertBreak = async (breakKind: "line" | "page") => { + setInsertingBreak(breakKind); + setError(null); + try { + await insertBreakAtSelection(breakKind); + } catch (insertError) { + setError( + getErrorMessage(insertError, breakKind === "page" ? "แทรกตัวแบ่งหน้าไม่สำเร็จ" : "แทรกตัวแบ่งบรรทัดไม่สำเร็จ"), + ); + } finally { + setInsertingBreak(null); + } + }; + + const handleApply = async () => { + setApplying(true); + setError(null); + setSuccess(null); + setWarning(null); + try { + const result = await applyTestValues(values); + if (result.appliedCount === 0 && result.failures.length === 0) { + setWarning("ไม่พบช่องที่ตรงกับค่าที่กรอก (อาจถูกลบไปหลังสแกน) — กดสแกนใหม่"); + } else { + if (result.appliedCount > 0) { + setSuccess(`เขียนค่าทดสอบลงแล้ว ${result.appliedCount} ช่อง`); + } + const problems: string[] = []; + if (result.missingTags.length > 0) { + problems.push(`ไม่พบช่อง tag: ${result.missingTags.join(", ")}`); + } + if (result.failures.length > 0) { + const first = result.failures[0]; + problems.push( + `เขียนไม่สำเร็จ ${result.failures.length} tag (${result.failures + .map((failure) => failure.tag) + .join(", ")}) — ${first.reason}`, + ); + } + if (problems.length > 0) { + setWarning( + `${problems.join(" · ")} — ถ้าเอกสารนี้ถูกล็อค (Review → Restrict Editing) ให้ปลดก่อนจึงเขียนค่าได้`, + ); + } + } + // สแกนใหม่ให้ค่าย่อใน list ตรงเอกสารจริง — คงค่าฟอร์มไว้ให้แก้ต่อ + await runScan(true); + } catch (applyError) { + setError(getErrorMessage(applyError, "เขียนค่าลงเอกสารไม่สำเร็จ")); + } finally { + setApplying(false); + } + }; + + return ( + /* ห้ามใส่ items-start — ลูกทุกตัวจะหดตามเนื้อหา (เหมือน branch tracking ของ TaskpaneApp) */ +
+ {error ? {error} : null} + {warning ? {warning} : null} + {success ? {success} : null} + {capabilityWarning ? {capabilityWarning} : null} + + {/* §1 ตรวจสอบ Content Controls */} +
+

ตรวจสอบ Content Controls

+ {fields ? {fields.length} ช่อง : null} +
+ + + {fields && (emptyTagCount > 0 || duplicateTags.length > 0) ? ( + + {emptyTagCount > 0 ? `มี ${emptyTagCount} ช่องที่ยังไม่ได้ตั้ง tag` : ""} + {emptyTagCount > 0 && duplicateTags.length > 0 ? " · " : ""} + {duplicateTags.length > 0 + ? `tag ซ้ำ: ${duplicateTags.join(", ")} — ตอน render ระบบอ่านเฉพาะช่องแรกของ tag` + : ""} + + ) : null} + + {!fields && !scanning ? ( +

กดสแกนเพื่อดูรายการช่องในเอกสาร

+ ) : null} + {fields && fields.length === 0 && !scanning ? ( +

ไม่พบ content control ในเอกสาร

+ ) : null} + + {fields && fields.length > 0 ? ( +
    + {fields.length > 1 ? : null} + {fields.map((field, index) => ( +
  1. + +
    + {index + 1}. + + {field.tag.trim() === "" ? "(ไม่มี tag)" : field.tag.trim()} + + + {field.kind === "inline" ? "ในบรรทัด" : "บล็อก"} + + + + +
    +

    + {field.text.trim() === "" ? "(ว่าง)" : field.text} +

    +
  2. + ))} +
+ ) : null} + + {/* §2 กรอกค่าทดสอบ */} + {uniqueTags.length > 0 ? ( + <> +

กรอกค่าทดสอบ

+

+ แก้ค่าแล้วกดเขียนลงเอกสารเพื่อดูผลจัดหน้าจริง — ช่องที่เว้นว่างจะไม่ถูกแตะ +

+
+ {uniqueTags.map((tag, index) => ( +
+ + setValues((previous) => ({ ...previous, [tag]: event.target.value }))} + disabled={busy} + /> +
+ ))} +
+ + + ) : fields ? ( +

ไม่มีช่องที่ตั้ง tag ไว้ — สแกนก่อนจึงจะกรอกค่าทดสอบได้

+ ) : ( +

สแกนก่อนเพื่อกรอกค่าทดสอบ

+ )} + + {/* §3 ปุ่มแทรกองค์ประกอบหน้า — เสริมตอนจัดโครงสร้าง template (ตรวจด้วย formatting marks ตามปกติ: + Ctrl+Shift+8 — สลับผ่าน add-in ไม่ได้เพราะ Office.js ไม่มี API) */} +

แทรกย่อหน้า / ตัวแบ่ง

+
+ + + +
+
+ ); +} + +export default TemplateToolsPanel; diff --git a/docs/addin-ui-flow.md b/docs/addin-ui-flow.md index df079ad..83ef6da 100644 --- a/docs/addin-ui-flow.md +++ b/docs/addin-ui-flow.md @@ -10,6 +10,8 @@ Task pane ทั้งหมดเป็น state machine เดียว **ไม่มี routing** — component เดียว (`App`) สลับหน้าตาตาม `phase`: +ตั้งแต่ 2026-10-19 taskpane มี **2 แท็บ** (toggle กลุ่มปุ่ม `aria-pressed` ใต้ header — แสดงเฉพาะ phase `untracked`/`tracking`): **Versioning** คือทุกอย่างในเอกสารนี้ตามเดิม ส่วน **เครื่องมือ Template** (ดูหัวข้อ 10) คือเครื่องมือจัดการ Content Controls ฝั่ง Office.js ล้วน ไม่ยิง backend จึงใช้ได้ทั้งสอง phase ปุ่ม toggle **ทั้งสองปิดระหว่าง flow ที่เขียนเอกสาร** — `saving || starting` (versioning), `saveBusy` (บันทึก local/ปิดเอกสาร), `templateBusy` (operation ของแท็บ template รายงานผ่าน `onBusyChange` — กันสลับกลาง operation แล้ว flow versioning มา stamp/capture ซ้อน หรือกลับกัน; code review 2026-10-20) · สถานะแท็บรีเซ็ตกลับ `versioning` ตอน sign out / AuthRequiredError กัน surprise หลัง login ใหม่ · banner auto-save error (FR-17) ยกไว้เหนือ toggle ให้เห็นทั้งสองแท็บ + ``` checking ──(ไม่มี token)──────────────→ signed-out │ @@ -28,6 +30,7 @@ State ที่ UI ถือนอกเหนือจาก phase: | State | ความหมาย | |---|---| +| `activeTab` | แท็บที่เปิดอยู่ `versioning` (default) / `template` — ดูหัวข้อ 10 | | `username` | ชื่อผู้ใช้จาก token (แสดงใน header) | | `documentId` | tracking id ของเอกสาร (มีเฉพาะ `tracking`) | | `documentTemplateId` | template ต้นทางของ**ไฟล์ร่าง** (มีเฉพาะ `untracked` ที่เป็นไฟล์ร่าง — FR-16) | @@ -104,7 +107,7 @@ Component ใช้ร่วมทั้ง `untracked` และ `tracking` — - `saveBusy && status === "saving"` → `Spinner "กำลังบันทึก…"` - `!saveBusy && status === "saved"` → text จางๆ `บันทึกแล้ว` — ค้างไว้จนกดบันทึกรอบใหม่ (ไม่ reset เมื่อแก้ไขเอกสาร ไม่มี timeout) - `saveError` → MessageBar **error** - - `autoSaveError` → MessageBar **warning** (default: "บันทึกอัตโนมัติไม่สำเร็จ — ลองกด \"บันทึก\" ด้วยตนเอง") + - `autoSaveError` → MessageBar **warning** (default: "บันทึกอัตโนมัติไม่สำเร็จ — ลองกด \"บันทึก\" ด้วยตนเอง") — **ย้ายไป render เหนือ toggle แท็บใน TaskpaneApp (2026-10-20)** ให้เห็นทั้งแท็บ Versioning และ Template ไม่มาใน SaveControls แล้ว - ทั้งสองปุ่ม `disabled` เมื่อ `disabled` (flow versioning กำลังรัน) **หรือ** `saveBusy` พฤติกรรมปุ่ม: @@ -272,3 +275,43 @@ response: `{ url: string, expiresAt: string }` — UI เปิด `url` ใน 3. ของเดิมใช้ Fluent UI v9 (`@fluentui/react-components` + `@fluentui/react-icons`) — mapping ไป library อื่น: Button / Dialog / MessageBar / Spinner / Badge / Textarea / Text 4. UI ทั้งหมดภาษาไทย — ข้อความสำเนาได้จากไฟล์ต้นฉบับที่อ้างด้านบน 5. Task pane แคบ (side panel) — layout เป็นคอลัมน์เดียว ปุ่ม/actions ต้องรองรับความกว้างจำกัด +6. id ของ Content Control **ไม่เสถียรข้าม session/coauthor** (docstring ของ Office.js เอง) — เครื่องมือ Template ใช้ id เฉพาะภายในอายุผลสแกนรอบเดียว ห้ามเก็บไปใช้ภายหลัง +7. **การมองเห็นช่องตามรุ่นของ Word**: collection `contentControls` ธรรมดาเห็น**เฉพาะ rich text CC** (docstring ของ `Body.contentControls`) — เครื่องมือ Template จึง feature-detect `WordApi 1.5` (`isSetSupported`) แล้วใช้ `Document.getContentControls()` ที่เห็นทุกชนิด (รวม plain text ซึ่งเป็นส่วนใหญ่ของ template ศาล — เคสจริง: T3 มี plain 26 + rich 1 สแกนเห็นเฉพาะ `detail1`) บน Word ต่ำกว่า 1.5 (เช่น Office 2019 = 1.3 ตาม manifest) มองเห็น/เขียนได้เฉพาะ rich text พร้อม warning แจ้งผู้ใช้ · **ห้ามยก MinVersion ของ manifest** เพื่อปลดข้อจำกัดนี้ (Office 2019 จะโหลด add-in ไม่ขึ้น — บทเรียน 2026-10-16) + +--- + +## 10. แท็บ "เครื่องมือ Template" (2026-10-19) + +เครื่องมือช่วยทำ template สำหรับคนออกแบบเอกสาร — `TemplateToolsPanel` (state ภายในทั้งหมด มีแค่ prop `onBusyChange` รายงาน busy ให้ TaskpaneApp ปิด toggle แท็บ) + service `wordaddin/services/templateService.ts` — **Office.js ล้วน ไม่ยิง backend** (ส่วนใหญ่ WordApi 1.1 · `getByIdOrNullObject` 1.3 · `Document.getContentControls()` 1.5 ผ่าน feature-detect — ดู §9 ข้อ 7) ใช้ได้ทั้ง phase `untracked`/`tracking` · ไม่ auto-scan ตอนเปิดแท็บ (กดปุ่มเท่านั้น) · สลับแท็บแล้ว panel unmount = **ทิ้ง state ทิ้งโดยตั้งใจ** (snapshot เก่าเก็บไว้ก็ stale เสียเอง) · ปุ่มทั้ง panel disable ด้วย `busy` กันกดซ้อน + +### §1 ตรวจสอบ Content Controls + +- ปุ่ม `สแกนช่องในเอกสาร` (icon ScanSearch → spinner + `aria-busy` ตอนรัน) → list timeline แบบ VersionHistoryPanel: ลำดับ · tag (`break-all` + `title` แสดง tag/title/type ดิบ · ช่องไม่มี tag แสดง "(ไม่มี tag)" และจุดโหนดเป็นวงกลมขอบเหลือง) · badge ชนิด "ในบรรทัด"/"บล็อก" (heuristics: enum type ลงท้าย "Inline") · ค่าย่อ truncate 1 บรรทัด + `title` เต็ม · ปุ่ม `ดูช่อง` (icon Crosshair) → `cc.select()` ให้ Word เลื่อนไปเลือกช่อง +- สรุปหัวกลุ่ม: "N ช่อง" +- warning ใต้ปุ่มเมื่อพบปัญหา: "มี N ช่องที่ยังไม่ได้ตั้ง tag" / "tag ซ้ำ: a, b — ตอน render ระบบอ่านเฉพาะช่องแรกของ tag" +- empty 2 แบบ: ยังไม่สแกน ("กดสแกนเพื่อดูรายการช่องในเอกสาร") กับสแกนแล้วว่าง ("ไม่พบ content control ในเอกสาร") + +### §2 กรอกค่าทดสอบ + +- แสดงเฉพาะมีช่องที่ตั้ง tag ≥1 ช่อง (จากผลสแกน): ฟอร์ม label+input ต่อ tag (id `wordaddin-template-value-`) ค่าเริ่ม = ค่าปัจจุบันของช่องแรกของ tag นั้น + ปุ่ม primary `เขียนค่าลงเอกสาร` (icon PenLine → "กำลังเขียนค่า…" ตอนรัน) +- **ช่องที่เว้นว่าง = ไม่ถูกแตะ** (กันเผลอล้างค่าจริง) · เขียนทุก CC ที่ tag ตรง (tag ซ้ำหลายช่อง = เขียนทุกช่องแบบ mail merge) · อ่าน id+tag สดรอบเดียวแล้วเขียน**แยก Word.run ราย tag** — tag ที่ Word ปฏิเสธ fail เฉพาะตัว เหลืออื่นยังเขียนได้ (ผลลัพธ์: `appliedCount` = จำนวนช่องที่เขียนจริง + `missingTags` ไม่พบช่อง + `failures` พร้อม code/errorLocation จาก OfficeExtension.Error) · UI รวมเป็น warning พร้อมชี้นำ "ถ้าเอกสารถูกล็อค (Review → Restrict Editing) ให้ปลดก่อน" — เคสจริงที่เจอ: error `0x800A16C6` บนเอกสารที่ `make-protected.mjs` ล็อคไว้ (readOnly + enforcement) +- ผลลัพธ์: success "เขียนค่าทดสอบลงแล้ว N ช่อง" (N = จำนวนช่องที่เขียนจริง — tag ซ้ำหลายช่องนับทุกช่อง) + warning missingTags ถ้ามี → **สแกนใหม่โดยคงค่าฟอร์ม** (เฉพาะ tag ที่ยังอยู่ในเอกสาร — tag ที่ถูกลบไปตัดทิ้ง) ให้แก้ต่อ · ปุ่ม disabled เมื่อยังไม่มีช่องที่กรอกค่าเลย · หลังเขียนสำเร็จ `notifyDocumentChanged()` ตั้ง debounce auto-save 3 วิ ส่วน banner FR-3 ตั้งเมื่อ periodic tick ถัดไปของ autoSaveService เห็น document dirty (พฤติกรรมเดิมของ service — ไม่ใช่ทันที) + +### §3 แทรกย่อหน้า / ตัวแบ่ง (2026-10-19) + +- 3 ปุ่มแนวตั้ง (icon บน label ล่าง, grid 3 คอลัมน์, `title` อธิบายเต็ม): **ย่อหน้า** (CornerDownLeft — `insertParagraph("", "After")` เหมือนกด Enter) · **แบ่งบรรทัด** (WrapText — `insertBreak("Line", "After")` เหมือน Shift+Enter จัดที่อยู่/รายการในย่อหน้าเดิม) · **แบ่งหน้า** (FilePlus — เหมือน Ctrl+Enter เอกสารหลายหน้า: `insertBreak("Page", "After")` ก่อน ถ้า host ปฏิเสธ E_FAIL 0x80004005 (บางบริบท เช่น cursor ในตาราง — เคสจริงบน T3 2026-10-19) สลับไป `insertOoxml` แบบ w:p สมบูรณ์ครอบ `` — fragment ต้องเป็น block-level เต็มตัว ใช้ run เดี่ยวก็ยัง E_FAIL) +- กดแล้วทำที่ cursor ทันที (ไม่มี success message — เห็นผลในเอกสารเลย) · icon สลับเป็น spinner ตอนรันตัวนั้น · ทุกปุ่ม disable ด้วย `busy` ร่วมกับเครื่องมืออื่น · fallback error: "แทรกย่อหน้าไม่สำเร็จ" / "แทรกตัวแบ่งบรรทัดไม่สำเร็จ" / "แทรกตัวแบ่งหน้าไม่สำเร็จ" + +### หมายเหตุ: ไม่มีปุ่มสลับ formatting marks (และไม่มีชุดแทรกอักขระพิเศษ) + +- **Office.js ไม่มี API สลับแสดง/ซ่อน formatting marks** (¶ ท้ายย่อหน้า/จุดช่องว่าง) — เป็น application setting ของ Word ที่ WordApi ไม่เปิดให้ add-in แตะ ผู้ใช้กด **Ctrl+Shift+8** หรือปุ่ม **¶ บนแท็บ Home** ของ Word เอง (workflow จริงของผู้ใช้: แทรกย่อหน้าเพิ่ม → เปิด marks ตรวจ → ซ่อนกลับ — จึงมีปุ่มแทรกย่อหน้าให้ ส่วน marks ใช้ของ Word) +- เคยมีชุด "แทรกอักขระพิเศษ" (เลขไทย ๐–๙ + สัญลักษณ์) ต่อท้าย panel แล้ว**ถอดออก 2026-10-19** ตามคำขอผู้ใช้ — ที่ต้องการจริงคือ formatting marks ซึ่งทำผ่าน add-in ไม่ได้ (อ่านบันทึกเต็มใน git history รอบนั้น) +- เคยมีเครื่องมือ **"สร้างช่องจากข้อความที่เลือก"** (dialog ตั้ง tag + `insertContentControl` ครอบ selection + `validateTag` + `TagInputDialog` + unit test ของ validateTag) แล้ว**ถอดออกทั้งเส้น 2026-10-19** ตามคำขอผู้ใช้ — การสร้างช่องใหม่ทำใน Word ด้วย Developer tab แทน + +### ข้อความ error กลางของ panel (MessageBar intent error) + +- สแกนพัง: fallback "สแกนช่องในเอกสารไม่สำเร็จ" · ดูช่องพัง: "ไม่พบช่องนี้ในเอกสาร (อาจถูกลบไปแล้ว) — กดสแกนใหม่" / fallback "เลือกช่องไม่สำเร็จ" · เขียนค่าพัง: fallback "เขียนค่าลงเอกสารไม่สำเร็จ" · แทรกพัง: fallback "แทรกย่อหน้าไม่สำเร็จ" / "แทรกตัวแบ่งบรรทัดไม่สำเร็จ" / "แทรกตัวแบ่งหน้าไม่สำเร็จ" (แบ่งหน้าที่พังทั้งสองวิธีจะรวมสาเหตุของทั้ง insertBreak และ OOXML fallback ในข้อความเดียว) + +### พฤติกรรมใน browser ธรรมดา / `?addindemo` + +ทุกปุ่มที่แตะเอกสาร (`สแกน`/`ดูช่อง`/`เขียนค่า`/`ย่อหน้า`/`แบ่งบรรทัด`/`แบ่งหน้า`) จบที่ error MessageBar ("Word is not defined") — ตามปรัชญา demo ของ repo เหมือนปุ่ม Save & Version ส่วน toggle/สลับแท็บ/ฟอร์มค่าทดสอบ ทดสอบได้ครบใน browser diff --git a/wordaddin/services/errors.ts b/wordaddin/services/errors.ts index 7c78432..1744ebd 100644 --- a/wordaddin/services/errors.ts +++ b/wordaddin/services/errors.ts @@ -8,3 +8,21 @@ export function getErrorMessage(error: unknown, fallback: string): string { } return fallback; } + +/** เหมือน getErrorMessage แต่ต่อท้าย code/errorLocation จาก debugInfo ของ OfficeExtension.Error — + * สำหรับ error จาก Word.run ที่ต้องการสาเหตุละเอียด (เช่น ราย tag ที่เขียนค่าไม่สำเร็จ) */ +export function getDetailedErrorMessage( + error: unknown, + fallback = "เกิดข้อผิดพลาดที่ไม่ทราบสาเหตุ", +): string { + if (error instanceof Error && error.message) { + const debugInfo = (error as { debugInfo?: { code?: string; errorLocation?: string } }).debugInfo; + const code = debugInfo?.code ? ` [${debugInfo.code}]` : ""; + const location = debugInfo?.errorLocation ? ` (${debugInfo.errorLocation})` : ""; + return `${error.message}${code}${location}`; + } + if (typeof error === "string" && error) { + return error; + } + return fallback; +} diff --git a/wordaddin/services/templateService.ts b/wordaddin/services/templateService.ts new file mode 100644 index 0000000..85c5c99 --- /dev/null +++ b/wordaddin/services/templateService.ts @@ -0,0 +1,240 @@ +/* global Word, Office */ + +/** + * แท็บ "เครื่องมือ Template" (แท็บที่สองของ taskpane) — เครื่องมือจัดการ Content Controls + * ฝั่ง Office.js ล้วน ไม่ยิง backend: สแกนรายช่อง / กระโดดไปเลือกช่อง / เขียนค่าทดสอบกลับ + * เข้าเอกสาร / แทรกย่อหน้า-ตัวแบ่งบรรทัด-ตัวแบ่งหน้าที่ cursor + * + * API ที่ใช้เป็น WordApi 1.1 เกือบทั้งหมด (ยืนยันจาก @types/office-js 1.0.612) ยกเว้น + * getByIdOrNullObject (1.3) — ไม่เกินความต้องการต่ำสุดของ manifest (1.3) จึงใช้ได้ทุก platform + * รวม Office 2019 และ Word on the web + * + * ⚠️ collection `contentControls` ธรรมดามองเห็น**เฉพาะ rich text CC** (docstring ของ + * Body.contentControls: "rich text content control objects") — template ของศาลส่วนใหญ่เป็น + * plain text CC จึงต้องใช้ `Document.getContentControls()` (WordApi 1.5, ครอบทุกชนิดที่รองรับ) + * ผ่าน feature-detect ตอน runtime แทนการยก MinVersion ของ manifest (Office 2019 = 1.3 + * ต้องยังโหลดได้ — เห็นเฉพาะ rich text + UI แจ้งเตือน) + * + * กติกาตาม repo: ห้ามมี Office/Word access ระดับ top-level (module นี้ถูก evaluate ใน + * browser ธรรมดาตอนโหมด `?addindemo` ที่ไม่มี Office global — top-level access จะ throw ก่อน UI ทัน render) · operation ที่เขียนเอกสารต้อง suspend/resume + * auto-save คู่กันเสมอ และเรียก notifyDocumentChanged() หลังสำเร็จให้ debounce และ flag FR-3 + * ทำงานถูกต้อง (pattern เดียวกับ versionFlow.ts) + */ + +import { autoSaveService } from "./autoSaveService"; +import { getDetailedErrorMessage } from "./errors"; + +/** ผลสแกนหนึ่งช่อง — id ของ CC ไม่เสถียรข้าม session/coauthor (ตาม docstring ของ Office.js + * เอง: "The ID differs across sessions and coauthors") จึงใช้ได้เฉพาะอายุผลสแกนรอบเดียว ห้ามเก็บไปใช้ภายหลัง */ +export interface ScannedField { + id: number; + /** tag ดิบไม่ trim — เพื่อให้ UI เตือน "tag ว่าง / มี space นำ-ท้าย" ได้ (ตอนเทียบ/ใช้งานคือ trim เสมอ) */ + tag: string; + title: string; + text: string; + /** enum ดิบ เช่น "RichTextInline" — ไว้ debug ใน tooltip */ + type: string; + /** heuristics หยาบจาก enum type: ลงท้ายด้วย "Inline" = ในบรรทัด ไม่งั้น = บล็อก */ + kind: "inline" | "block"; +} + +function toScannedField(control: Word.ContentControl): ScannedField { + const type = control.type ?? ""; + return { + id: control.id, + tag: control.tag ?? "", + title: control.title ?? "", + text: control.text ?? "", + type, + kind: type.endsWith("Inline") ? "inline" : "block", + }; +} + +/** host นี้มองเห็น CC ทุกชนิด (รวม plain text) ได้ไหม — WordApi 1.5+ เท่านั้น + * (เรียกนอก Word เช่น browser ธรรมดา → catch คืน false เสมอ ตามแบบ canCloseDocument) */ +export function supportsAllContentControlTypes(): boolean { + try { + return Office.context.requirements.isSetSupported("WordApi", "1.5"); + } catch { + return false; + } +} + +/** collection ของ CC ที่ host รองรับสูงสุด — 1.5+ ใช้ getContentControls() (ทุกชนิด) + * ต่ำกว่านั้นต้องยอมรับ collection rich-text-only */ +function supportedContentControls(context: Word.RequestContext): Word.ContentControlCollection { + return supportsAllContentControlTypes() + ? context.document.getContentControls() + : context.document.contentControls; +} + +/** สแกน Content Controls ทั้งเอกสาร (1 sync) — เรียงตามลำดับในเอกสาร · ไม่มีช่อง = คืน [] ไม่ error + * อ่านอย่างเดียวจึงไม่ต้อง suspend auto-save */ +export async function listContentControls(): Promise { + return Word.run(async (context) => { + const controls = supportedContentControls(context); + controls.load("id, tag, title, text, type"); + await context.sync(); + return controls.items.map(toScannedField); + }); +} + +/** เลือก + เลื่อน Word ไปที่ช่องตาม id ของผลสแกน — select() ตาม docstring จะ scroll ให้เอง + * ไม่ใช่การแก้เอกสาร (document.saved ไม่ dirty) จึงไม่ต้อง suspend */ +export async function selectContentControl(id: number): Promise { + await Word.run(async (context) => { + const control = supportedContentControls(context).getByIdOrNullObject(id); + control.load("id"); + await context.sync(); + if (control.isNullObject) { + throw new Error("ไม่พบช่องนี้ในเอกสาร (อาจถูกลบไปแล้ว) — กดสแกนใหม่"); + } + control.select(); + await context.sync(); + }); +} + +/** แทรกย่อหน้าว่างใหม่หลังตำแหน่ง cursor (เหมือนกด Enter) — ปุ่มเสริมตอนจัดโครงสร้าง template + * (คำขอผู้ใช้ 2026-10-19: ตอนแก้ template ต้องเพิ่มย่อหน้าบ่อย แล้วเปิด formatting marks ตรวจ — + * ส่วนการเปิด/ปิด formatting marks เป็น application setting ที่ Office.js ไม่มี API ให้แตะ) */ +export async function insertParagraphAtSelection(): Promise { + await autoSaveService.suspend(); + try { + await Word.run(async (context) => { + context.document.getSelection().insertParagraph("", "After"); + await context.sync(); + }); + autoSaveService.notifyDocumentChanged(); + } finally { + autoSaveService.resume(); + } +} + +/** page break แบบ paragraph สมบูรณ์ (block-level) — w:p ครอบ run ที่มี + * ใช้เป็นวิธีสำรองเมื่อ insertBreak("Page") โดน E_FAIL (0x80004005) ในบางบริบท เช่น cursor + * อยู่ในตาราง (เคสจริงบน T3.docx 2026-10-19 — template ของศาลมีตารางเป็นโครงหลัก) · ระดับ fragment + * ต้องเป็น block-level เต็มตัว ไม่ใช่ run เดี่ยว (ลองแล้วยัง E_FAIL อีกรอบ) */ +const PAGE_BREAK_PARAGRAPH_OOXML = + ''; + +/** แทรกตัวแบ่งที่ตำแหน่ง cursor — "line" = ขึ้นบรรทัดใหม่ในย่อหน้าเดิม (Shift+Enter) สำหรับ + * จัดที่อยู่/รายการให้ตรงแนว, "page" = ขึ้นหน้าใหม่ (Ctrl+Enter) สำหรับเอกสารหลายหน้า (คำขอผู้ใช้ 2026-10-19) */ +export async function insertBreakAtSelection(breakKind: "line" | "page"): Promise { + await autoSaveService.suspend(); + try { + if (breakKind === "line") { + await Word.run(async (context) => { + context.document.getSelection().insertBreak("Line", "After"); + await context.sync(); + }); + } else { + // วิธีมาตรฐานก่อน — ถ้า host ปฏิเสธ (E_FAIL ในบางบริบท) สลับไป OOXML block-level + // (sync ที่พัง = คำสั่งถูกปฏิเสธ ไม่มีอะไรถูกเขียนค้าง จึง retry ได้; ถ้า retry ก็พัง + // โยน error ที่รวมทั้งสองสาเหตุ ไม่กลืนต้นเหตุแรกหาย) + let firstError: unknown; + try { + await Word.run(async (context) => { + context.document.getSelection().insertBreak("Page", "After"); + await context.sync(); + }); + autoSaveService.notifyDocumentChanged(); + return; + } catch (error) { + firstError = error; + } + try { + await Word.run(async (context) => { + context.document.getSelection().insertOoxml(PAGE_BREAK_PARAGRAPH_OOXML, "End"); + await context.sync(); + }); + } catch (fallbackError) { + throw new Error( + `แทรกตัวแบ่งหน้าไม่สำเร็จทั้งสองวิธี — insertBreak: ${getDetailedErrorMessage(firstError)} · OOXML: ${getDetailedErrorMessage(fallbackError)}`, + ); + } + } + autoSaveService.notifyDocumentChanged(); + } finally { + autoSaveService.resume(); + } +} + +export interface ApplyFailure { + tag: string; + /** สาเหตุที่อ่านได้ (รวม code/errorLocation จาก OfficeExtension.Error ถ้ามี) */ + reason: string; +} + +export interface ApplyTestValuesResult { + /** จำนวนช่อง (CC) ที่ถูกเขียนค่าจริง — tag เดียวซ้ำหลายช่องนับทุกช่อง + * ⚠️ ข้อจำกัด granularity ราย tag: ถ้า tag หนึ่งผูกหลายช่องและ batch sync พังกลางคัน + * (ช่องแรกถูกเขียนแล้วแต่ช่องหลังโดนปฏิเสธ) ทั้ง tag จะถูกนับเป็น failures แม้บางช่องเขียนสำเร็จ — + * เรียก listContentControls หลังเขียนเพื่อเห็นค่าจริงในเอกสาร */ + appliedCount: number; + /** tag ที่ส่งค่ามาแต่ไม่มีช่องตรงในเอกสารตอนเขียน (เช่น ถูกลบไปหลังสแกน) */ + missingTags: string[]; + /** tag ที่ Word ปฏิเสธการเขียน (เช่น เอกสารถูก Restrict Editing หรือช่องถูกล็อค/แก้ไม่ได้) — + * เขียนแบบแยกราย tag เพื่อให้เหลืออื่น ๆ ยังสำเร็จและรู้สาเหตุเป็นรายตัว */ + failures: ApplyFailure[]; +} + +/** เขียนค่าทดสอบกลับเข้าทุก CC ที่ tag ตรง (tag ซ้ำหลายช่อง = เขียนทุกช่อง แบบตัวแปรเดียวหลายจุด) + * - ค่าที่เว้นว่างจะไม่ถูกแตะ (กันเผลอล้างค่าจริงของช่องนั้น) — ว่างทั้งหมด = ไม่เปิด Word.run + * - อ่าน id+tag สดรอบเดียว ไม่เชื่อ snapshot สแกน แล้วเขียน**แยก Word.run ราย tag** — + * tag ที่ Word ปฏิเสธ (เอกสารถูก Restrict Editing / ช่องแก้ไม่ได้) fail เฉพาะตัว เหลืออื่นยังเขียนได้ + * และเหตุผลถูกเก็บลง failures (กรณีเคยเจอจริง: error 0x800A16C6 บนเอกสารที่ make-protected ล็อค) */ +export async function applyTestValues(values: Record): Promise { + const entries = Object.entries(values).filter(([, value]) => value.trim() !== ""); + if (entries.length === 0) { + return { appliedCount: 0, missingTags: [], failures: [] }; + } + await autoSaveService.suspend(); + try { + // สำรวจ id ของ CC ราย tag รอบเดียว (เทียบหลัง trim เหมือนฟอร์ม) — id ใช้ได้ทั้ง session นี้ + const idsByTag = new Map(); + await Word.run(async (context) => { + const controls = supportedContentControls(context); + controls.load("id, tag"); + await context.sync(); + for (const control of controls.items) { + const tag = (control.tag ?? "").trim(); + if (tag === "" || !(tag in values)) continue; + const ids = idsByTag.get(tag) ?? []; + ids.push(control.id); + idsByTag.set(tag, ids); + } + }); + + let appliedCount = 0; + const missingTags: string[] = []; + const failures: ApplyFailure[] = []; + for (const [tag, value] of entries) { + const ids = idsByTag.get(tag); + if (!ids || ids.length === 0) { + missingTags.push(tag); + continue; + } + try { + let written = 0; + await Word.run(async (context) => { + const targets = ids.map((id) => supportedContentControls(context).getByIdOrNullObject(id)); + targets.forEach((control) => control.load("id")); + await context.sync(); + const alive = targets.filter((control) => !control.isNullObject); + alive.forEach((control) => control.insertText(value, "Replace")); + await context.sync(); + written = alive.length; + }); + appliedCount += written; + } catch (tagError) { + failures.push({ tag, reason: getDetailedErrorMessage(tagError) }); + } + } + if (appliedCount > 0) { + autoSaveService.notifyDocumentChanged(); + } + return { appliedCount, missingTags, failures }; + } finally { + autoSaveService.resume(); + } +} From 765946f0d5f8b093777cfe0ed1f71762cd6d1ada Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 20 Oct 2026 10:52:35 +0700 Subject: [PATCH 06/21] feat(docver): add document versioning API (docx+pdf in MinIO) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .env.example | 13 + CLAUDE.md | 13 +- README.md | 49 +- .../[versionId]/download-link/route.ts | 48 ++ app/api/documents/[docId]/versions/route.ts | 162 ++++ db/schema.ts | 94 +++ docker-compose.yml | 58 ++ docs/PLAN-DOCVER.md | 181 +++++ drizzle/0005_icy_bloodaxe.sql | 29 + drizzle/meta/0005_snapshot.json | 735 ++++++++++++++++++ drizzle/meta/_journal.json | 7 + lib/document-versioning.ts | 273 +++++++ lib/docver-store.ts | 275 +++++++ lib/openapi.ts | 243 +++++- middleware.ts | 6 +- package.json | 1 + pnpm-lock.yaml | 459 +++++++---- pnpm-workspace.yaml | 6 + tests/lib/document-versioning.test.ts | 244 ++++++ 19 files changed, 2711 insertions(+), 185 deletions(-) create mode 100644 app/api/documents/[docId]/versions/[versionId]/download-link/route.ts create mode 100644 app/api/documents/[docId]/versions/route.ts create mode 100644 docker-compose.yml create mode 100644 docs/PLAN-DOCVER.md create mode 100644 drizzle/0005_icy_bloodaxe.sql create mode 100644 drizzle/meta/0005_snapshot.json create mode 100644 lib/document-versioning.ts create mode 100644 lib/docver-store.ts create mode 100644 pnpm-workspace.yaml create mode 100644 tests/lib/document-versioning.test.ts diff --git a/.env.example b/.env.example index 7e1100d..0a4a54b 100644 --- a/.env.example +++ b/.env.example @@ -5,3 +5,16 @@ DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:54322/ecourt-note # N10 (optional): API key ของ /api/external/* (backend e-Court เรียก server-to-server) # ไม่ตั้ง/ว่าง = ปฏิเสธทุก request (fail closed) — ตั้งค่ายาวและสุ่ม เช่น 32+ ตัวอักษร # EXTERNAL_API_KEY= + +# MinIO ของ API เอกสาร versioning (docs/PLAN-DOCVER.md §6) — ค่านี้ตรง docker-compose.yml +# (MINIO_ENDPOINT/MINIO_ACCESS_KEY/MINIO_SECRET_KEY ไม่ตั้ง = ทุก endpoint ของกลุ่มนี้ตอบ 500) +# หมายเหตุ: พอร์ตโฮสต์เป็น 19000 เพราะ 9000/9001 ชนกับโปรเจกต์อื่นในเครื่อง dev +# (ใน production ตั้งเป็นพอร์ตจริงที่ deploy) และ presigned URL อ้าง endpoint นี้ตรง ๆ — +# production ต้องเป็นโฮสต์ที่เบราว์เซอร์ของผู้ใช้เข้าถึงได้ ไม่ใช่แค่ที่ Next server เข้าถึงได้ +MINIO_ENDPOINT=127.0.0.1 +MINIO_PORT=19000 +MINIO_USE_SSL=false +MINIO_ACCESS_KEY=minioadmin +MINIO_SECRET_KEY=minioadmin +# MINIO_BUCKET=docver +# DOCVER_LINK_TTL_SECONDS=900 diff --git a/CLAUDE.md b/CLAUDE.md index 8023344..da41165 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,7 +21,7 @@ 1. **Non-Destructive:** ไฟล์ต้นฉบับ (PDF/รูปภาพ) ไม่ถูกแก้ไขเด็ดขาด — annotation เก็บใน DB เป็นพิกัดเท่านั้น (export PDF ของ F8/N3 สร้างไฟล์ใหม่จากสำเนาในหน่วยความจำ — อ่าน `public/` อย่างเดียว ห้ามเขียนทับ; อัปโหลด N4 เขียนไฟล์**ใหม่**ที่ `public/note/uploads/` เท่านั้น ห้ามแตะไฟล์เดิม) 2. **พิกัดทุกอย่างใน DB เป็น % ของขนาดหน้า** — `rects`, `paths`, `position`, `strokeWidth` (% ของ width), `yOffset` (% ของ height) — แปลง px⇄% ผ่าน `utils/coordinates.ts` เท่านั้น และก่อนบันทึก HIGHLIGHT ต้องรวบ rects ด้วย `mergeRectsByLine` เหลือ 1 rect ต่อบรรทัด 3. **ลำดับ layer ตาม PLAN.md §1:** canvas (`pointer-events: none` เสมอ) → highlight-layer (`none` เสมอ, `mix-blend-mode: multiply`) → textLayer → InkLayer (SVG) → comment pins — สลับ pointer-events ตาม tool ปัจจุบัน -4. **เขียนผ่าน Server Actions เท่านั้น** (`app/actions/annotations.ts`) และ**หลังเขียน DB สำเร็จทุกครั้ง ต้อง publish event ผ่าน `lib/annotate-events.ts` + `revalidatePath` หน้า viewer และ summary ของ fileId นั้น** (helper `revalidateFilePages` — สั่ง browser ทิ้ง Router Cache ฝั่ง client, `force-dynamic` ปิดแค่ฝั่ง server ไม่พอ: ไม่งั้นกด back แล้วคลิกเข้าไฟล์ใหม่ยังได้ initial data เก่าจนต้อง hard refresh) / **อ่าน + sync ผ่าน SSE hook** (`hooks/useAnnotationRealtime.ts` เปิด `EventSource` ไป `/api/note/annotations/stream?fileId=`) — event เป็น DTO (camelCase + ISO string) จาก server ตรง ๆ — **ยกเว้น `registerExternalFile` (N8)** ที่เขียน row `files` ระหว่าง render ของหน้า `/note` และ `/note/annotate` (entry point ทั้งสองของสัญญาลิงก์ `?fileUrl=`) แล้ว `redirect` ออกทันที (หน้า `force-dynamic` + รายการไฟล์ไม่ได้ sync ผ่าน SSE — ไม่ต้อง publish event/`revalidatePath`) +4. **เขียนผ่าน Server Actions เท่านั้น** (`app/actions/annotations.ts`) และ**หลังเขียน DB สำเร็จทุกครั้ง ต้อง publish event ผ่าน `lib/annotate-events.ts` + `revalidatePath` หน้า viewer และ summary ของ fileId นั้น** (helper `revalidateFilePages` — สั่ง browser ทิ้ง Router Cache ฝั่ง client, `force-dynamic` ปิดแค่ฝั่ง server ไม่พอ: ไม่งั้นกด back แล้วคลิกเข้าไฟล์ใหม่ยังได้ initial data เก่าจนต้อง hard refresh) / **อ่าน + sync ผ่าน SSE hook** (`hooks/useAnnotationRealtime.ts` เปิด `EventSource` ไป `/api/note/annotations/stream?fileId=`) — event เป็น DTO (camelCase + ISO string) จาก server ตรง ๆ — **ยกเว้น `registerExternalFile` (N8)** ที่เขียน row `files` ระหว่าง render ของหน้า `/note` และ `/note/annotate` (entry point ทั้งสองของสัญญาลิงก์ `?fileUrl=`) แล้ว `redirect` ออกทันที (หน้า `force-dynamic` + รายการไฟล์ไม่ได้ sync ผ่าน SSE — ไม่ต้อง publish event/`revalidatePath`) · เช่นกัน **กลุ่ม docver `app/api/documents/**`** (จัด version เอกสาร — `docs/PLAN-DOCVER.md`) เขียนตาราง `documents`/`document_versions` ผ่าน route handler ไม่ใช่ Server Action (ผู้เรียกเป็นระบบภายนอก stand-alone บอก userId/userName เองใน request ไม่มี session ของแอป note) — ไม่มีหน้า note ใดอ่านตารางนั้น จึงไม่ต้อง publish event/`revalidatePath` 5. **Timestamp ข้าม server→client→SSE เป็น ISO string เสมอ** (DTO types) — ห้ามส่ง Date object ตรง ๆ 6. **pdfjs-dist ต้อง `dynamic import()` ภายใน useEffect เท่านั้น** — ห้าม import top-level ใน module scope ที่ server evaluate และต้องตั้ง `GlobalWorkerOptions.workerSrc = withBasePath("/pdf.worker.min.mjs")` (postinstall `scripts/patch-pdf-worker.mjs` คัดลอกไฟล์ไป `public/note/` **แล้ว patch จุดเดียว: ถอด `if (scaledDim)` รอบ `textChunk.prevTransform = getCurrentTextTransform()` ให้อัปเดตทุก glyph** — pdf.js เดิมข้าม glyph กว้าง 0 อย่างสระ/วรรณยุกต์ไทย ทำให้ charSpacing ของ glyph เหล่านั้นสะสมเป็น gap จน `addFakeSpaces` แทรก " " กลางคำ "ฉะนั้ น" — bug ของ pdf.js ที่ upstream ยังไม่แก้; การ patch ทำบนโค้ด minified ด้วย pattern จำเพาะ ถ้าอัปเกรด pdfjs-dist แล้ว pattern ไม่ตรง postinstall จะ **fail ดัง ๆ โดยตั้งใจ** กัน patch หลุดเงียบ — test กัน regression: `tests/scripts/patch-pdf-worker.test.ts` + fixture `tests/fixtures/thai_letterspacing.pdf` — Docker build ต้องใช้ script นี้ด้วย ไม่ใช่ copyFileSync เปล่า ดู `docker/web.Dockerfile`) — ห้ามใช้ `new URL(..., import.meta.url)` 7. **InkLayer ใช้ SVG แบบไม่มี viewBox** — render ด้วย px จริง แปลง % → px จากขนาดที่ ResizeObserver วัด (viewBox จะทำให้ความหนาเส้นบิดเบี้ยว) @@ -34,12 +34,14 @@ ## โครงสร้างไฟล์ (ตามแผน) ``` +docker-compose.yml ← dev dependencies: `db` (judes-note-db — ชื่อ container/วอลุ่มเดียวกับคำสั่ง docker run เดิมใน README migrate ได้เลย, POSTGRES_DB ecourt-note) + `minio` (judes-note-minio — object storage ของกลุ่ม docver; โฮสต์แมพ 19000 API/19001 console เพราะ 9000/9001 ชน container โปรเจกต์อื่นในเครื่อง dev, image จาก quay.io ทางการ; bucket `docver` ไม่ต้องสร้างเอง app สร้างตอน first use ผ่าน ensureBucket) +docs/PLAN-DOCVER.md ← แผนกลุ่ม API docver: จัด version เอกสาร .docx+.pdf ต่อ docId (ห้ามซ้ำ) เก็บ object ใน MinIO + ผู้อัปโหลดต่อ version — contract 3 endpoint, ดีไซน์ตาราง, env MINIO_* ทั้งหมด scripts/patch-pdf-worker.mjs ← postinstall: คัดลอก pdf.worker.min.mjs จาก node_modules ไป public/note/ + patch fake-space ไทย (invariant #6 — เจอ pattern ≠ 1 จุดหลังอัปเกรด pdfjs-dist = exit 1 ดัง ๆ) scripts/copy-swagger-ui.mjs ← postinstall ตัวที่สอง: คัดลอก swagger-ui-dist (bundle+css) ไป public/note/swagger-ui/ สำหรับหน้า Swagger UI ที่ GET /api/swagger (intranet ไม่ใช้ CDN — หาไฟล์ไม่เจอหลังอัปเกรด = exit 1 ดัง ๆ เหมือน patch-pdf-worker; Docker build รัน script นี้ด้วย ดู docker/web.Dockerfile) scripts/fetch-office-js.mjs ← postinstall ตัวที่สาม: ดาวน์โหลด Office.js runtime **ฉบับ CDN ล่าสุด** (appsforoffice.microsoft.com — ชุดไฟล์เฉพาะ Word: loader + host files ราย platform + legacy 15.x + o15apptofilemappingtable/microsoftajax + office_strings.js ต่อ locale en-us/th-th บังคับ) ไป public/wordaddin/office-js/ ให้ taskpane โหลด same-origin — npm `@microsoft/office-js` เลิกอัปเดตที่ 1.1.110 (~2018) ไม่มี `Document.close`/enum `CloseBehavior` ที่ runtime ใหม่+`@types/office-js` มี; ไฟล์หลักหาย/ตรวจไม่เจอ `"Document.close"`+`CloseBehavior` ใน word-win32-16.01.js หลังโหลด = exit 1 ดัง ๆ (ไฟล์ปลายทาง gitignore — ดาวน์โหลดใหม่ทุกครั้ง; ติดตั้งต้องออกเน็ตถึง CDN ของ Microsoft ส่วน runtime ตัวระบบ intranet ล้วน) db/schema.ts ← ตาราง + JSONB types + DTO + mapper toDTO db/index.ts ← postgres.js + drizzle (DATABASE_URL → container judes-note-db, prepare:false) -middleware.ts ← บังคับ login ทุกหน้า — ไม่มี cookie oacCase ที่ parse ได้ = redirect /note/login (path ที่ตัด basePath แล้ว: หน้าขึ้นต้น /note/… ส่วน API ขึ้นต้น /api/note/… — ยกเว้น note/login, api/note/*, api/template/* (กลุ่ม docx template เปิดกว้างให้ทางศาล/ผู้ทดสอบภายนอกยิงได้เลย — ไม่แตะ DB), api/swagger/* (หน้า Swagger UI), wordaddin/* (taskpane ของ Word add-in — auth mock ของตัวเอง), note/uploads/*, note/samples, note/swagger-ui/*, note/pdf.worker.min.mjs, _next, fonts, favicon และ **root "/" เอง** (matcher ใช้ `.+` — root เป็น redirect สู่ /wordaddin ซึ่ง public ตามคำขอผู้ใช้ 2026-10-14, ลิงก์ e-Court N8 ที่ root ยังถูกคุมผ่าน /note ที่รับส่งต่อ)) +middleware.ts ← บังคับ login ทุกหน้า — ไม่มี cookie oacCase ที่ parse ได้ = redirect /note/login (path ที่ตัด basePath แล้ว: หน้าขึ้นต้น /note/… ส่วน API ขึ้นต้น /api/note/… — ยกเว้น note/login, api/note/*, api/template/* (กลุ่ม docx template เปิดกว้างให้ทางศาล/ผู้ทดสอบภายนอกยิงได้เลย — ไม่แตะ DB), api/documents/* (กลุ่ม docver จัด version เอกสาร — stand-alone ผู้เรียกบอก userId/userName เอง ไม่มี session, docs/PLAN-DOCVER.md), api/swagger/* (หน้า Swagger UI), wordaddin/* (taskpane ของ Word add-in — auth mock ของตัวเอง), note/uploads/*, note/samples, note/swagger-ui/*, note/pdf.worker.min.mjs, _next, fonts, favicon และ **root "/" เอง** (matcher ใช้ `.+` — root เป็น redirect สู่ /wordaddin ซึ่ง public ตามคำขอผู้ใช้ 2026-10-14, ลิงก์ e-Court N8 ที่ root ยังถูกคุมผ่าน /note ที่รับส่งต่อ)) lib/annotate-events.ts ← pub/sub in-process (actions publish → SSE route subscribe) lib/server-pdf.ts ← path guard อ่าน PDF จาก public/note/ (export ใช้) lib/session.ts ← getSessionUser — identity จาก cookie oacCase (mock login e-Court) @@ -47,11 +49,13 @@ lib/external-import.ts ← N8 registerExternalFile — ลงทะเบีย lib/remote-file.ts ← N8 ดึงไฟล์รีโมตจาก allowlist (validate ซ้ำหลัง redirect + จำกัด 100MB + timeout 60s — /api/note/pdf กับ export ใช้ร่วม) lib/external-api-auth.ts ← N10 auth ของ /api/note/external/* — requireExternalApiKey เทียบ header `x-api-key` กับ env `EXTERNAL_API_KEY` ด้วย SHA-256 + crypto.timingSafeEqual (env ไม่ตั้ง/ว่าง = 401 fail closed ทุก request; ข้อความ 401 อันเดียวกันทั้งไม่ใส่/key ผิด กัน enumerate; ห้าม log ค่า key/header ดิบ) + externalApiError สร้าง Response ผิดพลาด JSON {"error": ไทย} ใช้ร่วมทุก route ของ N10 — **module นี้ห้าม import อะไรที่ลาก @/db มาด้วย** (db/index.ts throw ตอน import เมื่อไม่มี DATABASE_URL — unit test tests/lib/external-api-auth.test.ts ต้องรันได้โดยไม่แตะ DB) lib/external-api.ts ← N10 ส่วนกลางของ route — parseQueryBody (validate JSON body ของ /api/note/external/documents: รับเฉพาะ fileId/typeId/filter, ต้องมี fileId หรือ typeId อย่างน้อยหนึ่งอย่าง — ส่งทั้งคู่ได้ = AND เหลือเฉพาะเอกสารเดียวที่ตรงทั้งสองเงื่อนไข, คำขอผู้ใช้ 2026-09-25 (รอบเดียวกันเคยเป็น OR แล้วเปลี่ยนหลังทดสอบจริง), filter รับเฉพาะ type ∈ bookmarks|annotations ไม่ส่ง = ทั้งสอง + userId (string|array กรองตามเจ้าของรายการ annotations+bookmarks) + annotationTypes (ค่า enum annotation_type เฉพาะ annotations ค่าอื่น = 400 — เดิมชื่อ types เปลี่ยน 2026-09-25) — คืน discriminated mode "file" (fileId + typeId อาจมาด้วย) / "type" (typeId เดี่ยว)) + loadDocumentBundles(fileRows, parts) (bulk: inArray ทุกไฟล์ในชุด query เดียว = 3 query ต่อ request ไม่ว่ากลุ่มกี่ไฟล์ แทนยิง 3 query ต่อไฟล์พร้อมกันทั้งกลุ่ม, code review 2026-09-25 — กรอง userIds/annotationTypes ใน SQL; คืน bundle ยุบชั้น: field เอกสาร + annotations + bookmarks ระดับเดียวกัน ไม่มี object document ซ้อน; ต่อรายการมี `url` deep link F12 กลับหน้า `/annotate/[id]` — annotation `?ann=` / bookmark `?page=<หน้า 1-based>` — URL เต็ม: origin จาก request ผู้เรียก (route อ่าน x-forwarded-proto/host แล้ว fallback host ตรง ส่งเข้าทาง parameter origin) + basePath ผ่าน withBasePath, คำขอผู้ใช้ 2026-09-25) + type ExternalDocumentDTO (externalFileId เป็น null สำหรับไฟล์ url:-fallback/อัปโหลด N4 ในกลุ่ม) -lib/openapi.ts ← OpenAPI 3.0 spec ของ API ทดสอบ (กลุ่ม template + N10 — 2026-10-06) — แหล่งเดียวของ Swagger UI: GET /api/template inline ลง HTML ตรง ๆ ไม่มี endpoint spec แยก; URL ใน paths เป็น path เต็ม /api/… (servers เติมแค่ basePath ตาม invariant #12) — แก้ contract ของ API ต้องมาแก้ไฟล์นี้ให้ตรงด้วย +lib/openapi.ts ← OpenAPI 3.0 spec ของ API ทดสอบ (กลุ่ม template + N10 + docver — 2026-10-06) — แหล่งเดียวของ Swagger UI: GET /api/template inline ลง HTML ตรง ๆ ไม่มี endpoint spec แยก; URL ใน paths เป็น path เต็ม /api/… (servers เติมแค่ basePath ตาม invariant #12) — แก้ contract ของ API ต้องมาแก้ไฟล์นี้ให้ตรงด้วย +lib/document-versioning.ts ← docver pure validation (ห้าม import อะไรที่ลาก @/db หรือ minio มาด้วย — unit test tests/lib/document-versioning.test.ts ต้องรันได้เหมือน lib/external-api-auth.ts): parseDocId (charset [A-Za-z0-9._:-] 1–200), parseVersionUploadForm (allowlist field + userId/userName บังคับ + fields JSON ≤100 keys + message ≤2048), isDocxBytes/isPdfBytes (magic bytes), parseDownloadFormat/parseVersionIdParam, contentDispositionValue (ASCII fallback + filename*=UTF-8'') + docverError สร้าง Response JSON {"error": ไทย} ใช้ร่วมทุก route ของกลุ่ม +lib/docver-store.ts ← docver server-side MinIO + DB (env MINIO_* อ่านตอนเรียกไม่ใช่ module load; client + cached ensureBucket เก็บบน globalThis แบบ db/index.ts): object key `docver//document.docx|pdf` (uuid ล้วน ไม่มี input ผู้ใช้), putVersionObjects (docx ก่อน — pdf ล้ม = ลบ docx คืนแล้วโยนต่อ), removeVersionObjects (best-effort), presignVersionUrl (TTL env DOCVER_LINK_TTL_SECONDS), insertVersionRecord (**tx เดียว**: upsert documents ON CONFLICT DO NOTHING → SELECT id FOR UPDATE → max(versionNumber)+1 → insert — จ่ายเลข version ปลอดภัยเรียกคู่ขนาน, UNIQUE(document_id, version_number) เป็น safety net) wordaddin/ ← **client-only zone ของ Word add-in** (ห้าม import จาก lib/ หรือถูก import โดย server component — พอร์ตดิบจาก D:\word-addin-poc\addin\src, spec: docs/addin-ui-flow.md, คู่มือ dev: docs/WORDADDIN-DEV.md) wordaddin/config.ts ← env NEXT_PUBLIC_ADDIN_API_BASE_URL ("" = ยังไม่ต่อ backend จริง) + NEXT_PUBLIC_ADDIN_MOCK_USERNAME (mock auth) wordaddin/models/versionRecord.ts ← types VersionRecord/FieldsSnapshot + readSnapshotField (พอร์ตดิบ) -wordaddin/services/ ← documentService/fileService/metadataService/autoSaveService/versionFlow/apiClient/errors พอร์ตดิบทั้งไฟล์ · **authService = MOCK** (login ผ่านทันที คง surface เดิมครบไว้สลับของจริง — ต้นฉบับ Keycloak PKCE อยู่ที่ D:\word-addin-poc\addin + **`signInFromDocument` e-Court pass-through 2026-10-16: ไฟล์ที่ stamp custom properties `token`/`owner` มา (`readTrackedProperties` อ่านรวม) = `detect()` ของ TaskpaneApp ผ่านเข้าเลยไม่ต้องกด login — username จาก owner → uid ใน JWT → mockUsername, token ไฟล์เก็บ memory ให้ getAccessToken ใช้ต่อ**) · **versioningService = MOCK** (memory + **metadata persist ลง localStorage ต่อ documentId** เพื่อทดสอบวงปิดเอกสาร→เปิดใหม่ได้ — blob ไฟล์จริงเฉพาะ session; คงชื่อ/ลายเซ็น uploadVersion/getVersions/getDownloadLink/newClientVersionId เป๊ะ, download-link คืน about:blank) +wordaddin/services/ ← documentService/fileService/metadataService/autoSaveService/versionFlow/apiClient/errors พอร์ตดิบทั้งไฟล์ · **authService = MOCK** (login ผ่านทันที คง surface เดิมครบไว้สลับของจริง — ต้นฉบับ Keycloak PKCE อยู่ที่ D:\word-addin-poc\addin + **`signInFromDocument` e-Court pass-through 2026-10-16: ไฟล์ที่ stamp custom properties `token`/`owner` มา (`readTrackedProperties` อ่านรวม) = `detect()` ของ TaskpaneApp ผ่านเข้าเลยไม่ต้องกด login — username จาก owner → uid ใน JWT → mockUsername, token ไฟล์เก็บ memory ให้ getAccessToken ใช้ต่อ**) · **versioningService = MOCK** (memory + **metadata persist ลง localStorage ต่อ documentId** เพื่อทดสอบวงปิดเอกสาร→เปิดใหม่ได้ — blob ไฟล์จริงเฉพาะ session; คงชื่อ/ลายเซ็น uploadVersion/getVersions/getDownloadLink/newClientVersionId เป๊ะ, download-link คืน about:blank — **backend จริงของ surface นี้มีแล้ว**: กลุ่ม `app/api/documents/**` (docver, docs/PLAN-DOCVER.md — สลับ mock ด้วยการตั้ง `NEXT_PUBLIC_ADDIN_API_BASE_URL`) utils/coordinates.ts ← pixel⇄% + getPointerPercent + clamp + mergeRectsByLine utils/geometry.ts ← bbox/translate/scale/hitTestPath ของ annotation (pure, หน่วย %) utils/annotation-display.ts ← label/ข้อความย่อต่อชนิด + TYPE_SHORT/TYPE_ORDER ชื่อย่อ/ลำดับชนิด (บรรทัดสรุปหน้าของหน้าสรุป + แผงตัวกรอง explorer — ย้ายจาก 2 ที่ซ้ำกันมารวมที่นี่ 2026-09-24) + ค่า default TEXTBOX/STAMP + describeAnnotationChange สรุปสิ่งที่เปลี่ยนของ version history F19 (sidebar/สรุป/groups/CSV ใช้ร่วม) @@ -74,6 +78,7 @@ app/api/note/annotations/export/route.ts ← export PDF ฝัง annotation app/api/note/external/documents/route.ts ← N10 API ภายนอกเส้นเดียว (POST + JSON body {fileId และ/หรือ typeId, filter:{type, userId, annotationTypes}} — auth ผ่าน requireExternalApiKey ก่อนแตะ DB, ถาม fileId = ค้น files.external_id = "ecourt:" ไม่เจอ 404 แม้ส่ง typeId มาด้วย / ถาม typeId = ทุกไฟล์ในกลุ่ม กลุ่มว่าง = 200 documents:[] / ส่งทั้งคู่ = AND กรอง externalId+typeId ใน query เดียว เหลือเอกสารเดียว ไม่ตรงคู่ = 404 แยกสาเหตุในข้อความ (2026-09-25 เดิมเคยเป็น OR แล้วเป็น 400); response รูปเดียว {documents:[bundle…]} ผ่าน loadDocumentBundles ทั้งสอง mode; ครอบ try/catch ทุก DB call — ล้ม = log ฝั่ง server + 500 JSON ตาม contract ไม่ปล่อยเป็น 500 ของ framework; read-only ไม่ publish SSE ไม่ revalidatePath — snapshot ณ ตอนเรียก) app/api/swagger/route.ts ← **GET /api/swagger = หน้า Swagger UI** (2026-10-06 — document ของ API ทุกกลุ่ม: spec inline จาก lib/openapi.ts + asset จาก public/note/swagger-ui/ ของ postinstall scripts/copy-swagger-ui.mjs, persistAuthorization เก็บ x-api-key ไว้ใช้ข้าม reload — เปิดกว้างไม่ต้อง login) app/api/template/{test,extract,render}/route.ts ← กลุ่ม API docx template ของ wordaddin (WIP — GET test ตรวจ service / POST extract ดึงค่า Content Control / POST render = template(.docx)+data(JSON ไฟล์หรือ field dataJson) คืน .docx ใหม่; logic รวมอยู่ app/api/template/lib-tmpl.ts เป็นแชร์ไลบรารี pizzip+xpath; ไฟล์ทดสอบ/โน้ตอยู่ app/api/template/test/ — ไม่แตะ DB, URL /docedit/api/template/…) +app/api/documents/[docId]/versions/route.ts + [versionId]/download-link/route.ts ← **กลุ่ม API docver จัด version เอกสาร** (docs/PLAN-DOCVER.md — URL /docedit/api/documents/…, ไม่มี auth เปิดกว้างเหมือน api/template): POST versions = multipart `file`(.docx บังคับ ≤100MB เช็ค size ก่อน buffer → 413)+`pdf`(optional ไฟล์เปล่า = ไม่มี)+userId/userName+fields/message/clientVersionId/templateId/fileName → สร้างเอกสารอัตโนมัติถ้า docId ใหม่, MinIO ก่อน DB (DB ล้มหลัง put = เก็บกวาด object แบบ N4) → `{hasPdf, versionId, versionNumber}` (เลขจาก server เป็นค่าจริง) / GET versions = DocumentVersionDTO[] ใหม่→เก่า (ไม่เคยมี docId = 404) / GET download-link?format=docx|pdf = presigned URL อายุสั้น (เช็ค docId+versionId คู่กันกันโยน id ข้ามเอกสาร; ขอ pdf ที่ version ไม่มี = 404) — carve-out invariant #4: ไม่ publish SSE ไม่ revalidatePath app/note/uploads/[name]/route.ts ← เสิร์ฟไฟล์อัปโหลดจาก public/note/uploads/ (N4 — Next production เห็นไฟล์ static เฉพาะที่มีตอน startup ไฟล์ที่เขียน runtime ต้องเสิร์ฟผ่าน route) app/note/page.tsx ← รายการไฟล์จัด section ตามกลุ่ม type_id + "ไม่มีกลุ่ม" ท้ายสุด (N9 — server component) app/note/groups/[typeId]/page.tsx ← N9 หน้าเอกสารในกลุ่ม (server component force-dynamic — files where type_id orderBy createdAt, ไม่มี = 404; ~~หมายเหตุ "แสดงเฉพาะเอกสารที่เคยเปิดในระบบนี้"~~ ถอดออกตามคำขอผู้ใช้ 2026-09-24; **redesign 2026-09-24 ตามคำขอผู้ใช้: ไม่มี navbar** — explorer 2 คอลัมน์ ตัวกรองซ้าย + list เดียวรวม bookmark+annotation ทั้งกลุ่มไม่แยกไฟล์: query ทั้งหมดฝั่ง server แล้วส่ง `GroupFileSummary[]` + `GroupListItem[]` (เรียง ไฟล์ตาม createdAt → หน้า → bookmark ก่อน annotation → createdAt) ให้ `GroupExplorer` กรองใน memory, แถวรายการ deep link `?ann=`/`?page=` เหมือนหน้าสรุป F12 + ไฟล์ที่มาต้น meta line) diff --git a/README.md b/README.md index 31ffd89..c914253 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ | เครื่องมือ | เวอร์ชันที่ใช้พัฒนา/ทดสอบ | หมายเหตุ | |---|---|---| | Node.js | v24.3.0 | ≥ 20 น่าจะใช้ได้ | -| Docker | 29.1.3 | ใช้รัน PostgreSQL container ตัวเดียว | +| Docker | 29.1.3 | ใช้รัน PostgreSQL + MinIO ผ่าน `docker-compose.yml` | ## ขั้นตอน Setup จากศูนย์ @@ -31,17 +31,16 @@ npm install > postinstall จะคัดลอก `pdf.worker.min.mjs` จาก `node_modules/pdfjs-dist/build/` มาไว้ที่ `public/` ให้อัตโนมัติ -### 2. รัน PostgreSQL container +### 2. รัน PostgreSQL + MinIO ด้วย docker compose ```bash -docker run -d --name judes-note-db \ - -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=postgres \ - -p 54322:5432 \ - -v judes-note-db-data:/var/lib/postgresql/data \ - postgres:17-alpine +docker compose up -d ``` -ครั้งต่อไปแค่ `docker start judes-note-db` — ข้อมูลอยู่ใน volume `judes-note-db-data` ไม่หาย +- `db` = PostgreSQL (`judes-note-db` พอร์ต 54322, database `ecourt-note` สร้างตอน first init) — ใช้ชื่อ container/วอลุ่มเดียวกับคำสั่ง `docker run` เดิม: ถ้ามี container เก่าจาก `docker run` อยู่ ให้ `docker rm -f judes-note-db` ก่อน (วอลุ่ม `judes-note-db-data` เดิม ข้อมูลไม่หาย — ถ้าวอลุ่มเดิม init ด้วย database `postgres` ให้สร้าง `ecourt-note` เอง: `docker exec judes-note-db psql -U postgres -c 'CREATE DATABASE "ecourt-note"'`) · จะรันแค่ MinIO ก็ได้ด้วย `docker compose up -d minio` +- `minio` = object storage ของ API เอกสาร versioning (`judes-note-minio` — API ที่ **พอร์ตโฮสต์ 19000**, console ดูผ่าน browser ที่ http://localhost:19001; ข้างใน container ยังเป็น 9000/9001 — host เลี่ยงมา 19000/19001 เพราะ 9000/9001 มักชนกับ service อื่นในเครื่อง dev; image ดึงจาก `quay.io/minio/minio` ซึ่งเป็น registry ทางการของ MinIO) — bucket ไม่ต้องสร้างเอง app สร้างตอน first use + +ครั้งต่อไปแค่ `docker compose start` — ข้อมูลอยู่ใน volume `judes-note-db-data`/`judes-note-minio-data` ไม่หาย ### 3. สร้างไฟล์ `.env` @@ -50,6 +49,12 @@ DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:54322/ecourt-note # N10 (optional): API key ของ /api/note/external/* สำหรับ backend e-Court เรียกแบบ server-to-server # ไม่ตั้งหรือเว้นว่าง = API ปฏิเสธทุก request (fail closed) — ตั้งค่ายาวและสุ่ม เช่น 32+ ตัวอักษร # EXTERNAL_API_KEY= +# MinIO ของ API เอกสาร versioning (ค่าตรง docker-compose.yml — ไม่ต้องแก้ถ้ารันตามขั้นตอน 2) +MINIO_ENDPOINT=127.0.0.1 +MINIO_PORT=19000 +MINIO_USE_SSL=false +MINIO_ACCESS_KEY=minioadmin +MINIO_SECRET_KEY=minioadmin ``` (เทียบกับ `.env.example`) @@ -125,6 +130,28 @@ curl -X POST -H "x-api-key: $EXTERNAL_API_KEY" -H "content-type: application/jso "http://localhost:3000/docedit/api/note/external/documents" ``` +## API จัด version เอกสาร (docver — stand-alone) + +กลุ่ม API ให้ **Word add-in / ระบบภายนอก** เก็บเอกสารเป็น version: เอกสารเดียวต่อ `docId` (unique) แต่ละ version เก็บไฟล์ **.docx + .pdf ใน MinIO** พร้อม `userId`/`userName`/`createdAt` ของผู้อัปโหลด — **ไม่มี auth ไม่มี session** (stand-alone): ผู้เรียกบอก identity เองผ่าน field `userId`/`userName` ของ multipart · contract ตรง mock ของ wordaddin (`versioningService`) เป๊ะเพื่อวันสลับของจริง · สเปก/เหตุผล: `docs/PLAN-DOCVER.md` + +```bash +# POST อัปโหลด version (เอกสารถูกสร้างแบบ implicit เมื่อเป็น version แรก) +curl -F "file=@คำสั่งเรียก.docx" -F "pdf=@คำสั่งเรียก.pdf" \ + -F "userId=u0001" -F "userName=ผู้พิพากษา ก." \ + "http://localhost:3000/docedit/api/documents/CASE-2569-0001/versions" +# → {"hasPdf":true,"versionId":"","versionNumber":1} (versionNumber เป็นค่าจาก server) + +# GET history (ใหม่ → เก่า) — docId ที่ยังไม่เคยมี version = 404 +curl "http://localhost:3000/docedit/api/documents/CASE-2569-0001/versions" + +# GET presigned URL อายุสั้น (default docx, ?format=pdf — version ไม่มี pdf = 404) +curl "http://localhost:3000/docedit/api/documents/CASE-2569-0001/versions//download-link?format=pdf" +``` + +- ไฟล์แนบ `file` (.docx) บังคับ · `pdf` optional (add-in แนบ blob เปล่าได้ = ถือว่าไม่มี ดู `hasPdf`) · ทั้งคู่ ≤ 100MB + ตรวจ magic bytes · field นอก allowlist = 400 กัน typo เงียบ ๆ +- error ทุกสถานะเป็น JSON `{ "error": "<ข้อความไทย>" }` — 400 (รูปผิด/ไม่ใช่ multipart/เนื้อไฟล์เพี้ยน) · 413 (เกิน 100MB) · 404 (ไม่มีเอกสาร/version/pdf) · 500 (MinIO env ไม่ตั้ง/ล้ม, DB ล้ม — log เต็มฝั่ง server) +- **presigned URL อ้าง `MINIO_ENDPOINT:MINIO_PORT` ตรง ๆ** — production ต้องเป็นโฮสต์ที่เบราว์เซอร์ของผู้ใช้เข้าถึงได้ ไม่ใช่แค่ที่ Next server เข้าถึงได้ + ## Troubleshooting | อาการ | สาเหตุ/ทางแก้ | @@ -132,7 +159,8 @@ curl -X POST -H "x-api-key: $EXTERNAL_API_KEY" -H "content-type: application/jso | Realtime ไม่ซิงก์ | sync ใช้ pub/sub **in-memory ของ process เดียว** — ทุกแท็บต้องเข้าผ่าน dev server เดียวกัน (ถ้าวันหนึ่งรันหลาย instance ต้องเพิ่ม Redis pub/sub หรือ Postgres LISTEN/NOTIFY ก่อน) | | หน้าเว็บ 500 หลังรัน `next build` ระหว่างที่ dev server กำลังรัน | build เขียนทับ cache ของ dev — ปิด dev server ก่อน build หรือ restart dev server หลัง build | | `tsc --noEmit` error ชี้ route ที่ถูกลบไปแล้ว (เช่น `app/api/external/document*` หลังรวมเป็น `query` รอบสองของ N10) | type stub ค้างใน `.next/types/` ของ dev/build รอบก่อน — ลบ `.next` หรือรัน `next build`/restart dev server หนึ่งครั้งให้ regenerate แล้วรันใหม่ | -| ต่อ DB ไม่ได้ | ตรวจว่า container รันอยู่ (`docker start judes-note-db`) และพอร์ต 54322 ไม่ถูกใช้โดย container อื่น | +| ต่อ DB ไม่ได้ | ตรวจว่า container รันอยู่ (`docker compose start db` — เดิม `docker start judes-note-db`) และพอร์ต 54322 ไม่ถูกใช้โดย container อื่น | +| API เอกสาร versioning ตอบ 500 ทุก call | ตรวจ env `MINIO_ENDPOINT`/`MINIO_ACCESS_KEY`/`MINIO_SECRET_KEY` และ container minio (`docker compose start minio`) — รายละเอียดอยู่ใน log ฝั่ง server | | Highlight ใช้ไม่ได้กับไฟล์คดีตัวอย่าง | ไฟล์ `01012-*.pdf` ทั้งสองเป็น**ภาพสแกนล้วน** (ไม่มี text layer) — ใช้ `dashboard-report-2026-09-03.pdf` ซึ่งเป็น text-based PDF แทน จึงจะคลุมดำได้ (DRAW/COMMENT/BOOKMARK ใช้ได้ปกติกับทุกไฟล์) | ## หมายเหตุทางเทคนิค @@ -148,11 +176,14 @@ curl -X POST -H "x-api-key: $EXTERNAL_API_KEY" -H "content-type: application/jso db/schema.ts ตาราง + JSONB strict union + DTO + toDTO db/index.ts postgres.js + drizzle (prepare: false) lib/annotate-events.ts pub/sub in-process (actions → SSE route) +lib/document-versioning.ts validation ล้วนของกลุ่ม /api/documents (docver) +lib/docver-store.ts MinIO client + tx จ่ายเลข version + query ของ docver utils/coordinates.ts px ⇄ % + clamp + mergeRectsByLine utils/user.ts parse/build cookie oacCase (mock login e-Court) hooks/useAnnotationRealtime.ts EventSource รับ SSE event + dedupe ด้วย id app/actions/annotations.ts Server Actions CRUD + publish event (ทางเขียนทางเดียว) app/api/note/annotations/stream/route.ts SSE endpoint (GET ?fileId=) +app/api/documents/[docId]/versions/ API จัด version เอกสาร (POST/GET + download-link) app/note/page.tsx รายการไฟล์ + ฟอร์มอัปโหลด (N4) app/note/annotate/[id]/page.tsx โหลด file + initial data → PDFViewer components/pdf/ PDFViewer (root + virtualization), InkLayer, HighlightToolbar diff --git a/app/api/documents/[docId]/versions/[versionId]/download-link/route.ts b/app/api/documents/[docId]/versions/[versionId]/download-link/route.ts new file mode 100644 index 0000000..0b91a6c --- /dev/null +++ b/app/api/documents/[docId]/versions/[versionId]/download-link/route.ts @@ -0,0 +1,48 @@ +// PLAN-DOCVER.md §1 — GET /api/documents/{docId}/versions/{versionId}/download-link?format=docx|pdf +// คืน presigned URL อายุสั้น (env DOCVER_LINK_TTL_SECONDS) ให้ UI เปิดในเบราว์เซอร์จริง +// ของผู้ใช้ — เช็คทั้ง docId และ versionId พร้อมกันกันโยน id ข้ามเอกสาร +import { + docverError, + parseDocId, + parseDownloadFormat, + parseVersionIdParam, +} from "@/lib/document-versioning"; +import { findVersionForDoc, presignVersionUrl } from "@/lib/docver-store"; + +export const runtime = "nodejs"; +export const dynamic = "force-dynamic"; + +export async function GET( + request: Request, + { params }: { params: { docId: string; versionId: string } }, +) { + const docId = parseDocId(params.docId); + if (!docId.ok) return docverError(docId.error, 400); + const versionId = parseVersionIdParam(params.versionId); + if (!versionId.ok) return docverError(versionId.error, 400); + let format: string | null = null; + try { + format = new URL(request.url).searchParams.get("format"); + } catch { + return docverError("URL ของ request ไม่ถูกต้อง", 400); + } + const parsedFormat = parseDownloadFormat(format); + if (!parsedFormat.ok) return docverError(parsedFormat.error, 400); + + try { + const row = await findVersionForDoc(docId.docId, versionId.versionId); + if (!row) { + return docverError("ไม่พบ version นี้ของเอกสาร", 404); + } + if (parsedFormat.format === "pdf" && row.pdfObjectKey === null) { + return docverError("version นี้ไม่มีสำเนา PDF", 404); + } + const link = await presignVersionUrl(row.id, parsedFormat.format); + return new Response(JSON.stringify(link), { + headers: { "content-type": "application/json; charset=utf-8" }, + }); + } catch (err) { + console.error("docver: สร้าง download link ล้ม", err); + return docverError("เกิดข้อผิดพลาดของระบบ โปรดลองใหม่อีกครั้ง", 500); + } +} diff --git a/app/api/documents/[docId]/versions/route.ts b/app/api/documents/[docId]/versions/route.ts new file mode 100644 index 0000000..d022100 --- /dev/null +++ b/app/api/documents/[docId]/versions/route.ts @@ -0,0 +1,162 @@ +// PLAN-DOCVER.md §1 — POST อัปโหลด version ใหม่ + GET history ของ /api/documents/{docId}/versions +// กลุ่ม API stand-alone ไม่มี auth (เปิดกว้างเหมือน api/template — intranet) — ผู้เรียกบอก +// identity เองผ่าน field userId/userName เพราะไม่มี session ร่วมกับใคร +import { createHash, randomUUID } from "node:crypto"; + +import { toDocumentVersionDTO } from "@/db/schema"; +import { + docverError, + DOCVER_MAX_UPLOAD_BYTES, + DOCX_CONTENT_TYPE, + isDocxBytes, + isPdfBytes, + parseDocId, + parseVersionUploadForm, + sanitizeFilePartName, +} from "@/lib/document-versioning"; +import { + findDocumentByDocId, + insertVersionRecord, + listVersionsByDocId, + putVersionObjects, + removeVersionObjects, + versionObjectKeys, +} from "@/lib/docver-store"; + +export const runtime = "nodejs"; +export const dynamic = "force-dynamic"; + +/** POST — อัปโหลด version ใหม่ (เอกสารสร้างแบบ implicit ถ้าเป็น version แรก) + * ลำดับ: validate text field ก่อนแตะไฟล์ → cap ขนาดก่อน buffer → magic bytes → + * putObject MinIO → insert DB (tx) — DB ล้ม = เก็บกวาด object (pattern N4) + * response = UploadVersionResult เป๊ะ โดย versionNumber เป็นค่า authoritative + * จาก server (client เดา latest+1 แล้วแก้เองเมื่ออัปโหลดชนกัน) */ +export async function POST(request: Request, { params }: { params: { docId: string } }) { + const docId = parseDocId(params.docId); + if (!docId.ok) return docverError(docId.error, 400); + + let form: FormData; + try { + form = await request.formData(); + } catch { + return docverError("body ต้องเป็น multipart/form-data", 400); + } + + const parsed = parseVersionUploadForm(form); + if (!parsed.ok) return docverError(parsed.error, 400); + + const file = form.get("file"); + if (!(file instanceof File)) { + return docverError('ต้องแนบไฟล์ .docx ใน field "file"', 400); + } + if (file.size === 0) { + return docverError('ไฟล์ใน field "file" ว่าง (0 ไบต์)', 400); + } + // route handler ไม่ได้รับเคราะห์ bodySizeLimit ของ Server Actions — ต้องเช็คเองก่อน buffer + if (file.size > DOCVER_MAX_UPLOAD_BYTES) { + return docverError('ไฟล์ใน field "file" ใหญ่เกิน 100MB', 413); + } + if (file.type !== DOCX_CONTENT_TYPE && !file.name.toLowerCase().endsWith(".docx")) { + return docverError('field "file" ต้องเป็นไฟล์ .docx', 400); + } + + // add-in อาจแนบ pdf เป็น blob เปล่าตอน capture ไม่สำเร็จ — ถือว่าไม่มี pdf (best-effort) + const rawPdf = form.get("pdf"); + let pdf: File | null = null; + if (rawPdf instanceof File && rawPdf.size > 0) { + if (rawPdf.size > DOCVER_MAX_UPLOAD_BYTES) { + return docverError('ไฟล์ใน field "pdf" ใหญ่เกิน 100MB', 413); + } + pdf = rawPdf; + } + + let docxBytes: Buffer; + let pdfBytes: Buffer | null = null; + try { + docxBytes = Buffer.from(await file.arrayBuffer()); + pdfBytes = pdf ? Buffer.from(await pdf.arrayBuffer()) : null; + } catch (err) { + console.error("docver: อ่านไฟล์แนบเข้าหน่วยความจำไม่สำเร็จ", err); + return docverError("เกิดข้อผิดพลาดของระบบ โปรดลองใหม่อีกครั้ง", 500); + } + if (!isDocxBytes(docxBytes)) { + return docverError("เนื้อไฟล์ .docx ไม่ถูกต้อง (ไม่ใช่ OOXML/ZIP)", 400); + } + if (pdfBytes && !isPdfBytes(pdfBytes)) { + return docverError("เนื้อไฟล์ .pdf ไม่ถูกต้อง", 400); + } + + const contentHash = createHash("sha256").update(docxBytes).digest("hex"); + const versionId = randomUUID(); + // ชื่อแสดงผลตอนดาวน์โหลด: field "fileName" ของผู้เรียก (add-in ส่งมาเอง) → + // ไม่ส่งค่อยใช้ชื่อของ File (ผ่าน decodeUploadFilename — undici ถอดชื่อไทยเป็น latin1) + const docxDisplayName = + parsed.form.fileName ?? sanitizeFilePartName(file.name); + + try { + await putVersionObjects({ + versionId, + docx: { bytes: docxBytes, fileName: docxDisplayName }, + pdf: pdf ? { bytes: pdfBytes!, fileName: sanitizeFilePartName(pdf.name) } : undefined, + }); + } catch (err) { + console.error("docver: เขียน object ลง MinIO ไม่สำเร็จ", err); + return docverError("เกิดข้อผิดพลาดของระบบ โปรดลองใหม่อีกครั้ง", 500); + } + + const keys = versionObjectKeys(versionId); + let row; + try { + row = await insertVersionRecord({ + versionId, + docId: docId.docId, + clientVersionId: parsed.form.clientVersionId, + contentHash, + message: parsed.form.message, + fields: parsed.form.fields, + templateId: parsed.form.templateId, + fileName: docxDisplayName, + docxObjectKey: keys.docx, + pdfObjectKey: pdfBytes ? keys.pdf : null, + docxSize: docxBytes.length, + pdfSize: pdfBytes?.length ?? null, + userId: parsed.form.userId, + userName: parsed.form.userName, + }); + } catch (err) { + // DB ล้มหลังเขียน object — เก็บกวาดก่อนตอบ กัน object กำพร้าไม่มี row ชี้ + console.error("docver: insert version ล้ม — เก็บกวาด object ที่เขียนไปแล้ว", err); + await removeVersionObjects(versionId, pdfBytes !== null); + return docverError("เกิดข้อผิดพลาดของระบบ โปรดลองใหม่อีกครั้ง", 500); + } + + return new Response( + JSON.stringify({ + hasPdf: pdfBytes !== null, + versionId: row.id, + versionNumber: row.versionNumber, + }), + { headers: { "content-type": "application/json; charset=utf-8" } }, + ); +} + +/** GET — history ของเอกสาร (ใหม่ → เก่า) · docId ที่ยังไม่เคยมี version = 404 + * (สัญญาณ REST ตรง ๆ แบบ N10 — UI ฝั่ง add-in แสดง error + ปุ่มลองใหม่ ไม่พัง) */ +export async function GET(_request: Request, { params }: { params: { docId: string } }) { + const docId = parseDocId(params.docId); + if (!docId.ok) return docverError(docId.error, 400); + + try { + const doc = await findDocumentByDocId(docId.docId); + if (!doc) { + return docverError("ไม่พบเอกสารนี้ในระบบ (ยังไม่เคยอัปโหลด version)", 404); + } + const rows = await listVersionsByDocId(doc.id); + return new Response(JSON.stringify(rows.map(toDocumentVersionDTO)), { + headers: { "content-type": "application/json; charset=utf-8" }, + }); + } catch (err) { + console.error("docver: โหลด version history ล้ม", err); + return docverError("เกิดข้อผิดพลาดของระบบ โปรดลองใหม่อีกครั้ง", 500); + } +} diff --git a/db/schema.ts b/db/schema.ts index 0e3c789..840c79d 100644 --- a/db/schema.ts +++ b/db/schema.ts @@ -253,12 +253,76 @@ export const annotationVersions = pgTable( (t) => [index("annotation_versions_file_idx").on(t.fileId)], ); +// ─── e-Court Documents Versioning (docs/PLAN-DOCVER.md) ────────────────────── + +/** เอกสารหนึ่งชิ้นต่อ docId ที่ผู้เรียกกำหนด — สร้าง implicitly ตอน upload version แรก + * (ไม่มี endpoint/หน้าจอบริหารแยก) · **ไม่เกี่ยวกับตาราง files ของแอป note** — + * ไม่มี FK ไม่มี field ร่วม ไม่มีหน้า note ใดอ่าน (ของกลุ่ม /api/documents ล้วน ๆ) */ +export const documents = pgTable( + "documents", + { + id: uuid("id").primaryKey().defaultRandom(), + /** รหัสเอกสารจากผู้เรียก (add-in ใช้ GUID DocumentTrackingId / e-Court ใช้รหัสของตัวเอง) + * — unique ทั้งระบบ: เอกสารเดียวมี row เดียว (ห้ามซ้ำ) */ + docId: text("doc_id").notNull(), + createdAt: timestamp("created_at", { withTimezone: true }) + .defaultNow() + .notNull(), + }, + (t) => [uniqueIndex("documents_doc_id_key").on(t.docId)], +); + +/** version ของเอกสาร — ไฟล์จริงอยู่ใน MinIO ตาม object key (docx + pdf ต่อ version) + * · pdf เป็น optional ตาม contract ของ add-in (best-effort — NULL = version นี้ไม่มี pdf) + * · object key เป็น uuid ล้วน `docver//document.{docx,pdf}` ไม่มี input ผู้ใช้ */ +export const documentVersions = pgTable( + "document_versions", + { + id: uuid("id").primaryKey().defaultRandom(), + documentId: uuid("document_id") + .notNull() + .references(() => documents.id, { onDelete: "cascade" }), + versionNumber: integer("version_number").notNull(), + /** GUID ที่ client generate ก่อน upload — เก็บไว้เทียบ/สืบสวน ไม่ใช้เป็น identity + * (ไม่มี idempotency — retry ของผู้เรียกเกิด version ใหม่ ตามแผน §4) */ + clientVersionId: text("client_version_id"), + /** SHA-256 hex ของไบต์ docx — คำนวณฝั่ง server เสมอ (NOT NULL เพราะ buffer ก่อน insert ทุกครั้ง) */ + contentHash: text("content_hash").notNull(), + /** commit message (FR-13) — trim แล้วว่าง = NULL */ + message: text("message"), + /** snapshot ตอน upload: map ดิบ `{ [sdtTag]: text }` จาก content controls + * (รูปแบบ {value,missing} ของเอกสารผูก template ยังไม่มีผู้ส่ง — วันหน้ามีค่อยขยาย $type) */ + fields: jsonb("fields").$type>(), + templateId: text("template_id"), + /** ชื่อไฟล์ดั้งเดิมที่ browser ส่ง (ผ่าน decodeUploadFilename + strip path แล้ว) — + * ใช้ตั้ง Content-Disposition ของ object ไม่ใช่ชื่อ object */ + fileName: text("file_name"), + docxObjectKey: text("docx_object_key").notNull(), + pdfObjectKey: text("pdf_object_key"), + docxSize: integer("docx_size").notNull(), + pdfSize: integer("pdf_size"), + /** ผู้อัปโหลด — endpoint รับมาแบบ plain field เพราะระบบ stand-alone ไม่มี session */ + userId: text("user_id").notNull(), + userName: text("user_name").notNull(), + createdAt: timestamp("created_at", { withTimezone: true }) + .defaultNow() + .notNull(), + }, + (t) => [ + // safety net ของการจ่ายเลข max+1 ภายใต้ FOR UPDATE (PLAN-DOCVER.md §3) + uniqueIndex("document_versions_document_number_key").on(t.documentId, t.versionNumber), + index("document_versions_document_created_idx").on(t.documentId, t.createdAt), + ], +); + // ─── DTO (timestamp เป็น ISO string เสมอ — ห้ามส่ง Date ข้าม server→client) ── export type AnnotationRow = typeof annotations.$inferSelect; export type BookmarkRow = typeof bookmarks.$inferSelect; export type ReplyRow = typeof annotationReplies.$inferSelect; export type AnnotationVersionRow = typeof annotationVersions.$inferSelect; +export type DocumentRow = typeof documents.$inferSelect; +export type DocumentVersionRow = typeof documentVersions.$inferSelect; export type AnnotationDTO = { id: string; @@ -371,3 +435,33 @@ export function toVersionDTO(row: AnnotationVersionRow): AnnotationVersionDTO { createdAt: row.createdAt.toISOString(), }; } + +/** item ของ GET /api/documents/{docId}/versions — surface ตรง VersionRecord ของ + * wordaddin/models/versionRecord.ts (คัดลอกมา define เอง — ห้าม import จาก wordaddin/ + * ซึ่งเป็น client-only zone) + createdByUserName เป็นส่วนเสริม (field เพิ่มไม่ทำ + * UI ฝั่ง client พัง) · hasPdf derive จาก pdfObjectKey !== null ตัวเดียว (PLAN-DOCVER.md §1) */ +export type DocumentVersionDTO = { + versionId: string; + versionNumber: number; + createdAt: string; + createdByUserId: string; + createdByUserName: string; + contentHash: string; + fields: Record | null; + message: string | null; + hasPdf: boolean; +}; + +export function toDocumentVersionDTO(row: DocumentVersionRow): DocumentVersionDTO { + return { + versionId: row.id, + versionNumber: row.versionNumber, + createdAt: row.createdAt.toISOString(), + createdByUserId: row.userId, + createdByUserName: row.userName, + contentHash: row.contentHash, + fields: row.fields, + message: row.message, + hasPdf: row.pdfObjectKey !== null, + }; +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..b6a099c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,58 @@ +# Dev dependencies ของ judes-note — รันด้วย `docker compose up -d` (ทั้งคู่) +# หรือ `docker compose up -d minio` (ถ้าจะรัน DB แบบเดิมด้วย docker run) +# +# service `db` ใช้ชื่อ container/วอลุ่มเดียวกับคำสั่ง docker run เดิมใน README +# (วอลุ่ม pin ชื่อไม่มี prefix ของ compose ผ่าน `name:` ด้านล่าง) — ถ้ามี container +# judes-note-db เก่ารันอยู่ (สร้างด้วย docker run) ให้ `docker rm -f judes-note-db` +# ก่อน แล้ว compose จะต่อวอลุ่ม judes-note-db-data เดิมได้เลย ข้อมูลไม่หาย +services: + db: + image: postgres:17-alpine + container_name: judes-note-db + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + # ชื่อ database ตรง DATABASE_URL ใน .env (สร้างตอน first init ของวอลุ่มเท่านั้น + # — วอลุ่มเดิมจาก docker run ที่ init ด้วย postgres ต้องสร้างเอง: + # docker exec judes-note-db psql -U postgres -c 'CREATE DATABASE "ecourt-note"') + POSTGRES_DB: ecourt-note + ports: + - "54322:5432" + volumes: + - judes-note-db-data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"] + interval: 5s + timeout: 3s + retries: 10 + + # Object storage ของกลุ่ม API เอกสาร versioning (docs/PLAN-DOCVER.md §6) — + # bucket docver ไม่ต้องสร้างเอง: app สร้างตอน first use ผ่าน ensureBucket + # (ใช้ registry ทางการของ MinIO ที่ quay.io + pin RELEASE — Docker Hub + # ของ minio/minio เจอ rate limit/เข้าไม่ได้บ่อย) + minio: + image: quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z + container_name: judes-note-minio + command: server /data --console-address ":9001" + environment: + MINIO_ROOT_USER: minioadmin + MINIO_ROOT_PASSWORD: minioadmin + ports: + # โฮสต์ใช้ 19000/19001 เพราะ 9000/9001 ชนกับ container ของโปรเจกต์อื่นในเครื่องนี้ + # (ข้างใน container ยังเป็น 9000/9001 ตาม default ของ MinIO) + - "19000:9000" # S3 API (MINIO_ENDPOINT/MINIO_PORT) + - "19001:9001" # console ดู bucket/object ผ่าน browser + volumes: + - judes-note-minio-data:/data + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 5s + timeout: 3s + retries: 10 + +volumes: + # pin ชื่อจริงไม่มี prefix _ ของ compose ให้ตรงชื่อวอลุ่มจาก docker run เดิม + judes-note-db-data: + name: judes-note-db-data + judes-note-minio-data: + name: judes-note-minio-data diff --git a/docs/PLAN-DOCVER.md b/docs/PLAN-DOCVER.md new file mode 100644 index 0000000..771669a --- /dev/null +++ b/docs/PLAN-DOCVER.md @@ -0,0 +1,181 @@ +# PLAN-DOCVER — Document Versioning API (stand-alone) + +> วันที่ตกลงแผน: 2026-10-20 · สถานะ: implement รอบแรก (API ก่อน — ยังไม่ต่อ wordaddin) + +## 0. บริบท/เป้าหมาย + +ทำ backend จริงของฟีเจอร์ **"e-Court Documents Versioning"** (ตอนนี้ wordaddin ใช้ mock ใน +`wordaddin/services/versioningService.ts`): + +- รับ `docId` จากผู้เรียก → ตารางเอกสารที่ `docId` **ห้ามซ้ำ** (unique index) +- แต่ละ version อ้าง **object ใน MinIO** — เก็บทั้ง `.docx` และ `.pdf` (pdf เป็น + optional/best-effort ตาม contract ของ add-in — `pdf.size === 0` = ไม่มี, มี `hasPdf` บอกใน + response) +- เก็บ `userId`, `userName`, `createdAt` ต่อ version เพื่อรู้ว่าใครอัปโหลด +- **endpoint รับ `userId`/`userName` ใน request** เพราะเป็นระบบ stand-alone ไม่มี session + ร่วมกับใคร (ผู้เรียกคือ add-in ใน Word หรือระบบภายนอก — identity ส่งมาแบบ plain field) +- **ไม่มี auth** — เปิดกว้างเหมือนกลุ่ม `api/template` (intranet) · ไม่มี CORS + +ข้อตกลงกับผู้ใช้ (2026-10-20): ทำ API ก่อน **ไม่แต้ wordaddin รอบนี้** — contract ตรง surface +ที่ mock คงไว้เป๊ะ วันหน้าสลับของจริงแค่ตั้ง `NEXT_PUBLIC_ADDIN_API_BASE_URL` แล้วก้อบ body +ของต้นฉบับกลับมา (ดู `docs/WORDADDIN-DEV.md`) · MinIO dev รันผ่าน `docker-compose.yml` +(ผู้เรียกอัปโหลดทั้ง docx+pdf — ฝั่ง add-in ใช้ Office.js แปลง Word→PDF เอง) + +Contract อ้างอิง: `docs/addin-ui-flow.md` §7 + `wordaddin/services/versioningService.ts` + +`wordaddin/models/versionRecord.ts` — **โค้ด server ห้าม import จาก `wordaddin/`** +(client-only zone) type define เองใน `db/schema.ts`/`lib` + +## 1. Endpoint (URL เต็ม = `/docedit/api/documents/…` — prefix ใหม่ ได้รับยกเว้นจาก +middleware เหมือน `api/template`) + +### `POST /api/documents/{docId}/versions` — อัปโหลด version ใหม่ + +multipart/form-data: + +| field | type | หมายเหตุ | +|---|---|---| +| `file` | File (.docx) | บังคับ — ≤ 100MB, magic bytes `PK\x03\x04` | +| `pdf` | File (.pdf) | optional — `size === 0` = ถือว่าไม่มี (add-in แนบ blob เปล่าได้) | +| `userId` | string | **บังคับ** (stand-alone) — trim 1–100 | +| `userName` | string | **บังคับ** (stand-alone) — trim 1–200 | +| `clientVersionId` | string | optional ≤100 — GUID ที่ client generate เอง | +| `fields` | string (JSON) | optional — map `{ [sdtTag]: text }` (≤100 keys, value ≤10,000) | +| `message` | string | optional — commit message ≤2048 (trim ว่าง = NULL) | +| `templateId` | string | optional ≤100 | +| `fileName` | string | optional — ชื่อไฟล์ดั้งเดิม (ผ่าน `decodeUploadFilename` แล้ว) | + +- field ที่ไม่อยู่ในตาราง = **400 กัน typo เงียบ ๆ** (pattern `parseQueryBody` ของ N10) +- เอกสาร (`documents` row) สร้าง **แบบ implicit** ตอน upload version แรก — ไม่มี endpoint + "สร้างเอกสาร" แยก (ตาม flow ของ add-in ที่ generate documentId เองแล้วอัปโหลดทันที) +- response 200: `{ "hasPdf": boolean, "versionId": string, "versionNumber": number }` + (= `UploadVersionResult` เป๊ะ) — `versionNumber` เป็นค่า **authoritative จาก server** + (client เดา `latest+1` แล้วแก้ XML part ตัวเองเมื่อไม่ตรง — กรณีอัปโหลดชนกัน) + +### `GET /api/documents/{docId}/versions` — history + +response: `DocumentVersionDTO[]` เรียง **ใหม่ → เก่า** (ตาม `VersionRecord[]` ของ add-in + +ส่วนเสริม `createdByUserName`): + +```ts +{ + versionId: string; // = id ของ row (uuid) + versionNumber: number; + createdAt: string; // ISO-8601 + createdByUserId: string; // = user_id + createdByUserName: string; // = user_name (ส่วนเสริมนอก contract เดิม) + contentHash: string; // SHA-256 hex ของ docx (คำนวณฝั่ง server) + fields: Record | null; + message: string | null; + hasPdf: boolean; // derive จาก pdf_object_key !== null ตัวเดียว +} +``` + +- `docId` ไม่รู้จัก (ยังไม่เคยมี version) = **404** (สัญญาณ REST ตรง ๆ แบบ N10 — UI ของ + add-in แสดง error + ปุ่มลองใหม่ ไม่พัง) + +### `GET /api/documents/{docId}/versions/{versionId}/download-link?format=docx|pdf` + +- format ไม่ส่ง/"" = `docx` · ค่าอื่น = 400 +- query version row **เช็คทั้ง docId และ versionId พร้อมกัน** (กันโยน id ข้ามเอกสาร) — ไม่เจอ = 404 +- `format=pdf` แต่ version นั้นไม่มี pdf = 404 "version นี้ไม่มีสำเนา PDF" +- response 200: `{ "url": string, "expiresAt": string }` — url เป็น **MinIO presigned GET** + (อายุตาม env `DOCVER_LINK_TTL_SECONDS` default 900 วิ) ให้ UI เปิดในเบราว์เซอร์จริงของ + ผู้ใช้ (webview ของ Word ทิ้ง blob download เงียบ ๆ — เหตุผลเดียวกับแผน add-in เดิม) +- URL อ้าง `MINIO_ENDPOINT:MINIO_PORT` โดยตรง — **โฮสต์นี้ต้องเข้าถึงได้จากเบราว์เซอร์ + ของผู้ใช้** ไม่ใช่แค่จาก Next server (หมายเหตุนี้อยู่ใน `.env.example`/README ด้วย) + +### ตาราง error + +| เหตุ | สถานะ | +|---|---| +| รูป docId/versionId/format ผิด · field ไม่รู้จัก · ไม่ใช่ multipart · magic bytes ผิด | 400 | +| ไฟล์เกิน 100MB (เช็ค `file.size` ก่อน buffer เสมอ — route ไม่มี `bodySizeLimit` คุม) | 413 | +| เอกสาร/version ไม่มี · ขอ pdf ที่ version ไม่มี | 404 | +| MinIO env ไม่ตั้ง/ล้ม · DB ล้ม (หลังเก็บกวาด object กำพร้า) | 500 (log เต็มฝั่ง server + ข้อความกลาง ๆ ไทย) | + +## 2. ตาราง (db/schema.ts) + +``` +documents — id uuid PK · doc_id text UNIQUE NOT NULL · created_at timestamptz +document_versions — id uuid PK · document_id uuid FK→documents CASCADE + · version_number int NOT NULL (+ UNIQUE(document_id, version_number)) + · client_version_id · content_hash NOT NULL · message · fields jsonb + · template_id · file_name · docx_object_key NOT NULL · pdf_object_key + · docx_size int NOT NULL · pdf_size int · user_id NOT NULL + · user_name NOT NULL · created_at + + index (document_id, created_at) +``` + +- **`documents` ≠ `files`** ของแอป note — ไม่มี FK ไม่มี field ร่วม ไม่มีหน้า note ไหนอ่าน + (ชื่อคล้ายกันแต่คนละโดเมน: ตารางนี้เป็นของกลุ่ม API นี้ล้วน ๆ) +- `content_hash`/`docx_size` เป็น NOT NULL เพราะ server buffer ไฟล์และคำนวณทั้งคู่ก่อน + insert เสมอ · `hasPdf` **ไม่มี column** — derive จาก `pdf_object_key !== null` +- object key ใน MinIO: `docver//document.docx` / `document.pdf` — **uuid ล้วน** + ไม่มี input ผู้ใช้ (เหตุผลเดียวกับ N4 ที่ห้ามชื่อผู้ใช้ตกแต่ง path) · `versionId` สร้าง + ด้วย `randomUUID()` ฝั่ง server แล้วส่งเป็น `id` ตอน insert (object เขียนไปก่อนแล้ว) + +## 3. จ่ายเลข version ปลอดภัยเรียกคู่ขนาน + +เลือก: **insert-on-conflict + `SELECT … FOR UPDATE` ใน transaction เดียว** (ใช้ `.for("update")` +ที่ repo ใช้อยู่แล้วใน `app/actions/annotations.ts`): + +1. `INSERT INTO documents (doc_id) VALUES (…) ON CONFLICT DO NOTHING` — สร้างเอกสารถ้ายังไม่มี +2. `SELECT id FROM documents WHERE doc_id = … FOR UPDATE` — serialize ผู้จ่ายเลขรายเอกสาร + (unique `doc_id` → ได้ row เดียวเสมอ ไม่มี deadlock ข้ามเอกสาร) +3. `SELECT max(version_number) + 1` แล้ว insert row version + +ทางเลือกที่ตัดทิ้ง: retry-on-23505 (ต้องเก็บกวาด object ซ้ำทุกรอบ ไม่มี ordering) · +`pg_advisory_xact_lock` (ได้แต่ต้อง raw SQL และซ้ำซ้อนกับ row lock) — UNIQUE +`(document_id, version_number)` เก็บไว้เป็น safety net + +## 4. ลำดับงานต่อ request (POST) + เก็บกวาด object กำพร้า + +validate (text field ก่อนแตะไฟล์) → cap ขนาด → buffer → magic bytes → sha256 → `versionId` +→ **putObject MinIO** (docx ก่อน pdf; pdf ล้ม = ลบ docx ทิ้ง) → **insert DB (tx §3)** — +ล้ม = `removeObject` ทั้งคู่แล้วโยนต่อ (pattern เดียวกับ `unlink` + rethrow ของ N4) + +- crash ระหว่าง putObject↔insert เหลือ object ไร้ row ชี้ (key เป็น uuid ไม่ชนใคร) — + **ไม่ทำ GC รอบนี้** บันทึกไว้เป็นงานอนาคต +- ไม่มี idempotency บน `clientVersionId` — retry หลัง network timeout เกิด version ใหม่ + (client แก้เลขจาก response เองได้) ยอมรับได้ + +## 5. Carve-out จาก invariant #4 (CLAUDE.md) + +กลุ่ม API นี้ **เขียน DB ผ่าน route handler** (ไม่ใช่ Server Action) และไม่ publish SSE +ไม่ `revalidatePath` — ตาราง `documents`/`document_versions` เป็นของกลุ่มนี้ล้วน ไม่มีหน้า +note ใดอ่าน จึงไม่มี Router Cache/SSE ที่ต้อง invalidate (นับเป็นข้อยกเว้นเดียวกับลักษณะ +read-only ของ N10 แต่ตัวนี้เขียนได้) + +## 6. Env (อ่านตอนเรียก ไม่ใช่ตอน module load — ขาด/ล้ม = 500 ไทยกลาง ๆ + log เต็มฝั่ง server) + +| env | default | หมายเหตุ | +|---|---|---| +| `MINIO_ENDPOINT` | — บังคับ | host อย่างเดียว ไม่มี scheme | +| `MINIO_PORT` | `9000` | dev compose แมพโฮสต์เป็น 19000 (9000/9001 ชน service อื่นในเครื่อง) | +| `MINIO_USE_SSL` | `false` | | +| `MINIO_ACCESS_KEY` / `MINIO_SECRET_KEY` | — บังคับ | dev compose = `minioadmin` | +| `MINIO_BUCKET` | `docver` | app สร้างเองตอน first use (`ensureBucket` — จับ race ข้าม process) | +| `DOCVER_LINK_TTL_SECONDS` | `900` | อายุ presigned URL | + +`putObject` แนบ `Content-Type` + `Content-Disposition` (ชื่อไฟล์ไทยผ่าน `filename*=UTF-8''` +best-effort + ASCII fallback `document.docx`/`document.pdf`) — presigned GET ได้ชื่อไฟล์สวย +ตอนดาวน์โหลด + +## 7. ไฟล์ของกลุ่มนี้ + +- `db/schema.ts` — ตาราง + `DocumentVersionDTO`/`toDocumentVersionDTO` +- `lib/document-versioning.ts` — pure validation (ห้าม import `@/db`/`minio` — unit test ได้ + โดยไม่ต้องตั้ง env อะไรเลย ตาม pattern `lib/external-api-auth.ts`) +- `lib/docver-store.ts` — MinIO client (เก็บบน `globalThis` ใน dev แบบ `db/index.ts`) + + `ensureBucket` + put/remove/presign + `insertVersionRecord` (tx) + query helpers +- `app/api/documents/[docId]/versions/route.ts` — POST + GET +- `app/api/documents/[docId]/versions/[versionId]/download-link/route.ts` — GET +- `docker-compose.yml` — db (ชื่อ/วอลุ่มเดิมกับ `docker run` ใน README) + minio + (โฮสต์ 19000 API / 19001 console — ข้างใน container 9000/9001; image จาก quay.io) +- `tests/lib/document-versioning.test.ts` — unit test ส่วน pure + +## 8. Out of scope รอบนี้ + +- ไม่แต้ `wordaddin/` หรือ client ใด ๆ (สลับ mock ทีหลังผ่าน `NEXT_PUBLIC_ADDIN_API_BASE_URL`) +- ไม่มีหน้า UI ดูรายการเอกสาร/version ของกลุ่มนี้ · ไม่มี GC object กำพร้า · ไม่มี idempotency + บน `clientVersionId` · ไม่มีการแปลง docx→pdf ฝั่ง server (Office.js ของ add-in ทำ) diff --git a/drizzle/0005_icy_bloodaxe.sql b/drizzle/0005_icy_bloodaxe.sql new file mode 100644 index 0000000..09b130c --- /dev/null +++ b/drizzle/0005_icy_bloodaxe.sql @@ -0,0 +1,29 @@ +CREATE TABLE "document_versions" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "document_id" uuid NOT NULL, + "version_number" integer NOT NULL, + "client_version_id" text, + "content_hash" text NOT NULL, + "message" text, + "fields" jsonb, + "template_id" text, + "file_name" text, + "docx_object_key" text NOT NULL, + "pdf_object_key" text, + "docx_size" integer NOT NULL, + "pdf_size" integer, + "user_id" text NOT NULL, + "user_name" text NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "documents" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "doc_id" text NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "document_versions" ADD CONSTRAINT "document_versions_document_id_documents_id_fk" FOREIGN KEY ("document_id") REFERENCES "public"."documents"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE UNIQUE INDEX "document_versions_document_number_key" ON "document_versions" USING btree ("document_id","version_number");--> statement-breakpoint +CREATE INDEX "document_versions_document_created_idx" ON "document_versions" USING btree ("document_id","created_at");--> statement-breakpoint +CREATE UNIQUE INDEX "documents_doc_id_key" ON "documents" USING btree ("doc_id"); \ No newline at end of file diff --git a/drizzle/meta/0005_snapshot.json b/drizzle/meta/0005_snapshot.json new file mode 100644 index 0000000..e664561 --- /dev/null +++ b/drizzle/meta/0005_snapshot.json @@ -0,0 +1,735 @@ +{ + "id": "d9645a8e-eaf0-4f76-8c5e-bf978f6e61f0", + "prevId": "84f979ad-4a95-41f5-8082-30a994f8644d", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.annotation_replies": { + "name": "annotation_replies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "annotation_id": { + "name": "annotation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_name": { + "name": "user_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "annotation_replies_annotation_idx": { + "name": "annotation_replies_annotation_idx", + "columns": [ + { + "expression": "annotation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "annotation_replies_annotation_id_annotations_id_fk": { + "name": "annotation_replies_annotation_id_annotations_id_fk", + "tableFrom": "annotation_replies", + "tableTo": "annotations", + "columnsFrom": [ + "annotation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.annotation_versions": { + "name": "annotation_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "annotation_id": { + "name": "annotation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "file_id": { + "name": "file_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "page_index": { + "name": "page_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_name": { + "name": "user_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "annotation_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "annotation_versions_file_idx": { + "name": "annotation_versions_file_idx", + "columns": [ + { + "expression": "file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "annotation_versions_file_id_files_id_fk": { + "name": "annotation_versions_file_id_files_id_fk", + "tableFrom": "annotation_versions", + "tableTo": "files", + "columnsFrom": [ + "file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.annotations": { + "name": "annotations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "file_id": { + "name": "file_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "page_index": { + "name": "page_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_name": { + "name": "user_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "annotation_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "resolved": { + "name": "resolved", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "annotations_file_page_idx": { + "name": "annotations_file_page_idx", + "columns": [ + { + "expression": "file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "page_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "annotations_file_id_files_id_fk": { + "name": "annotations_file_id_files_id_fk", + "tableFrom": "annotations", + "tableTo": "files", + "columnsFrom": [ + "file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bookmarks": { + "name": "bookmarks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "file_id": { + "name": "file_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "page_index": { + "name": "page_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_name": { + "name": "user_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "y_offset": { + "name": "y_offset", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "bookmarks_file_id_files_id_fk": { + "name": "bookmarks_file_id_files_id_fk", + "tableFrom": "bookmarks", + "tableTo": "files", + "columnsFrom": [ + "file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_versions": { + "name": "document_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version_number": { + "name": "version_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "client_version_id": { + "name": "client_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fields": { + "name": "fields", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_name": { + "name": "file_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "docx_object_key": { + "name": "docx_object_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pdf_object_key": { + "name": "pdf_object_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "docx_size": { + "name": "docx_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "pdf_size": { + "name": "pdf_size", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_name": { + "name": "user_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_versions_document_number_key": { + "name": "document_versions_document_number_key", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_versions_document_created_idx": { + "name": "document_versions_document_created_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_versions_document_id_documents_id_fk": { + "name": "document_versions_document_id_documents_id_fk", + "tableFrom": "document_versions", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documents": { + "name": "documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "doc_id": { + "name": "doc_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "documents_doc_id_key": { + "name": "documents_doc_id_key", + "columns": [ + { + "expression": "doc_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.files": { + "name": "files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pdf_url": { + "name": "pdf_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type_id": { + "name": "type_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "files_type_id_idx": { + "name": "files_type_id_idx", + "columns": [ + { + "expression": "type_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "files_external_id_key": { + "name": "files_external_id_key", + "columns": [ + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.annotation_type": { + "name": "annotation_type", + "schema": "public", + "values": [ + "HIGHLIGHT", + "DRAW", + "COMMENT", + "SHAPE", + "REDACTION", + "TEXTBOX", + "STAMP" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index e3fa33a..7dd298b 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -36,6 +36,13 @@ "when": 1790072946313, "tag": "0004_right_ted_forrester", "breakpoints": true + }, + { + "idx": 5, + "version": "7", + "when": 1792467537332, + "tag": "0005_icy_bloodaxe", + "breakpoints": true } ] } \ No newline at end of file diff --git a/lib/document-versioning.ts b/lib/document-versioning.ts new file mode 100644 index 0000000..9973286 --- /dev/null +++ b/lib/document-versioning.ts @@ -0,0 +1,273 @@ +// PLAN-DOCVER.md §1 — validation ล้วนของกลุ่ม /api/documents (document versioning) +// pattern เดียวกับ lib/external-api.ts ของ N10: strict กัน typo เงียบ ๆ (field ที่ +// ไม่รู้จัก = error), trim-ว่าง = ถือว่าไม่ส่ง, ข้อความ error เป็นไทยทุกอัน +// +// ห้าม import อะไรที่ลาก @/db หรือ minio มาด้วย — unit test (tests/lib/) ต้องรันได้ +// โดยไม่ต้องตั้ง DATABASE_URL/MINIO_* เลย (pattern เดียวกับ lib/external-api-auth.ts) +import { decodeUploadFilename } from "@/utils/upload-name"; + +/** cap เดียวกับ N4 / api/template — route ต้องเช็คขนาดก่อน buffer เสมอ + * (route handler ไม่ได้รับเคราะห์ bodySizeLimit ของ Server Actions) */ +export const DOCVER_MAX_UPLOAD_BYTES = 100 * 1024 * 1024; + +/** ตรง maxLength ของ commit message dialog ฝั่ง add-in (FR-13) */ +export const DOCVER_MESSAGE_MAX = 2048; + +export const DOCX_CONTENT_TYPE = + "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; +export const PDF_CONTENT_TYPE = "application/pdf"; + +/** รหัสเอกสารจากผู้เรียก — URL-path-safe ล้วน (ไม่มี / .. ช่องว่าง ไทย) กันถูก + * ฝังลง path/log แปลก ๆ และตัดขนาดให้จำกัด */ +const DOC_ID_RE = /^[A-Za-z0-9._:-]{1,200}$/; + +const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + +/** field ที่รับใน multipart ของ POST …/versions — อะไรนอกนี้ = 400 กัน typo เงียบ ๆ */ +const FORM_FIELDS = [ + "file", + "pdf", + "userId", + "userName", + "clientVersionId", + "fields", + "message", + "templateId", + "fileName", +] as const; + +export type DocverError = { ok: false; error: string }; + +/** Response ผิดพลาดของกลุ่มนี้ — JSON {"error": "<ไทย"} รูปเดียวกับ N10 + * (define ที่นี่เพื่อให้ route ทั้งสองใช้ร่วมโดยไม่ต้องพึ่ง module ของ N10) */ +export function docverError(message: string, status: number): Response { + return new Response(JSON.stringify({ error: message }), { + status, + headers: { "content-type": "application/json; charset=utf-8" }, + }); +} + +/** ตรวจ docId จาก path param — trim แล้วว่าง/เกิน 200/มีตัวอักษรนอก charset = ไม่ผ่าน + * (+ ปฏิเสธ "." / ".." เป็นพิเศษแม้อยู่ใน charset — path-traversal specials) */ +export function parseDocId(raw: string): + | { ok: true; docId: string } + | DocverError { + const docId = raw.trim(); + if (docId === "." || docId === ".." || !DOC_ID_RE.test(docId)) { + return { + ok: false, + error: + "รหัสเอกสาร (docId) ต้องยาว 1–200 ตัวอักษร และใช้ได้เฉพาะ A-Z a-z 0-9 . _ : -", + }; + } + return { ok: true, docId }; +} + +/** magic bytes ของ .docx — OOXML เป็น ZIP จึงขึ้นต้น PK\x03\x04 (mirror รูปแบบ + * matchesSignature ของ app/actions/files.ts — utils/file-kind.ts เป็นของ note files) */ +export function isDocxBytes(bytes: Buffer): boolean { + return bytes.subarray(0, 4).toString("latin1") === "PK\x03\x04"; +} + +export function isPdfBytes(bytes: Buffer): boolean { + return bytes.subarray(0, 5).toString("latin1") === "%PDF-"; +} + +/** versionId จาก path param — เก็บเป็น uuid เสมอจึงบังคับรูป (normalize ลงตัวพิมพ์เล็ก) */ +export function parseVersionIdParam(raw: string): + | { ok: true; versionId: string } + | DocverError { + const versionId = raw.trim().toLowerCase(); + if (!UUID_RE.test(versionId)) { + return { ok: false, error: "versionId ต้องเป็น UUID" }; + } + return { ok: true, versionId }; +} + +/** ?format= ของ download-link — ไม่ส่ง/ว่าง = docx (ค่าเริ่มต้นตาม contract) */ +export function parseDownloadFormat(raw: string | null): + | { ok: true; format: "docx" | "pdf" } + | DocverError { + const format = raw?.trim() ?? ""; + if (format === "") return { ok: true, format: "docx" }; + if (format === "docx" || format === "pdf") return { ok: true, format }; + return { ok: false, error: "format รับค่า docx หรือ pdf เท่านั้น" }; +} + +function trimOrNull(value: string): string | null { + const trimmed = value.trim(); + return trimmed === "" ? null : trimmed; +} + +/** อ่าน text field จาก FormData — ไม่ส่ง = null · ส่งมาเป็น File = 400 (ไม่ใช่ string) */ +function formString(form: FormData, field: string): + | { ok: true; value: string | null } + | DocverError { + const entry = form.get(field); + if (entry === null) return { ok: true, value: null }; + if (typeof entry !== "string") { + return { ok: false, error: `field '${field}' ต้องเป็น string` }; + } + return { ok: true, value: trimOrNull(entry) }; +} + +/** ชื่อไฟล์แสดงผล (Content-Disposition): ตัด path ที่ฝังมา + trim + cap 200 — + * ว่าง = null (ให้ presign ใช้ชื่อ default document.docx/pdf) */ +export function sanitizeDisplayFileName(raw: string | null): string | null { + if (raw === null) return null; + const base = raw.split(/[\\/]/).pop() ?? raw; + return trimOrNull(base)?.slice(0, 200) ?? null; +} + +/** ชื่อไฟล์จาก File.name ของ multipart — undici ถอด Content-Disposition เป็น latin1 + * ทำชื่อไทยเพี้ยน (N4) จึงต้องผ่าน decodeUploadFilename ก่อน sanitize เสมอ */ +export function sanitizeFilePartName(raw: string): string | null { + return sanitizeDisplayFileName(decodeUploadFilename(raw)); +} + +/** fields ของ multipart (JSON string) → map ดิบ { [sdtTag]: text } — + * `{}` หรือไม่ส่ง = null · ไม่ใช่ object ล้วน/key หรือ value ผิดกติกา = 400 */ +export function parseFieldsJson(raw: string): + | { ok: true; fields: Record | null } + | DocverError { + let parsed: unknown; + try { + parsed = JSON.parse(raw); + } catch { + return { ok: false, error: 'field "fields" ต้องเป็น JSON object เช่น { "tag": "ค่า" }' }; + } + if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) { + return { ok: false, error: 'field "fields" ต้องเป็น JSON object เช่น { "tag": "ค่า" }' }; + } + const record = parsed as Record; + const keys = Object.keys(record); + if (keys.length > 100) { + return { ok: false, error: 'field "fields" มี key เกิน 100 รายการ' }; + } + for (const key of keys) { + if (key.length > 200) { + return { ok: false, error: 'key แต่ละรายการใน "fields" ยาวเกิน 200 ตัวอักษร' }; + } + const value = record[key]; + if (typeof value !== "string") { + return { ok: false, error: 'ค่าของแต่ละรายการใน "fields" ต้องเป็น string' }; + } + if (value.length > 10_000) { + return { ok: false, error: 'ค่าของแต่ละรายการใน "fields" ยาวเกิน 10,000 ตัวอักษร' }; + } + } + return { ok: true, fields: keys.length === 0 ? null : (parsed as Record) }; +} + +export type ParsedVersionUploadForm = { + userId: string; + userName: string; + clientVersionId: string | null; + message: string | null; + templateId: string | null; + /** ชื่อไฟล์ดั้งเดิมจาก field "fileName" (แยกจากชื่อของ File เอง — add-in ส่งมาเอง + * เพราะ desktop Word ได้ชื่อจาก path จริง) — strip path + cap แล้ว */ + fileName: string | null; + fields: Record | null; +}; + +/** ตรวจ text field ทั้งหมดของ POST …/versions (file/pdf เช็คที่ route เพราะต้องอ่าน + * size ก่อน buffer) — userId/userName บังคับเพราะระบบ stand-alone ไม่มี session */ +export function parseVersionUploadForm(form: FormData): + | { ok: true; form: ParsedVersionUploadForm } + | DocverError { + // Array.from เพราะ tsconfig ไม่ได้ตั้ง target ≥ es2015 (for...of บน iterator ต้อง + // downlevelIteration — array ธรรมดาไม่ต้อง) + for (const key of Array.from(form.keys())) { + if (!(FORM_FIELDS as readonly string[]).includes(key)) { + return { + ok: false, + error: `ไม่รู้จัก field '${key}' (รับเฉพาะ ${FORM_FIELDS.join(", ")})`, + }; + } + } + + const userId = formString(form, "userId"); + if (!userId.ok) return userId; + if (userId.value === null) { + return { ok: false, error: "ต้องระบุ userId (ระบบ stand-alone — บอกผู้อัปโหลดเอง)" }; + } + if (userId.value.length > 100) { + return { ok: false, error: "userId ยาวเกิน 100 ตัวอักษร" }; + } + + const userName = formString(form, "userName"); + if (!userName.ok) return userName; + if (userName.value === null) { + return { ok: false, error: "ต้องระบุ userName (ระบบ stand-alone — บอกผู้อัปโหลดเอง)" }; + } + if (userName.value.length > 200) { + return { ok: false, error: "userName ยาวเกิน 200 ตัวอักษร" }; + } + + const clientVersionId = formString(form, "clientVersionId"); + if (!clientVersionId.ok) return clientVersionId; + if (clientVersionId.value && clientVersionId.value.length > 100) { + return { ok: false, error: "clientVersionId ยาวเกิน 100 ตัวอักษร" }; + } + + const templateId = formString(form, "templateId"); + if (!templateId.ok) return templateId; + if (templateId.value && templateId.value.length > 100) { + return { ok: false, error: "templateId ยาวเกิน 100 ตัวอักษร" }; + } + + const message = formString(form, "message"); + if (!message.ok) return message; + if (message.value && message.value.length > DOCVER_MESSAGE_MAX) { + return { ok: false, error: `message ยาวเกิน ${DOCVER_MESSAGE_MAX} ตัวอักษร` }; + } + + const rawFields = formString(form, "fields"); + if (!rawFields.ok) return rawFields; + let fields: Record | null = null; + if (rawFields.value !== null) { + const parsed = parseFieldsJson(rawFields.value); + if (!parsed.ok) return parsed; + fields = parsed.fields; + } + + const rawFileName = formString(form, "fileName"); + if (!rawFileName.ok) return rawFileName; + + return { + ok: true, + form: { + userId: userId.value, + userName: userName.value, + clientVersionId: clientVersionId.value, + message: message.value, + templateId: templateId.value, + fileName: sanitizeDisplayFileName(rawFileName.value), + fields, + }, + }; +} + +/** ค่า Content-Disposition ของ object ใน MinIO — presigned GET เสิร์ฟ metadata ที่ฝัง + * ตอน putObject จึงได้ชื่อไฟล์สวยตอนดาวน์โหลด: ASCII fallback (ตัด non-ASCII/" + * "/ควบคุม — เหลือไม่มีตัวอักษรเลข = ชื่อ default) + filename*=UTF-8'' สำหรับชื่อไทย */ +const DEFAULT_FILE_NAMES = { docx: "document.docx", pdf: "document.pdf" } as const; + +export function contentDispositionValue(displayName: string | null, kind: "docx" | "pdf"): string { + const ext = `.${kind}`; + const ascii = (displayName ?? "") + .replace(/[^\x20-\x7E]/g, "") + .replace(/["\\]/g, "") + .replace(/\s+/g, " ") + .trim(); + let asciiName: string = DEFAULT_FILE_NAMES[kind]; + if (/[A-Za-z0-9]/.test(ascii)) { + asciiName = ascii.toLowerCase().endsWith(ext) ? ascii : ascii + ext; + } + const display = displayName?.trim(); + if (display) { + return `attachment; filename="${asciiName}"; filename*=UTF-8''${encodeURIComponent(display)}`; + } + return `attachment; filename="${asciiName}"`; +} diff --git a/lib/docver-store.ts b/lib/docver-store.ts new file mode 100644 index 0000000..6191716 --- /dev/null +++ b/lib/docver-store.ts @@ -0,0 +1,275 @@ +// PLAN-DOCVER.md §2/§4/§6 — MinIO + DB ของกลุ่ม /api/documents (server-only) +// - env อ่านตอนเรียก (ไม่ใช่ตอน module load) — ขาด/ผิด = throw ให้ route catch เป็น 500 +// - object key เป็น uuid ล้วน `docver//document.{docx,pdf}` ไม่มี input ผู้ใช้ +// (เหตุผลเดียวกับ N4 ที่ห้ามชื่อผู้ใช้ตกแต่ง path) — versionId สร้างฝั่ง server แล้ว +// ส่งเป็น id ตอน insert เพราะ object เขียนลง MinIO ไปก่อนแล้ว +// - เขียน DB ผ่าน route handler (carve-out ของ invariant #4 — ตารางของกลุ่มนี้ไม่มีหน้า +// note ใดอ่าน จึงไม่มี SSE/revalidatePath ที่ต้องทำ) +import { and, desc, eq, sql } from "drizzle-orm"; +import { Client } from "minio"; + +import { db } from "@/db"; +import { + documents, + documentVersions, + type DocumentRow, + type DocumentVersionRow, +} from "@/db/schema"; +import { + contentDispositionValue, + DOCX_CONTENT_TYPE, + PDF_CONTENT_TYPE, +} from "@/lib/document-versioning"; + +type MinioConfig = { + endPoint: string; + port: number; + useSSL: boolean; + accessKey: string; + secretKey: string; + bucket: string; + ttlSeconds: number; +}; + +/** อ่าน env ทุกครั้งที่มีการเรียกใช้ — ค่าบังคับขาด = throw (route แปลงเป็น 500 กลาง ๆ + * ไม่บอกผู้เรียกว่า env ตัวไหนขาด — log เต็มอยู่ฝั่ง server แล้ว) */ +function readConfig(): MinioConfig { + const endPoint = process.env.MINIO_ENDPOINT?.trim(); + const accessKey = process.env.MINIO_ACCESS_KEY?.trim(); + const secretKey = process.env.MINIO_SECRET_KEY?.trim(); + if (!endPoint || !accessKey || !secretKey) { + throw new Error( + "ตั้งค่า MINIO_ENDPOINT / MINIO_ACCESS_KEY / MINIO_SECRET_KEY ก่อนใช้ API กลุ่มเอกสาร versioning (docs/PLAN-DOCVER.md §6)", + ); + } + const port = Number.parseInt(process.env.MINIO_PORT?.trim() ?? "", 10); + const ttl = Number.parseInt(process.env.DOCVER_LINK_TTL_SECONDS?.trim() ?? "", 10); + return { + endPoint, + port: Number.isFinite(port) && port > 0 ? port : 9000, + useSSL: /^(1|true|yes)$/i.test(process.env.MINIO_USE_SSL?.trim() ?? ""), + accessKey, + secretKey, + bucket: process.env.MINIO_BUCKET?.trim() || "docver", + // ไม่ให้ตั้งเกิน 7 วัน — ขอบบนของ S3 presign เอง + ttlSeconds: + Number.isFinite(ttl) && ttl > 0 ? Math.min(ttl, 7 * 24 * 60 * 60) : 900, + }; +} + +// เก็บ client/bucket บน globalThis แบบเดียวกับ db/index.ts — Next dev hot-reload +// re-evaluate module บ่อย ไม่งั้นสะสม client จนเปลือง; key คิดจาก config ที่อ่านได้ +// ตอนนั้นเพื่อ env เปลี่ยนกลาง dev แล้ว client เก่าไม่ติดค้าง +const globalForMinio = globalThis as unknown as { + docverClient?: { key: string; client: Client }; + docverBucketReady?: { bucket: string; promise: Promise }; +}; + +function getClient(): { client: Client; config: MinioConfig } { + const config = readConfig(); + const key = `${config.endPoint}|${config.port}|${config.useSSL}|${config.accessKey}`; + if (globalForMinio.docverClient?.key !== key) { + globalForMinio.docverClient = { + key, + client: new Client({ + endPoint: config.endPoint, + port: config.port, + useSSL: config.useSSL, + accessKey: config.accessKey, + secretKey: config.secretKey, + }), + }; + } + return { client: globalForMinio.docverClient.client, config }; +} + +/** สร้าง bucket ถ้ายังไม่มี — ครั้งเดียวต่อ process (cached promise กัน request + * พร้อมกันยิง makeBucket ซ้ำ) · ชน race ข้าม process (อีก process สร้างแย่งกัน) + * = ตรวจ bucketExists ซ้ำอีกรอบ มีแล้ว = ผ่าน · ล้มจริง = เคลียร์ cache ให้รอบหลังลองใหม่ */ +async function ensureBucket(client: Client, bucket: string): Promise { + if (globalForMinio.docverBucketReady?.bucket !== bucket) { + const promise = (async () => { + if (await client.bucketExists(bucket)) return; + try { + await client.makeBucket(bucket); + } catch (err) { + if (!(await client.bucketExists(bucket))) throw err; + } + })(); + globalForMinio.docverBucketReady = { bucket, promise }; + promise.catch(() => { + // อย่าปล่อย rejected promise ค้างใน cache — request ถัดไปต้องได้ลองใหม่ + if (globalForMinio.docverBucketReady?.promise === promise) { + globalForMinio.docverBucketReady = undefined; + } + }); + } + return globalForMinio.docverBucketReady.promise; +} + +/** object key ของ version หนึ่ง — uuid ล้วน ใช้ร่วมทั้งตอน put และตอน insert row */ +export function versionObjectKeys(versionId: string): { docx: string; pdf: string } { + return { + docx: `docver/${versionId}/document.docx`, + pdf: `docver/${versionId}/document.pdf`, + }; +} + +export type PutVersionInput = { + versionId: string; + docx: { bytes: Buffer; fileName: string | null }; + pdf?: { bytes: Buffer; fileName: string | null }; +}; + +/** เขียน object ของ version ลง MinIO (docx ก่อน pdf) — ฝัง Content-Type + + * Content-Disposition ตอน put เพื่อให้ presigned GET ได้ชื่อไฟล์สวยตอนดาวน์โหลด + * · pdf ล้มกลางทาง = เก็บกวาด docx ที่เขียนไปแล้ว (ไม่ให้เกิด version ครึ่ง ๆ) */ +export async function putVersionObjects(input: PutVersionInput): Promise { + const { client, config } = getClient(); + await ensureBucket(client, config.bucket); + const keys = versionObjectKeys(input.versionId); + await client.putObject(config.bucket, keys.docx, input.docx.bytes, input.docx.bytes.length, { + "Content-Type": DOCX_CONTENT_TYPE, + "Content-Disposition": contentDispositionValue(input.docx.fileName, "docx"), + }); + if (!input.pdf) return; + try { + await client.putObject(config.bucket, keys.pdf, input.pdf.bytes, input.pdf.bytes.length, { + "Content-Type": PDF_CONTENT_TYPE, + "Content-Disposition": contentDispositionValue(input.pdf.fileName, "pdf"), + }); + } catch (err) { + await client.removeObject(config.bucket, keys.docx).catch(() => undefined); + throw err; + } +} + +/** เก็บกวาด object หลัง DB ล้ม (pattern unlink + rethrow ของ N4) — best-effort ไม่โยน: + * เหลือไว้ก็เป็น object ไร้ row ชี้ (key เป็น uuid ไม่ชนใคร — PLAN-DOCVER.md §4) */ +export async function removeVersionObjects( + versionId: string, + hadPdf: boolean, +): Promise { + try { + const { client, config } = getClient(); + const keys = versionObjectKeys(versionId); + await client.removeObject(config.bucket, keys.docx); + if (hadPdf) await client.removeObject(config.bucket, keys.pdf); + } catch (err) { + console.error("docver: เก็บกวาด object หลังงานล้มไม่สำเร็จ (ไม่มีผลต่อ response)", err); + } +} + +/** presigned GET URL อายุสั้นให้ UI เปิดในเบราว์เซอร์จริงของผู้ใช้ (webview ของ Word + * ทิ้ง blob download เงียบ ๆ — เหตุผลเดียวกับแผน add-in เดิม) · presign เป็นการ + * คำนวณ local ล้วนไม่มี network call */ +export async function presignVersionUrl( + versionId: string, + format: "docx" | "pdf", +): Promise<{ url: string; expiresAt: string }> { + const { client, config } = getClient(); + const key = versionObjectKeys(versionId)[format]; + const url = await client.presignedGetObject(config.bucket, key, config.ttlSeconds); + return { + url, + expiresAt: new Date(Date.now() + config.ttlSeconds * 1000).toISOString(), + }; +} + +// ─── DB ────────────────────────────────────────────────────────────────────── + +export async function findDocumentByDocId(docId: string): Promise { + const [row] = await db.select().from(documents).where(eq(documents.docId, docId)).limit(1); + return row ?? null; +} + +/** history ของเอกสาร — ใหม่ → เก่า ตาม contract ของ add-in */ +export async function listVersionsByDocId(documentId: string): Promise { + return db + .select() + .from(documentVersions) + .where(eq(documentVersions.documentId, documentId)) + .orderBy(desc(documentVersions.versionNumber)); +} + +/** หา version เดียวโดยเช็คทั้ง docId และ versionId พร้อมกัน — กันโยน id ข้ามเอกสาร */ +export async function findVersionForDoc( + docId: string, + versionId: string, +): Promise { + const [row] = await db + .select({ version: documentVersions }) + .from(documentVersions) + .innerJoin(documents, eq(documentVersions.documentId, documents.id)) + .where(and(eq(documents.docId, docId), eq(documentVersions.id, versionId))) + .limit(1); + return row?.version ?? null; +} + +export type InsertVersionInput = { + /** กำหนดเอง (ไม่ปล่อย defaultRandom) เพราะ object ใน MinIO เขียนด้วย uuid นี้ไปก่อนแล้ว */ + versionId: string; + docId: string; + clientVersionId: string | null; + contentHash: string; + message: string | null; + fields: Record | null; + templateId: string | null; + fileName: string | null; + docxObjectKey: string; + pdfObjectKey: string | null; + docxSize: number; + pdfSize: number | null; + userId: string; + userName: string; +}; + +/** สร้าง (ถ้ายังไม่มี) เอกสาร + insert version พร้อมจ่ายเลข version ปลอดภัยเรียกคู่ขนาน + * (PLAN-DOCVER.md §3): ON CONFLICT DO NOTHING สร้าง row เอกสาร → SELECT … FOR UPDATE + * serialize ผู้จ่ายเลขรายเอกสาร (unique doc_id ได้ row เดียวเสมอ ไม่มี deadlock) → + * max+1 → insert (UNIQUE(document_id, version_number) เป็น safety net) */ +export async function insertVersionRecord(input: InsertVersionInput): Promise { + return db.transaction(async (tx) => { + await tx + .insert(documents) + .values({ docId: input.docId }) + .onConflictDoNothing({ target: documents.docId }); + + const [doc] = await tx + .select({ id: documents.id }) + .from(documents) + .where(eq(documents.docId, input.docId)) + .for("update"); + if (!doc) { + // ไปไม่ถึงเว้นแต่ unique index หาย — เผื่อไว้ให้ error อ่านง่ายกว่า undefined + throw new Error("docver: ไม่พบ row เอกสารหลัง upsert (unique index documents.doc_id)"); + } + + const [latest] = await tx + .select({ max: sql`max(${documentVersions.versionNumber})` }) + .from(documentVersions) + .where(eq(documentVersions.documentId, doc.id)); + + const [row] = await tx + .insert(documentVersions) + .values({ + id: input.versionId, + documentId: doc.id, + versionNumber: (latest?.max ?? 0) + 1, + clientVersionId: input.clientVersionId, + contentHash: input.contentHash, + message: input.message, + fields: input.fields, + templateId: input.templateId, + fileName: input.fileName, + docxObjectKey: input.docxObjectKey, + pdfObjectKey: input.pdfObjectKey, + docxSize: input.docxSize, + pdfSize: input.pdfSize, + userId: input.userId, + userName: input.userName, + }) + .returning(); + return row!; + }); +} diff --git a/lib/openapi.ts b/lib/openapi.ts index 558a2ce..4cd07d3 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -1,5 +1,6 @@ // OpenAPI 3.0 spec ของ API ทดสอบทั้งหมดของ judes-note — เส้นเดียวกับที่ Swagger UI -// แสดงผล: กลุ่ม template (DOCX Content Control) + N10 external API +// แสดงผล: กลุ่ม template (DOCX Content Control) + N10 external API + docver +// (จัด version เอกสาร) // route handler GET /api/swagger (app/api/swagger/route.ts) เป็นคนเดียวที่ใช้ // — inline เป็น JSON ลงใน HTML ตอน respond (ไม่มี endpoint spec แยก) // @@ -23,8 +24,8 @@ export const openApiSpec = { title: "Document Template & External API ของระบบ Judge's Note", version: "1.0.0", description: - "API ทดสอบของระบบ Judge's Note — กลุ่ม Template (DOCX Content Control) " + - "และ API ภายนอกสำหรับ backend e-Court (N10)", + "API ทดสอบของระบบ Judge's Note — กลุ่ม Template (DOCX Content Control), " + + "API ภายนอกสำหรับ backend e-Court (N10) และ API จัด version เอกสาร (docver)", }, servers: [{ url: BASE_PATH, description: "origin เดียวกับหน้านี้ (basePath /docedit)" }], tags: [ @@ -34,6 +35,13 @@ export const openApiSpec = { "Template เอกสาร DOCX ด้วย Content Control — ส่ง template (.docx) + data (JSON) " + "แล้วได้เอกสารที่กรอกค่าแล้วกลับมา", }, + { + name: "documents", + description: + "จัด version เอกสาร (docver — docs/PLAN-DOCVER.md): เอกสารเดียวต่อ docId แต่ละ " + + "version เก็บ .docx + .pdf ใน MinIO พร้อมผู้อัปโหลด — ไม่มี auth (stand-alone): " + + "ผู้เรียกบอก userId/userName เองใน multipart", + }, { name: "Judge's Note", description: @@ -217,6 +225,193 @@ export const openApiSpec = { }, }, }, + "/api/documents/{docId}/versions": { + post: { + tags: ["documents"], + summary: "อัปโหลด version ใหม่ของเอกสาร (docx + pdf)", + description: + "สร้างเอกสารอัตโนมัติเมื่อ docId ยังไม่เคยมี (docId ห้ามซ้ำ) แล้วเก็บไฟล์ " + + ".docx (+ .pdf ถ้าแนบมา) ลง MinIO พร้อมผู้อัปโหลด — versionNumber ที่ server จ่าย " + + "คือค่าจริงเสมอ (client ที่เลขไม่ตรงต้องแก้ XML part ของตัวเอง) · pdf เป็น " + + "optional: ไม่แนบ/ไฟล์เปล่า = version นี้ไม่มีสำเนา PDF (ดู hasPdf ใน response)", + parameters: [ + { + name: "docId", + in: "path", + required: true, + schema: { type: "string" }, + description: "รหัสเอกสารจากผู้เรียก (add-in GUID / รหัส e-Court) — [A-Za-z0-9._:-]", + }, + ], + requestBody: { + required: true, + content: { + "multipart/form-data": { + schema: { + type: "object", + required: ["file", "userId", "userName"], + properties: { + file: { + type: "string", + format: "binary", + description: "ไฟล์ .docx ของ version นี้ (บังคับ)", + }, + pdf: { + type: "string", + format: "binary", + description: + "ไฟล์ .pdf ของ version นี้ (optional — ไม่แนบหรือไฟล์เปล่า = ไม่มี)", + }, + userId: { + type: "string", + description: "รหัสผู้อัปโหลด (ระบบ stand-alone ไม่มี session)", + }, + userName: { + type: "string", + description: "ชื่อผู้อัปโหลด (แสดงใน version history)", + }, + clientVersionId: { + type: "string", + description: "GUID ที่ client generate ก่อนเรียก (เก็บไว้สืบสวน ไม่ใช่ identity)", + }, + fields: { + type: "string", + description: + 'JSON string ของค่า Content Control เช่น {"blackno":"คดีหมายเลขดำที่"}', + }, + message: { + type: "string", + description: "commit message ของ version (≤ 2048 ตัวอักษร)", + }, + templateId: { + type: "string", + description: "รหัส template ที่ใช้สร้างเอกสาร (ถ้ามี)", + }, + fileName: { + type: "string", + description: + "ชื่อไฟล์สำหรับแสดงตอนดาวน์โหลด (ไม่ส่ง = ใช้ชื่อไฟล์ที่แนบ)", + }, + }, + }, + }, + }, + }, + responses: { + 200: { + description: "version ถูกบันทึกแล้ว", + content: { + "application/json": { + schema: { $ref: "#/components/schemas/UploadVersionResult" }, + }, + }, + }, + 400: { $ref: "#/components/responses/BadRequest" }, + 413: { + description: "ไฟล์แนบใหญ่เกิน 100MB", + content: { + "application/json": { + schema: { $ref: "#/components/schemas/Error" }, + }, + }, + }, + 500: { $ref: "#/components/responses/ServerError" }, + }, + }, + get: { + tags: ["documents"], + summary: "รายการ version ทั้งหมดของเอกสาร (ใหม่ → เก่า)", + description: + "version ทุกตัวของ docId นี้ พร้อมผู้อัปโหลด (userId/userName), เวลา, commit " + + "message, ค่า fields และ hasPdf — ใช้เขียน UI version history ของ add-in", + parameters: [ + { + name: "docId", + in: "path", + required: true, + schema: { type: "string" }, + description: "รหัสเอกสารจากผู้เรียก — [A-Za-z0-9._:-]", + }, + ], + responses: { + 200: { + description: "รายการ version เรียงใหม่ → เก่า", + content: { + "application/json": { + schema: { + type: "array", + items: { $ref: "#/components/schemas/VersionRecord" }, + }, + }, + }, + }, + 400: { $ref: "#/components/responses/BadRequest" }, + 404: { + description: "ไม่พบเอกสารนี้ในระบบ (ยังไม่เคยอัปโหลด version)", + content: { + "application/json": { + schema: { $ref: "#/components/schemas/Error" }, + }, + }, + }, + 500: { $ref: "#/components/responses/ServerError" }, + }, + }, + }, + "/api/documents/{docId}/versions/{versionId}/download-link": { + get: { + tags: ["documents"], + summary: "ขอ presigned URL ดาวน์โหลดไฟล์ของ version (docx/pdf)", + description: + "คืน URL อายุสั้น (env DOCVER_LINK_TTL_SECONDS, default 900 วินาที) ให้เปิดใน " + + "เบราว์เซอร์/Word ของผู้ใช้ — URL อ้าง endpoint MinIO ตรง ๆ จึงต้องเป็นโฮสต์ที่ " + + "ผู้ใช้เข้าถึงได้ · ขอ pdf ใน version ที่ไม่มีสำเนา = 404", + parameters: [ + { + name: "docId", + in: "path", + required: true, + schema: { type: "string" }, + description: "รหัสเอกสาร (ต้องตรงกับ version นี้ — โยน id ข้ามเอกสารไม่ได้)", + }, + { + name: "versionId", + in: "path", + required: true, + schema: { type: "string", format: "uuid" }, + description: "id ของ version (field versionId ใน VersionRecord)", + }, + { + name: "format", + in: "query", + required: false, + schema: { type: "string", enum: ["docx", "pdf"], default: "docx" }, + description: "ชนิดไฟล์ที่จะดาวน์โหลด (ไม่ส่ง = docx)", + }, + ], + responses: { + 200: { + description: "presigned URL พร้อมเวลาหมดอายุ", + content: { + "application/json": { + schema: { $ref: "#/components/schemas/DownloadLink" }, + }, + }, + }, + 400: { $ref: "#/components/responses/BadRequest" }, + 404: { + description: + "ไม่พบ version นี้ของเอกสาร หรือขอ format=pdf ใน version ที่ไม่มีสำเนา PDF", + content: { + "application/json": { + schema: { $ref: "#/components/schemas/Error" }, + }, + }, + }, + 500: { $ref: "#/components/responses/ServerError" }, + }, + }, + }, }, components: { securitySchemes: { @@ -407,6 +602,48 @@ export const openApiSpec = { createdAt: { type: "string", format: "date-time" }, }, }, + UploadVersionResult: { + type: "object", + description: "ตอบกลับหลังอัปโหลด version สำเร็จ", + properties: { + hasPdf: { type: "boolean", description: "true = version นี้มีสำเนา PDF" }, + versionId: { type: "string", format: "uuid" }, + versionNumber: { + type: "integer", + description: "เลข version ที่ server จ่าย (ค่าจริง — ไม่ใช่ที่ client คาด)", + }, + }, + }, + VersionRecord: { + type: "object", + description: "รายการ version หนึ่งตัว (GET /versions คืนเป็น array ใหม่ → เก่า)", + properties: { + versionId: { type: "string", format: "uuid" }, + versionNumber: { type: "integer" }, + createdAt: { type: "string", format: "date-time" }, + createdByUserId: { type: "string" }, + createdByUserName: { type: "string" }, + contentHash: { + type: "string", + description: "SHA-256 hex ของไฟล์ .docx (คำนวณฝั่ง server เสมอ)", + }, + fields: { + type: "object", + nullable: true, + description: "ค่า Content Control ตอนอัปโหลด (ไม่ส่ง = null)", + additionalProperties: { type: "string" }, + }, + message: { type: "string", nullable: true, description: "commit message" }, + hasPdf: { type: "boolean" }, + }, + }, + DownloadLink: { + type: "object", + properties: { + url: { type: "string", description: "presigned URL ของ MinIO (GET โดยตรง)" }, + expiresAt: { type: "string", format: "date-time" }, + }, + }, }, }, } as const; diff --git a/middleware.ts b/middleware.ts index 4d5d27d..f05c44b 100644 --- a/middleware.ts +++ b/middleware.ts @@ -23,7 +23,9 @@ export function middleware(req: NextRequest) { // ยกเว้น: หน้า login เอง, API/SSE/export (อยู่ใต้ api/note/**), API template // (api/template/** — เปิดกว้างให้ทางศาล/ผู้ทดสอบภายนอกยิงได้เลย ไม่แตะ DB -// ไม่มีข้อมูลรับ), หน้า Swagger UI (api/swagger — document ของทุกกลุ่ม), +// ไม่มีข้อมูลรับ), 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) @@ -35,6 +37,6 @@ export function middleware(req: NextRequest) { // ยังถูกคุมอยู่ดีเพราะ root page ส่งต่อให้ /note ซึ่ง middleware จับตามปกติ export const config = { matcher: [ - "/((?!note/login|api/note|api/template|api/swagger|wordaddin|note/uploads|note/samples|note/swagger-ui|note/pdf\\.worker\\.min\\.mjs|_next|fonts|favicon\\.ico).+)", + "/((?!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).+)", ], }; diff --git a/package.json b/package.json index e5d19b2..701ecae 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@xmldom/xmldom": "^0.9.12", "drizzle-orm": "^0.45.2", "lucide-react": "^1.42.0", + "minio": "^8.0.7", "next": "^14.2.35", "pdf-lib": "^1.17.1", "pdfjs-dist": "^4.10.38", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3f881ad..772836e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: lucide-react: specifier: ^1.42.0 version: 1.57.0(@types/react@18.3.31)(react@18.3.1) + minio: + specifier: ^8.0.7 + version: 8.0.7 next: specifier: ^14.2.35 version: 14.2.35(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -80,10 +83,10 @@ importers: version: 29.1.1 office-addin-debugging: specifier: ^7.0.1 - version: 7.0.1(@microsoft/m365agentstoolkit-cli@1.1.18(@types/node@26.6.5)(zod@4.6.7))(@types/node@26.6.5) + version: 7.0.1(@microsoft/m365agentstoolkit-cli@1.1.18(@types/node@26.6.5)(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)(zod@4.6.7))(@types/node@26.6.5) office-addin-dev-settings: specifier: ^4.0.1 - version: 4.0.1(@microsoft/m365agentstoolkit-cli@1.1.18(@types/node@26.6.5)(zod@4.6.7))(@types/node@26.6.5) + version: 4.0.1(@microsoft/m365agentstoolkit-cli@1.1.18(@types/node@26.6.5)(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)(zod@4.6.7))(@types/node@26.6.5) postcss: specifier: ^8.5.28 version: 8.5.30 @@ -1166,36 +1169,42 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.2.13': resolution: {integrity: sha512-xU+Kvv9N2A3SY35zFh1FWWNjxbadE/BKeqnkXe7vScU4GZsw757BlVmDLITZEq8DPpk6m7uoDYkK7P+x/uU5Ow==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@rolldown/binding-linux-ppc64-gnu@1.2.13': resolution: {integrity: sha512-XMk/hnhh25oQ756Lclb1Q11h4ll0xC0Esa5w4Vzp+mAE2S3AGJgeM0SonrIgY8fHhjarb6hcoTuwoFG0VVP2ig==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-s390x-gnu@1.2.13': resolution: {integrity: sha512-U1BoTHYmlywE2n3HUWjzZ+8EQsKr+OwpxYuDWaubx42tUL8QSL7nOqRz/NVHanibbFP3M1Rxuprz/wNcbMKjqw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.2.13': resolution: {integrity: sha512-J4cm80Pc6wEwV5AZ42o62sIQgDk+Y7R2s646jIwuvO+4V46QB58N4vVuMPWCBaNgursXLpmUWJV//WQAkPvHBQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-musl@1.2.13': resolution: {integrity: sha512-W0euZS40U4V6JkmEnVCIX9mcsL6cbXQ4X8c2w+MMOIjFg68TXUW+u3RT+2e1t1pv9mg/EP3pDtK3POXdQ7Rxyw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@rolldown/binding-openharmony-arm64@1.2.13': resolution: {integrity: sha512-do/AF/4n9MRshia3a5aXoy8xM2c6mgfT6D7H/dY6my5z+hfCIR4ab7mk4IRj++nsEQpKy39kd0DQnDq+r692tw==} @@ -1430,6 +1439,9 @@ packages: async-mutex@0.3.2: resolution: {integrity: sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==} + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -1472,6 +1484,9 @@ packages: bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + block-stream2@2.1.0: + resolution: {integrity: sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==} + body-parser@1.20.8: resolution: {integrity: sha512-JNcyFQ64OiijEkPzUBTCe+hyPXUD/3LEldGQ6iF5LR1w00mx9o7xtDWHXBY2iItjdCFGoilOLNQbH943ut7pHA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -1492,11 +1507,18 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + browser-or-node@2.1.1: + resolution: {integrity: sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==} + browserslist@4.29.3: resolution: {integrity: sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + buffer-crc32@1.0.0: + resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} + engines: {node: '>=8.0.0'} + buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} @@ -1734,6 +1756,10 @@ packages: decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + decode-uri-component@0.2.2: + resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} + engines: {node: '>=0.10'} + decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -2031,6 +2057,9 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} @@ -2109,6 +2138,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + filter-obj@1.1.0: + resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==} + engines: {node: '>=0.10.0'} + finalhandler@1.3.2: resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} engines: {node: '>= 0.8'} @@ -2322,6 +2355,10 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} + ipaddr.js@2.5.0: + resolution: {integrity: sha512-aq+t5NAc+cS6rZQQVWC2x98CPqGtKKTMDd4Gaodv0wShnItdKg/51djkGJ1hqH+Oy0ivDftCbSLCQob8zso01w==} + engines: {node: '>= 10'} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -2498,24 +2535,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.33.0: resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.33.0: resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.33.0: resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.33.0: resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} @@ -2674,6 +2715,10 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minio@8.0.7: + resolution: {integrity: sha512-E737MgufW8CeQAsTAtnEMrxZ9scMSf29kkhZoXzDTKj/Jszzo2SfeZUH9wbDQH2Rsq6TCtl/yQL0+XdVKZansQ==} + engines: {node: ^16 || ^18 || >=20} + minipass@7.1.3: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} @@ -3065,6 +3110,10 @@ packages: resolution: {integrity: sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==} engines: {node: '>=0.6'} + query-string@7.1.3: + resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==} + engines: {node: '>=6'} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -3290,6 +3339,10 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + split-on-first@1.1.0: + resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} + engines: {node: '>=6'} + stack-chain@1.3.7: resolution: {integrity: sha512-D8cWtWVdIe/jBA7v5p5Hwl5yOSOrmZPWDPe2KxQ5UAGD+nxbxU0lKXA4h85Ta6+qgdKVL3vUxsbIZjc1kBG7ug==} @@ -3300,6 +3353,12 @@ packages: std-env@4.3.0: resolution: {integrity: sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==} + stream-chain@2.2.5: + resolution: {integrity: sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==} + + stream-json@1.9.1: + resolution: {integrity: sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==} + streamroller@3.1.5: resolution: {integrity: sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==} engines: {node: '>=8.0'} @@ -3308,6 +3367,10 @@ packages: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} + strict-uri-encode@2.0.0: + resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} + engines: {node: '>=4'} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -3405,6 +3468,9 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + through2@4.0.2: + resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + tinybench@6.2.2: resolution: {integrity: sha512-nIZ2N2dFpjBsg2hgitKu7mA+FvsIJuGJOwysRngmVRRmWgR/UyPjwJj8c/fT/dhm8EkHnF5EOogRf+0Z2qrimA==} engines: {node: '>=20.0.0'} @@ -3850,85 +3916,85 @@ snapshots: dependencies: tslib: 2.8.1 - '@azure/arm-appservice@13.0.3': + '@azure/arm-appservice@13.0.3(supports-color@7.2.0)': dependencies: '@azure/abort-controller': 1.1.0 - '@azure/core-auth': 1.11.0 - '@azure/core-client': 1.11.1 - '@azure/core-lro': 2.7.2 + '@azure/core-auth': 1.11.0(supports-color@7.2.0) + '@azure/core-client': 1.11.1(supports-color@7.2.0) + '@azure/core-lro': 2.7.2(supports-color@7.2.0) '@azure/core-paging': 1.7.0 - '@azure/core-rest-pipeline': 1.25.0 + '@azure/core-rest-pipeline': 1.25.0(supports-color@7.2.0) tslib: 2.8.1 transitivePeerDependencies: - supports-color - '@azure/arm-resources@5.0.1': + '@azure/arm-resources@5.0.1(supports-color@7.2.0)': dependencies: '@azure/abort-controller': 1.1.0 - '@azure/core-auth': 1.11.0 - '@azure/core-client': 1.11.1 - '@azure/core-lro': 2.7.2 + '@azure/core-auth': 1.11.0(supports-color@7.2.0) + '@azure/core-client': 1.11.1(supports-color@7.2.0) + '@azure/core-lro': 2.7.2(supports-color@7.2.0) '@azure/core-paging': 1.7.0 - '@azure/core-rest-pipeline': 1.25.0 + '@azure/core-rest-pipeline': 1.25.0(supports-color@7.2.0) tslib: 2.8.1 transitivePeerDependencies: - supports-color - '@azure/arm-storage@17.2.1': + '@azure/arm-storage@17.2.1(supports-color@7.2.0)': dependencies: '@azure/abort-controller': 1.1.0 - '@azure/core-auth': 1.11.0 - '@azure/core-client': 1.11.1 - '@azure/core-lro': 2.7.2 + '@azure/core-auth': 1.11.0(supports-color@7.2.0) + '@azure/core-client': 1.11.1(supports-color@7.2.0) + '@azure/core-lro': 2.7.2(supports-color@7.2.0) '@azure/core-paging': 1.7.0 - '@azure/core-rest-pipeline': 1.25.0 + '@azure/core-rest-pipeline': 1.25.0(supports-color@7.2.0) tslib: 2.8.1 transitivePeerDependencies: - supports-color - '@azure/arm-subscriptions@5.1.1': + '@azure/arm-subscriptions@5.1.1(supports-color@7.2.0)': dependencies: '@azure/abort-controller': 2.2.0 - '@azure/core-auth': 1.11.0 - '@azure/core-client': 1.11.1 - '@azure/core-lro': 2.7.2 + '@azure/core-auth': 1.11.0(supports-color@7.2.0) + '@azure/core-client': 1.11.1(supports-color@7.2.0) + '@azure/core-lro': 2.7.2(supports-color@7.2.0) '@azure/core-paging': 1.7.0 - '@azure/core-rest-pipeline': 1.25.0 + '@azure/core-rest-pipeline': 1.25.0(supports-color@7.2.0) tslib: 2.8.1 transitivePeerDependencies: - supports-color - '@azure/core-auth@1.11.0': + '@azure/core-auth@1.11.0(supports-color@7.2.0)': dependencies: '@azure/abort-controller': 2.2.0 - '@azure/core-util': 1.14.0 + '@azure/core-util': 1.14.0(supports-color@7.2.0) tslib: 2.8.1 transitivePeerDependencies: - supports-color - '@azure/core-client@1.11.1': + '@azure/core-client@1.11.1(supports-color@7.2.0)': dependencies: '@azure/abort-controller': 2.2.0 - '@azure/core-auth': 1.11.0 - '@azure/core-rest-pipeline': 1.25.0 + '@azure/core-auth': 1.11.0(supports-color@7.2.0) + '@azure/core-rest-pipeline': 1.25.0(supports-color@7.2.0) '@azure/core-tracing': 1.4.0 - '@azure/core-util': 1.14.0 - '@azure/logger': 1.4.0 + '@azure/core-util': 1.14.0(supports-color@7.2.0) + '@azure/logger': 1.4.0(supports-color@7.2.0) tslib: 2.8.1 transitivePeerDependencies: - supports-color - '@azure/core-http-compat@2.5.0(@azure/core-client@1.11.1)(@azure/core-rest-pipeline@1.25.0)': + '@azure/core-http-compat@2.5.0(@azure/core-client@1.11.1(supports-color@7.2.0))(@azure/core-rest-pipeline@1.25.0(supports-color@7.2.0))': dependencies: '@azure/abort-controller': 2.2.0 - '@azure/core-client': 1.11.1 - '@azure/core-rest-pipeline': 1.25.0 + '@azure/core-client': 1.11.1(supports-color@7.2.0) + '@azure/core-rest-pipeline': 1.25.0(supports-color@7.2.0) - '@azure/core-lro@2.7.2': + '@azure/core-lro@2.7.2(supports-color@7.2.0)': dependencies: '@azure/abort-controller': 2.2.0 - '@azure/core-util': 1.14.0 - '@azure/logger': 1.4.0 + '@azure/core-util': 1.14.0(supports-color@7.2.0) + '@azure/logger': 1.4.0(supports-color@7.2.0) tslib: 2.8.1 transitivePeerDependencies: - supports-color @@ -3939,14 +4005,14 @@ snapshots: '@azure/core-process@1.0.0': {} - '@azure/core-rest-pipeline@1.25.0': + '@azure/core-rest-pipeline@1.25.0(supports-color@7.2.0)': dependencies: '@azure/abort-controller': 2.2.0 - '@azure/core-auth': 1.11.0 + '@azure/core-auth': 1.11.0(supports-color@7.2.0) '@azure/core-tracing': 1.4.0 - '@azure/core-util': 1.14.0 - '@azure/logger': 1.4.0 - '@typespec/ts-http-runtime': 0.3.9 + '@azure/core-util': 1.14.0(supports-color@7.2.0) + '@azure/logger': 1.4.0(supports-color@7.2.0) + '@typespec/ts-http-runtime': 0.3.9(supports-color@7.2.0) tslib: 2.8.1 transitivePeerDependencies: - supports-color @@ -3955,10 +4021,10 @@ snapshots: dependencies: tslib: 2.8.1 - '@azure/core-util@1.14.0': + '@azure/core-util@1.14.0(supports-color@7.2.0)': dependencies: '@azure/abort-controller': 2.2.0 - '@typespec/ts-http-runtime': 0.3.9 + '@typespec/ts-http-runtime': 0.3.9(supports-color@7.2.0) tslib: 2.8.1 transitivePeerDependencies: - supports-color @@ -3968,16 +4034,16 @@ snapshots: fast-xml-parser: 5.11.2 tslib: 2.8.1 - '@azure/identity@4.14.0': + '@azure/identity@4.14.0(supports-color@7.2.0)': dependencies: '@azure/abort-controller': 2.2.0 - '@azure/core-auth': 1.11.0 - '@azure/core-client': 1.11.1 + '@azure/core-auth': 1.11.0(supports-color@7.2.0) + '@azure/core-client': 1.11.1(supports-color@7.2.0) '@azure/core-process': 1.0.0 - '@azure/core-rest-pipeline': 1.25.0 + '@azure/core-rest-pipeline': 1.25.0(supports-color@7.2.0) '@azure/core-tracing': 1.4.0 - '@azure/core-util': 1.14.0 - '@azure/logger': 1.4.0 + '@azure/core-util': 1.14.0(supports-color@7.2.0) + '@azure/logger': 1.4.0(supports-color@7.2.0) '@azure/msal-browser': 5.25.1 '@azure/msal-node': 6.0.1 open: 11.0.4 @@ -3985,9 +4051,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@azure/logger@1.4.0': + '@azure/logger@1.4.0(supports-color@7.2.0)': dependencies: - '@typespec/ts-http-runtime': 0.3.9 + '@typespec/ts-http-runtime': 0.3.9(supports-color@7.2.0) tslib: 2.8.1 transitivePeerDependencies: - supports-color @@ -4015,35 +4081,35 @@ snapshots: '@azure/msal-common': 16.14.1 jsonwebtoken: 9.0.3 - '@azure/storage-blob@12.34.0': + '@azure/storage-blob@12.34.0(supports-color@7.2.0)': dependencies: '@azure/abort-controller': 2.2.0 - '@azure/core-auth': 1.11.0 - '@azure/core-client': 1.11.1 - '@azure/core-http-compat': 2.5.0(@azure/core-client@1.11.1)(@azure/core-rest-pipeline@1.25.0) - '@azure/core-lro': 2.7.2 + '@azure/core-auth': 1.11.0(supports-color@7.2.0) + '@azure/core-client': 1.11.1(supports-color@7.2.0) + '@azure/core-http-compat': 2.5.0(@azure/core-client@1.11.1(supports-color@7.2.0))(@azure/core-rest-pipeline@1.25.0(supports-color@7.2.0)) + '@azure/core-lro': 2.7.2(supports-color@7.2.0) '@azure/core-paging': 1.7.0 - '@azure/core-rest-pipeline': 1.25.0 + '@azure/core-rest-pipeline': 1.25.0(supports-color@7.2.0) '@azure/core-tracing': 1.4.0 - '@azure/core-util': 1.14.0 + '@azure/core-util': 1.14.0(supports-color@7.2.0) '@azure/core-xml': 1.6.0 - '@azure/logger': 1.4.0 - '@azure/storage-common': 12.5.0(@azure/core-client@1.11.1) + '@azure/logger': 1.4.0(supports-color@7.2.0) + '@azure/storage-common': 12.5.0(@azure/core-client@1.11.1(supports-color@7.2.0))(supports-color@7.2.0) apache-arrow: 21.2.0 events: 3.3.0 tslib: 2.8.1 transitivePeerDependencies: - supports-color - '@azure/storage-common@12.5.0(@azure/core-client@1.11.1)': + '@azure/storage-common@12.5.0(@azure/core-client@1.11.1(supports-color@7.2.0))(supports-color@7.2.0)': dependencies: '@azure/abort-controller': 2.2.0 - '@azure/core-auth': 1.11.0 - '@azure/core-http-compat': 2.5.0(@azure/core-client@1.11.1)(@azure/core-rest-pipeline@1.25.0) - '@azure/core-rest-pipeline': 1.25.0 + '@azure/core-auth': 1.11.0(supports-color@7.2.0) + '@azure/core-http-compat': 2.5.0(@azure/core-client@1.11.1(supports-color@7.2.0))(@azure/core-rest-pipeline@1.25.0(supports-color@7.2.0)) + '@azure/core-rest-pipeline': 1.25.0(supports-color@7.2.0) '@azure/core-tracing': 1.4.0 - '@azure/core-util': 1.14.0 - '@azure/logger': 1.4.0 + '@azure/core-util': 1.14.0(supports-color@7.2.0) + '@azure/logger': 1.4.0(supports-color@7.2.0) events: 3.3.0 tslib: 2.8.1 transitivePeerDependencies: @@ -4508,20 +4574,20 @@ snapshots: node-fetch: 3.3.2 strip-bom: 5.0.0 - '@microsoft/dev-tunnels-contracts@1.1.9': + '@microsoft/dev-tunnels-contracts@1.1.9(supports-color@7.2.0)': dependencies: buffer: 5.7.1 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) vscode-jsonrpc: 4.0.0 transitivePeerDependencies: - supports-color - '@microsoft/dev-tunnels-management@1.1.9': + '@microsoft/dev-tunnels-management@1.1.9(supports-color@7.2.0)': dependencies: - '@microsoft/dev-tunnels-contracts': 1.1.9 - axios: 1.20.0(debug@4.4.3) + '@microsoft/dev-tunnels-contracts': 1.1.9(supports-color@7.2.0) + axios: 1.20.0(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0) buffer: 5.7.1 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) vscode-jsonrpc: 4.0.0 transitivePeerDependencies: - supports-color @@ -4544,33 +4610,33 @@ snapshots: transitivePeerDependencies: - encoding - '@microsoft/m365agentstoolkit-cli@1.1.18(@types/node@26.6.5)(zod@4.6.7)': + '@microsoft/m365agentstoolkit-cli@1.1.18(@types/node@26.6.5)(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)(zod@4.6.7)': dependencies: - '@azure/arm-subscriptions': 5.1.1 - '@azure/core-auth': 1.11.0 - '@azure/identity': 4.14.0 + '@azure/arm-subscriptions': 5.1.1(supports-color@7.2.0) + '@azure/core-auth': 1.11.0(supports-color@7.2.0) + '@azure/identity': 4.14.0(supports-color@7.2.0) '@azure/msal-node': 6.0.1 '@azure/msal-node-extensions': 1.5.32 '@inquirer/core': 5.1.2 '@inquirer/prompts': 7.10.1(@types/node@26.6.5) '@inquirer/type': 1.5.5 - '@microsoft/teamsfx-api': 0.24.3 - '@microsoft/teamsfx-core': 3.1.4(@types/node@26.6.5)(zod@4.6.7) + '@microsoft/teamsfx-api': 0.24.3(supports-color@7.2.0) + '@microsoft/teamsfx-core': 3.1.4(@types/node@26.6.5)(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)(zod@4.6.7) ansi-escapes: 4.3.2 applicationinsights: 1.8.10 async-mutex: 0.3.2 chalk: 4.1.2 cli-table3: 0.6.5 dotenv: 8.6.0 - express: 4.22.3 + express: 4.22.3(supports-color@7.2.0) figures: 3.2.0 fs-extra: 9.1.0 lodash: 4.18.1 - log4js: 6.9.1 + log4js: 6.9.1(supports-color@7.2.0) node-machine-id: 1.1.12 open: 8.4.2 semver: 7.8.5 - tas-client: 0.1.73 + tas-client: 0.1.73(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0) tree-kill: 1.2.2 underscore: 1.13.8 optionalDependencies: @@ -4584,9 +4650,9 @@ snapshots: - supports-color - zod - '@microsoft/teamsfx-api@0.24.3': + '@microsoft/teamsfx-api@0.24.3(supports-color@7.2.0)': dependencies: - '@azure/core-auth': 1.11.0 + '@azure/core-auth': 1.11.0(supports-color@7.2.0) '@microsoft/app-manifest': 1.1.3 chai: 4.5.0 jsonschema: 1.5.0 @@ -4595,38 +4661,38 @@ snapshots: transitivePeerDependencies: - supports-color - '@microsoft/teamsfx-core@3.1.4(@types/node@26.6.5)(zod@4.6.7)': + '@microsoft/teamsfx-core@3.1.4(@types/node@26.6.5)(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)(zod@4.6.7)': dependencies: '@apidevtools/swagger-parser': 10.1.1(openapi-types@7.2.3) - '@azure/arm-appservice': 13.0.3 - '@azure/arm-resources': 5.0.1 - '@azure/arm-storage': 17.2.1 - '@azure/arm-subscriptions': 5.1.1 - '@azure/core-auth': 1.11.0 - '@azure/core-rest-pipeline': 1.25.0 - '@azure/identity': 4.14.0 - '@azure/storage-blob': 12.34.0 + '@azure/arm-appservice': 13.0.3(supports-color@7.2.0) + '@azure/arm-resources': 5.0.1(supports-color@7.2.0) + '@azure/arm-storage': 17.2.1(supports-color@7.2.0) + '@azure/arm-subscriptions': 5.1.1(supports-color@7.2.0) + '@azure/core-auth': 1.11.0(supports-color@7.2.0) + '@azure/core-rest-pipeline': 1.25.0(supports-color@7.2.0) + '@azure/identity': 4.14.0(supports-color@7.2.0) + '@azure/storage-blob': 12.34.0(supports-color@7.2.0) '@feathersjs/hooks': 0.6.5 - '@microsoft/dev-tunnels-contracts': 1.1.9 - '@microsoft/dev-tunnels-management': 1.1.9 + '@microsoft/dev-tunnels-contracts': 1.1.9(supports-color@7.2.0) + '@microsoft/dev-tunnels-management': 1.1.9(supports-color@7.2.0) '@microsoft/kiota': 1.31.1 '@microsoft/m365-spec-parser': 0.3.3 - '@microsoft/teamsfx-api': 0.24.3 - '@modelcontextprotocol/sdk': 1.32.1(zod@4.6.7) + '@microsoft/teamsfx-api': 0.24.3(supports-color@7.2.0) + '@modelcontextprotocol/sdk': 1.32.1(supports-color@7.2.0)(zod@4.6.7) adm-zip: 0.6.1 ajv: 8.20.0 - axios: 1.20.0(debug@4.4.3) + axios: 1.20.0(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0) axios-retry: 3.9.1 comment-json: 4.6.2 cryptr: 6.4.0 deep-diff: 1.0.2 - detect-port: 1.6.1 + detect-port: 1.6.1(supports-color@7.2.0) dotenv: 8.6.0 form-data: 4.0.6 fs-extra: 9.1.0 glob: 13.0.6 handlebars: 4.7.10 - https-proxy-agent: 7.0.6 + https-proxy-agent: 7.0.6(supports-color@7.2.0) iconv-lite: 0.6.3 ignore: 5.3.2 js-base64: 3.9.4 @@ -4667,7 +4733,7 @@ snapshots: - supports-color - zod - '@modelcontextprotocol/sdk@1.32.1(zod@4.6.7)': + '@modelcontextprotocol/sdk@1.32.1(supports-color@7.2.0)(zod@4.6.7)': dependencies: '@hono/node-server': 2.1.5(hono@4.13.13) ajv: 8.20.0 @@ -4677,8 +4743,8 @@ snapshots: cross-spawn: 7.0.6 eventsource: 3.0.7 eventsource-parser: 3.1.1 - express: 5.3.0 - express-rate-limit: 8.7.1(express@5.3.0) + express: 5.3.0(supports-color@7.2.0) + express-rate-limit: 8.7.1(express@5.3.0(supports-color@7.2.0))(supports-color@7.2.0) hono: 4.13.13 jose: 6.2.12 json-schema-typed: 8.0.2 @@ -4909,10 +4975,10 @@ snapshots: '@types/wrap-ansi@3.0.0': {} - '@typespec/ts-http-runtime@0.3.9': + '@typespec/ts-http-runtime@0.3.9(supports-color@7.2.0)': dependencies: - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 + http-proxy-agent: 7.0.2(supports-color@7.2.0) + https-proxy-agent: 7.0.6(supports-color@7.2.0) tslib: 2.8.1 transitivePeerDependencies: - supports-color @@ -4954,9 +5020,9 @@ snapshots: adm-zip@0.6.1: {} - agent-base@6.0.2: + agent-base@6.0.2(supports-color@7.2.0): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -5039,6 +5105,8 @@ snapshots: dependencies: tslib: 2.8.1 + async@3.2.6: {} + asynckit@0.4.0: {} at-least-node@1.0.0: {} @@ -5057,11 +5125,11 @@ snapshots: '@babel/runtime': 7.29.10 is-retry-allowed: 2.2.0 - axios@1.20.0(debug@4.4.3): + axios@1.20.0(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0): dependencies: - follow-redirects: 1.16.1(debug@4.4.3) + follow-redirects: 1.16.1(debug@4.4.3(supports-color@7.2.0)) form-data: 4.0.6 - https-proxy-agent: 5.0.1 + https-proxy-agent: 5.0.1(supports-color@7.2.0) proxy-from-env: 2.1.0 transitivePeerDependencies: - debug @@ -5085,11 +5153,15 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - body-parser@1.20.8: + block-stream2@2.1.0: + dependencies: + readable-stream: 3.6.2 + + body-parser@1.20.8(supports-color@7.2.0): dependencies: bytes: 3.1.2 content-type: 1.0.5 - debug: 2.6.9 + debug: 2.6.9(supports-color@7.2.0) depd: 2.0.0 destroy: 1.2.0 http-errors: 2.0.1 @@ -5102,11 +5174,11 @@ snapshots: transitivePeerDependencies: - supports-color - body-parser@2.3.0: + body-parser@2.3.0(supports-color@7.2.0): dependencies: bytes: 3.1.2 content-type: 2.1.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) http-errors: 2.0.1 iconv-lite: 0.7.3 on-finished: 2.4.1 @@ -5126,6 +5198,8 @@ snapshots: dependencies: fill-range: 7.1.1 + browser-or-node@2.1.1: {} + browserslist@4.29.3: dependencies: baseline-browser-mapping: 2.11.28 @@ -5134,6 +5208,8 @@ snapshots: node-releases: 2.0.58 update-browserslist-db: 1.3.4(browserslist@4.29.3) + buffer-crc32@1.0.0: {} + buffer-equal-constant-time@1.0.1: {} buffer-from@1.1.2: {} @@ -5339,16 +5415,22 @@ snapshots: date-format@4.0.14: {} - debug@2.6.9: + debug@2.6.9(supports-color@7.2.0): dependencies: ms: 2.0.0 + optionalDependencies: + supports-color: 7.2.0 - debug@4.4.3: + debug@4.4.3(supports-color@7.2.0): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 7.2.0 decimal.js@10.6.0: {} + decode-uri-component@0.2.2: {} + decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 @@ -5380,10 +5462,10 @@ snapshots: detect-libc@2.1.2: {} - detect-port@1.6.1: + detect-port@1.6.1(supports-color@7.2.0): dependencies: address: 1.2.2 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -5596,6 +5678,8 @@ snapshots: etag@1.8.1: {} + eventemitter3@5.0.4: {} + events@3.3.0: {} eventsource-parser@3.1.1: {} @@ -5608,29 +5692,29 @@ snapshots: expect-type@1.4.0: {} - express-rate-limit@8.7.1(express@5.3.0): + express-rate-limit@8.7.1(express@5.3.0(supports-color@7.2.0))(supports-color@7.2.0): dependencies: - debug: 4.4.3 - express: 5.3.0 + debug: 4.4.3(supports-color@7.2.0) + express: 5.3.0(supports-color@7.2.0) ip-address: 10.7.4 transitivePeerDependencies: - supports-color - express@4.22.3: + express@4.22.3(supports-color@7.2.0): dependencies: accepts: 1.3.8 array-flatten: 1.1.1 - body-parser: 1.20.8 + body-parser: 1.20.8(supports-color@7.2.0) content-disposition: 0.5.4 content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.0.7 - debug: 2.6.9 + debug: 2.6.9(supports-color@7.2.0) depd: 2.0.0 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 1.3.2 + finalhandler: 1.3.2(supports-color@7.2.0) fresh: 0.5.2 http-errors: 2.0.1 merge-descriptors: 1.0.3 @@ -5642,8 +5726,8 @@ snapshots: qs: 6.16.0 range-parser: 1.2.1 safe-buffer: 5.2.1 - send: 0.19.2 - serve-static: 1.16.3 + send: 0.19.2(supports-color@7.2.0) + serve-static: 1.16.3(supports-color@7.2.0) setprototypeof: 1.2.0 statuses: 2.0.2 type-is: 1.6.18 @@ -5652,20 +5736,20 @@ snapshots: transitivePeerDependencies: - supports-color - express@5.3.0: + express@5.3.0(supports-color@7.2.0): dependencies: accepts: 2.0.0 - body-parser: 2.3.0 + body-parser: 2.3.0(supports-color@7.2.0) content-disposition: 2.0.1 content-type: 2.1.0 cookie: 0.7.2 cookie-signature: 1.2.2 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) depd: 2.0.0 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 2.1.1 + finalhandler: 2.1.1(supports-color@7.2.0) fresh: 2.0.0 http-errors: 2.0.1 merge-descriptors: 2.0.0 @@ -5676,9 +5760,9 @@ snapshots: proxy-addr: 2.0.8 qs: 6.16.0 range-parser: 1.3.0 - router: 2.2.0 - send: 1.2.1 - serve-static: 2.2.1 + router: 2.2.0(supports-color@7.2.0) + send: 1.2.1(supports-color@7.2.0) + serve-static: 2.2.1(supports-color@7.2.0) statuses: 2.0.2 type-is: 2.1.0 vary: 1.1.2 @@ -5734,9 +5818,11 @@ snapshots: dependencies: to-regex-range: 5.0.1 - finalhandler@1.3.2: + filter-obj@1.1.0: {} + + finalhandler@1.3.2(supports-color@7.2.0): dependencies: - debug: 2.6.9 + debug: 2.6.9(supports-color@7.2.0) encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 @@ -5746,9 +5832,9 @@ snapshots: transitivePeerDependencies: - supports-color - finalhandler@2.1.1: + finalhandler@2.1.1(supports-color@7.2.0): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 @@ -5761,9 +5847,9 @@ snapshots: flatted@3.4.4: {} - follow-redirects@1.16.1(debug@4.4.3): + follow-redirects@1.16.1(debug@4.4.3(supports-color@7.2.0)): optionalDependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) form-data@4.0.6: dependencies: @@ -5911,26 +5997,26 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 - http-proxy-agent@7.0.2: + http-proxy-agent@7.0.2(supports-color@7.2.0): dependencies: agent-base: 7.1.4 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) transitivePeerDependencies: - supports-color http2-client@1.3.5: {} - https-proxy-agent@5.0.1: + https-proxy-agent@5.0.1(supports-color@7.2.0): dependencies: - agent-base: 6.0.2 - debug: 4.4.3 + agent-base: 6.0.2(supports-color@7.2.0) + debug: 4.4.3(supports-color@7.2.0) transitivePeerDependencies: - supports-color - https-proxy-agent@7.0.6: + https-proxy-agent@7.0.6(supports-color@7.2.0): dependencies: agent-base: 7.1.4 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -5970,6 +6056,8 @@ snapshots: ipaddr.js@1.9.1: {} + ipaddr.js@2.5.0: {} + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 @@ -6190,13 +6278,13 @@ snapshots: lodash@4.18.1: {} - log4js@6.9.1: + log4js@6.9.1(supports-color@7.2.0): dependencies: date-format: 4.0.14 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) flatted: 3.4.4 rfdc: 1.4.1 - streamroller: 3.1.5 + streamroller: 3.1.5(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -6271,6 +6359,22 @@ snapshots: minimist@1.2.8: {} + minio@8.0.7: + dependencies: + async: 3.2.6 + block-stream2: 2.1.0 + browser-or-node: 2.1.1 + buffer-crc32: 1.0.0 + eventemitter3: 5.0.4 + fast-xml-parser: 5.11.2 + ipaddr.js: 2.5.0 + lodash: 4.18.1 + mime-types: 2.1.35 + query-string: 7.1.3 + stream-json: 1.9.1 + through2: 4.0.2 + xml2js: 0.5.0 + minipass@7.1.3: {} minizlib@3.1.0: @@ -6426,14 +6530,14 @@ snapshots: transitivePeerDependencies: - encoding - office-addin-debugging@7.0.1(@microsoft/m365agentstoolkit-cli@1.1.18(@types/node@26.6.5)(zod@4.6.7))(@types/node@26.6.5): + office-addin-debugging@7.0.1(@microsoft/m365agentstoolkit-cli@1.1.18(@types/node@26.6.5)(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)(zod@4.6.7))(@types/node@26.6.5): dependencies: adm-zip: 0.6.1 commander: 13.1.0 node-fetch: 2.7.0 office-addin-cli: 3.0.1 office-addin-dev-certs: 3.0.1 - office-addin-dev-settings: 4.0.1(@microsoft/m365agentstoolkit-cli@1.1.18(@types/node@26.6.5)(zod@4.6.7))(@types/node@26.6.5) + office-addin-dev-settings: 4.0.1(@microsoft/m365agentstoolkit-cli@1.1.18(@types/node@26.6.5)(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)(zod@4.6.7))(@types/node@26.6.5) office-addin-manifest: 3.0.1 office-addin-node-debugger: 2.0.1 office-addin-usage-data: 3.0.1 @@ -6454,9 +6558,9 @@ snapshots: transitivePeerDependencies: - encoding - office-addin-dev-settings@4.0.1(@microsoft/m365agentstoolkit-cli@1.1.18(@types/node@26.6.5)(zod@4.6.7))(@types/node@26.6.5): + office-addin-dev-settings@4.0.1(@microsoft/m365agentstoolkit-cli@1.1.18(@types/node@26.6.5)(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)(zod@4.6.7))(@types/node@26.6.5): dependencies: - '@microsoft/m365agentstoolkit-cli': 1.1.18(@types/node@26.6.5)(zod@4.6.7) + '@microsoft/m365agentstoolkit-cli': 1.1.18(@types/node@26.6.5)(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)(zod@4.6.7) adm-zip: 0.6.1 commander: 13.1.0 fs-extra: 11.4.1 @@ -6711,6 +6815,13 @@ snapshots: es-define-property: 1.0.1 side-channel: 1.1.1 + query-string@7.1.3: + dependencies: + decode-uri-component: 0.2.2 + filter-obj: 1.1.0 + split-on-first: 1.1.0 + strict-uri-encode: 2.0.0 + queue-microtask@1.2.3: {} range-parser@1.2.1: {} @@ -6809,9 +6920,9 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.2.13 '@rolldown/binding-win32-x64-msvc': 1.2.13 - router@2.2.0: + router@2.2.0(supports-color@7.2.0): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) depd: 2.0.0 is-promise: 4.0.0 parseurl: 1.3.3 @@ -6851,9 +6962,9 @@ snapshots: semver@7.8.5: {} - send@0.19.2: + send@0.19.2(supports-color@7.2.0): dependencies: - debug: 2.6.9 + debug: 2.6.9(supports-color@7.2.0) depd: 2.0.0 destroy: 1.2.0 encodeurl: 2.0.0 @@ -6869,9 +6980,9 @@ snapshots: transitivePeerDependencies: - supports-color - send@1.2.1: + send@1.2.1(supports-color@7.2.0): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -6885,21 +6996,21 @@ snapshots: transitivePeerDependencies: - supports-color - serve-static@1.16.3: + serve-static@1.16.3(supports-color@7.2.0): dependencies: encodeurl: 2.0.0 escape-html: 1.0.3 parseurl: 1.3.3 - send: 0.19.2 + send: 0.19.2(supports-color@7.2.0) transitivePeerDependencies: - supports-color - serve-static@2.2.1: + serve-static@2.2.1(supports-color@7.2.0): dependencies: encodeurl: 2.0.0 escape-html: 1.0.3 parseurl: 1.3.3 - send: 1.2.1 + send: 1.2.1(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -6990,22 +7101,32 @@ snapshots: source-map@0.6.1: {} + split-on-first@1.1.0: {} + stack-chain@1.3.7: {} statuses@2.0.2: {} std-env@4.3.0: {} - streamroller@3.1.5: + stream-chain@2.2.5: {} + + stream-json@1.9.1: + dependencies: + stream-chain: 2.2.5 + + streamroller@3.1.5(supports-color@7.2.0): dependencies: date-format: 4.0.14 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) fs-extra: 8.1.0 transitivePeerDependencies: - supports-color streamsearch@1.1.0: {} + strict-uri-encode@2.0.0: {} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -7128,9 +7249,9 @@ snapshots: minizlib: 3.1.0 yallist: 5.0.0 - tas-client@0.1.73: + tas-client@0.1.73(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0): dependencies: - axios: 1.20.0(debug@4.4.3) + axios: 1.20.0(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0) transitivePeerDependencies: - debug - supports-color @@ -7150,6 +7271,10 @@ snapshots: dependencies: any-promise: 1.3.0 + through2@4.0.2: + dependencies: + readable-stream: 3.6.2 + tinybench@6.2.2: {} tinyexec@1.3.1: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..69f1148 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +allowBuilds: + '@azure/msal-node-extensions': set this to true or false + '@microsoft/m365agentstoolkit-cli': set this to true or false + '@scarf/scarf': set this to true or false + esbuild: set this to true or false + keytar: set this to true or false diff --git a/tests/lib/document-versioning.test.ts b/tests/lib/document-versioning.test.ts new file mode 100644 index 0000000..635fb84 --- /dev/null +++ b/tests/lib/document-versioning.test.ts @@ -0,0 +1,244 @@ +import { describe, expect, it } from "vitest"; + +import { + DOCVER_MESSAGE_MAX, + contentDispositionValue, + docverError, + isDocxBytes, + isPdfBytes, + parseDocId, + parseDownloadFormat, + parseFieldsJson, + parseVersionIdParam, + parseVersionUploadForm, + sanitizeDisplayFileName, + sanitizeFilePartName, +} from "@/lib/document-versioning"; + +// docver (PLAN-DOCVER.md) — validation ล้วนของ /api/documents/*: ไฟล์นี้ตั้งใจไม่แตะ +// module ใดที่ลาก @/db หรือ minio มาด้วย (pattern เดียวกับ external-api-auth.test.ts +// — vitest ไม่โหลด .env จึงต้องรันได้โดยไม่ตั้ง DATABASE_URL/MINIO_*) + +function ok(result: { ok: boolean } & T): asserts result is { ok: true } & T { + expect(result.ok).toBe(true); +} + +describe("parseDocId", () => { + it("รับ GUID / รหัส e-Court / charset ที่อนุญาตทั้งหมด และ trim รอบ ๆ", () => { + for (const raw of [ + "123e4567-e89b-12d3-a456-426614174000", + "ecourt:123", + "DOC_2026.10", + " doc1 ", + "a".repeat(200), + ]) { + const result = parseDocId(raw); + ok(result); + expect(result.docId).toBe(raw.trim()); + } + }); + + it("ตกเมื่อว่าง / เกิน 200 / มีช่องว่างกลาง / ไทย / slash / '..'", () => { + for (const raw of ["", " ", "a".repeat(201), "doc 1", "เอกสาร1", "a/b", "..", "a\\b"]) { + expect(parseDocId(raw).ok).toBe(false); + } + }); +}); + +describe("parseVersionIdParam", () => { + it("รับ UUID ทั้งพิมพ์ใหญ่/เล็ก (normalize ลงตัวพิมพ์เล็ก) + trim", () => { + const result = parseVersionIdParam(" 6F9619FF-8B86-D011-B42D-00C04FC964FF "); + ok(result); + expect(result.versionId).toBe("6f9619ff-8b86-d011-b42d-00c04fc964ff"); + }); + + it("ตกเมื่อไม่ใช่รูป UUID", () => { + for (const raw of ["not-a-uuid", "123", "{6f9619ff-8b86-d011-b42d-00c04fc964ff}", ""]) { + expect(parseVersionIdParam(raw).ok).toBe(false); + } + }); +}); + +describe("parseDownloadFormat", () => { + it("ไม่ส่ง/ว่าง/ข้างในมี whitespace = docx (default ตาม contract)", () => { + for (const raw of [null, "", " ", " docx "]) { + const result = parseDownloadFormat(raw); + ok(result); + expect(result.format).toBe("docx"); + } + }); + + it("รับ pdf ตรง ๆ และตกเมื่อค่าอื่น (รวมพิมพ์ใหญ่)", () => { + const pdf = parseDownloadFormat("pdf"); + ok(pdf); + expect(pdf.format).toBe("pdf"); + for (const raw of ["exe", "PDF", "docxx"]) { + expect(parseDownloadFormat(raw).ok).toBe(false); + } + }); +}); + +describe("isDocxBytes / isPdfBytes", () => { + it("ผ่านเมื่อ magic bytes ตรง (แม้ buffer สั้นกว่าที่อ่าน)", () => { + expect(isDocxBytes(Buffer.from("PK\x03\x04rest-of-zip"))).toBe(true); + expect(isPdfBytes(Buffer.from("%PDF-1.7\n..."))).toBe(true); + }); + + it("ตกเมื่อ signature ผิดหรือ buffer ว่าง/สั้นกว่า signature", () => { + expect(isDocxBytes(Buffer.from("%PDF-1.7"))).toBe(false); + expect(isDocxBytes(Buffer.from("PK"))).toBe(false); + expect(isDocxBytes(Buffer.alloc(0))).toBe(false); + expect(isPdfBytes(Buffer.from("PK\x03\x04"))).toBe(false); + expect(isPdfBytes(Buffer.from("%PD"))).toBe(false); + expect(isPdfBytes(Buffer.alloc(0))).toBe(false); + }); +}); + +describe("parseFieldsJson", () => { + it("แยก JSON object ล้วนได้ และ `{}` = null (ถือว่าไม่ส่ง)", () => { + const result = parseFieldsJson('{"blackno":"คดีหมายเลขดำที่"}'); + ok(result); + expect(result.fields).toEqual({ blackno: "คดีหมายเลขดำที่" }); + const empty = parseFieldsJson("{}"); + ok(empty); + expect(empty.fields).toBeNull(); + }); + + it("ตกเมื่อ JSON เพี้ยน / ไม่ใช่ object / value ไม่ใช่ string", () => { + for (const raw of ["ไม่ใช่ JSON", "[1,2]", '"string"', "null", '{"a":1}']) { + expect(parseFieldsJson(raw).ok).toBe(false); + } + }); +}); + +describe("parseVersionUploadForm", () => { + function baseForm(): FormData { + const form = new FormData(); + form.append("file", new File([Buffer.from("PK\x03\x04")], "draft.docx")); + form.append("userId", "u0001"); + form.append("userName", "สมชาย ใจดี"); + return form; + } + + it("happy path: แยกทุก field + trim ค่าว่างเป็น null + fileName ตัด path ที่ฝังมา", () => { + const form = baseForm(); + form.append("clientVersionId", " 11111111-1111-4111-8111-111111111111 "); + form.append("message", " "); + form.append("templateId", "tmpl-main"); + form.append("fields", '{"blackno":"1"}'); + form.append("fileName", "C:\\Users\\judge\\คำสั่งเรียก.docx"); + const result = parseVersionUploadForm(form); + ok(result); + expect(result.form).toEqual({ + userId: "u0001", + userName: "สมชาย ใจดี", + clientVersionId: "11111111-1111-4111-8111-111111111111", + message: null, + templateId: "tmpl-main", + fileName: "คำสั่งเรียก.docx", + fields: { blackno: "1" }, + }); + }); + + it("ตกเมื่อมี field ที่ไม่รู้จัก (กัน typo เงียบ ๆ)", () => { + const form = baseForm(); + form.append("oops", "1"); + const result = parseVersionUploadForm(form); + expect(result.ok).toBe(false); + if (!result.ok) expect(result.error).toContain("oops"); + }); + + it("ตกเมื่อขาด userId หรือ userName (stand-alone บอกผู้อัปโหลดเอง)", () => { + const noUser = baseForm(); + noUser.delete("userId"); + expect(parseVersionUploadForm(noUser).ok).toBe(false); + const noName = baseForm(); + noName.delete("userName"); + expect(parseVersionUploadForm(noName).ok).toBe(false); + }); + + it("ตกเมื่อ text field ถูกส่งมาเป็นไฟล์", () => { + const form = baseForm(); + form.delete("userId"); + form.append("userId", new File(["x"], "u.txt")); + const result = parseVersionUploadForm(form); + expect(result.ok).toBe(false); + if (!result.ok) expect(result.error).toContain("userId"); + }); + + it(`message ยาว ${DOCVER_MESSAGE_MAX} ผ่าน แต่เกิน = ตก`, () => { + const edge = baseForm(); + edge.append("message", "a".repeat(DOCVER_MESSAGE_MAX)); + ok(parseVersionUploadForm(edge)); + const over = baseForm(); + over.append("message", "a".repeat(DOCVER_MESSAGE_MAX + 1)); + expect(parseVersionUploadForm(over).ok).toBe(false); + }); + + it("fields ใน form ผิดกติกาส่งต่อ error ของ parseFieldsJson", () => { + const form = baseForm(); + form.append("fields", '{"a":1}'); + expect(parseVersionUploadForm(form).ok).toBe(false); + }); +}); + +describe("sanitizeDisplayFileName / sanitizeFilePartName", () => { + it("ตัด path ฝังมา (ทั้ง / และ \\) + trim ว่าง = null + cap 200", () => { + expect(sanitizeDisplayFileName("C:\\Users\\judge\\a.docx")).toBe("a.docx"); + expect(sanitizeDisplayFileName("/tmp/b.pdf")).toBe("b.pdf"); + expect(sanitizeDisplayFileName(" ")).toBeNull(); + expect(sanitizeDisplayFileName(null)).toBeNull(); + expect(sanitizeDisplayFileName(`${"a".repeat(250)}.docx`)).toHaveLength(200); + }); + + it("File.name ที่ undici ถอดเป็น latin1 (ชื่อไทยเพี้ยนแบบ N4) กู้คืนเป็น UTF-8 ได้", () => { + const original = "คำสั่งเรียกเอกสาร.docx"; + const mangled = Buffer.from(original, "utf8").toString("latin1"); + expect(mangled).not.toBe(original); // ต้องเพี้ยนจริงก่อน ไม่ใช่ทดสอบตัวเอง + expect(sanitizeFilePartName(mangled)).toBe(original); + // ชื่อ ASCII ไม่มีตัว U+0080–U+00FF จึงไม่ถูกแตะ + expect(sanitizeFilePartName("plain.docx")).toBe("plain.docx"); + }); +}); + +describe("docverError", () => { + it("สร้าง Response JSON ไทยพร้อม status ที่ขอ", async () => { + const res = docverError("ไม่พบ version นี้ของเอกสาร", 404); + expect(res.status).toBe(404); + expect(res.headers.get("content-type")).toBe("application/json; charset=utf-8"); + expect(await res.json()).toEqual({ error: "ไม่พบ version นี้ของเอกสาร" }); + }); +}); + +describe("contentDispositionValue", () => { + it("ไม่มีชื่อ = ชื่อ default อย่างเดียว", () => { + expect(contentDispositionValue(null, "docx")).toBe('attachment; filename="document.docx"'); + expect(contentDispositionValue(" ", "pdf")).toBe('attachment; filename="document.pdf"'); + }); + + it("ชื่อ ASCII: เติม extension ที่ขาด / คงชื่อที่มีอยู่แล้ว / ตัด quote-backslash", () => { + expect(contentDispositionValue("Report Final", "docx")).toBe( + 'attachment; filename="Report Final.docx"; filename*=UTF-8\'\'Report%20Final', + ); + expect(contentDispositionValue("Report Final.docx", "docx")).toBe( + 'attachment; filename="Report Final.docx"; filename*=UTF-8\'\'Report%20Final.docx', + ); + expect(contentDispositionValue('bad"name\\', "pdf")).toBe( + 'attachment; filename="badname.pdf"; filename*=UTF-8\'\'bad%22name%5C', + ); + }); + + it("ชื่อไทยล้วน = ASCII fallback เป็นชื่อ default แต่ filename* ยังชื่อเต็ม", () => { + expect(contentDispositionValue("คำสั่งเรียก", "docx")).toBe( + 'attachment; filename="document.docx"; filename*=UTF-8\'\'' + + encodeURIComponent("คำสั่งเรียก"), + ); + }); + + it("ชื่อผสมไทย+เลข = ส่วน ASCII ที่เหลือใช้เป็น fallback ได้", () => { + expect(contentDispositionValue("รายงาน 2026", "pdf")).toBe( + 'attachment; filename="2026.pdf"; filename*=UTF-8\'\'' + + encodeURIComponent("รายงาน 2026"), + ); + }); +}); From ac6c0dc71efcbf51e9abb649abfd7a948acba654 Mon Sep 17 00:00:00 2001 From: oom Date: Thu, 22 Oct 2026 22:33:14 +0700 Subject: [PATCH 07/21] Add new template format with hashing algorithm --- app/api/template/extract/route.ts | 37 +- app/api/template/render/route.ts | 79 +- lib/lib-tmpl.ts | 1263 ++++++++++++++++++++++++++++ lib/lib-utils.ts | 112 +++ lib/openapi.ts | 32 +- middleware.ts | 2 +- package-lock.json | 929 ++++++++------------ package.json | 3 +- public/tmpl/T12_FD155_3s-long.json | 44 + public/tmpl/T12_X.docx | Bin 0 -> 154674 bytes public/tmpl/T12_b2_IN.json | 41 + public/tmpl/T12_b2_IN.out.json | 51 ++ public/tmpl/TF_no_warning_P2.json | 50 ++ public/tmpl/TS_P2_IN.json | 52 ++ public/tmpl/T_X.docx | Bin 0 -> 157789 bytes public/tmpl/T_no_warning.json | 51 ++ public/tmpl/index.html | 20 + 17 files changed, 2157 insertions(+), 609 deletions(-) create mode 100755 lib/lib-tmpl.ts create mode 100644 lib/lib-utils.ts create mode 100644 public/tmpl/T12_FD155_3s-long.json create mode 100755 public/tmpl/T12_X.docx create mode 100644 public/tmpl/T12_b2_IN.json create mode 100644 public/tmpl/T12_b2_IN.out.json create mode 100755 public/tmpl/TF_no_warning_P2.json create mode 100755 public/tmpl/TS_P2_IN.json create mode 100755 public/tmpl/T_X.docx create mode 100755 public/tmpl/T_no_warning.json create mode 100644 public/tmpl/index.html diff --git a/app/api/template/extract/route.ts b/app/api/template/extract/route.ts index dc5b49c..e723f35 100644 --- a/app/api/template/extract/route.ts +++ b/app/api/template/extract/route.ts @@ -1,5 +1,6 @@ import { NextResponse } from "next/server"; -import { extractContentControls } from "../lib-tmpl"; +import { extractContentControls, extractBookmark, templateContentControls, setCustomDocProps, getDocProps,templateDynamicContents } from "@/lib/lib-tmpl"; +import { verifyDataEd25519, verifyHmacSha256 } from "@/lib/lib-utils"; export const runtime = "nodejs"; @@ -14,6 +15,9 @@ export async function POST(request: Request) { try { const formData = await request.formData(); const file = formData.get("file"); + const TEST_SECRET = String(formData.get("secret"))??""; + const TEST_PUBLIC_KEY_HEX = String(formData.get("public_key"))??""; + if (!(file instanceof File)) { return NextResponse.json( @@ -52,12 +56,35 @@ export async function POST(request: Request) { const buffer = Buffer.from(await file.arrayBuffer()); // ใช้ lib-tmpl.ts เดิม - const data = extractContentControls(buffer); + // const data = extractContentControls(buffer); - return NextResponse.json({ + const cc = extractContentControls(buffer); + const bookmark = extractBookmark(buffer) + const properties = getDocProps(buffer) + let verify: Record ={} + const ticket_data = String(properties?.custom?.ticket_data) + const HmacSha256 = String(properties?.custom?.HmacSha256) + const Ed25519 = String(properties?.custom?.Ed25519) + + if(ticket_data){ + const ticketDataObj= JSON.parse(ticket_data) + const unix_timestamp = Number(ticketDataObj.exp)*1000 + if(unix_timestamp){ + verify['exp'] = new Date(unix_timestamp).toLocaleString("th-TH", { timeZone: "Asia/Bangkok" }) + } + if(TEST_SECRET && HmacSha256){ + verify['verifyHmacSha256'] = verifyHmacSha256(ticket_data,TEST_SECRET,HmacSha256) + } + if(TEST_PUBLIC_KEY_HEX&& Ed25519){ + verify["Ed25519"]=verifyDataEd25519(ticket_data,Ed25519,TEST_PUBLIC_KEY_HEX) + } + } + let returnData : Record = { success: true, - data, - }); + cc,bookmark,properties,verify + } + + return NextResponse.json(returnData); } catch (error) { console.error(error); diff --git a/app/api/template/render/route.ts b/app/api/template/render/route.ts index df6501c..8f04c9f 100644 --- a/app/api/template/render/route.ts +++ b/app/api/template/render/route.ts @@ -1,8 +1,6 @@ import { NextResponse } from "next/server"; -import { - templateContentControls, - type TemplateData, -} from "../lib-tmpl"; +import { DocumentTemplate, templateContentControls, setCustomDocProps, getDocProps, templateDynamicContents } from "@/lib/lib-tmpl"; +import { generateHmacSha256, verifyHmacSha256, signDataEd25519, verifyDataEd25519, sortObjectKeys } from '@/lib/lib-utils'; export const runtime = "nodejs"; @@ -20,7 +18,13 @@ export async function POST(request: Request) { const template = formData.get("template"); const dataFile = formData.get("data"); const dataJson = formData.get("dataJson"); + const TEST_SECRET = (typeof formData.get("secret") === "string") ? + String(formData.get("secret")) : "super_secret"; + const TEST_PRIVATE_KEY_HEX = (typeof formData.get("private_key") === "string") ? + String(formData.get("private_key")) : "8f2077b57b39399b6a7828f35da03a63ade8e3a36a6bf437b091a8bdb3d18ee1"; + console.log(`secret:${TEST_SECRET}`) + console.log(`private:${TEST_PRIVATE_KEY_HEX}`) // ----------------------------- // ตรวจสอบ Template // ----------------------------- @@ -62,14 +66,14 @@ export async function POST(request: Request) { // อ่าน JSON // ----------------------------- - let data: TemplateData; + let templateData: DocumentTemplate; // JSON ผิด syntax เป็นความผิดของฝั่งผู้เรียก — คืน 400 ไม่ใช่ 500 // (catch กลางท้ายฟังก์ชันสงวนไว้ให้ error ของระบบเท่านั้น) try { if (typeof dataJson === "string" && dataJson.length > 0) { // กรณีส่ง JSON ผ่าน form field - data = JSON.parse(dataJson) as TemplateData; + templateData = JSON.parse(dataJson) as DocumentTemplate; } else if (dataFile instanceof File) { if (dataFile.size > MAX_UPLOAD_BYTES) { return NextResponse.json( @@ -84,7 +88,7 @@ export async function POST(request: Request) { // กรณี upload data.json const text = await dataFile.text(); - data = JSON.parse(text) as TemplateData; + templateData = JSON.parse(text) as DocumentTemplate; } else { return NextResponse.json( { @@ -99,9 +103,8 @@ export async function POST(request: Request) { return NextResponse.json( { success: false, - error: `Invalid JSON data (${ - error instanceof Error ? error.message : "parse error" - }).`, + error: `Invalid JSON data (${error instanceof Error ? error.message : "parse error" + }).`, }, { status: 400 }, ); @@ -109,9 +112,9 @@ export async function POST(request: Request) { // JSON ต้องเป็น object if ( - data === null || - typeof data !== "object" || - Array.isArray(data) + templateData === null || + typeof templateData !== "object" || + Array.isArray(templateData) ) { return NextResponse.json( { @@ -126,18 +129,52 @@ export async function POST(request: Request) { // Template File → Buffer // ----------------------------- - const templateBuffer = Buffer.from( + let templateBuffer: Buffer = Buffer.from( await template.arrayBuffer(), ); - // ----------------------------- - // ใช้ lib-tmpl.ts เดิม - // ----------------------------- + //Calculate hash + const originalTicket_data = templateData?.info?.ticket_data //expect valid json string + // console.log("templateData",templateData?.info?.ticket_data) + if (originalTicket_data && typeof originalTicket_data === 'string') { + try { - const output = templateContentControls( - templateBuffer, - data, - ); + const ttlSeconds: number = 300 + const originalTicketObj = JSON.parse(originalTicket_data)//sort and add expire to ticket data + if (originalTicketObj && typeof originalTicketObj === 'object') { + console.log("Found valid ticket data: hash it") + // คำนวณค่า expire (ปัจจุบัน + จำนวนวินาทีที่ต้องการให้หมดอายุ) + // หาร 1000 เพราะต้องการหน่วยวินาที (sec) ไม่ใช่มิลลิวินาที (ms) + const expireTimestamp = Math.floor(Date.now() / 1000) + ttlSeconds; + const fullObj = { ...originalTicketObj, exp: expireTimestamp }; + const sortedObj = sortObjectKeys(fullObj); + + // แปลงกลับเป็น JSON string เพื่อนำไปใช้ส่งและทำ Hash + const ticket_data = JSON.stringify(sortedObj); + // console.log("check secret:",TEST_SECRET,TEST_PRIVATE_KEY_HEX,ticket_data) + const HmacSha256 = generateHmacSha256(ticket_data, TEST_SECRET) + + const Ed25519 = signDataEd25519(ticket_data, TEST_PRIVATE_KEY_HEX) + templateBuffer = setCustomDocProps(templateBuffer, { ticket_data, HmacSha256, Ed25519 }) + } + } catch (error) { + console.error("Set custom property fail",error); + + } + } + + // Apply Dynamic Content + if (templateData?.dc && typeof templateData?.dc === "object") { + console.log("Apply Dynamic Content") + templateBuffer = Buffer.from(await templateDynamicContents(templateBuffer, templateData.dc)) + } + // Apply Content Control + if (templateData?.cc && typeof templateData?.cc === "object") { + console.log("Apply Content Control") + templateBuffer = templateContentControls(templateBuffer, templateData.cc); + } + + const output = templateBuffer; // ----------------------------- // ส่ง DOCX กลับ diff --git a/lib/lib-tmpl.ts b/lib/lib-tmpl.ts new file mode 100755 index 0000000..4bf4b70 --- /dev/null +++ b/lib/lib-tmpl.ts @@ -0,0 +1,1263 @@ +import createReport from 'docx-templates'; +import PizzaZip from "pizzip"; +import { + DOMParser, + XMLSerializer, + type Document as XmlDocument, + type Element as XmlElement, + type Node as XmlNode, + type Attr as XmlAttr, +} from "@xmldom/xmldom"; +import xpath from "xpath"; +const WORD_NS = + "http://schemas.openxmlformats.org/wordprocessingml/2006/main"; + +export interface ExtractedData { + [tagName: string]: string; +} + +const asXpathNode = (node: XmlNode): globalThis.Node => + node as unknown as globalThis.Node; + +function createSelector() { + return xpath.useNamespaces({ w: WORD_NS }); +} + + +/** + * ฟังก์ชันสำหรับดึงข้อมูลจาก docx ออกมาเป็น JSON โดยอิงจาก Bookmark (w:bookmarkStart) + */ +export function extractBookmark(docxBuffer: Buffer,): ExtractedData { + const zip = new PizzaZip(docxBuffer); + const docXmlFile = zip.file("word/document.xml"); + + if (!docXmlFile) { + throw new Error("Invalid docx file: word/document.xml not found."); + } + const docXmlText: string = docXmlFile.asText(); + + // 3. Parse ข้อความ XML ให้เป็น DOM Object + const parser = new DOMParser(); + const doc = parser.parseFromString(docXmlText, "text/xml"); + + // 4. ตั้งค่า Namespace ของ Word XML เพื่อใช้ค้นหาด้วย XPath + const select = xpath.useNamespaces({ + 'w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main' + }); + + const result: ExtractedData = {}; + + // ค้นหา Bookmark หรือ Tag ที่ใช้แบ่ง Section + // const bookmarks = select('//w:bookmarkStart', doc as unknown as globalThis.Node) as Element[]; + const bookmarks = select( + "//w:bookmarkStart", + doc as unknown as globalThis.Node + ) as unknown as XmlElement[]; + + bookmarks.forEach((bookmark: XmlElement) => { + const sectionName = bookmark.getAttribute('w:name'); + const bookmarkId = bookmark.getAttribute('w:id'); + + // กรองเอาเฉพาะ Bookmark ที่เราสนใจ (ไม่เอาพวกระบบสร้างเองอย่าง _GoBack) + if (sectionName && !sectionName.startsWith('_')) { + let extractedText = ""; + let currentNode: XmlNode | null = bookmark.nextSibling; + + // วนลูปอ่าน Node ถัดไปเรื่อยๆ จนกว่าจะเจอจุดสิ้นสุดของ Bookmark + while (currentNode) { + // ถ้าเจอจุดจบของ Bookmark ตัวเอง ให้หยุด + if ( + currentNode.nodeType === 1 && // Element node + (currentNode as XmlElement).nodeName === 'w:bookmarkEnd' && + (currentNode as XmlElement).getAttribute('w:id') === bookmarkId + ) { + break; + } + + // ดึง Text (w:t) ที่อยู่ภายใน Node ปัจจุบัน + const textNodes = select('.//w:t/text()', currentNode as unknown as globalThis.Node) as unknown as XmlNode[]; + textNodes.forEach((textNode: XmlNode) => { + if (textNode.nodeValue) { + extractedText += textNode.nodeValue; + } + }); + + currentNode = currentNode.nextSibling; + } + + // บันทึกลงผลลัพธ์ JSON + result[sectionName] = extractedText.trim(); + } + }); + + return result; +} + + + +/** + * อ่าน Content Control (w:sdt) จาก DOCX Buffer + * ปัจจุบันอ่านจาก word/document.xml ตาม logic ของโค้ดต้นฉบับ + */ +export function extractContentControls( + docxBuffer: Buffer, +): ExtractedData { + const zip = new PizzaZip(docxBuffer); + const docXmlFile = zip.file("word/document.xml"); + + if (!docXmlFile) { + throw new Error("Invalid docx file: word/document.xml not found."); + } + + const parser = new DOMParser(); + const doc = parser.parseFromString(docXmlFile.asText(), "text/xml"); + const select = createSelector(); + const result: ExtractedData = {}; + + const sdtNodes = select( + "//w:sdt", + asXpathNode(doc), + ) as unknown as XmlElement[]; + + sdtNodes.forEach((sdt) => { + const tagAttr = select( + "./w:sdtPr/w:tag/@w:val", + asXpathNode(sdt), + true, + ) as unknown as XmlAttr | undefined; + + const tagName = tagAttr?.value; + + if (!tagName) return; + + const sdtContent = select( + "./w:sdtContent", + asXpathNode(sdt), + true, + ) as unknown as XmlElement | undefined; + + if (!sdtContent) { + result[tagName] = ""; + return; + } + + const nodes = select( + ".//w:p | .//w:t | .//w:tab | .//w:br | .//w:noBreakHyphen | .//w:softHyphen | .//w:sym", + asXpathNode(sdtContent), + ) as unknown as XmlElement[]; + + let extractedText = ""; + + nodes.forEach((node) => { + switch (node.nodeName) { + case "w:p": + if ( + extractedText.length > 0 && + !extractedText.endsWith("\n") + ) { + extractedText += "\n\n"; + } + break; + + case "w:t": + extractedText += node.textContent || ""; + break; + + case "w:tab": + extractedText += "\t"; + break; + + case "w:br": + extractedText += "\n"; + break; + + case "w:noBreakHyphen": + extractedText += "\u2011"; + break; + + case "w:softHyphen": + extractedText += "\u00ad"; + break; + + case "w:sym": { + const charAttr = node.getAttribute("w:char"); + if (charAttr) { + const charCode = Number.parseInt(charAttr, 16); + extractedText += String.fromCharCode(charCode); + } + break; + } + } + }); + + result[tagName] = extractedText; + }); + + return result; +} + +type SdtControlType = "plain" | "rich" | "other"; +type SdtStructure = "block" | "inline"; + +function detectSdtControlType( + sdt: XmlElement, + select: xpath.XPathSelect, +): { type: SdtControlType; multiLine: boolean } { + const sdtPr = select( + "./w:sdtPr", + asXpathNode(sdt), + true, + ) as unknown as XmlElement | undefined; + + if (!sdtPr) { + return { type: "other", multiLine: false }; + } + + const textEl = select( + "./w:text", + asXpathNode(sdtPr), + true, + ) as unknown as XmlElement | undefined; + + if (textEl) { + const multiLineAttr = select( + "./@w:multiLine", + asXpathNode(textEl), + true, + ) as unknown as XmlAttr | undefined; + + return { + type: "plain", + multiLine: + multiLineAttr?.value === "1" || + multiLineAttr?.value === "true", + }; + } + + const richTextEl = select( + "./w:richText", + asXpathNode(sdtPr), + true, + ) as unknown as XmlElement | undefined; + + if (richTextEl) { + return { type: "rich", multiLine: true }; + } + + const otherTypeTags = [ + "w:date", + "w:dropDownList", + "w:comboBox", + "w:checkbox", + "w:citation", + "w:group", + "w:docPartObj", + "w:docPartList", + ]; + + for (const tag of otherTypeTags) { + const found = select( + `./${tag}`, + asXpathNode(sdtPr), + true, + ) as unknown as XmlElement | undefined; + + if (found) { + return { type: "other", multiLine: false }; + } + } + + return { type: "rich", multiLine: true }; +} + +function detectSdtStructure( + sdtContent: XmlElement, + select: xpath.XPathSelect, +): SdtStructure { + const directP = select( + "./w:p", + asXpathNode(sdtContent), + ) as unknown as XmlElement[]; + + return directP.length > 0 ? "block" : "inline"; +} + +function buildRunChildren( + doc: XmlDocument, + text: string, +): XmlElement[] { + const nodes: XmlElement[] = []; + const tokens = text.split(/(\t|\n)/); + + tokens.forEach((token) => { + if (token === "") return; + + if (token === "\t") { + nodes.push(doc.createElementNS(WORD_NS, "w:tab")); + } else if (token === "\n") { + nodes.push(doc.createElementNS(WORD_NS, "w:br")); + } else { + const t = doc.createElementNS(WORD_NS, "w:t"); + t.setAttribute("xml:space", "preserve"); + t.textContent = token; + nodes.push(t); + } + }); + + if (nodes.length === 0) { + const t = doc.createElementNS(WORD_NS, "w:t"); + t.setAttribute("xml:space", "preserve"); + t.textContent = ""; + nodes.push(t); + } + + return nodes; +} + +function buildRun( + doc: XmlDocument, + text: string, + rPr?: XmlElement, +): XmlElement { + const run = doc.createElementNS(WORD_NS, "w:r"); + + if (rPr) { + run.appendChild(rPr.cloneNode(true)); + } + + buildRunChildren(doc, text).forEach((node) => + run.appendChild(node), + ); + + return run; +} + +function insertInlineContent( + doc: XmlDocument, + sdtContent: XmlElement, + select: xpath.XPathSelect, + text: string, + allowLineBreak: boolean, +): void { + const safeText = allowLineBreak + ? text + : text.replace(/\n/g, " "); + + const existingRuns = select( + "./w:r", + asXpathNode(sdtContent), + ) as unknown as XmlElement[]; + + const firstRPr = + existingRuns.length > 0 + ? (select( + "./w:rPr", + asXpathNode(existingRuns[0]), + true, + ) as unknown as XmlElement | undefined) + : undefined; + + existingRuns.forEach((r) => { + if (r.parentNode) r.parentNode.removeChild(r); + }); + + sdtContent.appendChild( + buildRun(doc, safeText, firstRPr), + ); +} + +// ในฟังก์ชัน insertBlockContent() +function insertBlockContent( + doc: XmlDocument, + sdtContent: XmlElement, + select: xpath.XPathSelect, + text: string, + allowMultiParagraph: boolean, +): void { + const allParagraphs = select( + "./w:p", + asXpathNode(sdtContent), + ) as unknown as XmlElement[]; + + if (allParagraphs.length === 0) { + const p = doc.createElementNS(WORD_NS, "w:p"); + sdtContent.appendChild(p); + allParagraphs.push(p); + } + + const firstP = allParagraphs[0]; + + const firstPPr = select( + "./w:pPr", + asXpathNode(firstP), + true, + ) as unknown as XmlElement | undefined; + + const firstRun = select( + "./w:r", + asXpathNode(firstP), + true, + ) as unknown as XmlElement | undefined; + + const firstRPr = firstRun + ? (select( + "./w:rPr", + asXpathNode(firstRun), + true, + ) as unknown as XmlElement | undefined) + : undefined; + + // ลบย่อหน้าเดิมทั้งหมดยกเว้นย่อหน้าแรก + allParagraphs.forEach((p) => { + if (p !== firstP && p.parentNode) { + p.parentNode.removeChild(p); + } + }); + + // ลบ Run เดิมออกจากย่อหน้าแรก + const existingRuns = select( + "./w:r", + asXpathNode(firstP), + ) as unknown as XmlElement[]; + + existingRuns.forEach((r) => { + if (r.parentNode) r.parentNode.removeChild(r); + }); + + if (!allowMultiParagraph) { + // ถ้าไม่ใช่ออกแบบให้ข้ามย่อหน้าได้ ให้เปลี่ยน \n ทั้งหมดเป็น Space หรือ buildRun ปกติ + firstP.appendChild(buildRun(doc, text.replace(/\n+/g, "\n"), firstRPr)); + return; + } + + // แยกด้วย \n\n สำหรับ Paragraph Break (w:p) + const paragraphs = text.split("\n\n"); + + paragraphs.forEach((pText, index) => { + let targetP: XmlElement; + + if (index === 0) { + targetP = firstP; + } else { + targetP = doc.createElementNS(WORD_NS, "w:p"); + if (firstPPr) { + targetP.appendChild(firstPPr.cloneNode(true)); + } + sdtContent.appendChild(targetP); + } + + const run = doc.createElementNS(WORD_NS, "w:r"); + if (firstRPr) { + run.appendChild(firstRPr.cloneNode(true)); + } + + // pText จะมี \n เดี่ยวๆ หลงเหลืออยู่ ซึ่ง buildRunChildren จะแปลงเป็น w:br ให้โดยอัตโนมัติ + buildRunChildren(doc, pText).forEach((node) => + run.appendChild(node), + ); + + targetP.appendChild(run); + }); +} + +function insertFallbackContent( + sdtContent: XmlElement, + select: xpath.XPathSelect, + text: string, +): void { + const textNodes = select( + ".//w:t", + asXpathNode(sdtContent), + ) as unknown as XmlElement[]; + + if (textNodes.length === 0) return; + + textNodes[0].textContent = text; + + for (let i = 1; i < textNodes.length; i++) { + textNodes[i].textContent = ""; + } +} + +export type PropData = Record; +export type DocumentTemplate = {info?:PropData,dc?:PropData,cc:PropData,}; + +function normalizeValue(value: unknown): string { + if (value === null || value === undefined) { + return ""; + } + + if (typeof value === "string") { + return value; + } + + if ( + typeof value === "number" || + typeof value === "boolean" || + typeof value === "bigint" + ) { + return String(value); + } + + // รองรับ JSON ที่ส่ง array/object มา แต่ Content Control ต้องการ text + return JSON.stringify(value); +} + +/** + * นำ JSON data ไปแทนค่า Content Control ใน DOCX template + * คืนค่าเป็น Buffer ของ DOCX ที่สร้างเสร็จแล้ว + */ +export function templateContentControls( + templateBuffer: Buffer|Uint8Array, + data: PropData, +): Buffer { + const zip = new PizzaZip(templateBuffer); + const docXmlFile = zip.file("word/document.xml"); + + if (!docXmlFile) { + throw new Error("Invalid docx file: word/document.xml not found."); + } + + const parser = new DOMParser(); + const doc = parser.parseFromString(docXmlFile.asText(), "text/xml"); + const select = createSelector(); + + const sdtNodes = select( + "//w:sdt", + asXpathNode(doc), + ) as unknown as XmlElement[]; + + sdtNodes.forEach((sdt) => { + const tagAttr = select( + "./w:sdtPr/w:tag/@w:val", + asXpathNode(sdt), + true, + ) as unknown as XmlAttr | undefined; + + const tagName = tagAttr?.value; + + if (!tagName || !Object.prototype.hasOwnProperty.call(data, tagName)) { + return; + } + + const newValue = normalizeValue(data[tagName]); + + const sdtContent = select( + "./w:sdtContent", + asXpathNode(sdt), + true, + ) as unknown as XmlElement | undefined; + + if (!sdtContent) return; + + const { type: controlType, multiLine } = + detectSdtControlType(sdt, select); + + const structure = detectSdtStructure(sdtContent, select); + + if (controlType === "other") { + insertFallbackContent(sdtContent, select, newValue); + return; + } + + if (structure === "inline") { + const allowLineBreak = + controlType === "plain" ? multiLine : true; + + insertInlineContent( + doc, + sdtContent, + select, + newValue, + allowLineBreak, + ); + } else { + insertBlockContent( + doc, + sdtContent, + select, + newValue, + controlType === "rich", + ); + } + }); + + const serializer = new XMLSerializer(); + zip.file( + "word/document.xml", + serializer.serializeToString(doc), + ); + + return zip.generate({ + type: "nodebuffer", + compression: "DEFLATE", + }); +} + + +const CUSTOM_NS = + "http://schemas.openxmlformats.org/officeDocument/2006/custom-properties"; + +const VT_NS = + "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"; + +const REL_NS = + "http://schemas.openxmlformats.org/package/2006/relationships"; + +const CONTENT_TYPES_NS = + "http://schemas.openxmlformats.org/package/2006/content-types"; + +const XMLNS_NS = "http://www.w3.org/2000/xmlns/"; + +const CUSTOM_REL_TYPE = + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties"; + +const CUSTOM_CONTENT_TYPE = + "application/vnd.openxmlformats-officedocument.custom-properties+xml"; + +const CUSTOM_FMTID = + "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}"; + +const CUSTOM_PATH = "docProps/custom.xml"; + +type CustomPropertyValue = + | string + | number + | boolean + | Date + | null + | undefined; + +function parseXml(xml: string, fileName: string): XmlDocument { + const errors: string[] = []; + + const parser = new DOMParser(); + + const doc = parser.parseFromString( + xml, + "application/xml", + ) as XmlDocument; + + if (errors.length > 0 || !doc.documentElement) { + throw new Error( + `Invalid ${fileName}: ${errors.join("; ")}`, + ); + } + + return doc; +} + +function assertRoot( + doc: XmlDocument, + namespace: string, + localName: string, + fileName: string, +): XmlElement { + const root = doc.documentElement; + + if (root===null || + root.localName !== localName || + root.namespaceURI !== namespace + ) { + throw new Error(`Invalid root element in ${fileName}`); + } + + return root; +} + +function createCustomXml(): XmlDocument { + const xml = [ + '', + ``, + ].join(""); + + return parseXml(xml, CUSTOM_PATH); +} + +function createValueElement( + doc: XmlDocument, + value: unknown, +): XmlElement { + let tagName: string; + let text: string; + + if (value == null) { + // null และ undefined เก็บเป็น String ว่าง + tagName = "vt:lpwstr"; + text = ""; + } else if (typeof value === "string") { + tagName = "vt:lpwstr"; + text = value; + } else if (typeof value === "boolean") { + tagName = "vt:bool"; + text = value ? "true" : "false"; + } else if (typeof value === "number") { + if (!Number.isFinite(value)) { + throw new TypeError( + `Invalid numeric property value: ${value}`, + ); + } + + if (Number.isInteger(value)) { + if (!Number.isSafeInteger(value)) { + throw new TypeError( + `Integer property exceeds safe integer range: ${value}`, + ); + } + + tagName = "vt:i8"; + } else { + tagName = "vt:r8"; + } + + text = String(value); + } else if (value instanceof Date) { + if (Number.isNaN(value.getTime())) { + throw new TypeError("Invalid Date property value"); + } + + tagName = "vt:filetime"; + text = value.toISOString(); + } else { + throw new TypeError( + `Unsupported custom property type: ${ + Object.prototype.toString.call(value) + }`, + ); + } + + const element = doc.createElementNS(VT_NS, tagName); + + element.appendChild(doc.createTextNode(text)); + + return element; +} + +function ensureCustomRelationship(zip: PizzaZip): void { + const fileName = "_rels/.rels"; + const file = zip.file(fileName); + + if (!file) { + throw new Error(`DOCX is missing ${fileName}`); + } + + const doc = parseXml(file.asText(), fileName); + + const root = assertRoot( + doc, + REL_NS, + "Relationships", + fileName, + ); + + const relationships = Array.from( + root.getElementsByTagNameNS(REL_NS, "Relationship"), + ) as XmlElement[]; + + const existing = relationships.find( + (item) => item.getAttribute("Type") === CUSTOM_REL_TYPE, + ); + + if (existing) { + if (existing.getAttribute("Target") !== CUSTOM_PATH) { + throw new Error( + "Existing custom-properties relationship has an unexpected Target", + ); + } + + return; + } + + const ids = new Set( + relationships.map((item) => item.getAttribute("Id")), + ); + + let index = 1; + let relationshipId = `rId${index}`; + + while (ids.has(relationshipId)) { + index++; + relationshipId = `rId${index}`; + } + + const relationship = doc.createElementNS( + REL_NS, + "Relationship", + ); + + relationship.setAttribute("Id", relationshipId); + relationship.setAttribute("Type", CUSTOM_REL_TYPE); + relationship.setAttribute("Target", CUSTOM_PATH); + + root.appendChild(relationship); + + zip.file( + fileName, + new XMLSerializer().serializeToString(doc), + ); +} + +function ensureCustomContentType(zip: PizzaZip): void { + const fileName = "[Content_Types].xml"; + const file = zip.file(fileName); + + if (!file) { + throw new Error(`DOCX is missing ${fileName}`); + } + + const doc = parseXml(file.asText(), fileName); + + const root = assertRoot( + doc, + CONTENT_TYPES_NS, + "Types", + fileName, + ); + + const overrides = Array.from( + root.getElementsByTagNameNS( + CONTENT_TYPES_NS, + "Override", + ), + ) as XmlElement[]; + + const existing = overrides.find( + (item) => item.getAttribute("PartName") === "/docProps/custom.xml", + ); + + if (existing) { + existing.setAttribute("ContentType", CUSTOM_CONTENT_TYPE); + } else { + const override = doc.createElementNS( + CONTENT_TYPES_NS, + "Override", + ); + + override.setAttribute( + "PartName", + "/docProps/custom.xml", + ); + + override.setAttribute( + "ContentType", + CUSTOM_CONTENT_TYPE, + ); + + root.appendChild(override); + } + + zip.file( + fileName, + new XMLSerializer().serializeToString(doc), + ); +} +/** how to use +const templateBuffer = readFileSync("template.docx"); +const outputBuffer = setDocPropsCustom(templateBuffer, { + DocumentCode: "DOC-2026-001", + Department: "ฝ่ายเทคโนโลยีสารสนเทศ", + Revision: 2, + IsApproved: true, + PublishedAt: new Date(), +}); +writeFileSync("output.docx", outputBuffer); + */ +export function setCustomDocProps( + docxBuffer: Buffer | Uint8Array, + data: Record, +): Buffer { + const zip = new PizzaZip(docxBuffer); + + // อ่าน Custom Properties เดิม หรือสร้างใหม่หากไม่มี + const customFile = zip.file(CUSTOM_PATH); + + const doc = customFile + ? parseXml(customFile.asText(), CUSTOM_PATH) + : createCustomXml(); + + const root = assertRoot( + doc, + CUSTOM_NS, + "Properties", + CUSTOM_PATH, + ); + + // รับรองว่ามี Namespace vt สำหรับ Value Elements + root.setAttributeNS(XMLNS_NS, "xmlns:vt", VT_NS); + + const properties = Array.from( + root.getElementsByTagNameNS(CUSTOM_NS, "property"), + ) as XmlElement[]; + + const propertyMap = new Map(); + const usedPids = new Set(); + + for (const property of properties) { + const name = property.getAttribute("name"); + const pidText = property.getAttribute("pid"); + const pid = Number(pidText); + + if (!name) { + throw new Error( + "Custom property is missing its name attribute", + ); + } + + if (propertyMap.has(name)) { + throw new Error(`Duplicate custom property name: ${name}`); + } + + if (pidText === null || + !/^[1-9]\d*$/.test(pidText) || + !Number.isSafeInteger(pid) || + pid < 2 + ) { + throw new Error( + `Invalid custom property pid for "${name}": ${pidText}`, + ); + } + + if (usedPids.has(pid)) { + throw new Error(`Duplicate custom property pid: ${pid}`); + } + + propertyMap.set(name, property); + usedPids.add(pid); + } + + let nextPid = 2; + + for (const [name, value] of Object.entries(data)) { + if (!name.trim()) { + throw new Error("Custom property name must not be empty"); + } + + let property = propertyMap.get(name); + + if (!property) { + while (usedPids.has(nextPid)) { + nextPid++; + } + + property = doc.createElementNS( + CUSTOM_NS, + "property", + ); + + property.setAttribute("fmtid", CUSTOM_FMTID); + property.setAttribute("pid", String(nextPid)); + property.setAttribute("name", name); + + usedPids.add(nextPid); + nextPid++; + + root.appendChild(property); + propertyMap.set(name, property); + } + + // สร้างค่าใหม่ก่อนแก้ไข Property เดิม + const valueElement = createValueElement(doc, value); + + while (property.firstChild) { + property.removeChild(property.firstChild); + } + + property.appendChild(valueElement); + } + + // อัปเดตไฟล์ Custom Properties + zip.file( + CUSTOM_PATH, + new XMLSerializer().serializeToString(doc), + ); + + // ตรวจสอบส่วนประกอบที่เชื่อมโยงกับ Custom Properties + ensureCustomRelationship(zip); + ensureCustomContentType(zip); + + return Buffer.from( + zip.generate({ + type: "nodebuffer", + compression: "DEFLATE", + }), + ); +} + +/** + * ดึงค่า Custom Document Properties จาก DOCX Buffer + */ +export function getCustomDocProps( + docxBuffer: Buffer | Uint8Array, +): Record { + const zip = new PizzaZip(docxBuffer); + const customFile = zip.file(CUSTOM_PATH); + + if (!customFile) { + return {}; + } + + const doc = parseXml(customFile.asText(), CUSTOM_PATH); + const root = assertRoot( + doc, + CUSTOM_NS, + "Properties", + CUSTOM_PATH, + ); + + const properties = Array.from( + root.getElementsByTagNameNS(CUSTOM_NS, "property"), + ) as XmlElement[]; + + const result: Record = {}; + + for (const property of properties) { + const name = property.getAttribute("name"); + if (!name) continue; + + // หา child element แรกที่อยู่ใน VT_NS namespace + let valueNode: XmlElement | undefined; + for (let i = 0; i < property.childNodes.length; i++) { + const node = property.childNodes[i] as XmlElement; + if (node.nodeType === 1 && node.namespaceURI === VT_NS) { + valueNode = node; + break; + } + } + + if (!valueNode) { + result[name] = ""; + continue; + } + + const textContent = valueNode.textContent || ""; + + // แปลงค่ากลับตาม VT Data Type + switch (valueNode.localName) { + case "bool": + result[name] = textContent === "true" || textContent === "1"; + break; + + case "i1": + case "i2": + case "i4": + case "i8": + case "int": + case "ui1": + case "ui2": + case "ui4": + case "ui8": + case "uint": + case "r4": + case "r8": + case "decimal": { + const num = Number(textContent); + result[name] = Number.isNaN(num) ? textContent : num; + break; + } + + case "filetime": + case "date": { + const date = new Date(textContent); + result[name] = Number.isNaN(date.getTime()) ? textContent : date; + break; + } + + default: + // vt:lpwstr, vt:lpstr, vt:bstr และอื่นๆ + result[name] = textContent; + break; + } + } + + return result; +} + +const CORE_NS = + "http://schemas.openxmlformats.org/package/2006/metadata/core-properties"; +const DC_NS = "http://purl.org/dc/elements/1.1/"; +const DCTERMS_NS = "http://purl.org/dc/terms/"; +const APP_NS = + "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"; + +export interface DocPropsOptions { + standard?: boolean; + extended?: boolean; + custom?: boolean; +} + +export interface StandardDocProps { + title?: string; + subject?: string; + creator?: string; + keywords?: string; + description?: string; + lastModifiedBy?: string; + revision?: string; + created?: Date; + modified?: Date; + category?: string; + contentStatus?: string; +} + +export interface ExtendedDocProps { + template?: string; + totalTime?: number; + pages?: number; + words?: number; + characters?: number; + charactersWithSpaces?: number; + lines?: number; + paragraphs?: number; + application?: string; + appVersion?: string; + company?: string; + manager?: string; + docSecurity?: number; +} + +export interface DocPropsResult { + standard?: StandardDocProps; + extended?: ExtendedDocProps; + custom?: Record; +} + +/** + * ดึงข้อมูล Properties ของเอกสาร DOCX ตามประเภทที่เลือก + * example: + * const allProps = getDocProps(docxBuffer); + * const selectedProps = getDocProps(docxBuffer, { + * standard: true, + * custom: true, + * extended: false, + * }); + * console.log(allProps); + * console.log(selectedProps.standard?.title); + */ + +export function getDocProps( + docxBuffer: Buffer | Uint8Array, + options: DocPropsOptions = { standard: true, extended: true, custom: true }, +): DocPropsResult { + const zip = new PizzaZip(docxBuffer); + const result: DocPropsResult = {}; + + // 1. Standard / Core Properties (docProps/core.xml) + if (options.standard) { + const coreFile = zip.file("docProps/core.xml"); + if (coreFile) { + const doc = parseXml(coreFile.asText(), "docProps/core.xml"); + const getText = (ns: string, localName: string) => { + const els = doc.getElementsByTagNameNS(ns, localName); + return els.length > 0 ? els[0].textContent || "" : undefined; + }; + + const parseDate = (dateStr?: string) => { + if (!dateStr) return undefined; + const d = new Date(dateStr); + return Number.isNaN(d.getTime()) ? undefined : d; + }; + + result.standard = { + title: getText(DC_NS, "title"), + subject: getText(DC_NS, "subject"), + creator: getText(DC_NS, "creator"), + keywords: getText(CORE_NS, "keywords"), + description: getText(DC_NS, "description"), + lastModifiedBy: getText(CORE_NS, "lastModifiedBy"), + revision: getText(CORE_NS, "revision"), + created: parseDate(getText(DCTERMS_NS, "created")), + modified: parseDate(getText(DCTERMS_NS, "modified")), + category: getText(CORE_NS, "category"), + contentStatus: getText(CORE_NS, "contentStatus"), + }; + } + } + + // 2. Extended / Automatically Updated Properties (docProps/app.xml) + if (options.extended) { + const appFile = zip.file("docProps/app.xml"); + if (appFile) { + const doc = parseXml(appFile.asText(), "docProps/app.xml"); + const getText = (localName: string) => { + const els = doc.getElementsByTagNameNS(APP_NS, localName); + return els.length > 0 ? els[0].textContent || "" : undefined; + }; + + const parseNum = (val?: string) => { + if (!val) return undefined; + const n = Number(val); + return Number.isNaN(n) ? undefined : n; + }; + + result.extended = { + template: getText("Template"), + totalTime: parseNum(getText("TotalTime")), + pages: parseNum(getText("Pages")), + words: parseNum(getText("Words")), + characters: parseNum(getText("Characters")), + charactersWithSpaces: parseNum(getText("CharactersWithSpaces")), + lines: parseNum(getText("Lines")), + paragraphs: parseNum(getText("Paragraphs")), + application: getText("Application"), + appVersion: getText("AppVersion"), + company: getText("Company"), + manager: getText("Manager"), + docSecurity: parseNum(getText("DocSecurity")), + }; + } + } + + // 3. Custom Properties (docProps/custom.xml) + if (options.custom) { + result.custom = getCustomDocProps(docxBuffer); + } + + return result; +} + + +//**************************** dynamic content ******************************************* +// sdtLocked(ห้ามลบ CC), contentLocked(ลบได้ห้ามแก้), sdtContentLocked(ห้ามทุกอย่าง) +type LockType = 'none' | 'sdtLocked' | 'contentLocked' | 'sdtContentLocked'; + +/** + * ฟังก์ชันสร้าง Plain Text Content Control XML String ครอบด้วย || + * ระวังการเกิดหลายรัน จะทำให้ xml เสีย + */ +function p_cc(title:string,tag:string,text:string='',isMultiLine:boolean=true,lock:LockType='sdtLocked'): string { + const lockTag = lock=='none'?"":`` + const multiLineTag = isMultiLine ? '':'' + const content = escapeXml(text) + const xmlString =`${lockTag}${multiLineTag}${content}` + return `||${xmlString}||`; +} +function br(n: number) +{ + let text="" + for(let i=0;i||`; +} +/** + * Escapes XML Special Characters + */ +function escapeXml(unsafe: string): string { + return unsafe + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} +export const templateDynamicContents = async (template: Buffer, data: Record): Promise => { + return await createReport({ + template, data, cmdDelimiter: ['{{', '}}'], + additionalJsContext: { + bm,br, + p_cc, + } + }); +} diff --git a/lib/lib-utils.ts b/lib/lib-utils.ts new file mode 100644 index 0000000..f17a247 --- /dev/null +++ b/lib/lib-utils.ts @@ -0,0 +1,112 @@ + +import { createHmac, createPrivateKey, createPublicKey, timingSafeEqual, verify, sign } from 'node:crypto'; +/** + * คำนวณ HMAC-SHA256 จาก string หรือ JSON Object + */ +export function generateHmacSha256(data: string | object, secretKey: string): string { + const payload = typeof data === 'string' ? data : JSON.stringify(data); + return createHmac('sha256', secretKey) + .update(payload, 'utf8') + .digest('hex'); +} + +/** + * ตรวจสอบความถูกต้องของ HMAC (ใช้ timingSafeEqual เพื่อป้องกัน Timing Attacks) + */ +export function verifyHmacSha256(data: string | object, secretKey: string, expectedHmac: string): boolean { + const calculatedHmac = generateHmacSha256(data, secretKey); + const a = Buffer.from(calculatedHmac, 'hex'); + const b = Buffer.from(expectedHmac, 'hex'); + + if (a.length !== b.length) { + return false; + } + return timingSafeEqual(a, b); +} +// ========================================== +// ตัวอย่างการใช้งาน +// ========================================== +/* +const secret = 'my-super-secret-key'; +const jsonData = { + userId: 12345, + action: 'transfer', + amount: 500 +}; + +// 1. สร้าง HMAC +const hmacSignature = generateHmacSha256(jsonData, secret); +console.log('HMAC Signature (Hex):', hmacSignature); + +// 2. ตรวจสอบ HMAC +const isValid = verifyHmacSha256(jsonData, secret, hmacSignature); +console.log('Is Signature Valid?:', isValid); // true +*/ + + +/** + * ฟังก์ชันสำหรับลงนามดิจิทัล (Sign) ข้อมูลด้วย Ed25519 Private Key (Hex) + * @param message ข้อความดิบที่ต้องการเซ็น (string หรือ Buffer) + * @param privateKeyHex คีย์ลับในรูปแบบ Hex String (ความยาว 64 ตัวอักษร) + * @returns ลายเซ็น (Signature) ในรูปแบบ Hex String (ความยาว 128 ตัวอักษร) + */ +export function signDataEd25519(message: string | Buffer, privateKeyHex: string): string { + const rawKeyBuffer = Buffer.from(privateKeyHex, 'hex'); + // ตัดเอาเฉพาะ 32 bytes (Seed) กรณีที่ Hex ยาว 64 bytes (ที่ต่อกับ Public Key มา) + const seed = rawKeyBuffer.length === 64 ? rawKeyBuffer.subarray(0, 32) : rawKeyBuffer; + // PKCS#8 Header สำหรับ Ed25519 Private Key + const pkcs8Header = Buffer.from('302e020100300506032b657004220420', 'hex'); + const pkcs8Buffer = Buffer.concat([pkcs8Header, seed]); + + const privateKey = createPrivateKey({ + key: pkcs8Buffer, + format: 'der', + type: 'pkcs8', + }); + + const dataBuffer = typeof message === 'string' ? Buffer.from(message, 'utf-8') : message; + const signatureBuffer = sign(null, dataBuffer, privateKey); + return signatureBuffer.toString('hex'); +} + +/** + * ฟังก์ชันสำหรับตรวจสอบลายเซ็น (Verify) ด้วย Ed25519 Public Key (Hex) + * @param message ข้อความดิบที่ใช้ในการเซ็น (string หรือ Buffer) + * @param signatureHex ลายเซ็นในรูปแบบ Hex String + * @param publicKeyHex คีย์สาธารณะในรูปแบบ Hex String (ความยาว 64 ตัวอักษร) + * @returns ผลลัพธ์ความถูกต้อง (true = ผ่าน, false = ไม่ผ่าน) + */ +export function verifyDataEd25519(message: string | Buffer, signatureHex: string, publicKeyHex: string): boolean { + try { + const rawPublicKey = Buffer.from(publicKeyHex, 'hex'); + + // SPKI Header สำหรับ Ed25519 Public Key (32 bytes) + const spkiHeader = Buffer.from('302a300506032b6570032100', 'hex'); + const spkiBuffer = Buffer.concat([spkiHeader, rawPublicKey]); + + const publicKey = createPublicKey({ + key: spkiBuffer, + format: 'der', + type: 'spki', + }); + + const dataBuffer = typeof message === 'string' ? Buffer.from(message, 'utf-8') : message; + const signatureBuffer = Buffer.from(signatureHex, 'hex'); + + return verify(null, dataBuffer, publicKey, signatureBuffer); + } catch (error) { + return false; + } +} + +/** + * ฟังก์ชันสำหรับเรียงคีย์ของ Object ตามตัวอักษรเพื่อไม่ให้โครงสร้าง JSON เพี้ยน + */ +export function sortObjectKeys(obj: Record): Record { + return Object.keys(obj) + .sort() + .reduce((result: Record, key: string) => { + result[key] = obj[key]; + return result; + }, {}); +} \ No newline at end of file diff --git a/lib/openapi.ts b/lib/openapi.ts index 4cd07d3..967b6bf 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -92,6 +92,20 @@ export const openApiSpec = { format: "binary", description: "ไฟล์ .docx ที่มี Content Control", }, + secret: { + type: "string", + description: + "ค่า secret สำหรับ validate HMAC-SHA256 กับข้อมูลใน custom property" , + example: + "super_secret", + }, + public_key: { + type: "string", + description: + "Public key ในรูปแบบ HEX
openssl genpkey -algorithm ED25519 -out private.pem
openssl pkey -in private.pem -outform DER | tail -c 32 | xxd -p -c 32 > private.hex
openssl pkey -in private.pem -pubout -outform DER | tail -c 32 | xxd -p -c 32 > public.hex", + example: + "66b09b4a1b66271d5728c592c2409657e1c070a7bf371bf9f6ed457855270546", + }, }, }, }, @@ -140,9 +154,23 @@ export const openApiSpec = { dataJson: { type: "string", description: - "JSON เป็น string ตรง ๆ (ทางเลือกแทนการแนบไฟล์ data)", + "JSON เป็น string ตรง ๆ (ทางเลือกแทนการแนบไฟล์ data)
{\"info:\"{\"ticket_data\":\"JSON String\"},\"dc:\"{..},\"dc:\"{\"blackyear\": \"๙๐/๒๕๖๙\",...}}", example: - '{"notname":"คำสั่งเรียก ให้ทำคำให้การ","blackno":"คดีหมายเลขดำที่","blackyear":"๑๐/๒๕๖๙"}', + '', + }, + secret: { + type: "string", + description: + "ค่า secret สำหรับทดสอบการทำ HMAC-SHA256", + example: + 'super_secret', + }, + private_key: { + type: "string", + description: + "Private key ในรูปแบบ HEX
openssl genpkey -algorithm ED25519 -out private.pem
openssl pkey -in private.pem -outform DER | tail -c 32 | xxd -p -c 32 > private.hex
openssl pkey -in private.pem -pubout -outform DER | tail -c 32 | xxd -p -c 32 > public.hex", + example: + '8f2077b57b39399b6a7828f35da03a63ade8e3a36a6bf437b091a8bdb3d18ee1', }, }, }, diff --git a/middleware.ts b/middleware.ts index f05c44b..a67185a 100644 --- a/middleware.ts +++ b/middleware.ts @@ -37,6 +37,6 @@ export function middleware(req: NextRequest) { // ยังถูกคุมอยู่ดีเพราะ 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).+)", + "/((?!note/login|api/note|api/template|api/documents|api/swagger|tmpl|wordaddin|note/uploads|note/samples|note/swagger-ui|note/pdf\\.worker\\.min\\.mjs|_next|fonts|favicon\\.ico).+)", ], }; diff --git a/package-lock.json b/package-lock.json index 748a8c7..466a9f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,8 +11,10 @@ "dependencies": { "@pdf-lib/fontkit": "^1.1.1", "@xmldom/xmldom": "^0.9.12", + "docx-templates": "^4.15.0", "drizzle-orm": "^0.45.2", "lucide-react": "^1.42.0", + "minio": "^8.0.7", "next": "^14.2.35", "pdf-lib": "^1.17.1", "pdfjs-dist": "^4.10.38", @@ -26,7 +28,7 @@ "xpath": "^0.0.34" }, "devDependencies": { - "@types/node": "^26.5.0", + "@types/node": "^26.6.5", "@types/office-js": "^1.0.612", "@types/pngjs": "^6.0.5", "@types/react": "^18.3.31", @@ -61,7 +63,6 @@ "integrity": "sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", @@ -80,7 +81,6 @@ "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" } @@ -90,8 +90,7 @@ "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@apidevtools/swagger-parser": { "version": "10.1.1", @@ -99,7 +98,6 @@ "integrity": "sha512-u/kozRnsPO/x8QtKYJOqoGtC4kH6yg1lfYkB9Au0WhYB0FNLpyFusttQtvhlwjtG3rOwiRz4D8DnnXa8iEpIKA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@apidevtools/json-schema-ref-parser": "11.7.2", "@apidevtools/openapi-schemas": "^2.1.0", @@ -170,7 +168,6 @@ "integrity": "sha512-fNAjWnA/nZ2jz31kxR/AqRaUT8ewHBw/WuBIosK0moMy1C9e5ValbDfFdIxJzVOOYaYkV/b2F1S4H/aHiqfVQg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -184,7 +181,6 @@ "integrity": "sha512-Vu011o3/bikQNwtjouwmUJud+Z6Brcjij2D0omPWClRGg8i5gBfOYSpDkFGkHbhGlaky4fgvfkxD0uHGq34uYA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@azure/abort-controller": "^1.0.0", "@azure/core-auth": "^1.3.0", @@ -204,7 +200,6 @@ "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.2.0" }, @@ -218,7 +213,6 @@ "integrity": "sha512-JbZtIqfEulsIA0rC3zM7jfF4KkOnye9aKcaO/jJqxJRm/gM6lAjEv7sL4njW8D+35l50P1f+UuH5OqN+UKJqNA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@azure/abort-controller": "^1.0.0", "@azure/core-auth": "^1.3.0", @@ -238,7 +232,6 @@ "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.2.0" }, @@ -252,7 +245,6 @@ "integrity": "sha512-J2jmTPv8ZraSHDTz9l2Bx8gNL3ktfDDWo2mxWfzarn64O9Fjhb+l85YWyubGy2xUdeGuZPKzvQLltGv8bSu8eQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@azure/abort-controller": "^1.0.0", "@azure/core-auth": "^1.3.0", @@ -272,7 +264,6 @@ "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.2.0" }, @@ -286,7 +277,6 @@ "integrity": "sha512-DR/H2nfKtHNqfpuJ4L/B4irX1nX77QizulmfrxcLNZmkfinm0SdZpypXSvzaI5rHZSXfhXNUfBvMfi+jMkjWtw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", @@ -306,7 +296,6 @@ "integrity": "sha512-IUZydyTUkDnYdstOW9pFOOUQlBjAepK5teihDE3x6yxsPJs/hsAaaYpeGxdxrgtOiJbBKSjKW7MDk7AEhb4LRg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-util": "^1.13.0", @@ -342,7 +331,6 @@ "integrity": "sha512-BoSmXPx2er1Ai+wKlDvj29jIQespCNBwEmKyZVHO2kEFsWbGjAjwMCGzug3DJM5/QYIV3vej0S1zcU5bq9fa8w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@azure/abort-controller": "^2.1.2" }, @@ -360,7 +348,6 @@ "integrity": "sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-util": "^1.2.0", @@ -377,7 +364,6 @@ "integrity": "sha512-7GEAoIsaoBr6KELNRb8nypowCqvk8dnCHFCYg4XD4lOQGY2GqjQg5IhkRjyBFRO18CGSMq05PaNqSOE9GQro3g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -391,7 +377,6 @@ "integrity": "sha512-/shnJ+ooO8WPxDhPEeI/2oRQuubn16gZ6CvlbpWbEswZfzwI9tI/sMAHmF3x1LuQ9yZYXfLW3TjzGMLEC5blKg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=22.0.0" } @@ -422,7 +407,6 @@ "integrity": "sha512-eGwxD0AtncrxeBM4tG8R55Pc3rdX1hNW2WibJAgYpCVA6E93mvvVH+LcssoVjOBrSKWS55yEIHsk0X8ctHmfOQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -436,7 +420,6 @@ "integrity": "sha512-9n2pWK61veAuN0V20t9lOuoV4CFMdyAZ1ygZzvBGk/pBBJRib/PjL9PLXa/aI2CcPpyHfqVsxxqLCYl6uZlfDw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@azure/abort-controller": "^2.1.2", "@typespec/ts-http-runtime": "^0.3.0", @@ -452,7 +435,6 @@ "integrity": "sha512-e7lX/dk//F6Qf7BB6PTY4+p2yuOQtyOeHGyapYHNwqSp2OnYpwQt49A/Nin2XmKBQ69pwagR4k/lQBq8lbHQkA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-xml-parser": "^5.5.9", "tslib": "^2.8.1" @@ -467,7 +449,6 @@ "integrity": "sha512-8utCLdTZn14CiAe0HW4JbtzUMsc0mdDwnjUvStFMrR4IxQP8S2eL9zVAdplpfORVJGYmidZElpeXRYjJQ4UyUw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-auth": "^1.9.0", @@ -492,7 +473,6 @@ "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -506,7 +486,6 @@ "integrity": "sha512-++Zlftm0kVLPmzC06t6epuWmcRMDbI4z5P3NNX979WA/k23+NtSOynEGzsVfZwguKw2mi5umVgnBlJQMwRz4Pg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "default-browser": "^5.5.1", "define-lazy-prop": "^3.0.0", @@ -528,7 +507,6 @@ "integrity": "sha512-rbAE25KUfjU/s3XHUdJgceoCP5dEOpMx85J04kF+QMdta73XkuG9JGHHinch+XIoKpBdqljin+KqURpJriSzLA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" @@ -543,7 +521,6 @@ "integrity": "sha512-NDW2/bz2Pz71EJAlhSw+jg1kpRIWtrzj0gCzcHuG1u7iLvSOHpUc5lICkDA9x0bbeityxTneiunSBluvS+/y4w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@azure/msal-common": "16.14.2" }, @@ -557,7 +534,6 @@ "integrity": "sha512-HOBR0Gfo39TMrkxNG8k6cfjRkrRjXZc3JQs3ebMWQc9S4cihuknMYra3sgmzbZB752oQBqBN9+WF7c826p2vIQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.8.0" } @@ -568,7 +544,6 @@ "integrity": "sha512-ixSO1Y/kCVRthRs+hSx/5qkwaunX1/RAePhlMN0wIpIQ4WEZ6AREGGnGd1AP0qspHVsAwzWQKGubpjh50JyJIQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@azure/msal-common": "16.14.1", "jsonwebtoken": "^9.0.0" @@ -584,7 +559,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@azure/msal-common": "15.17.0", "@azure/msal-node-runtime": "^0.20.0", @@ -600,7 +574,6 @@ "integrity": "sha512-VQ5/gTLFADkwue+FohVuCqlzFPUq4xSrX8jeZe+iwZuY6moliNC8xt86qPVNYdtbQfELDf2Nu6LI+demFPHGgw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.8.0" } @@ -610,8 +583,7 @@ "resolved": "https://registry.npmjs.org/@azure/msal-node-runtime/-/msal-node-runtime-0.20.6.tgz", "integrity": "sha512-89Al7l5c8sOEA954d35WZOg1sCa2WKfgf7KyTbN9vIkKsoZrXdJ5+CPjy8+tQqWcsJTAMbQD4OWr7V09/xN8KA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@azure/msal-node/node_modules/@azure/msal-common": { "version": "16.14.1", @@ -619,7 +591,6 @@ "integrity": "sha512-Or6xhPNyi4zHW25158yxBoyxuCqNSPa5YBVqfF1J5Ks4MJWBo/USXdp05DQIPu1Zli00YZu6t0+h6KvHJealxQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.8.0" } @@ -630,7 +601,6 @@ "integrity": "sha512-cXDTCAMwDx8Ng9yUe82kOOd2kPnZ8AQqe4FkLMcpaQ3Mvr3/PXVdvRhyP0FtY5/U5Oh6JGk3EcEibaFOymRLFw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", @@ -658,7 +628,6 @@ "integrity": "sha512-bttzuhQiCIwrkzjPDA+AtAR7dg19L/CC6ztcqJ5LfvWpXuys9mHp0UQ0udYnoUvv9SCT9KTR5kqFvFr0e6k0lQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", @@ -680,7 +649,6 @@ "integrity": "sha512-NpugTzbKgGrNe45Ui32cN8vCFFMxVtP/9rtfwv0nP5FRDAjvYBxJcLVpYA9mr1jxP6cnIT7x9LUzuDTbWYYNRA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -705,7 +673,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "engines": { "node": ">=0.1.90" } @@ -798,6 +765,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=20.19.0" }, @@ -846,6 +814,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=20.19.0" } @@ -1758,8 +1727,7 @@ "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.3.0.tgz", "integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@feathersjs/hooks": { "version": "0.6.5", @@ -1767,7 +1735,6 @@ "integrity": "sha512-WtcEoG/imdHRvC3vofGi/OcgH+cjHHhO0AfEeTlsnrKLjVKKBXV6aoIrB2nHZPpE7iW5sA7AZMR6bPD8ytxN+w==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 10" } @@ -1778,7 +1745,6 @@ "integrity": "sha512-MgP/I06GdvsilDspMk/qmRzpk8LOUStP6jFAbSRi40W09DhoyUn+Mj7eyKICPqeYKybOjVFLVIAQzvu1bfIa7Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=20" }, @@ -1961,7 +1927,6 @@ "integrity": "sha512-w3PMZH5rahrukn8/I7P9Ihil+twgLTUHDZtJlJyBbUKyPaOSSQjLZkb0PpncVhin1gCaMgOFXy6iNPgcZUoo2w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@inquirer/type": "^1.1.6", "@types/mute-stream": "^0.0.4", @@ -1988,7 +1953,6 @@ "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -1998,8 +1962,7 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@inquirer/editor": { "version": "4.2.23", @@ -2702,7 +2665,6 @@ "integrity": "sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "mute-stream": "^1.0.0" }, @@ -2716,7 +2678,6 @@ "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "minipass": "^7.0.4" }, @@ -2751,7 +2712,6 @@ "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -2780,8 +2740,7 @@ "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@microsoft/app-manifest": { "version": "1.1.3", @@ -2805,7 +2764,6 @@ "integrity": "sha512-OayhehwI+CnO0Wr53e29ZJZWGsNA5yVG7r54qmZSLc5HxA5Cozk4hP7EbYDCXkxh4NbQoT1dhTzC8bkRo+wWXw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "buffer": "^5.2.1", "debug": "^4.1.1", @@ -2818,7 +2776,6 @@ "integrity": "sha512-wGuFEzvRiWZmDxQMGKEjOKhEIVnLiG6vRUuM9Hwqxpe/kbiyA2WiUyEVpniNPaaw8gDHTf9zJHnPNNj0JiL5mA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@microsoft/dev-tunnels-contracts": ">1.1.8", "axios": "^1.6.2", @@ -2833,7 +2790,6 @@ "integrity": "sha512-YlAI2jC0grK2p2fvxDV9Hin5EtRXL6yOMhLKqtjRVlH8Q0+hI9UWFl7RhLWSZjsbDyl+0LIlI2HjlcbsUhXGsQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "adm-zip": "^0.5.17", "original-fs": "^1.2.0", @@ -2847,7 +2803,6 @@ "integrity": "sha512-ufJnssQGbxzLNS1Ho9bCtX4rQKCCvoVuDLHoJyc3F9dOGDB4BkWs2Ci0kv53lqocAEQ/Cbi+I2XCsNYGqVYqng==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12.0" } @@ -2862,7 +2817,6 @@ "https://github.com/sponsors/ctavan" ], "license": "MIT", - "peer": true, "bin": { "uuid": "dist-node/bin/uuid" } @@ -2873,7 +2827,6 @@ "integrity": "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=14.0.0" } @@ -2884,7 +2837,6 @@ "integrity": "sha512-mih5trnmvIKpjLgqzsoxqiWFvG5eMeETPCAdtFvDaXyferWzFt9UrdARfRoYboPhfFjYRYw8EoF3EA4yFVWCsQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@apidevtools/swagger-parser": "^10.1.1", "@microsoft/app-manifest": "1.1.3", @@ -2903,7 +2855,6 @@ "integrity": "sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -2968,7 +2919,6 @@ "integrity": "sha512-cSVQHqK7oYG+KvSR8y+nvOiXcLNU/t/8ihDyXbdJtZCnRft7ZtHpnt8yxL96xdFe2UUHLSipHbZSJBotQrIB+g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@azure/core-auth": "^1.4.0", "@microsoft/app-manifest": "1.1.3", @@ -2984,7 +2934,6 @@ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": "*" } @@ -2995,7 +2944,6 @@ "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.3", @@ -3015,7 +2963,6 @@ "integrity": "sha512-156FXuXXl8DZ6aPU/GR0qFpo7JIHK9sBTqhUBOiO0ljRl+asP/S9nREQG4Vg4lPWL1UCCd6WeTpTcOPyyYQdJQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@apidevtools/swagger-parser": "^10.1.0", "@azure/arm-appservice": "^13.0.0", @@ -3086,7 +3033,6 @@ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -3100,7 +3046,6 @@ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -3111,7 +3056,6 @@ "integrity": "sha512-2DdE+SJDtzLEEWzY1ZjY7Q+VcPhcV1KisD3zI4u0XZyktsjHum1mwbMI+JaulUBi2OZk+KJAi2uPXzxichPkdw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@hono/node-server": "^1.19.9 || ^2.0.5", "ajv": "^8.17.1", @@ -3153,7 +3097,6 @@ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" @@ -3168,7 +3111,6 @@ "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "bytes": "^3.1.2", "content-type": "^2.0.0", @@ -3194,7 +3136,6 @@ "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -3209,7 +3150,6 @@ "integrity": "sha512-e+H0ZXHSWYrENhQzw1LPuP4oF5MzVKmDU6d3hxlvaPEYLLg62MxtQNPRx4SYSuYJSBUgnQIG4HIN2tEtNv7Dog==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -3224,7 +3164,6 @@ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6.6.0" } @@ -3235,7 +3174,6 @@ "integrity": "sha512-qCqy1xSoaugxOD4IVHb1ZWdkvCF2W3aE2okKouWj8VwJ+ztskSiXoBnCSJqfKT+cVPJhuJqe9YLbY425+Zh00g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.3.0", @@ -3280,7 +3218,6 @@ "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -3295,7 +3232,6 @@ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", @@ -3318,7 +3254,6 @@ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -3329,7 +3264,6 @@ "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" }, @@ -3344,7 +3278,6 @@ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -3358,7 +3291,6 @@ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -3369,7 +3301,6 @@ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "mime-db": "^1.54.0" }, @@ -3387,7 +3318,6 @@ "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "content-type": "^2.1.0" }, @@ -3405,7 +3335,6 @@ "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -3420,7 +3349,6 @@ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", @@ -3448,7 +3376,6 @@ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", @@ -3469,7 +3396,6 @@ "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "content-type": "^2.0.0", "media-typer": "^1.1.0", @@ -3489,7 +3415,6 @@ "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -3902,15 +3827,13 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-3.1.0.tgz", "integrity": "sha512-LsS/DjHr+uDM647Gru/cA8+J3a3HfhttwCKLyuoyN7yXTFCxKBtSgMQBvrW9yNPa2/zDRUNuGPaH5QUFoa4arQ==", - "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/nodable" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", @@ -3956,7 +3879,6 @@ "integrity": "sha512-Efcc+iF0j3Bf67YjEqIqWXbX5XddXoK/Mw4K1/JuXwRCZ8N16VR7iT23nlCc9XrveFVh/E5Rqs2StT0V8v9LdA==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/oxc-project" } @@ -4001,7 +3923,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4019,7 +3940,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4037,7 +3957,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4055,7 +3974,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4073,7 +3991,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4091,7 +4008,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4109,7 +4025,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4127,7 +4042,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4145,7 +4059,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4163,7 +4076,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4181,7 +4093,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4199,7 +4110,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4217,7 +4127,6 @@ "os": [ "openharmony" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4235,7 +4144,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4253,7 +4161,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4263,8 +4170,7 @@ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@scarf/scarf": { "version": "1.4.0", @@ -4330,8 +4236,7 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/jsonfile": { "version": "6.1.4", @@ -4349,15 +4254,14 @@ "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/node": { - "version": "26.5.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.5.0.tgz", - "integrity": "sha512-dVSGpriSoCgz8WnDNTuSSuSv1PC/ALXihO4ulRZt7Md8k9mlbdin3lGOcDE8SnWOgf513ByWlXd7BK4azmyg/A==", + "version": "26.6.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.5.tgz", + "integrity": "sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg==", "dev": true, "license": "MIT", "dependencies": { @@ -4394,6 +4298,7 @@ "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -4425,8 +4330,7 @@ "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@typespec/ts-http-runtime": { "version": "0.3.9", @@ -4434,7 +4338,6 @@ "integrity": "sha512-edSdeAqkdxBVzA1yL1LrLCml1YjyCVvPMtMqJpbF+6K609tHe8V6sQUzFQSGcYNhcuhOceZtjvN32+mpIth30A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", @@ -4497,7 +4400,6 @@ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" @@ -4512,7 +4414,6 @@ "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4526,7 +4427,6 @@ "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 10.0.0" } @@ -4547,7 +4447,6 @@ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 14" } @@ -4608,7 +4507,6 @@ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "type-fest": "^0.21.3" }, @@ -4670,15 +4568,13 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.1.tgz", "integrity": "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==", - "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/apache-arrow": { "version": "21.2.0", @@ -4686,7 +4582,6 @@ "integrity": "sha512-Hxe6Agq26gQOM954qpzYSllJBPJl+e16U5CkfuMUhLrNba+5nKkttIVlflaovN6oaTratqMGAO8H5u/aNhmHWQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@types/node": "^25.2.0", "flatbuffers": "^25.1.24", @@ -4703,7 +4598,6 @@ "integrity": "sha512-b4e2xxj/yMeT2hNlD6x7zNFiQK2Dlmkb9CQ6v3/1G2RezKN4YS2lcEv8lzGm1mhs4f2lM4yNAMI/zUDBZBBt7w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } @@ -4713,8 +4607,7 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/applicationinsights": { "version": "1.8.10", @@ -4722,7 +4615,6 @@ "integrity": "sha512-ZLDA7mShh4mP2Z/HlFolmvhBPX1LfnbIWXrselyYVA7EKjHhri1fZzpu2EiWAmfbRxNBY6fRjoPJWbx5giKy4A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cls-hooked": "^4.2.2", "continuation-local-storage": "^3.2.1", @@ -4742,24 +4634,21 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, - "license": "Python-2.0", - "peer": true + "license": "Python-2.0" }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/array-timsort": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/assertion-error": { "version": "2.0.1", @@ -4771,13 +4660,18 @@ "node": ">=12" } }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, "node_modules/async-hook-jl": { "version": "1.7.6", "resolved": "https://registry.npmjs.org/async-hook-jl/-/async-hook-jl-1.7.6.tgz", "integrity": "sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "stack-chain": "^1.3.7" }, @@ -4791,7 +4685,6 @@ "integrity": "sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "semver": "^5.3.0", "shimmer": "^1.1.0" @@ -4806,7 +4699,6 @@ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver" } @@ -4817,7 +4709,6 @@ "integrity": "sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.3.1" } @@ -4827,8 +4718,7 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/at-least-node": { "version": "1.0.0", @@ -4836,7 +4726,6 @@ "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", "dev": true, "license": "ISC", - "peer": true, "engines": { "node": ">= 4.0.0" } @@ -4884,7 +4773,6 @@ "integrity": "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.6", @@ -4898,7 +4786,6 @@ "integrity": "sha512-8PJDLJv7qTTMMwdnbMvrLYuvB47M81wRtxQmEdV5w4rgbTXTt+vtPkXwajOfOdSyv/wZICJOC+/UhXH4aQ/R+w==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@babel/runtime": "^7.15.4", "is-retry-allowed": "^2.2.0" @@ -4910,7 +4797,6 @@ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "debug": "4" }, @@ -4924,7 +4810,6 @@ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -4939,7 +4824,6 @@ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": "18 || 20 || >=22" } @@ -4963,8 +4847,7 @@ "url": "https://feross.org/support" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/baseline-browser-mapping": { "version": "2.11.21", @@ -5008,20 +4891,27 @@ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, + "node_modules/block-stream2": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/block-stream2/-/block-stream2-2.1.0.tgz", + "integrity": "sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==", + "license": "MIT", + "dependencies": { + "readable-stream": "^3.4.0" + } + }, "node_modules/body-parser": { "version": "1.20.8", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.8.tgz", "integrity": "sha512-JNcyFQ64OiijEkPzUBTCe+hyPXUD/3LEldGQ6iF5LR1w00mx9o7xtDWHXBY2iItjdCFGoilOLNQbH943ut7pHA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", @@ -5047,7 +4937,6 @@ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -5058,7 +4947,6 @@ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -5071,8 +4959,7 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/body-parser/node_modules/raw-body": { "version": "2.5.3", @@ -5080,7 +4967,6 @@ "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", @@ -5097,7 +4983,6 @@ "integrity": "sha512-DkVaaQHymRhpYEYo9x1oo7Q7B0Y6KJUsjm3c9eTyFDby4MHLBTwZ6ZDWBel5zrYxj1WsZgC5oLpiz+93MluXeA==", "dev": true, "license": "ISC", - "peer": true, "engines": { "node": ">=20.19.0" }, @@ -5112,7 +4997,6 @@ "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^4.0.2" }, @@ -5133,6 +5017,12 @@ "node": ">=8" } }, + "node_modules/browser-or-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-2.1.1.tgz", + "integrity": "sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==", + "license": "MIT" + }, "node_modules/browserslist": { "version": "4.28.9", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.9.tgz", @@ -5153,6 +5043,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.11.20", "caniuse-lite": "^1.0.30001810", @@ -5187,19 +5078,26 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "dev": true, - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/buffer-from": { "version": "1.1.2", @@ -5214,7 +5112,6 @@ "integrity": "sha512-DdH81/zPLVS11EUgWq3tEu/xn+EzljlMYooDNdzWEnFha3R3NBMpMV1UqYIpjYHV/SgpFKMIX1Oh7o06SjM/oA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "run-applescript": "^7.0.0" }, @@ -5242,7 +5139,6 @@ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -5253,7 +5149,6 @@ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -5268,7 +5163,6 @@ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" @@ -5285,8 +5179,7 @@ "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/camelcase-css": { "version": "2.0.1", @@ -5334,7 +5227,6 @@ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -5359,7 +5251,6 @@ "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "engines": { "node": "*" } @@ -5370,7 +5261,6 @@ "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "get-func-name": "^2.0.2" }, @@ -5422,7 +5312,6 @@ "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", "dev": true, "license": "BlueOak-1.0.0", - "peer": true, "engines": { "node": ">=18" } @@ -5433,7 +5322,6 @@ "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" }, @@ -5447,7 +5335,6 @@ "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "string-width": "^4.2.0" }, @@ -5480,7 +5367,6 @@ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -5496,7 +5382,6 @@ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -5515,7 +5400,6 @@ "integrity": "sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "async-hook-jl": "^1.7.6", "emitter-listener": "^1.0.1", @@ -5531,7 +5415,6 @@ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver" } @@ -5562,7 +5445,6 @@ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -5586,7 +5468,6 @@ "integrity": "sha512-R2rze/hDX30uul4NZoIZ76ImSJLFxn/1/ZxtKC1L77y2X1k+yYu1joKbAtMA2Fg3hZrTOiw0I5mwVMo0cf250w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "array-timsort": "^1.0.3", "esprima": "^4.0.1" @@ -5601,7 +5482,6 @@ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "safe-buffer": "5.2.1" }, @@ -5615,7 +5495,6 @@ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -5626,7 +5505,6 @@ "integrity": "sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "async-listener": "^0.6.0", "emitter-listener": "^1.1.1" @@ -5638,7 +5516,6 @@ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -5648,8 +5525,13 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" }, "node_modules/cors": { "version": "2.8.6", @@ -5657,7 +5539,6 @@ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "object-assign": "^4", "vary": "^1" @@ -5676,7 +5557,6 @@ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -5692,7 +5572,6 @@ "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "engines": { "node": "*" } @@ -5702,8 +5581,7 @@ "resolved": "https://registry.npmjs.org/cryptr/-/cryptr-6.4.0.tgz", "integrity": "sha512-9jpMU9HMt1vhMUqNO+MPuGEpbh/f7HHZdxrd6L2DMwTuYGyt9pgUJfQyTS1Ei4/sn7qPM4FkjxUoiW79k0x8sA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/css-select": { "version": "7.0.0", @@ -5711,7 +5589,6 @@ "integrity": "sha512-snmjEVXy+1LnwXdxhYvTMj1d9tOh4HxkA1YmoayVBeeyR2C14Pum7fcxJIm4SswYspVy866eYNwlH6xC3/VH5g==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "boolbase": "^2.0.0", "css-what": "^8.0.0", @@ -5747,7 +5624,6 @@ "integrity": "sha512-DH0Bqq3DNp5tdOReuNyAA+Ev4Y2GS5FMbZpeTLP6C4CDi0h5nL0BmUPChXw3o/qbHLDWHl49sbNqQVY7bMSDdw==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=20.19.0" }, @@ -5774,8 +5650,7 @@ "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/csstype": { "version": "3.2.3", @@ -5814,7 +5689,6 @@ "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4.0" } @@ -5825,7 +5699,6 @@ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ms": "^2.1.3" }, @@ -5845,13 +5718,21 @@ "dev": true, "license": "MIT" }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "mimic-response": "^3.1.0" }, @@ -5868,8 +5749,7 @@ "integrity": "sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==", "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/deep-eql": { "version": "4.1.4", @@ -5877,7 +5757,6 @@ "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "type-detect": "^4.0.0" }, @@ -5891,7 +5770,6 @@ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4.0.0" } @@ -5902,7 +5780,6 @@ "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" @@ -5920,7 +5797,6 @@ "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -5934,7 +5810,6 @@ "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -5945,7 +5820,6 @@ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.4.0" } @@ -5956,7 +5830,6 @@ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -5967,7 +5840,6 @@ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" @@ -5979,7 +5851,6 @@ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=8" } @@ -5990,7 +5861,6 @@ "integrity": "sha512-CmnVc+Hek2egPx1PeTFVta2W78xy2K/9Rkf6cC4T59S50tVnzKj+tnx5mmx5lwvCkujZ4uRrpRSuV+IVs3f90Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "address": "^1.0.1", "debug": "4" @@ -6009,7 +5879,6 @@ "integrity": "sha512-6eb9YRrimz8oTr5+JDzGmSYnXy5V7YnK5y/hd8AUDK1MssHjQKm9LlD6NSrHx4vMDF3+e/spI2hmWTviElgWZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "semver": "^5.3.0" } @@ -6020,7 +5889,6 @@ "integrity": "sha512-l126t01d2ZS9EreskvEtZPrcgstuvH3rbKy82oUhUrVmBaGx4hO9wECdl3cvZbKDYjMF3QJDB5z5dL9yWAjvZQ==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "diagnostic-channel": "*" } @@ -6031,7 +5899,6 @@ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver" } @@ -6050,13 +5917,31 @@ "dev": true, "license": "MIT" }, + "node_modules/docx-templates": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/docx-templates/-/docx-templates-4.15.0.tgz", + "integrity": "sha512-H0JEDFVCF5j64Do9fqHVWwF86MOWUnHhQ2dfGWy7C2o9yPwtbCT1pXpotE6165Px07fia1Ted1Xh1fj1UZ9u1g==", + "license": "MIT", + "dependencies": { + "jszip": "^3.10.1", + "sax": "1.3.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/docx-templates/node_modules/sax": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==", + "license": "ISC" + }, "node_modules/dom-serializer": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-3.1.1.tgz", "integrity": "sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "domelementtype": "^3.0.0", "domhandler": "^6.0.0", @@ -6082,7 +5967,6 @@ } ], "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=20.19.0" } @@ -6093,7 +5977,6 @@ "integrity": "sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "domelementtype": "^3.0.0" }, @@ -6111,7 +5994,6 @@ "integrity": "sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "dom-serializer": "^3.0.0", "domelementtype": "^3.0.0", @@ -6131,7 +6013,6 @@ "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=10" } @@ -6767,7 +6648,6 @@ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -6783,7 +6663,6 @@ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "safe-buffer": "^5.0.1" } @@ -6793,8 +6672,7 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/electron-to-chromium": { "version": "1.5.423", @@ -6809,7 +6687,6 @@ "integrity": "sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "shimmer": "^1.2.0" } @@ -6827,7 +6704,6 @@ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -6838,7 +6714,6 @@ "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "once": "^1.4.0" } @@ -6862,7 +6737,6 @@ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" } @@ -6890,7 +6764,6 @@ "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "es-errors": "^1.3.0" }, @@ -6904,7 +6777,6 @@ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", @@ -6920,8 +6792,7 @@ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/esbuild": { "version": "0.28.2", @@ -6980,8 +6851,7 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/escape-string-regexp": { "version": "1.0.5", @@ -6999,7 +6869,6 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -7024,18 +6893,22 @@ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.8.x" } @@ -7046,7 +6919,6 @@ "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "eventsource-parser": "^3.0.1" }, @@ -7060,7 +6932,6 @@ "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=18.0.0" } @@ -7071,7 +6942,6 @@ "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", "dev": true, "license": "(MIT OR WTFPL)", - "peer": true, "engines": { "node": ">=6" } @@ -7140,7 +7010,6 @@ "integrity": "sha512-2FXbqkygil3mZiXzEtM0IjE/UBmBNot0J1GEVrJT4JbrvwOSoEtqtO+wHbiMQELWe1eNvHoYRg2dkjOid+6JkA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "debug": "^4.4.3", "ip-address": "^10.2.0" @@ -7161,7 +7030,6 @@ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -7171,8 +7039,7 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -7216,8 +7083,7 @@ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/fast-uri": { "version": "3.1.8", @@ -7240,7 +7106,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.1.tgz", "integrity": "sha512-pIM/1n3ntFXKYrUZwW7QCK0gAW7XY+wzj1YMIV3tLDvPj/V+zTGJK5e3/4WJfwj0qWw2ElNXiTixda/R+3YSug==", - "dev": true, "funding": [ { "type": "github", @@ -7248,7 +7113,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "path-expression-matcher": "^1.6.2", "xml-naming": "^0.3.0" @@ -7258,7 +7122,6 @@ "version": "5.11.2", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.11.2.tgz", "integrity": "sha512-R9iDuNrQYeQut46cn2r2wHKn4HYzVDvDm5J1wW+koZewykv0yuO2HChTYeZtrULyHIDM9cj9TUXloCsueCQUog==", - "dev": true, "funding": [ { "type": "github", @@ -7266,7 +7129,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@nodable/entities": "^3.0.1", "fast-xml-builder": "^1.2.0", @@ -7319,7 +7181,6 @@ "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "escape-string-regexp": "^1.0.5" }, @@ -7343,13 +7204,21 @@ "node": ">=8" } }, + "node_modules/filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/finalhandler": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", @@ -7369,7 +7238,6 @@ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -7379,24 +7247,21 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/flatbuffers": { "version": "25.9.23", "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-25.9.23.tgz", "integrity": "sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==", "dev": true, - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/flatted": { "version": "3.4.4", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/follow-redirects": { "version": "1.16.1", @@ -7410,7 +7275,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=4.0" }, @@ -7426,7 +7290,6 @@ "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -7457,7 +7320,6 @@ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -7482,7 +7344,6 @@ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -7492,8 +7353,7 @@ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/fs-extra": { "version": "9.1.0", @@ -7501,7 +7361,6 @@ "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", @@ -7543,7 +7402,6 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, "license": "ISC", - "peer": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -7554,7 +7412,6 @@ "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": "*" } @@ -7565,7 +7422,6 @@ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", @@ -7591,7 +7447,6 @@ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -7618,8 +7473,7 @@ "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/glob": { "version": "13.0.6", @@ -7627,7 +7481,6 @@ "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, "license": "BlueOak-1.0.0", - "peer": true, "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", @@ -7659,7 +7512,6 @@ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" }, @@ -7679,7 +7531,6 @@ "integrity": "sha512-P5VJMVM7qgBn6vjXMw8WG9uVI+ncf2pi72j4de4yz5ZULLj2RGqLYaKOYGsgyrViQ0tePOVlN1tDCCXXtFqXKg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "minimist": "^1.2.8", "neo-async": "^2.6.2", @@ -7702,7 +7553,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -7713,7 +7563,6 @@ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" }, @@ -7727,7 +7576,6 @@ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "has-symbols": "^1.0.3" }, @@ -7780,8 +7628,7 @@ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/htmlparser2": { "version": "10.1.0", @@ -7796,7 +7643,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", @@ -7810,7 +7656,6 @@ "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -7826,7 +7671,6 @@ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=0.12" }, @@ -7845,8 +7689,7 @@ "url": "https://github.com/sponsors/fb55" } ], - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/htmlparser2/node_modules/domhandler": { "version": "5.0.3", @@ -7854,7 +7697,6 @@ "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "domelementtype": "^2.3.0" }, @@ -7871,7 +7713,6 @@ "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -7887,7 +7728,6 @@ "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=0.12" }, @@ -7901,7 +7741,6 @@ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", @@ -7923,7 +7762,6 @@ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" @@ -7937,8 +7775,7 @@ "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz", "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/https-proxy-agent": { "version": "7.0.6", @@ -7946,7 +7783,6 @@ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "agent-base": "^7.1.2", "debug": "4" @@ -7991,8 +7827,7 @@ "url": "https://feross.org/support" } ], - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/ignore": { "version": "5.3.2", @@ -8000,26 +7835,28 @@ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/inquirer": { "version": "12.11.1", @@ -8120,7 +7957,6 @@ "integrity": "sha512-DWBMKyeM25D70nrIiTkgQth59/G+naKOTep6huF9j+GmeK3DMD60Zvxg5d5qQnz+38AHgou6ean2LECX+lrKDg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 12" } @@ -8131,7 +7967,6 @@ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.10" } @@ -8154,8 +7989,7 @@ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/is-core-module": { "version": "2.16.2", @@ -8179,7 +8013,6 @@ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "is-docker": "cli.js" }, @@ -8229,7 +8062,6 @@ "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=20" }, @@ -8243,7 +8075,6 @@ "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "is-docker": "^3.0.0" }, @@ -8263,7 +8094,6 @@ "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "is-docker": "cli.js" }, @@ -8296,8 +8126,7 @@ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/is-retry-allowed": { "version": "2.2.0", @@ -8305,7 +8134,6 @@ "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -8317,15 +8145,13 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-2.0.2.tgz", "integrity": "sha512-HgbIHPBH0KHHCcjLfGsCvhtPTVxjaAZlXjwdz7/GQC40SjSe4sfQsar8J5VFo8JOSbarkpV0OLG95bbaNd9aAQ==", - "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/is-wsl": { "version": "2.2.0", @@ -8333,7 +8159,6 @@ "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "is-docker": "^2.0.0" }, @@ -8341,13 +8166,18 @@ "node": ">=8" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/jiti": { "version": "1.21.7", @@ -8355,6 +8185,7 @@ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, "license": "MIT", + "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -8365,7 +8196,6 @@ "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/panva" } @@ -8375,8 +8205,7 @@ "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.9.4.tgz", "integrity": "sha512-PtOMXpEGuP0RRiRXsjzHzl44dMHxSu2CPvAhinupR1tBa88me+1DPqsobl7eupn5UukjLkln1ZnAOAOXOrYG0Q==", "dev": true, - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/js-tokens": { "version": "4.0.0", @@ -8400,7 +8229,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "argparse": "^2.0.1" }, @@ -8414,6 +8242,7 @@ "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@asamuzakjp/css-color": "^5.1.11", "@asamuzakjp/dom-selector": "^7.1.1", @@ -8468,16 +8297,14 @@ "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", "dev": true, - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/json-with-bigint": { "version": "3.5.12", "resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.12.tgz", "integrity": "sha512-uwbF/wSSuOgC7qqlq27Xp5B6a2MHVug3t0idZdTqu0JnlFvgJuH7ju+KAk/J06C7GfhoYy2gnb9wz2INqcne7w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jsonfile": { "version": "6.2.1", @@ -8498,7 +8325,6 @@ "integrity": "sha512-K+A9hhqbn0f3pJX17Q/7H6yQfD/5OXgdrR5UE12gMXCiN9D5Xq2o5mddV2QEcX/bjla99ASsAAQUyMCCRWAEhw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": "*" } @@ -8509,7 +8335,6 @@ "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", @@ -8527,6 +8352,48 @@ "npm": ">=6" } }, + "node_modules/jszip": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.2.tgz", + "integrity": "sha512-3l+rb15IOWtUhU0H5MFqES/T6Kh7abYwjosBey/vD6hDt8zoEffkSC5Ws5SGtgVw3gBx2NEbhTeSW1+kWkpyTQ==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/junk": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", @@ -8543,7 +8410,6 @@ "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", @@ -8556,7 +8422,6 @@ "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" @@ -8569,7 +8434,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "node-addon-api": "^4.3.0", "prebuild-install": "^7.0.1" @@ -8581,18 +8445,25 @@ "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "graceful-fs": "^4.1.9" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lightningcss": { "version": "1.33.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "dev": true, "license": "MPL-2.0", - "peer": true, "dependencies": { "detect-libc": "^2.0.3" }, @@ -8630,7 +8501,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8652,7 +8522,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8674,7 +8543,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8696,7 +8564,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8718,7 +8585,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8740,7 +8606,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8762,7 +8627,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8784,7 +8648,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8806,7 +8669,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8828,7 +8690,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8850,7 +8711,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -8885,7 +8745,6 @@ "integrity": "sha512-ES/o9qotMpzpN2MHs+Iq/JcVoOj8Fa5wiQYrTdFpvAnwXL0g66XHHUc9WUMk6nAlBtGsFQ24ne+SYnvnaQ2FSw==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "css-select": "^7.0.0", "cssom": "^0.5.0", @@ -8909,65 +8768,56 @@ "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", - "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.isnumber": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.isstring": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/log4js": { "version": "6.9.1", @@ -8975,7 +8825,6 @@ "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "date-format": "^4.0.14", "debug": "^4.3.4", @@ -9005,7 +8854,6 @@ "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "get-func-name": "^2.0.1" } @@ -9045,7 +8893,6 @@ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" } @@ -9056,7 +8903,6 @@ "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "charenc": "0.0.2", "crypt": "0.0.2", @@ -9076,7 +8922,6 @@ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -9087,7 +8932,6 @@ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -9108,7 +8952,6 @@ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -9133,7 +8976,6 @@ "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "mime": "cli.js" }, @@ -9145,9 +8987,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -9156,9 +8996,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "license": "MIT", - "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -9172,7 +9010,6 @@ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -9186,7 +9023,6 @@ "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "dev": true, "license": "BlueOak-1.0.0", - "peer": true, "dependencies": { "brace-expansion": "^5.0.8" }, @@ -9203,18 +9039,49 @@ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minio": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/minio/-/minio-8.0.7.tgz", + "integrity": "sha512-E737MgufW8CeQAsTAtnEMrxZ9scMSf29kkhZoXzDTKj/Jszzo2SfeZUH9wbDQH2Rsq6TCtl/yQL0+XdVKZansQ==", + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.4", + "block-stream2": "^2.1.0", + "browser-or-node": "^2.1.1", + "buffer-crc32": "^1.0.0", + "eventemitter3": "^5.0.1", + "fast-xml-parser": "^5.3.4", + "ipaddr.js": "^2.0.1", + "lodash": "^4.17.21", + "mime-types": "^2.1.35", + "query-string": "^7.1.3", + "stream-json": "^1.8.0", + "through2": "^4.0.2", + "xml2js": "^0.5.0 || ^0.6.2" + }, + "engines": { + "node": "^16 || ^18 || >=20" + } + }, + "node_modules/minio/node_modules/ipaddr.js": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.5.0.tgz", + "integrity": "sha512-aq+t5NAc+cS6rZQQVWC2x98CPqGtKKTMDd4Gaodv0wShnItdKg/51djkGJ1hqH+Oy0ivDftCbSLCQob8zso01w==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/minipass": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, "license": "BlueOak-1.0.0", - "peer": true, "engines": { "node": ">=16 || 14 >=14.17" } @@ -9225,7 +9092,6 @@ "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "minipass": "^7.1.2" }, @@ -9265,16 +9131,14 @@ "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/mustache": { "version": "4.2.0", @@ -9282,7 +9146,6 @@ "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "mustache": "bin/mustache" } @@ -9293,7 +9156,6 @@ "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", "dev": true, "license": "ISC", - "peer": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -9333,8 +9195,7 @@ "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/negotiator": { "version": "0.6.3", @@ -9342,7 +9203,6 @@ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -9352,16 +9212,14 @@ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/neverthrow": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/neverthrow/-/neverthrow-3.2.0.tgz", "integrity": "sha512-AINA32QbYO83L+3CBI6I5lH4LpBSlLwWteJ+uI25s4AQy6g/xz3RZuedmuNo91lLw2rY+AbPEPQdxd7mg1rXoQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/next": { "version": "14.2.35", @@ -9447,7 +9305,6 @@ "integrity": "sha512-lLdwkeg2XUVPTHqWjPltTxH81zbQNbUWC+AEb9ni50KjxkUzAAx1HGtMT21sbntzfUGfIphhcklvs3imJeqDXw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "semver": "^7.3.5" }, @@ -9460,8 +9317,7 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/node-domexception": { "version": "1.0.0", @@ -9509,7 +9365,6 @@ "integrity": "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "http2-client": "^1.2.5" }, @@ -9532,8 +9387,7 @@ "resolved": "https://registry.npmjs.org/node-machine-id/-/node-machine-id-1.1.12.tgz", "integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/node-readfiles": { "version": "0.2.0", @@ -9541,7 +9395,6 @@ "integrity": "sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "es6-promise": "^3.2.1" } @@ -9579,7 +9432,6 @@ "integrity": "sha512-GX0gsdbGVCgnRgbeGaubfjpBXyYRWOOCVeYh08bSQvDZqxz5ndXs1OTfAt/h36G1xvI94YIspsI0sVFqAV9+RQ==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "boolbase": "^2.0.0" }, @@ -9597,7 +9449,6 @@ "integrity": "sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "fast-safe-stringify": "^2.0.7" } @@ -9608,7 +9459,6 @@ "integrity": "sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@exodus/schemasafe": "^1.0.0-rc.2", "should": "^13.2.1", @@ -9624,7 +9474,6 @@ "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "dev": true, "license": "ISC", - "peer": true, "engines": { "node": ">= 6" } @@ -9635,7 +9484,6 @@ "integrity": "sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "node-fetch-h2": "^2.3.0", "oas-kit-common": "^1.0.8", @@ -9656,7 +9504,6 @@ "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "dev": true, "license": "ISC", - "peer": true, "engines": { "node": ">= 6" } @@ -9667,7 +9514,6 @@ "integrity": "sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "funding": { "url": "https://github.com/Mermade/oas-kit?sponsor=1" } @@ -9678,7 +9524,6 @@ "integrity": "sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "call-me-maybe": "^1.0.1", "oas-kit-common": "^1.0.8", @@ -9699,7 +9544,6 @@ "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "dev": true, "license": "ISC", - "peer": true, "engines": { "node": ">= 6" } @@ -9730,7 +9574,6 @@ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" }, @@ -10380,7 +10223,6 @@ "integrity": "sha512-kxV247eRDdGhBKOZYBhAvgzm/PveXdUXvZR5bi0mDYWFeY6wHoDuCSjRw00khRR///1ZvgixfvS4CqSfezyptQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@microsoft/app-manifest": "^1.0.2", "adm-zip": "0.5.16", @@ -10402,7 +10244,6 @@ "integrity": "sha512-2eOdCCYJ5bhCe2p9KKETdg1UNshsKaT0lDU/jNopAg3t7zC1WxwvofTSO/+4Log5L4Re+wUdV8MqrQikZBa7+Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@xmldom/xmldom": "^0.8.5", "commander": "^9.0.0", @@ -10418,7 +10259,6 @@ "integrity": "sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" } @@ -10429,7 +10269,6 @@ "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": "^12.20.0 || >=14" } @@ -10440,7 +10279,6 @@ "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12.0" } @@ -10451,7 +10289,6 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -10465,7 +10302,6 @@ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -10481,7 +10317,6 @@ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "color-name": "1.1.3" } @@ -10491,8 +10326,7 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/office-addin-manifest/node_modules/commander": { "version": "13.1.0", @@ -10500,7 +10334,6 @@ "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -10511,7 +10344,6 @@ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -10527,7 +10359,6 @@ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -10538,7 +10369,6 @@ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "dev": true, "license": "MIT", - "peer": true, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -10549,7 +10379,6 @@ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -10571,7 +10400,6 @@ "integrity": "sha512-MUbcqOodtlOUNhLKjSdqeLBHFVd7Qh5pUODqmQe2VLBKk7PuRgl4dCk6bp2dU79vl6BcDyrRERpkyVnQg6zdFg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "commander": "^13.0.0" }, @@ -10585,7 +10413,6 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -10598,8 +10425,7 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/office-addin-manifest/node_modules/universalify": { "version": "0.1.2", @@ -10607,7 +10433,6 @@ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 4.0.0" } @@ -10622,7 +10447,6 @@ "https://github.com/sponsors/ctavan" ], "license": "MIT", - "peer": true, "bin": { "uuid": "dist-node/bin/uuid" } @@ -10632,8 +10456,7 @@ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "dev": true, - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/office-addin-manifest/node_modules/whatwg-url": { "version": "5.0.0", @@ -10641,7 +10464,6 @@ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -10725,7 +10547,6 @@ "integrity": "sha512-WjthzxXY5gl78+jh91LczM9zg26zU8tc3afLi2bUcrMaPc6LaRaKJhw6NsyRB73kVDx76DGTe6Ws6sxufghOdQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "adm-zip": "0.5.12", "commander": "^13.0.0", @@ -10745,7 +10566,6 @@ "integrity": "sha512-6TVU49mK6KZb4qG6xWaaM4C7sA/sgUMLy/JYMOzkcp3BvVLpW0fXDFQiIzAuxFCt/2+xD7fNIiPFAoLZPhVNLQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6.0" } @@ -10756,7 +10576,6 @@ "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -10767,7 +10586,6 @@ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -10783,7 +10601,6 @@ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "dev": true, "license": "MIT", - "peer": true, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -10794,7 +10611,6 @@ "integrity": "sha512-MUbcqOodtlOUNhLKjSdqeLBHFVd7Qh5pUODqmQe2VLBKk7PuRgl4dCk6bp2dU79vl6BcDyrRERpkyVnQg6zdFg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "commander": "^13.0.0" }, @@ -10808,7 +10624,6 @@ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 4.0.0" } @@ -10842,7 +10657,6 @@ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ee-first": "1.1.1" }, @@ -10856,7 +10670,6 @@ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "wrappy": "1" } @@ -10867,7 +10680,6 @@ "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", @@ -10893,8 +10705,7 @@ "resolved": "https://registry.npmjs.org/original-fs/-/original-fs-1.2.0.tgz", "integrity": "sha512-IGo+qFumpIV65oDchJrqL0BOk9kr82fObnTesNJt8t3YgP6vfqcmRs0ofPzg3D9PKMeBHt7lrg1k/6L+oFdS8g==", "dev": true, - "license": "Unlicense", - "peer": true + "license": "Unlicense" }, "node_modules/pako": { "version": "1.0.11", @@ -10921,7 +10732,6 @@ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -10930,7 +10740,6 @@ "version": "1.6.2", "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz", "integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==", - "dev": true, "funding": [ { "type": "github", @@ -10938,7 +10747,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=14.0.0" } @@ -10949,7 +10757,6 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -10967,7 +10774,6 @@ "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "dev": true, "license": "BlueOak-1.0.0", - "peer": true, "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" @@ -10984,8 +10790,7 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/pathval": { "version": "1.1.1", @@ -10993,7 +10798,6 @@ "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": "*" } @@ -11088,7 +10892,6 @@ "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=16.20.0" } @@ -11122,6 +10925,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.18", "picocolors": "^1.1.1", @@ -11270,6 +11074,7 @@ "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.9.tgz", "integrity": "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw==", "license": "Unlicense", + "peer": true, "engines": { "node": ">=12" }, @@ -11284,7 +11089,6 @@ "integrity": "sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=20" }, @@ -11299,7 +11103,6 @@ "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", @@ -11321,13 +11124,18 @@ "node": ">=10" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, "node_modules/proper-lockfile": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", @@ -11339,8 +11147,7 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/proxy-addr": { "version": "2.0.8", @@ -11348,7 +11155,6 @@ "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -11367,7 +11173,6 @@ "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" } @@ -11378,7 +11183,6 @@ "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -11400,7 +11204,6 @@ "integrity": "sha512-5Px6J7rfHBAGUhNQ3es4YzcOdBBw0SDcVtThJml4RdnKw4tKs2epA0kPZ+K8X+WiHktT93RHVcQ/3ym5aTCgtg==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "es-define-property": "^1.0.1", "side-channel": "^1.1.1" @@ -11412,6 +11215,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/query-string": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", + "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", + "license": "MIT", + "dependencies": { + "decode-uri-component": "^0.2.2", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -11439,7 +11260,6 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -11450,7 +11270,6 @@ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", @@ -11467,7 +11286,6 @@ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "peer": true, "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -11483,6 +11301,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -11495,6 +11314,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -11528,9 +11348,7 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, "license": "MIT", - "peer": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -11558,8 +11376,7 @@ "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==", "dev": true, - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/reftools": { "version": "1.1.9", @@ -11567,7 +11384,6 @@ "integrity": "sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "funding": { "url": "https://github.com/Mermade/oas-kit?sponsor=1" } @@ -11578,7 +11394,6 @@ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -11631,7 +11446,6 @@ "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 4" } @@ -11652,8 +11466,7 @@ "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/rolldown": { "version": "1.2.8", @@ -11661,7 +11474,6 @@ "integrity": "sha512-Z67nTmhZe7anqnM/EjI392w5i/ANUinjip7QYsOyN37oayduxt3ksdX0hf5OOamkAd53BiIHfbfSzfUmzKFQqQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@oxc-project/types": "=0.149.0", "@rolldown/pluginutils": "^1.0.0" @@ -11696,7 +11508,6 @@ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", @@ -11714,7 +11525,6 @@ "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "dev": true, "license": "MIT", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" @@ -11726,7 +11536,6 @@ "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -11740,7 +11549,6 @@ "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.12.0" } @@ -11783,7 +11591,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -11798,8 +11605,7 @@ "url": "https://feross.org/support" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/safer-buffer": { "version": "2.1.2", @@ -11812,7 +11618,6 @@ "version": "1.6.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", - "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=11.0.0" @@ -11846,7 +11651,6 @@ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" }, @@ -11860,7 +11664,6 @@ "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -11886,7 +11689,6 @@ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -11896,8 +11698,7 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/send/node_modules/mime": { "version": "1.6.0", @@ -11905,7 +11706,6 @@ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "mime": "cli.js" }, @@ -11919,7 +11719,6 @@ "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", @@ -11930,13 +11729,18 @@ "node": ">= 0.8.0" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/shebang-command": { "version": "2.0.0", @@ -11944,7 +11748,6 @@ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -11958,7 +11761,6 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -11969,7 +11771,6 @@ "integrity": "sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" }, @@ -11982,8 +11783,7 @@ "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", "dev": true, - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/should": { "version": "13.2.3", @@ -11991,7 +11791,6 @@ "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "should-equal": "^2.0.0", "should-format": "^3.0.3", @@ -12006,7 +11805,6 @@ "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "should-type": "^1.4.0" } @@ -12017,7 +11815,6 @@ "integrity": "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "should-type": "^1.3.0", "should-type-adaptors": "^1.0.1" @@ -12028,8 +11825,7 @@ "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", "integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/should-type-adaptors": { "version": "1.1.0", @@ -12037,7 +11833,6 @@ "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "should-type": "^1.3.0", "should-util": "^1.0.0" @@ -12048,8 +11843,7 @@ "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/side-channel": { "version": "1.1.1", @@ -12057,7 +11851,6 @@ "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4", @@ -12078,7 +11871,6 @@ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" @@ -12096,7 +11888,6 @@ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -12116,7 +11907,6 @@ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -12170,8 +11960,7 @@ "url": "https://feross.org/support" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/simple-get": { "version": "4.0.1", @@ -12193,7 +11982,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", @@ -12230,13 +12018,21 @@ "source-map": "^0.6.0" } }, + "node_modules/split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/stack-chain": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/stack-chain/-/stack-chain-1.3.7.tgz", "integrity": "sha512-D8cWtWVdIe/jBA7v5p5Hwl5yOSOrmZPWDPe2KxQ5UAGD+nxbxU0lKXA4h85Ta6+qgdKVL3vUxsbIZjc1kBG7ug==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/stackback": { "version": "0.0.2", @@ -12251,7 +12047,6 @@ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -12263,13 +12058,27 @@ "dev": true, "license": "MIT" }, + "node_modules/stream-chain": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", + "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==", + "license": "BSD-3-Clause" + }, + "node_modules/stream-json": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.9.1.tgz", + "integrity": "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==", + "license": "BSD-3-Clause", + "dependencies": { + "stream-chain": "^2.2.5" + } + }, "node_modules/streamroller": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "date-format": "^4.0.14", "debug": "^4.3.4", @@ -12285,7 +12094,6 @@ "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", @@ -12301,7 +12109,6 @@ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "dev": true, "license": "MIT", - "peer": true, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -12312,7 +12119,6 @@ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 4.0.0" } @@ -12325,13 +12131,20 @@ "node": ">=10.0.0" } }, + "node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, "license": "MIT", - "peer": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -12383,7 +12196,6 @@ "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -12392,7 +12204,6 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.5.1.tgz", "integrity": "sha512-UM4nTLWzQiDBseVxUGxN0wJzIk0UU89Qi7pKXz9qISo+lhviJikTuikBsK/oFRlpvYHvSdz0TVoLyQ+sEpYwxA==", - "dev": true, "funding": [ { "type": "github", @@ -12400,7 +12211,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "anynum": "^1.0.1" } @@ -12457,7 +12267,6 @@ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -12493,7 +12302,6 @@ "integrity": "sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "call-me-maybe": "^1.0.1", "node-fetch": "^2.6.1", @@ -12522,7 +12330,6 @@ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -12543,16 +12350,14 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/swagger2openapi/node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "dev": true, - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/swagger2openapi/node_modules/whatwg-url": { "version": "5.0.0", @@ -12560,7 +12365,6 @@ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -12572,7 +12376,6 @@ "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "dev": true, "license": "ISC", - "peer": true, "engines": { "node": ">= 6" } @@ -12628,7 +12431,6 @@ "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", "dev": true, "license": "BlueOak-1.0.0", - "peer": true, "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", @@ -12646,7 +12448,6 @@ "integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", @@ -12659,8 +12460,7 @@ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/tar-stream": { "version": "2.2.0", @@ -12668,7 +12468,6 @@ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -12686,7 +12485,6 @@ "integrity": "sha512-UDdUF9kV2hYdlv+7AgqP2kXarVSUhjK7tg1BUflIRGEgND0/QoNpN64rcEuhEcM8AIbW65yrCopJWqRhLZ3m8w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "axios": "^1.6.1" } @@ -12697,7 +12495,6 @@ "integrity": "sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -12716,8 +12513,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/thenify": { "version": "3.3.1", @@ -12742,6 +12538,15 @@ "node": ">=0.8" } }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "license": "MIT", + "dependencies": { + "readable-stream": "3" + } + }, "node_modules/tinybench": { "version": "6.1.4", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.1.4.tgz", @@ -12803,6 +12608,7 @@ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -12836,7 +12642,6 @@ "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=14.14" } @@ -12860,7 +12665,6 @@ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.6" } @@ -12897,7 +12701,6 @@ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true, "license": "MIT", - "peer": true, "bin": { "tree-kill": "cli.js" } @@ -12940,7 +12743,6 @@ "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "safe-buffer": "^5.0.1" }, @@ -12954,7 +12756,6 @@ "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -12965,7 +12766,6 @@ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, "license": "(MIT OR CC0-1.0)", - "peer": true, "engines": { "node": ">=10" }, @@ -12979,7 +12779,6 @@ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -12993,8 +12792,7 @@ "resolved": "https://registry.npmjs.org/typedi/-/typedi-0.10.0.tgz", "integrity": "sha512-v3UJF8xm68BBj6AF4oQML3ikrfK2c9EmZUyLOfShpJuItAqVBHWP/KtpGinkSsIiP6EZyyb6Z3NXyW9dgS9X1w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/typescript": { "version": "5.9.3", @@ -13017,7 +12815,6 @@ "dev": true, "license": "BSD-2-Clause", "optional": true, - "peer": true, "bin": { "uglifyjs": "bin/uglifyjs" }, @@ -13030,16 +12827,14 @@ "resolved": "https://registry.npmjs.org/uhyphen/-/uhyphen-0.2.0.tgz", "integrity": "sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/underscore": { "version": "1.13.8", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/undici": { "version": "7.30.0", @@ -13074,7 +12869,6 @@ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -13123,7 +12917,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, "license": "MIT" }, "node_modules/utils-merge": { @@ -13132,7 +12925,6 @@ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4.0" } @@ -13147,7 +12939,6 @@ "https://github.com/sponsors/ctavan" ], "license": "MIT", - "peer": true, "bin": { "uuid": "dist/esm/bin/uuid" } @@ -13158,7 +12949,6 @@ "integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.10" } @@ -13169,7 +12959,6 @@ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -13180,7 +12969,6 @@ "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", @@ -13259,7 +13047,6 @@ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -13369,7 +13156,6 @@ "integrity": "sha512-perEnXQdQOJMTDFNv+UF3h1Y0z4iSiaN9jIlb0OqIYgosPCZGYh/MCUlkFtV2668PL69lRDO32hmvL2yiidUYg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8.0.0 || >=10.0.0" } @@ -13438,7 +13224,6 @@ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "isexe": "^2.0.0" }, @@ -13478,8 +13263,7 @@ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/wrap-ansi": { "version": "6.2.0", @@ -13501,8 +13285,7 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/ws": { "version": "7.5.13", @@ -13532,7 +13315,6 @@ "integrity": "sha512-c16PKLIRxBYpR3jSWsdS7SFp0COm5SOQdiFjY818dYFHJbMGuLBszpF07LBWmOS1uzrHa9VGdynsEgoeKy0oOQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.2.0" @@ -13550,7 +13332,6 @@ "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "is-inside-container": "^1.0.0" }, @@ -13575,7 +13356,6 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.3.0.tgz", "integrity": "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==", - "dev": true, "funding": [ { "type": "github", @@ -13583,7 +13363,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=16.0.0" } @@ -13592,7 +13371,6 @@ "version": "0.5.0", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", - "dev": true, "license": "MIT", "dependencies": { "sax": ">=0.6.0", @@ -13606,7 +13384,6 @@ "version": "11.0.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "dev": true, "license": "MIT", "engines": { "node": ">=4.0" @@ -13634,7 +13411,6 @@ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, "license": "ISC", - "peer": true, "engines": { "node": ">=10" } @@ -13645,7 +13421,6 @@ "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", "dev": true, "license": "BlueOak-1.0.0", - "peer": true, "engines": { "node": ">=18" } @@ -13656,7 +13431,6 @@ "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", "dev": true, "license": "ISC", - "peer": true, "bin": { "yaml": "bin.mjs" }, @@ -13673,7 +13447,6 @@ "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -13693,7 +13466,6 @@ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -13728,7 +13500,6 @@ "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "dev": true, "license": "ISC", - "peer": true, "peerDependencies": { "zod": "^3.25.28 || ^4" } diff --git a/package.json b/package.json index 701ecae..fabdcde 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "dependencies": { "@pdf-lib/fontkit": "^1.1.1", "@xmldom/xmldom": "^0.9.12", + "docx-templates": "^4.15.0", "drizzle-orm": "^0.45.2", "lucide-react": "^1.42.0", "minio": "^8.0.7", @@ -31,7 +32,7 @@ "xpath": "^0.0.34" }, "devDependencies": { - "@types/node": "^26.5.0", + "@types/node": "^26.6.5", "@types/office-js": "^1.0.612", "@types/pngjs": "^6.0.5", "@types/react": "^18.3.31", diff --git a/public/tmpl/T12_FD155_3s-long.json b/public/tmpl/T12_FD155_3s-long.json new file mode 100644 index 0000000..8a27173 --- /dev/null +++ b/public/tmpl/T12_FD155_3s-long.json @@ -0,0 +1,44 @@ +{ + "info": { + "ticket_data": "{\"fid\":\"T12_FD155_3s-long_ZZZ\",\"tid\":\"T12\",\"uid\":\"oom\",\"uname\":\"สรวิชญ์\"}" + }, + "dc": { + "Interpleader": false, + "docabbr": "(ต. ๒๒)", + "blackno": "คดีหมายเลขดำที่", + "redno": "คดีหมายเลขแดงที่", + "court": "ศาลปกครองนครราชสีมา", + "panels": [ + { + "pname": "นายวิรุฬห์ จินตนะกุล", + "pmname": "ตุลาการศาลปกครองกลาง", + "pm": "ตุลาการเจ้าของสำนวน" + }, + { + "pname": "นายสัมฤทธิ์ อ่อนคำ", + "pmname": "ตุลาการศาลปกครองสูงสุด" + }, + { + "pname": "นางสุมาลี ลิมปโอวาท", + "pmname": "ตุลาการศาลปกครองสูงสุด" + } + ], + "pnameT":"ชื่อ pnameT", + "pmnameT":"ตำแหน่งของ pnameT", + "pmT":"ตุลาการผู้แถลงคดี", + "acc_name":"ชื่อ acc_name" + + }, + "cc": { + "blackyear": "๙๐/๒๕๖๙", + "redyear": "", + "curdate": "วันที่ เดือน สิงหาคม พุทธศักราช ๒๕๖๙", + "accuser": "นายธีรพล ร่มเริง", + "acc1": "ผู้ฟ้องคดี", + "accused": "รร", + "acc2": "ผู้ถูกฟ้องคดี", + "interpleader": "สมชาย ใจดี", + "acc3": "ผู้ร้องสอด", + "detail1": "ตำแหน่งปลัดอำเภอ (เจ้าพนักงานปกครองชำนาญการพิเศษ) ที่ทำการปกครองอำเภอแม่สาย จังหวัดเชียงราย กรมการปกครอง ได้รับความเดือดร้อนเสียหายจากการที่ผู้ถูกฟ้องคดีที่ ๒ มีคำสั่งกรมการปกครอง ที่ ๑๔๐๐/๒๕๖๓ ลงวันที่ ๕ สิงหาคม ๒๕๖๓ ลงโทษไล่ผู้ฟ้องคดี ออกจากราชการ กรณีเมื่อครั้งผู้ฟ้องคดีดำรงตำแหน่งปลัดอำเภอ (เจ้าพนักงานปกครอง 5) ที่ทำการปกครองอำเภอพญาเม็งราย จังหวัดเชียงราย ถูกกล่าวหาว่ากระทำการทุจริตโครงการ จัดทำเวทีประชาคมหมู่บ้านเพื่อเสริมสร้างความเข้มแข็งให้ชุมชนอำเภอพญาเม็งราย จังหวัด เชียงราย และโครงการส่งเสริมการเกษตรแบบครบวงจร ซึ่งผู้ฟ้องคดีมีอำนาจหน้าที่เป็น คณะกรรมการตรวจรับพัสดุในโครงการดังกล่าว กระทำการจัดทำเอกสารหลักฐานการเบิกจ่ายเงิน อันเป็นเท็จและลงลายมือชื่อตรวจรับพัสดุอันเป็นเท็จ ซึ่งเป็นการไม่ปฏิบัติตามข้อ ๗๑ ของระเบียบสำนักนายกรัฐมนตรีว่าด้วยการพัสดุ พ.ศ. ๒๕๓๕ และที่แก้ไขเพิ่มเติม ผู้ถูกฟ้องคดีที่ ๑ ในการประชุมครั้งที่ ๑๐๕/๒๕๖๒ เมื่อวันที่ 4 ตุลาคม ๒๕๖๒ จึงมีมติชี้มูลความผิดอาญา ฐานเป็นเจ้าพนักงานปฏิบัติหรือละเว้นการปฏิบัติหน้าที่โดยมิชอบ เพื่อให้เกิดความเสียหาย แก่ผู้หนึ่งผู้ใด หรือปฏิบัติหรือละเว้นการปฏิบัติหน้าที่โดยทุจริต ตามมาตรา ๑๕๗ แห่งประมวล กฎหมายอาญา และความผิดทางวินัยอย่างร้ายแรง ฐานปฏิบัติหรือละเว้นการปฏิบัติหน้าที่ ราชการโดยมิชอบ เพื่อให้ตนเองหรือผู้อื่นได้ประโยชน์ที่มีควรได้ เป็นการทุจริตต่อหน้าที่ ราชการ ฐานปฏิบัติหน้าที่ราชการโดยจงใจไม่ปฏิบัติตามกฎหมาย ระเบียบของทางราชการ มติคณะรัฐมนตรี หรือนโยบายของรัฐบาล อันเป็นเหตุให้เสียหายแก่ราชการอย่างร้ายแรง และ ฐานกระทำการอื่นใดอันได้ชื่อว่าเป็นผู้ประพฤติชั่วอย่างร้ายแรง ตามมาตรา ๑๘๒ วรรคสาม มาตรา ๘๕ วรรคสอง และมาตรา ๔๘ วรรคสอง แห่งพระราชบัญญัติระเบียบข้าราชการพลเรือน พ.ศ. ๒๕๓๕ ประกอบมาตรา ๑๓๓ แห่งพระราชบัญญัติระเบียบข้าราชการพลเรือน พ.ศ. ๒๕๕๑ ผู้ถูกฟ้องคดีที่ ๑ ผู้ถูกฟ้องคดีที่ ๒ จึงได้ส่งรายงาน สำนวนการไต่สวน เอกสารหลักฐาน และคำวินิจฉัยไปยัง ซึ่งเป็นผู้บังคับบัญชาและผู้มีอำนาจบรรจุและแต่งตั้งผู้ฟ้องคดี ผู้ถูกฟ้องคดีที่ ๒ เสนอเรื่องต่อ อ.ก.พ. กระทรวงมหาดไทย ซึ่ง อ.ก.พ. กระทรวงมหาดไทย มีมติในการประชุม ครั้งที่ ๒/๒๕๖๓ เมื่อวันที่ ๑๘ มิถุนายน ๒๕๖๓ ให้ลงโทษไล่ผู้ฟ้องคดีออกจากราชการ ตามฐานความผิดที่ผู้ถูกฟ้องคดีที่ ๑ ชี้มูลความผิด ผู้ถูกฟ้องคดีที่ ๒ จึงมีคำสั่งกรมการปกครอง ที่ ๑๔๐๐/๒๕๖๓ ลงวันที่ ๕ สิงหาคม ๒๕๖๓ ลงโทษไล่ผู้ฟ้องคดีออกจากราชการ ต่อมา ผู้ฟ้องคดีมีหนังสือลงวันที่ ๑๕ กันยายน ๒๕๖๓ อุทธรณ์คำสั่งดังกล่าวต่อคณะกรรมการพิทักษ์ ที่ ระบบคุณธรรม ต่อมาคณะกรรมการพิทักษ์ระบบคุณธรรมได้มีคำวินิจฉัย เรื่องดำที่ ๖๓๑๐๑๖๖ เรื่องแดงที่ ๐๐๔๘๑๖๕ ลงวันที่ ๕ กรกฎาคม ๒๕๖๕ ให้ยกอุทธรณ์ และมีหนังสือ นร ๑๐๑๐.๓.๑.๔/๒๕ ลงวันที่ 6 กรกฎาคม ๒๕๖๕ แจ้งคำวินิจฉัยดังกล่าวให้ผู้ฟ้องคดีทราบ ผู้ฟ้องคดีไม่เห็นพ้องด้วย เนื่องจากโครงการจัดทำเวทีประชาคมหมู่บ้านเพื่อเสริมสร้าง ความเข็มแข็งให้ชุมชนอำเภอพญาเม็งราย จังหวัดเชียงราย เป็นโครงการที่นายบุญยัง เรือนกูล นายอำเภอพญาเม็งราย ในขณะนั้น ได้ดำเนินการไปก่อนที่ผู้ฟ้องคดีจะได้รับการแต่งตั้ง ให้ดำรงตำแหน่งปลัดอำเภอพญาเม็งราย ครั้นเมื่อผู้ฟ้องคดีได้รับการแต่งตั้งให้ดำรงตำแหน่ง ดังกล่าว นายบุญยังได้ออกคำสั่งให้ผู้ฟ้องคดีปฏิบัติหน้าที่รับผิดชอบงานด้านความมั่นคง ตามคำสั่งอำเภอพญาเม็งราย ที่ ๓๗๒/๒๕๕๗ ลงวันที่ ๓ ธันวาคม ๒๕๔๗ ซึ่งรวมถึงโครงการ จัดทำเวทีประชาคมหมู่บ้านเพื่อเสริมสร้างความเข็มแข็งให้ชุมชนอำเภอพญาเม็งรายด้วย ผู้ฟ้องคดีในฐานะผู้ใต้บังคับบัญชาจึงต้องเชื่อฟังและปฏิบัติตามคำสั่งของนายบุญยัง แม้ผู้ฟ้องคดี จะเป็นผู้ลงนามเสนอและเปลี่ยนแปลงโครงการ แต่ผู้ฟ้องคดีไม่มีความเป็นอิสระในการบริหาร โดยอำนาจบริหารอยู่ภายใต้การสั่งการของนายบุญยัง ส่วนโครงการส่งเสริมการเกษต แบบครบวงจรเป็นการบริหารโครงการที่นายบุญยังเป็นผู้ดำเนินการควบคุมสั่งการทุกขั้น จนกระทั่งเสร็จสิ้นโครงการเช่นเดียวกัน เมื่อดำเนินการโครงการดังกล่าวเสร็จสิ้น นายบุญยัง และนางวัลลภาภรณ์ เรือนปาง เสมียนตราอำเภอพญาเม็งรายและกรรมการรับเงินอุดหนุนโครงการ จึงได้รวบรวมเอกสารที่เกี่ยวข้องเสนอผู้ฟ้องคดีให้ลงนามในฐานะคณะกรรมการตรวจรับพัสดุ โดยผู้ฟ้องคดีไม่มีโอกาสได้ปฏิบัติหน้าที่ตามระเบียบสำนักนายกรัฐมนตรีว่าด้วยการพัสดุ พ.ศ. ๒๕๓๕ เมื่อนางวัลลภาภรณ์ผู้ตรวจรับพัสดุยืนยันว่าได้ตรวจสอบพัสดุครบถ้วนทุกรายการ และส่งมอบให้ นายบุญยังแจกจ่ายครบถ้วนแล้ว ผู้ฟ้องคดีจึงลงนามตรวจรับพัสดุ จึงเห็นได้ว่า ผู้ฟ้องคดีไม่มี เจตนาทุจริต ข้อเท็จจริงไม่ปรากฏว่าผู้ฟ้องคดีมีทรัพย์สินเพิ่มมากขึ้นผิดปกติ หรือมีการ ได้ประโยชน์ในลักษณะทรัพย์สิน หรือประโยชน์อื่นใดจากการดำเนินโครงการ ผู้ฟ้องคดี ไม่ได้กระทำการทุจริตต่อหน้าที่ อีกทั้ง ผู้ถูกฟ้องคดีที่ ๑ ไม่มีพยานหลักฐานที่สามารถพิสูจน์ โดยปราศจากข้อสงสัยว่าผู้ฟ้องคดีกระทำการทุจริต การที่ผู้ถูกฟ้องคดีที่ ๑ ไต่สวนข้อเท็จจริง และมีมติชี้มูลความผิดจึงเป็นการไต่สวนและชี้มูลความผิดไม่ตรงกับข้อเท็จจริงและเป็นการ พยายามบิดเบือนว่าผู้ฟ้องคดีกระทำความผิดทางวินัยฐานทุจริต ทั้งที่ผู้ฟ้องคดีกระทำความผิด ทางวินัยฐานอื่น มติของผู้ถูกฟ้องคดีที่ ๑ ในการประชุมครั้งที่ ๑๐๕/๒๕๖๓ เมื่อวันที่ 4 ตุลาคม ๒๕๖๒ ที่ชี้มูลความผิดผู้ฟ้องคดีจึงเป็นมติที่ไม่ชอบด้วยกฎหมาย นอกจากนี้ ผู้ถูกฟ้องคดีที่ ๑ มีอำนาจไต่สวนและชี้มูลความผิดทางวินัยเฉพาะความผิดฐานทุจริตต่อหน้าที่ราชการเท่านั้น เมื่อผู้ฟ้องคดีไม่ได้กระทำความผิดฐานทุจริตต่อหน้าที่ ผู้ถูกฟ้องคดีที่ ๑ จึงไม่มีอำนาจไต่สวน และชี้มูลความผิดผู้ฟ้องคดี และไม่ผูกพันผู้ถูกฟ้องคดีที่ ๒ ให้ต้องถือรายงานการไต่สวนของ ผู้ถูกฟ้องคดีที่ ๑ เป็นรายงานการสอบสวน โดยผู้ถูกฟ้องคดีที่ ๒ ต้องดำเนินการแต่งตั้ง คณะกรรมการสอบสวนความผิดทางวินัยเพื่อสอบสวนข้อเท็จจริง เมื่อผู้ถูกฟ้องคดีที่ ๒ ไม่ได้ แต่งตั้งคณะกรรมการดังกล่าว การที่ผู้ถูกฟ้องคดีที่ ๒ มีคำสั่งกรมการปกครอง ที่ ๑๔๐๐/๒๕๖๓ ลงวันที่ ๕ สิงหาคม ๒๕๖๓ ลงโทษไล่ผู้ฟ้องคดีออกจากราชการ จึงเป็นการออกคำสั่งโดยไม่ถูกต้อง ตามรูปแบบ ขั้นตอน หรือวิธีการ อันเป็นสาระสำคัญที่กำหนดไว้สำหรับการออกคำสั่งนั้น ผู้ฟ้องคดีจึงนำคดีมาฟ้องต่อศาลปกครองสูงสุดโดยวิธีส่งทางไปรษณีย์ลงทะเบียน เมื่อวันที่ ๓ ตุลาคม ๒๕๖\n\nขอให้ศาลมีคำพิพากษาหรือคำสั่ง ดังนี้ \n\n๑. เพิกถอนมติของผู้ถูกฟ้องคดีที่ ๑ ในการประชุมครั้งที่ ๑๐๕/๒๕๖๒ เมื่อวันที่ ๘ ตุลาคม ๒๕๖๒ ที่ชี้มูลความผิดของผู้ฟ้องคดี \n\n๒. เพิกถอนคำสั่งกรมการปกครอง ที่ ๑๔๐๐/๒๕๖๓ ลงวันที่ ๕ สิงหาคม ๒๕๖๓ ที่ลงโทษไล่ผู้ฟ้องคดีออกจากราชการ \n\n๓. ให้แก่ผู้ฟ้องคดี คดี\n\nศาลได้พิจารณาคำฟ้องและคำขอของผู้ฟ้องคดีแล้ว สามารถแปลความได้ว่า ผู้ฟ้องคดีมีความประสงค์จะขอให้ศาลมีคำพิพากษาหรือคำสั่งเพิกถอนคำวินิจฉัยอุทธรณ์ ของคณะกรรมการพิทักษ์ระบบคุณธรรม เรื่องดำที่ ๖๓๑๐๑๖๖ เรื่องแดงที่ ๑๐๔๘๑๖๕ ลงวันที่ ๕ กรกฎาคม ๒๕๖๕ ที่ให้ยกอุทธรณ์ของผู้ฟ้องคดี ศาลจึงมีคำสั่งลงวันที่ ๒๘ กุมภาพันธ์ ๒๕๖๖ เรียกให้คณะกรรมการพิทักษ์ระบบคุณธรรมเข้ามาเป็นผู้ถูกฟ้องคดีที่ ๓ ตาม มาตรา ๔๔ แห่งพระราชบัญญัติจัดตั้งศาลปกครองและวิธีพิจารณาคดีปกครอง พ.ศ. ๒๕๔๒ ประกอบกับข้อ ๗๘ แห่งระเบียบของที่ประชุมใหญ่ตุลาการในศาลปกครองสูงสุด ว่าด้วย วิธีพิจารณาคดีปกครอง พ.ศ. ๒๕๔๓ และมาตรา ๕๗ และมาตรา ๕๘ แห่งประมวลกฎหมาย วิธีพิจารณาความแพ่ง\n\nผู้ถูกฟ้องคดีที่ ๑ ให้การว่า ขณะเกิดเหตุคดีนี้ ผู้ฟ้องคดีดำรงตำแหน่งปลัดอำเภอ (พนักงานปกครอง 5) ที่ทำการปกครองอำเภอพญาเม็งราย จังหวัดเชียงราย มีอำนาจหน้าที่ ช่วยเหลือนายอำเภอในการปฏิบัติราชการให้เป็นไปตามพระราชบัญญัติระเบียบบริหารราชการ แผ่นดิน พ.ศ. ๒๕๓๔ และที่แก้ไขเพิ่มเติม โดยได้รับการแต่งตั้งให้เป็นคณะกรรมการตรวจรับ พัสดุในโครงการส่งเสริมการเกษตรแบบครบวงจรของที่ทำการปกครองอำเภอพญาเม็งราย ปีงบประมาณ พ.ศ. ๒๕๕๗ และได้รับการแต่งตั้งให้เป็นคณะกรรมการตรวจรับงานจ้างในโครงการ จัดทำเวทีประชาคมหมู่บ้านเพื่อเสริมสร้างความเข้มแข็งให้ชุมชนในอำเภอพญาเม็งราย จังหวัดเชียงราย งบประมาณปี พ.ศ. ๒๕๕๘ ตามระเบียบสำนักนายกรัฐมนตรีว่าด้วยการพัสดุ พ.ศ. ๒๕๓๕ และที่แก้ไขเพิ่มเติม ข้อเท็จจริงปรากฏว่า โครงการจัดทำเวทีประชาคมไม่มีการจัดโครงการ ตามที่มีการจัดซื้อจัดจ้าง และการจัดซื้อวัสดุตามโครงการส่งเสริมการเกษตรไม่ได้รับพัสดุ ครบถ้วนตามที่ได้มีการจัดซื้อ อันเป็นการไม่ปฏิบัติตามข้อ ๗๑ ของระเบียบสำนักนายกรัฐมนตรี ว่าด้วยการพัสดุ พ.ศ. ๒๕๓๕ และที่แก้ไขเพิ่มเติม ทำให้เกิดความเสียหายต่อทางราชการ มีมูล ความผิดทางอาญา ตามประมวลกฎหมายอาญา มาตรา ๑๕๗ และมีมูลความผิดทางวินัย อย่างร้ายแรง ตามพระราชบัญญัติระเบียบข้าราชการพลเรือน พ.ศ. ๒๕๓๕ มาตรา ๑๒ วรรคสาม มาตรา ๘๕ วรรคสอง และมาตรา ๔๘ วรรคสอง เมื่อผู้ถูกฟ้องคดีที่ ๑ ได้รับเรื่องกล่าวหา จากสำนักงานการตรวจเงินแผ่นดิน และสำนักตรวจสอบพิเศษ ภาค ๘ (จังหวัดเชียงใหม่) เรื่อง การตรวจสอบการใช้จ่ายเงินของที่ทำการปกครองอำเภอพญาเม็งราย จังหวัดเชียงราย จึงมีมติ แต่งตั้งคณะกรรมการไต่สวนเพื่อดำเนินการไต่สวน ซึ่งคณะกรรมการไต่สวนได้ไต่สวน ข้อเท็จจริงและรวบรวมพยานหลักฐานแล้วเห็นว่าพยานหลักฐานเพียงพอพิสูจน์ได้ว่าผู้ฟ้องคดี กระทำความผิดตามข้อกล่าวหา จึงแจ้งข้อกล่าวหากับผู้ฟ้องคดี ผู้ฟ้องคดีรับทราบข้อกล่าวหา และมีหนังสือชี้แจงแก้ข้อกล่าวหาแล้ว ผู้ถูกฟ้องคดีที่ ๑ พิจารณาข้อเท็จจริงพร้อมพยานหลักฐาน\n\nตามสำนวนการไต่สวนของคณะกรรมการไต่สวนในการประชุมครั้งที่ ๑๐๕/๒๕๖๒ เมื่อวันที่ ๘ ตุลาคม ๒๕๖๒ แล้วมีมติว่า การกระทำของผู้ฟ้องคดีมีมูลความผิดทางอาญา ตามประมวลกฎหมายอาญา มาตรา ๑๕๗ และมีมูลความผิดทางวินัยอย่างร้ายแรง ฐานปฏิบัติหรือละเว้นการปฏิบัติหน้าที่ราชการ โดยมิชอบ เพื่อให้ตนเองหรือผู้อื่นได้ประโยชน์ที่มีควรได้ เป็นการทุจริตต่อหน้าที่ราชการ ฐานปฏิบัติหน้าที่ราชการโดยจงใจไม่ปฏิบัติตามกฎหมาย ระเบียบของทางราชการ มติคณะรัฐมนตรี หรือนโยบายของรัฐบาล อันเป็นเหตุให้เสียหายแก่ราชการอย่างร้ายแรง และฐานกระทำการอื่นใด อันได้ชื่อว่าเป็นผู้ประพฤติชั่วอย่างร้ายแรง ตามพระราชบัญญัติระเบียบข้าราชการพลเรือน พ.ศ. ๒๕๓๕ มาตรา ๒ วรรคสาม มาตรา ๕๕ วรรคสอง และมาตรา ๔๘ วรรคสอง เมื่อผู้ถูกฟ้องคดีที่ ๑ ไต่สวนแล้วมีมติว่าผู้ฟ้องคดีกระทำความผิดฐานทุจริตต่อหน้าที่ หรือกระทำความผิดต่อตำแหน่ง หน้าที่ราชการ อันเป็นความผิดมูลฐานตามที่รัฐธรรมนูญกำหนดไว้แล้ว ผู้ถูกฟ้องคดีที่ ๑ จึงมี อำนาจชี้มูลความผิดทางวินัยผู้ฟ้องคดีฐานอื่นซึ่งเป็นความผิดที่เกี่ยวข้องกันได้ ตามบทบัญญัติ มาตรา ๑๙ (๔) ประกอบมาตรา ๘๔ แห่งพระราชบัญญัติประกอบรัฐธรรมนูญว่าด้วยการป้องกัน และปราบปรามการทุจริต พ.ศ. ๒๕๔๒ และที่แก้ไขเพิ่มเติม ดังนั้น การไต่สวนข้อเท็จจริง มติชี้มูลความผิด และคำวินิจฉัยของผู้ถูกฟ้องคดีที่ ๑ ตลอดจนการที่ผู้ถูกฟ้องคดีที่ ๑ ชี้มูล ความผิดทางวินัยและส่งเรื่องให้ผู้ถูกฟ้องคดีที่ ๒ ซึ่งผู้บังคับบัญชาของผู้ฟ้องคดีดำเนินการ ทางวินัย จึงเป็นการดำเนินการตามขั้นตอนที่กฎหมายและระเบียบกำหนดไว้แล้ว และมีผล ผูกพันผู้ถูกฟ้องคดีที่ ๒ ซึ่งเป็นผู้บังคับบัญชาของผู้ฟ้องคดีในการพิจารณาโทษทางวินัยตามมติ ของผู้ถูกฟ้องคดีที่ ๑ การที่ผู้ถูกฟ้องคดีที่ ๒ มีคำสั่งกรมการปกครอง ที่ ๑๔๐๐/๒๕๖๓ ลงวันที่ ๕ สิงหาคม ๒๕๖๓ ลงโทษไล่ผู้ฟ้องคดีออกจากราชการตามฐานความผิดที่ผู้ถูกฟ้องคดีที่ ๑ ได้มีมติจึงชอบด้วยกฎหมายแล้ว นอกจากนี้ อัยการสูงสุดได้ยื่นฟ้องผู้ฟ้องคดีเป็นจำเลยที่ ๒ ต่อศาลอาญาคดีทุจริตและประพฤติมิชอบภาค ๕ ในคดีหมายเลขดำที่ อท ๑๓๕, ๑๓๖/๒๕๖๒ คดีหมายเลขแดงที่ อท ๗๘, ๗๙/๒๕๖๓ ซึ่งศาลอาญาคดีทุจริตและประพฤติมิชอบภาค ๕ ได้วินิจฉัยว่า โครงการจัดทำเวทีประชาคมหมู่บ้านเพื่อเสริมสร้างความเข้มแข็งให้ชุมชน อำเภอ พญาเม็งราย จังหวัดเชียงราย ผู้ฟ้องคดีในฐานะประธานกรรมการตรวจการจ้างและตรวจรับพัสดุ ได้ลงนามรับรองเป็นหลักฐานว่า มีการส่งมอบการจ้างและพัสดุต่างๆ ถูกต้องครบถ้วน และยังสั่งการ ให้คณะกรรมการตรวจการจ้างและตรวจรับพัสดุคนอื่นที่เป็นสมาชิกอาสารักษาดินแดน ซึ่งเป็น ผู้ใต้บังคับบัญชาโดยตรงของผู้ฟ้องคดี ลงนามในเอกสารเพื่อใช้เป็นหลักฐานในการเบิกจ่ายเงิน ของทางราชการ ทั้งที่ข้อเท็จจริงไม่มีการจัดโครงการและไม่มีการตรวจการจ้างและตรวจรับพัสดุ ตามที่มีการเบิกจ่ายแต่อย่างใด และในส่วนของโครงการส่งเสริมการเกษตรแบบครบวงจร ผู้ฟ้องคดี มีเจตนาร่วมกับนายบุญยัง เรือนกูล ในการทำเอกสารหลักฐานการเบิกจ่ายอันเป็นเท็จ เนื่องจากไม่ปฏิบัติตามระเบียบสำนักนายกรัฐมนตรีว่าด้วยการพัสดุ พ.ศ. ๒๕๓๕ และที่แก้ไขเพิ่มเติม การกระทำของผู้ฟ้องคดีจึงเป็นความผิด ฐานเป็นเจ้าพนักงานปฏิบัติหรือละเว้นการปฏิบัติหน้าที่ โดยมิชอบ เพื่อให้เกิดความเสียหายแก่ผู้หนึ่งผู้ใด หรือปฏิบัติหรือละเว้นการปฏิบัติหน้าที่โดยทุจริต พิพากษาจำคุกผู้ฟ้องคดี 5 ปี\n\nผู้ถูกฟ้องคดีที่ ๒ ให้การในทำนองเดียวกับคำให้การของผู้ถูกฟ้องคดีที่ ๑ และ เพิ่มเติมว่า มูลเหตุคดีนี้เกิดจากอำเภอพญาเม็งรายได้จัดทำโครงการจัดทำเวทีประชาคม หมู่บ้านเพื่อเสริมสร้างความเข้มแข็งให้ชุมชนอำเภอพญาเม็งราย ปีงบประมาณ พ.ศ. ๒๕๕๘ เพื่อให้ ราษฎรมีส่วนร่วมในการป้องกันและเฝ้าระวังปัญหายาเสพติดในพื้นที่ ๕ ตำบล ๖๙ หมู่บ้าน ระยะเวลาดำเนินการตั้งแต่วันที่ ๑๕ ธันวาคม ๒๕๕๗ ถึงวันที่ ๓๑ มกราคม ๒๕๔๘ ได้รับการ สนับสนุนงบประมาณจากองค์การบริหารส่วนตำบล จำนวน ๕ แห่ง แห่งละ ๑๐,๐๐๐ บาท และเทศบาลตำบลพญาเม็งราย ๒๐,๐๐๐ บาท รวมเป็นเงิน ๗๐,๐๐๐ บาท โดยมีผู้ฟ้องคดี เป็นผู้เสนอโครงการ โดยผ่านการเห็นชอบจากนายประจญ ปรัชญ์สกุล ปลัดอำเภอหัวหน้าฝ่าย บริหารงานปกครอง และได้รับอนุมัติโครงการจากนายบุญยัง เรือนกูล นายอำเภอพญาเม็งราย และอำเภอพญาเม็งรายได้มีคำสั่ง ที่ ๓๙๖/๒๕๕๗ ลงวันที่ ๒๗ ธันวาคม ๒๕๕๗ แต่งตั้ง ดังกล่าว นางวัลลภาภรณ์ เรือนปาง เสมียนตราอำเภอพญาเม็งราย เป็นกรรมการรับเงินอุดหนุนโครงการ ต่อมา ผู้ฟ้องคดีได้เสนอขออนุมัติเปลี่ยนแปลงกำหนดการจัดทำโครงการการจัดทำเวที ประชาคมหมู่บ้าน โดยให้จัดประชุมให้แล้วเสร็จภายในวันที่ ๑๙ มกราคม ๒๕๕๘ ณ หอประชุม พญาเม็งราย ดังนี้ หมู่ที่ ๑ ๒๐ ตำบลแม่เปา วันที่ ๑๗ มกราคม ๒๕๔๗ หมู่ที่ ๑ - ๑๔ ตำบล เม็งราย วันที่ ๑๘ มกราคม ๒๕๕๗ หมู่ที่ ๑ ๑๑ ตำบลแม่ต่ำ วันที่ ๑๘ มกราคม ๒๕๕๗ หมู่ที่ ๑- ๑๘ ตำบลไม้ยา วันที่ ๑๙ มกราคม ๒๕๕๗ และหมู่ที่ ๑ ๘ ตำบลตาดควัน วันที่ ๑๙ มกราคม ๒๕๔๗ นายบุญยัง ได้ลงนามอนุมัติท้ายบันทึกดังกล่าว และมีผู้ฟ้องคดี นายพัฒน์ อินถา และนายทัศน์ ถาแก้ว เป็นคณะกรรมการตรวจรับพัสดุการดำเนินการจัดซื้อจัดจ้าง จำนวน ๓ รายการ ดังนี้ (๑) วัสดุแบบพิมพ์อบรมสร้างความเข้มแข็งให้ชุมชน และกระดาษถ่ายเอกสารจำนวน ๑๐ รีม รีมละ ๑๐๐ บาท เป็นเงิน ๑,๐๐๐ บาท จากการตรวจสอบข้อเท็จจริงพบว่า โครงการจัดทำเวที ประชาคมหมู่บ้านฯ ไม่มีการจัดเตรียมเอกสารหรือแจกจ่ายเอกสาร จึงไม่ได้มีการใช้วัสดุที่จัดซื้อ ให้เป็นไปตามวัตถุประสงค์ของโครงการ (๒) จ้างเหมาจัดทำอาหาร/อาหารว่าง เครื่องดื่ม ให้ผู้เข้าอบรมโครงการการจัดทำเวทีประชาคมหมู่บ้านฯ กับนายสว่าง วรรณสอน สมาชิก กองอาสารักษาดินแดน จากการตรวจสอบข้อเท็จจริงพบว่าไม่มีการจ้างนายสว่าง ผู้รับจ้าง ทำอาหารจริง และนายสว่างไม่มีอาชีพในการทำอาหาร ทั้งปรากฏหลักฐานการเบิกจ่ายซึ่งไม่มี รายละเอียดในการจัดหาอาหารในแต่ละวัน จำนวนอาหารในแต่ละวันมีความเหมาะสมกับราคาหรือไม่ เป็นการยากต่อการดำเนินการตรวจรับว่ามีการจัดจ้างตรงตามใบสั่งจ้างหรือไม่ (๓) จ้างเหมา จัดสถานที่และเช่าเครื่องเสียงจัดทำเวทีประชาคมฯ จำนวน ๓๐,๐๐๐ บาท กับนายสุวนันท์ ไชยานันตา สมาชิกกองอาสารักษาดินแดน จากการตรวจสอบข้อเท็จจริงพบว่า นายสุวนันท์ไม่มีอาชีพในการ จัดสถานที่หรือมีเครื่องเสียงให้เช่า ไม่มีการจ้างเหมาจัดสถานที่และเช่าเครื่องเสียง ไม่มีการ จ่ายเงินให้กับนายสุวนันท์จริง และเอกสารมีการดำเนินการไม่ถูกต้อง เช่น ใบส่งมอบงานระบุ วันที่ ๑๙ มกราคม ๒๕๕๙ และใบสำคัญรับเงินไม่ระบุวันที่ มกราคม ๒๕๕๐ เป็นต้น ส่วนที่ อำเภอพญาเม็งรายได้จัดทำโครงการจ่ายเงินอุดหนุนและการจัดซื้อวัสดุโครงการส่งเสริมเกษตร แบบครบวงจร จำนวน ๓๘๔,๐๐๐ บาท จากการตรวจสอบเอกสารปีงบประมาณ พ.ศ. ๒๕๔๗ ๒๕๔๘ พบว่า มีการจัดซื้อโดยแบ่งการจัดซื้อจำนวน 5 ครั้ง โดยแยกบันทึกเป็นแต่ละหมู่บ้านจำนวน 5 หมู่บ้าน ครั้งละ ๑๑ รายการ วงเงินจัดซื้อหมู่บ้านละ ๖๔,๐๐๐ บาท ตามใบสั่งซื้อเลขที่ ๗/๒๕๔๘ - ๑๒/๒๕๕๘ ลงวันที่ ๑๔ กุมภาพันธ์ ๒๕๕๘ รายการที่จัดซื้อประกอบด้วย (๑) เครื่องสกัดยาฆ่าแมลง (๒) เครื่องบดดิน (๓) ถังพ่นยา (๔) ถั่วฝักยาว (๕) ผักบุ้งจีน (๖) แตงกวา (๓) คะน้า (๘) กวางตุ้ง (๔) ผักชี (๑๐) ผักขึ้นฉ่าย ผักข (๑๑) บวบเหลี่ยม รวมเป็นเงินทั้งสิ้น ๓๘๔,๐๐๐ บาท เป็นรายการจัดซื้อวัสดุจากนายธนกร ไชยวรรณ์ โดยจัดซื้อในวิธีตกลงราคาให้กับ เกษตรกรในพื้นที่อำเภอพญาเม็งรายใน 5 หมู่บ้าน มีผู้ฟ้องคดี นายพัฒน์ อินถา และนายทัศน์ ถาแก้ว เป็นคณะกรรมการตรวจรับพัสดุ ซึ่งคณะกรรมการตรวจรับพัสดุได้ลงลายมือชื่อในใบตรวจรับ โดยเห็นว่ามีปริมาณและคุณภาพถูกต้อง พร้อมส่งมอบให้กับเจ้าหน้าที่พัสดุแล้ว แต่จากการ ตรวจสอบข้อเท็จจริงพบว่าโครงการส่งเสริมการเกษตรแบบครบวงจรไม่มีการจัดทำโครงการที่ระบุ รายละเอียดของวัตถุประสงค์ กลุ่มเป้าหมาย ผลที่ได้รับจากโครงการอันไม่มีจุดประสงค์ในการ ใช้งบประมาณ และในการขอซื้อวัสดุอุปกรณ์ในรายงานการขอซื้อ ไม่มีที่มาของราคาของวัสดุ และมีการจัดวัสดุชนิดเดียวกัน1 ๑๑ ๑๑ รายการ แยกเป็น 5 ครั้ง ในวันเดียวกัน อันทำให้อำนาจ ในการสั่งซื้อเปลี่ยนไปเป็นการแบ่งซื้อตามข้อ ๒๒ วรรคสอง ของระเบียบสำนักนายกรัฐมนตรี ว่าด้วยการพัสดุ พ.ศ. ๒๕๓๕ และที่แก้ไขเพิ่มเติม อีกทั้ง เมื่อตรวจสอบสำเนาเอกสารบัญชี รับพัสดุของที่ทำการปกครองอำเภอพญาเม็งราย ผู้ลงลายมือชื่อรับพัสดุแจ้งว่าลงลายมือชื่อตามที่ ที่ทำการปกครองอำเภอพญาเม็งรายให้ลงลายมือชื่อโดยไม่ได้ตรวจสอบ และบางรายยืนยันว่าไม่ใช่ ลายมือชื่อของตนและยืนยันว่าได้รับเฉพาะเครื่องสกัดยาฆ่าแมลงและเครื่องบดดิน ส่วนรายการอื่นๆ ไม่ได้รับ \n\nเมื่อผู้ถูกฟ้องคดีที่ ๑ ได้รับเรื่องกล่าวหา พิจารณาพฤติการณ์แห่งการกระทำ ความผิด และมีมติชี้มูลความผิดทางวินัยผู้ฟ้องคดีแล้ว จึงส่งรายงานสำนวนการสอบสวน เอกสารหลักฐาน พร้อมทั้งคำวินิจฉัยมายังผู้ถูกฟ้องคดีที่ ๒ ซึ่งเป็นผู้บังคับบัญชาของผู้ฟ้องคดี\n\nเพื่อพิจารณาโทษทางวินัยตามฐานความผิดที่ผู้ถูกฟ้องคดีที่ ๑ ได้มีมติ โดยไม่ต้องแต่งตั้ง คณะกรรมการสอบสวนวินัยอีก และให้ถือว่าสำนวนการไต่สวนของผู้ถูกฟ้องคดีที่ ๑ เป็นสำนวน การสอบสวนทางวินัยของคณะกรรมการสอบสวนวินัย ตามมาตรา ๔๘ แห่งพระราชบัญญัติ ขั้นตอน ประกอบรัฐธรรมนูญว่าด้วยการป้องกันและปราบปรามการทุจริต พ.ศ. ๒๕๖๑ การที่ผู้ถูกฟ้องคดีที่ ๒ มีคำสั่งกรมการปกครอง ที่ ๑๔๐๐/๒๕๖๓ ลงวันที่ ๕ สิงหาคม ๒๕๖๓ ลงโทษไล่ผู้ฟ้องคดี ออกจากราชการตามฐานความผิดที่ผู้ถูกฟ้องคดีที่ ๑ มีมติ เป็นการลงโทษตามแนวการลงโทษ ตามมติคณะรัฐมนตรี และระดับโทษเหมาะสมแก่กรณีกระทำผิดแล้ว โดยที่การพิจารณาโทษ ทางวินัยกับผู้ฟ้องคดีนั้น ผู้ถูกฟ้องคดีที่ ๒ ได้รับฟังข้อเท็จจริงถูกต้องตามหลักเกณฑ์ รูปแบบ หรือวิธีการอันเป็นสาระสำคัญเกี่ยวกับการลงโทษทางวินัยอย่างร้ายแรงตามที่กฎหมาย กำหนด แต่ผู้ถูกฟ้องคดีที่ ๒ ไม่สามารถวินิจฉัยกระบวนการไต่สวนหรือมติ มูลของผู้ถูกฟ้องคดีที่ ๑ เป็นอย่างอื่นได้ เนื่องจากสำนวนการไต่สวนของผู้ถูกฟ้องคดีที่ ๑ มีผลผูกพันผู้ถูกฟ้องคดีที่ ๒ ที่จะต้องพิจารณาโทษทางวินัยตามฐานความผิดที่ผู้ถูกฟ้องคดีที่ ๑ มีมติ ซึ่งผู้ถูกฟ้องคดีที่ ๒ ได้ดำเนินการตามหลักเกณฑ์ รูปแบบ ขั้นตอน หรือวิธีการอันเป็นสาระสำคัญถูกต้องตามที่ กฎหมายกำหนดแล้ว ดังนั้น คำสั่งกรมการปกครอง ที่ ๑๔๐๐/๒๕๖๓ ลงวันที่ ๕ สิงหาคม ๒๕๖๓ ลงโทษไล่ผู้ฟ้องคดีออกจากราชการ จึงเป็นคำสั่งที่ชอบด้วยกฎหมาย ไม่มีเหตุที่จะให้ ผู้ฟ้องคดีกลับเข้ารับราชการและคืนสิทธิประโยชน์ให้แก่ผู้ฟ้องคดีแต่อย่างใด\n\nผู้ถูกฟ้องคดีที่ ๓ ให้การในทำนองเดียวกับคำให้การของผู้ถูกฟ้องคดีที่ ๑ และ ผู้ถูกฟ้องคดีที่ ๓ ขอยืนยันตามวินิจฉัยของผู้ถูกฟ้องคดีที่ ๓ เรื่องคำที่ ผู้ถูกฟ้องคดีที่ ๒ และเพิ่มเติมว่า ๖๓๑๐๑๖๖ เรื่องแดงที่ ๑๐๔๘๑๖๕ ลงวันที่ ๕ กรกฎาคม ๒๕๖๕ ซึ่งผู้ถูกฟ้องคดีที่ ๓ ได้วินิจฉัยอุทธรณ์ของผู้ฟ้องคดีตามอำนาจหน้าที่ที่กฎหมายกำหนด และได้พิจารณาข้อเท็จจริง ตามพยานหลักฐานที่ปรากฏอย่างละเอียดรอบคอบ ปราศจากอคติ และให้ความเป็นธรรม แก่ผู้ฟ้องคดี ตามหลักเกณฑ์และวิธีการที่กฎ ก.พ.ค. ว่าด้วยการอุทธรณ์และการพิจารณา วินิจฉัยอุทธรณ์ พ.ศ. ๒๕๕๑ แล้ว ดังนั้น การที่ผู้ถูกฟ้องคดีที่ ๓ มีคำวินิจฉัยยกอุทธรณ์ของผู้ฟ้องคดี จึงเป็นการกระทำที่ถูกต้องและชอบด้วยกฎหมายแล้ว\n\nศาลปกครองสูงสุดออกนั่งพิจารณาคดีครั้งแรกเมื่อวันที่ 4 มีนาคม ๒๕๖๙ โดยได้รับฟังสรุปข้อเท็จจริงของตุลาการเจ้าของสำนวน พิจารณาคำชี้แจงด้วยวาจาประกอบ คำแถลงการณ์เป็นหนังสือของตุลาการผู้แถลงคดี\n\nศาลปกครองสูงสุดได้ตรวจพิจารณาพยานหลักฐานในคำฟ้อง คำให้การ คำคัดค้านคำให้การ คำให้การเพิ่มเติม แล้ว\n\nข้อเท็จจริงรับฟังได้ว่า เดิมผู้ฟ้องคดีรับราชการเป็นข้าราชการพลเรือนสามัญ ตำแหน่งปลัดอำเภอ (เจ้าพนักงานปกครองชำนาญการพิเศษ) ที่ทำการปกครองอำเภอแม่สาย จังหวัดเชียงราย กรมการปกครอง ได้รับความเดือดร้อนเสียหายจากการที่ผู้ถูกฟ้องคดีที่ ๒ มีคำสั่งกรมการปกครอง ที่ ๑๔๐๐/๒๕๖๓ ลงวันที่ ๕ สิงหาคม ๒๕๖๓ ลงโทษไล่ผู้ฟ้องคดี ออกจากราชการ ตั้งแต่วันที่ ๒๓ พฤษภาคม ๒๕๖๓ เป็นต้นไป กรณีเมื่อครั้งผู้ฟ้องคดีดำรงตำแหน่ง ปลัดอำเภอ (เจ้าพนักงานปกครอง 5) ที่ทำการปกครองอำเภอพญาเม็งราย จังหวัดเชียงราย ถูกกล่าวหาว่ากระทำการทุจริตโครงการส่งเสริมการเกษตรแบบครบวงจร และโครงการจัดทำ เวทีประชาคมหมู่บ้านเพื่อเสริมสร้างความเข้มแข็งให้ชุมชนอำเภอพญาเม็งราย จังหวัดเชียงราย ซึ่งผู้ฟ้องคดีมีอำนาจหน้าที่เป็นคณะกรรมการตรวจรับพัสดุในโครงการดังกล่าว กระทำการ จัดทำเอกสารหลักฐานการเบิกจ่ายเงินอันเป็นเท็จและลงลายมือชื่อตรวจรับพัสดุอันเป็นเท็จ ซึ่งเป็นการไม่ปฏิบัติตามข้อ ๗๑ ของระเบียบสำนักนายกรัฐมนตรีว่าด้วยการพัสดุ พ.ศ. ๒๕๓๕ และที่แก้ไขเพิ่มเติม ผู้ถูกฟ้องคดีที่ ๑ ในการประชุมครั้งที่ ๑๐๕/๒๕๖๒ เมื่อวันที่4 ตุลาคม ๒๕๖๒ จึงมีมติชี้มูลความผิดอาญา ฐานเป็นเจ้าพนักงานปฏิบัติหรือละเว้นการปฏิบัติหน้าที่ โดยมิชอบ เพื่อให้เกิดความเสียหายแก่ผู้หนึ่งผู้ใด หรือปฏิบัติหรือละเว้นการปฏิบัติหน้าที่ โดยทุจริต ตามมาตรา ๑๕๗ แห่งประมวลกฎหมายอาญา และความผิดทางวินัยอย่างร้ายแรง ฐานปฏิบัติหรือละเว้นการปฏิบัติหน้าที่ราชการโดยมิชอบ เพื่อให้ตนเองหรือผู้อื่นได้ประโยชน์ ที่มีควรได้ เป็นการทุจริตต่อหน้าที่ราชการ ฐานปฏิบัติหน้าที่ราชการโดยจงใจไม่ปฏิบัติตาม กฎหมาย ระเบียบของทางราชการ มติคณะรัฐมนตรี หรือนโยบายของรัฐบาล อันเป็นเหตุให้ เสียหายแก่ราชการอย่างร้ายแรง และฐานกระทำการอื่นใดอันได้ชื่อว่าเป็นผู้ประพฤติชั่วอย่างร้ายแรง ตามมาตรา ๘๒ วรรคสาม มาตรา ๘๕ วรรคสอง และมาตรา ๙๘ วรรคสอง แห่งพระราชบัญญัติ ระเบียบข้าราชการพลเรือน พ.ศ. ๒๕๓๕ ประกอบมาตรา ๑๓๓ แห่งพระราชบัญญัติระเบียบ ข้าราชการพลเรือน พ.ศ. ๒๕๕๑ ผู้ถูกฟ้องคดีที่ ๑ จึงมีหนังสือสำนักงาน ป.ป.ช. ที่ ปช ๐๑.๐๑ (ชร) ๑๔๔๒ ลงวันที่ 6 พฤศจิกายน ๒๕๖๒ ส่งรายงาน สำนวนการไต่สวน เอกสารหลักฐาน และคำวินิจฉัย ซึ่งเป็นผู้บังคับบัญชาและผู้มีอำนาจบรรจุและแต่งตั้งผู้ฟ้องคดี ไปยังผู้ถูกฟ้องคดีที่ ๒ ตามมาตรา ๕๗ (๔) แห่งพระราชบัญญัติระเบียบข้าราชการพลเรือน พ.ศ. ๒๕๕๑ ผู้ถูกฟ้องคดีที่ ๒ เสนอเรื่องให้ อ.ก.พ. กระทรวงมหาดไทย พิจารณา ซึ่ง อ.ก.พ. กระทรวงมหาดไทย มีมติในการประชุม ครั้งที่ ๒/๒๕๖๓ เมื่อวันที่ ๑๘ มิถุนายน ๒๕๖๓ ให้ลงโทษไล่ผู้ฟ้องคดีออกจากราชการตามฐาน ความผิดที่ผู้ถูกฟ้องคดีที่ ๑ ชี้มูลความผิด และตามมติคณะรัฐมนตรี เมื่อวันที่ ๒๑ ธันวาคม ๒๕๓๖ แจ้งตามหนังสือสำนักเลขาธิการคณะรัฐมนตรี ที่ นร ๐๒๐๕/ว ๒๓๔ ลงวันที่ ๒๔ ธันวาคม ๒๕๓๖ ผู้ถูกฟ้องคดีที่ ๒ จึงมีคำสั่งกรมการปกครอง ที่ ๑๔๐๐/๒๕๖๓ ลงวันที่ ๕ สิงหาคม\n\n๒๕๖๓ ลงโทษไล่ผู้ฟ้องคดีออกจากราชการ ตั้งแต่วันที่ ๒๓ พฤษภาคม ๒๕๖๓ เป็นต้นไป ซึ่งเป็นวันที่ผู้ฟ้องคดีถูกพักราชการไว้ก่อน ต่อมา ผู้ฟ้องคดีมีหนังสือลงวันที่ ๑๕ กันยายน ๒๕๖๓ อุทธรณ์คำสั่งดังกล่าวต่อผู้ถูกฟ้องคดีที่ ๓ ผู้ถูกฟ้องคดีที่ ๓ พิจารณาแล้วจึงมีคำวินิจฉัย เรื่องดำที่ ผู้ฟ้องคดี ๖๓๑๐๑๖๖ เรื่องแดงที่ ๐๐๔๘๑๖๕ ลงวันที่ ๕ กรกฎาคม ๒๕๖๕ ให้ยกอุทธรณ์ และมีหนังสือ ที่ นร ๑๐๑๐.๓.๑.๔/๒๕ ลงวันที่ 4 กรกฎาคม ๒๕๖๕ แจ้งคำวินิจฉัยดังกล่าว ให้ผู้ฟ้องคดีทราบ ผู้ฟ้องคดีไม่เห็นพ้องด้วย เนื่องจากผู้ฟ้องคดีปฏิบัติตามคำสั่งของผู้บังคับบัญชา ไม่มีเจตนาทุจริต ไม่มีพยานหลักฐานว่าผู้ฟ้องคดีทุจริต ผู้ถูกฟ้องคดีที่ ๑ จึงไม่มีอำนาจชี้มูล จึงนำคดีมาฟ้องต่อศาลปกครองสูงสุด\n\nศาลปกครองสูงสุดได้ตรวจพิจารณากฎหมาย ระเบียบ และข้อบังคับที่เกี่ยวข้อง ประกอบด้วยแล้ว\n\nคดีมีประเด็นที่จะต้องวินิจฉัยว่า คำวินิจฉัยเรื่องดำที่ ๖๓๑๐๑๖๖ เรื่องแดง ที่ ๑๐๔๘๑๖๕ ลงวันที่ ๕ กรกฎาคม ๒๕๖๕ ให้ยกอุทธรณ์ของผู้ฟ้องคดี เป็นการกระทำการ โดยชอบด้วยกฎหมายหรือไม่ โดยคดีมีปัญหาต้องวินิจฉัยก่อนว่า คำสั่งกรมการปกครอง ที่ ๑๔๐๐/๒๕๖๓ ลงวันที่ ๕ สิงหาคม ๒๕๖๓ ที่ลงโทษไล่ผู้ฟ้องคดีออกจากราชการ ตามมติ ของผู้ถูกฟ้องคดีที่ ๑ ในการประชุมครั้งที่ ๑๐๕/๒๕๖๒ เมื่อวันที่ 6 ตุลาคม ๒๕๖๒ เป็นการ กระทำโดยถูกต้องตามรูปแบบขั้นตอน หรือวิธีการอันเป็นสาระสำคัญที่กำหนดไว้สำหรับ การกระทำนั้นหรือไม\n\nพิเคราะห์แล้วเห็นว่า พระราชบัญญัติประกอบรัฐธรรมนูญว่าด้วยการป้องกัน และปราบปรามการทุจริต พ.ศ. ๒๕๖๑ มาตรา ๔ บัญญัติว่า “เจ้าหน้าที่ของรัฐ” หมายความว่า ข้าราชการหรือพนักงานส่วนท้องถิ่นซึ่งมีตำแหน่งหรือเงินเดือนประจำ ผู้ปฏิบัติงานในหน่วยงาน ของรัฐหรือในรัฐวิสาหกิจ ผู้บริหารท้องถิ่น รองผู้บริหารท้องถิ่น ผู้ช่วยผู้บริหารท้องถิ่น และ สมาชิกสภาท้องถิ่นขององค์กรปกครองส่วนท้องถิ่น เจ้าพนักงานตามกฎหมายว่าด้วยลักษณะ ปกครองท้องที่ หรือเจ้าพนักงานอื่นตามที่กฎหมายบัญญัติ และให้หมายความรวมถึงกรรมการ อนุกรรมการ ลูกจ้างของส่วนราชการ หน่วยงานของรัฐ หรือรัฐวิสาหกิจ และบุคคลหรือคณะบุคคล บรรดาซึ่งมีกฎหมายกำหนดให้ใช้อำนาจหรือได้รับมอบให้ใช้อำนาจทางปกครองที่จัดตั้งขึ้น ในระบบราชการ รัฐวิสาหกิจ หรือกิจการอื่นของรัฐด้วย แต่ไม่รวมถึงผู้ดำรงตำแหน่งทางการเมือง ตุลาการศาลรัฐธรรมนูญ ผู้ดำรงตำแหน่งในองค์กรอิสระ และคณะกรรมการ ป.ป.ช. มาตรา ๔๘ วรรคหนึ่ง บัญญัติว่า เมื่อความปรากฏต่อคณะกรรมการ ป.ป.ช. ไม่ว่าจะมีการกล่าวหาหรือไม่ ว่ามีการกระทำความผิดที่อยู่ในหน้าที่และอำนาจของคณะกรรมการ ป.ป.ช. ให้คณะกรรมการ ป.ป.ช. ดำเนินการตามหน้าที่และอำนาจโดยพลัน โดยในกรณีที่จำเป็นต้องมีการไต่สวน ต้องไต่สวน\n\nและมีความเห็นหรือวินิจฉัยให้แล้วเสร็จภายในกำหนดเวลาที่คณะกรรมการ ป.ป.ช. กำหนด ซึ่งต้องไม่เกินสองปีนับแต่วันเริ่มดำเนินการไต่สวน มาตรา ๔๑ บัญญัติว่า เมื่อคณะกรรมการ ป.ป.ช. ไต่สวนแล้วมีมติวินิจฉัยว่าเจ้าหน้าที่ของรัฐกระทำความผิดฐานทุจริตต่อหน้าที่ หรือ กระทำความผิดต่อตำแหน่งหน้าที่ราชการ หรือความผิดต่อตำแหน่งหน้าที่ในการยุติธรรม หรือ ความผิดที่เกี่ยวข้องกันให้ดำเนินการดังต่อไปนี้.. (๒) ถ้ามีมูลความผิดทางวินัย ให้คณะกรรมการ ป.ป.ช. ส่งรายงาน สำนวนการไต่สวน เอกสารหลักฐาน และคำวินิจฉัยไปยังผู้บังคับบัญชาหรือผู้มีอำนาจ แต่งตั้งถอดถอนภายในสามสิบวันเพื่อให้ดำเนินการทางวินัยต่อไป มาตรา ๔๘ วรรคหนึ่ง บัญญัติว่า เมื่อผู้บังคับบัญชาหรือผู้มีอำนาจแต่งตั้งถอดถอนผู้ถูกกล่าวหาได้รับสำนวนการไต่สวน ของคณะกรรมการ ป.ป.ช. ตามมาตรา ๑ แล้ว ให้ผู้บังคับบัญชาหรือผู้มีอำนาจแต่งตั้งถอดถอน ผู้ถูกกล่าวหาผู้นั้นพิจารณาโทษทางวินัยตามฐานความผิดที่คณะกรรมการ ป.ป.ช. ได้มีมติโดยไม่ต้อง แต่งตั้งคณะกรรมการสอบสวนวินัยอีก โดยในการพิจารณาโทษทางวินัยแก่ผู้ถูกกล่าวหา ให้ถือว่า สำนวนการไต่สวนของคณะกรรมการ ป.ป.ช. เป็นสำนวนการสอบสวนทางวินัยของคณะกรรมการ สอบสวนวินัยตามกฎหมาย ระเบียบ หรือข้อบังคับว่าด้วยการบริหารงานบุคคลของผู้ถูกกล่าวหานั้น แล้วแต่กรณี\n\nจากบทบัญญัติแห่งกฎหมายดังกล่าว การที่ผู้ถูกฟ้องคดีที่ ๑ จะใช้อำนาจในการ ไต่สวนและวินิจฉัยชี้มูลความผิดที่เกี่ยวข้องกันตามกฎหมายว่าด้วยการป้องกันและปราบปราม การทุจริตได้จะต้องเป็นกรณีที่เจ้าหน้าที่ของรัฐกระทำความผิดฐานทุจริตต่อหน้าที่ หรือกระทำ ความผิดต่อตำแหน่งหน้าที่ราชการ หรือความผิดต่อตำแหน่งหน้าที่ในการยุติธรรมเสียก่อน หากมีความผิดอื่นที่เกี่ยวข้องกับความผิดมูลฐานดังกล่าว ผู้ถูกฟ้องคดีที่ ๑ จึงจะมีอำนาจชี้มูล ความผิดทางวินัยอันเป็นความผิดที่เกี่ยวข้องกันได้ ข้อเท็จจริงปรากฏว่า เมื่อสำนักงานการ ดังกล่าว ตรวจเงินแผ่นดิน โดยสำนักตรวจสอบพิเศษ ภาค ๘ มีหนังสือ ที่ ตผ ๐๐๕๐ ชม/๐๐๑ ลงวันที่ ๙ มกราคม ๒๕๖๐ ถึงผู้ถูกฟ้องคดีที่ ๑ เรื่อง การตรวจสอบการใช้จ่ายเงินที่ได้รับอุดหนุน ของที่ทำการปกครองอำเภอพญาเม็งราย จังหวัดเชียงราย ผู้ถูกฟ้องคดีที่ ๑ ได้รับเรื่องกล่าวหา และมีมติในการประชุมครั้งที่ ๑๐๐๙ - ๘๐/๒๕๖๑ เมื่อวันที่ 4 กันยายน ๒๕๖๑ แต่งตั้งคณะกรรมการไต่สวนเพื่อดำเนินการไต่สวน ตามมาตรา ๕๑ แห่งพระราชบัญญัติ ประกอบรัฐธรรมนูญว่าด้วยการป้องกันและปราบปรามการทุจริต พ.ศ. ๒๕๖๑ คณะกรรมการไต่สวน ได้แจ้งข้อกล่าวหาให้ผู้ฟ้องคดีทราบ ผู้ฟ้องคดีรับทราบข้อกล่าวหาและเข้าใจข้อกล่าวหา ตลอดจน ได้ชี้แจงแก้ข้อกล่าวหา ซึ่งคณะกรรมการไต่สวนได้รวบรวมข้อเท็จจริงและพยานหลักฐาน ทั้งหมดแล้วเห็นว่ามีพยานหลักฐานเพียงพอที่จะสนับสนุนข้อกล่าวหา จึงได้จัดทำรายงานและ สำนวนไต่สวนพร้อมทั้งทำความเห็นเสนอต่อผู้ถูกฟ้องคดีที่ ๑ ผู้ถูกฟ้องคดีที่ ๑ พิจารณาแล้ว\n\nมีมติในการประชุมครั้งที่ ๑๐๕/๒๕๖๒ เมื่อวันที่ ๘ ตุลาคม ๒๕๖๒ ว่า การกระทำของผู้ฟ้องคดี มีมูลความผิดทางอาญา ตามมาตรา ๑๕๗ แห่งประมวลกฎหมายอาญา และความผิดทางวินัย อย่างร้ายแรง ฐานปฏิบัติหรือละเว้นการปฏิบัติหน้าที่ราชการโดยมิชอบ เพื่อให้ตนเองหรือผู้อื่น ได้ประโยชน์ที่มีควรได้ เป็นการทุจริตต่อหน้าที่ราชการ ฐานปฏิบัติหน้าที่ราชการโดยจงใจ ไม่ปฏิบัติตามกฎหมาย ระเบียบของทางราชการ มติคณะรัฐมนตรี หรือนโยบายของรัฐบาล อันเป็นเหตุให้เสียหายแก่ราชการอย่างร้ายแรง และฐานกระทำการอื่นใดอันได้ชื่อว่าเป็น ผู้ประพฤติชั่วอย่างร้ายแรง ตามมาตรา ๘๒ วรรคสาม มาตรา ๘๕ วรรคสอง และมาตรา ๔๘ วรรคสอง แห่งพระราชบัญญัติระเบียบข้าราชการพลเรือน พ.ศ. ๒๕๓๕ ประกอบมาตรา ๑๓๓ แห่งพระราชบัญญัติระเบียบข้าราชการพลเรือน พ.ศ. ๒๕๕๑ จึงเห็นได้ว่า ผู้ถูกฟ้องคดีที่ ๑ ไต่สวน ข้อเท็จจริงและมีมติชี้มูลความผิดทางวินัยผู้ฟ้องคดีในการกระทำความผิดฐานทุจริตต่อหน้าที่แล้ว ผู้ถูกฟ้องคดีที่ ๑ จึงมีอำนาจชี้มูลความผิดทางวินัยผู้ฟ้องคดีในฐานอื่นที่เกี่ยวข้องกันและส่งเรื่องไปยัง ผู้ถูกฟ้องคดีที่ 6 ซึ่งเป็นผู้บังคับบัญชาหรือผู้มีอำนาจแต่งตั้งถอดถอนผู้ฟ้องคดีเพื่อให้ดำเนินการ ทางวินัยได้ โดยไม่ต้องแต่งตั้งคณะกรรมการสอบสวนวินัยอีก และให้ถือว่าสำนวนการไต่สวนของ ผู้ถูกฟ้องคดีที่ ๑ เป็นสำนวนการสอบสวนทางวินัยของคณะกรรมการสอบสวนวินัย ตามมาตรา ๙๑ (๒) และมาตรา ๔๘ วรรคหนึ่ง แห่งพระราชบัญญัติประกอบรัฐธรรมนูญว่าด้วยการป้องกันและ ปราบปรามการทุจริต พ.ศ. ๒๕๖๑ ที่ผู้ฟ้องคดีอ้างว่า ผู้ถูกฟ้องคดีที่ ๑ มีอำนาจไต่สวนและชี้มูล ความผิดทางวินัยเฉพาะความผิดฐานทุจริตต่อหน้าที่ราชการเท่านั้น เมื่อผู้ฟ้องคดีไม่ได้กระทำ ความผิดฐานทุจริตต่อหน้าที่ ผู้ถูกฟ้องคดีที่ ๑ จึงไม่มีอำนาจไต่สวนและชี้มูลความผิดผู้ฟ้องคดี และไม่ผูกพันผู้ถูกฟ้องคดีที่ ๒ ให้ต้องถือรายงานการไต่สวนของผู้ถูกฟ้องคดีที่ ๑ เป็นรายงาน การสอบสวน โดยผู้ถูกฟ้องคดีที่ ๑ ต้องดำเนินการแต่งตั้งคณะกรรมการสอบสวนความผิดทางวินัย เพื่อสอบสวนข้อเท็จจริง นั้น จึงฟังไม่ขึ้น นอกจากนี้ เมื่อพิจารณามาตรา ๑๙๒ แห่งพระราชบัญญัติ ประกอบรัฐธรรมนูญดังกล่าว ซึ่งบัญญัติว่า การดำเนินการตรวจรับคำกล่าวหา แสวงหาข้อเท็จจริง และรวบรวมพยานหลักฐาน ไต่สวนและมีความเห็นหรือวินิจฉัย ตามพระราชบัญญัติประกอบ รัฐธรรมนูญว่าด้วยการป้องกันและปราบปรามการทุจริต พ.ศ. ๒๕๔๒ และที่แก้ไขเพิ่มเติม ในวันก่อนวันที่พระราชบัญญัติประกอบรัฐธรรมนูญนี้ใช้บังคับให้เป็นอันใช้ได้และให้ดำเนินการ ต่อไป โดยการดำเนินการดังกล่าวต้องดำเนินการให้แล้วเสร็จตามมาตรา ๔๘ กล่าวคือ ต้องไม่เกิน ๒๕๖๑ สองปีนับแต่วันเริ่มดำเนินการไต่สวน การที่ผู้ถูกฟ้องคดีที่ ๑ มีคำสั่งให้ไต่สวนเมื่อวันที่ 4 กันยายน จนถึงผู้ถูกฟ้องคดีที่ ๑ มีมติชี้มูลความผิดของผู้ฟ้องคดีในการประชุมครั้งที่ ๑๐๕/๒๕๖๒ เมื่อวันที่ ๘ ตุลาคม ๒๕๖๒ จึงยังไม่พ้นกำหนดระยะเวลาสองปี ตามมาตรา ๔๘ ประกอบมาตรา ๑๙๒ แห่งพระราชบัญญัติประกอบรัฐธรรมนูญว่าด้วยการป้องกันและปราบปรามการทุจริต พ.ศ. ๒๕๖๑\n\nที่ผู้ฟ้องคดีอ้างว่าผู้ถูกฟ้องคดีที่ ๑ ใช้เวลาในการดำเนินการไต่สวนประมาณสามปีเศษ ล่วงเลย ต่อมา เมื่อผู้ถูกฟ้องคดีที่ ระยะเวลาตามที่กฎหมายกำหนดไว้โดยไม่มีเหตุจำเป็น และผู้ถูกฟ้องคดีที่ ๑ ไม่ได้ขอขยาย ระยะเวลา อันเป็นการไม่ปฏิบัติตามพระราชบัญญัติประกอบรัฐธรรมนูญว่าด้วยการป้องกัน และปราบปรามการทุจริต พ.ศ. ๒๕๖๑ มาตรา ๔๘ ทำให้มติการชี้มูลความผิดของผู้ถูกฟ้องคดีที่ ๑ ไม่มีผลผูกพันต่อผู้ถูกฟ้องคดีที่ ๒ ที่จะดำเนินการลงโทษทางวินัยแก่ผู้ฟ้องคดี นั้น จึงฟังไม่ขึ้น ๒ ซึ่งเป็นผู้บังคับบัญชามีอำนาจบรรจุและแต่งตั้งผู้ฟ้องคดี ตามมาตรา ๕๗ (๔) แห่งพระราชบัญญัติระเบียบข้าราชการพลเรือน พ.ศ. ๒๕๕๑ ได้รับหนังสือสำนักงาน ป.ป.ช. ที่ ปช ๐๑.๐๑ (ชร/๑๔๔๒ ลงวันที่ 4 พฤศจิกายน ๒๕๖๒ ส่งรายงาน สำนวนการไต่สวน เอกสารหลักฐาน และคำวินิจฉัยแล้ว จึงส่งเรื่องให้ อ.ก.พ. กระทรวงมหาดไทย พิจารณาโทษทางวินัยผู้ฟ้องคดี ตามมาตรา ๔๗ วรรคสอง แห่งพระราชบัญญัติระเบียบข้าราชการพลเรือน พ.ศ. ๒๕๕๑ ซึ่ง อ.ก.พ. กระทรวงมหาดไทย ได้มีมติในการประชุมครั้งที่ ๒/๒๕๖๓ เมื่อวันที่ ๑๘ มิถุนายน ๒๕๖๓ ว่า พฤติการณ์ของผู้ฟ้องคดีเป็นความผิดวินัยอย่างร้ายแรงตามฐานความผิดที่ผู้ถูกฟ้องคดีที่ ๑ มีมติ ชี้มูลความผิด และตามมติคณะรัฐมนตรี เมื่อวันที่ ๒๑ ธันวาคม ๒๕๓๖ แจ้งตามหนังสือสำนัก เลขาธิการคณะรัฐมนตรี ที่ นร ๐๒๐๕/ว ๒๓๔ ลงวันที่ ๒๔ ธันวาคม ๒๕๓๖ ที่กำหนดให้ลงโทษ ผู้กระทำผิดวินัยฐานทุจริตต่อหน้าที่ราชการเป็นความผิดวินัยอย่างร้ายแรงควรลงโทษไล่ออก ที่ จากราชการ ให้ลงโทษไล่ผู้ฟ้องคดีออกจากราชการ ผู้ถูกฟ้องคดีที่ ๒ จึงมีคำสั่งกรมการปกครอง ๑๔๐๐/๒๕๖๓ ลงวันที่ 4 สิงหาคม ๒๕๖๓ ลงโทษไล่ผู้ฟ้องคดีออกจากราชการ ตั้งแต่วันที่ ๒๓ พฤษภาคม ๒๕๖๓ เป็นต้นไป ซึ่งเป็นวันที่ผู้ฟ้องคดีถูกพักราชการไว้ก่อน เห็นได้ว่า กระบวนการออกคำสั่งลงโทษไล่ผู้ฟ้องคดีออกจากราชการดังกล่าวเป็นการกระทำโดยถูกต้อง ตามรูปแบบขั้นตอน หรือวิธีการอันเป็นสาระสำคัญที่กำหนดไว้สำหรับการกระทำนั้นแล้ว\n\nคดีมีปัญหาต้องวินิจฉัยต่อไปว่า ผู้ฟ้องคดีกระทำผิดวินัยอย่างร้ายแรง ตามที่ ผู้ถูกฟ้องคดีที่๑ ชี้มูลหรือไม่\n\nพิเคราะห์แล้วเห็นว่า พระราชบัญญัติระเบียบบริหารราชการแผ่นดิน พ.ศ. ๒๕๓๔ มาตรา ๒๓ บัญญัติว่า ในอำเภอหนึ่ง นอกจากจะมีนายอำเภอเป็นผู้ปกครองบังคับบัญชาและ รับผิดชอบดังกล่าวในมาตรา ๒๒ ให้มีปลัดอำเภอและหัวหน้าส่วนราชการประจำอำเภอ บัญญัติว่า ซึ่งกระทรวง ทบวง กรมต่าง ๆ ส่งมาประจำให้ปฏิบัติหน้าที่เป็นผู้ช่วยเหลือนายอำเภอ และ มีอำนาจบังคับบัญชาข้าราชการฝ่ายบริหารส่วนภูมิภาคซึ่งสังกัดกระทรวง ทบวง กรมนั้น ในอำเภอนั้น พระราชบัญญัติระเบียบข้าราชการพลเรือน พ.ศ. ๒๕๓๕ มาตรา ๑๒ วรรคสาม การปฏิบัติหรือละเว้นการปฏิบัติหน้าที่ราชการโดยมิชอบ เพื่อให้ตนเองหรือผู้อื่น ได้ประโยชน์ที่มีควรได้ เป็นการทุจริตต่อหน้าที่ราชการและเป็นความผิดวินัยอย่างร้ายแรง\n\nมาตรา ๘๕ วรรคสอง บัญญัติว่า การปฏิบัติหน้าที่ราชการโดยจงใจไม่ปฏิบัติตามกฎหมาย ระเบียบของทางราชการ มติคณะรัฐมนตรี หรือนโยบายของรัฐบาล อันเป็นเหตุให้เสียหายแก่ ราชการอย่างร้ายแรง เป็นความผิดวินัยอย่างร้ายแรง และมาตรา ๔๘ วรรคสอง บัญญัติว่า การกระทำความผิดอาญาจนได้รับโทษจำคุก หรือโทษที่หนักกว่าจำคุกโดยคำพิพากษาถึงที่สุด ให้จำคุก หรือให้รับโทษที่หนักกว่าจำคุก เว้นแต่เป็นโทษสำหรับความผิดที่ได้กระทำโดยประมาท หรือความผิดลหุโทษ หรือกระทำการอื่นใดอันได้ชื่อว่าเป็นผู้ประพฤติชั่วอย่างร้ายแรงเป็นความผิด วินัยอย่างร้ายแรง พระราชบัญญัติระเบียบข้าราชการพลเรือน พ.ศ. ๒๕๕๑ มาตรา ๑๓๓ บัญญัติว่า ข้าราชการพลเรือนผู้ใดมีกรณีกระทำผิดวินัยหรือกรณีที่สมควรให้ออกจากราชการ อยู่ก่อนวันที่บทบัญญัติในลักษณะ ๔ ข้าราชการพลเรือนสามัญ และลักษณะ ๕ ข้าราชการ พลเรือนในพระองค์ แห่งพระราชบัญญัตินี้ใช้บังคับ ให้ผู้บังคับบัญชาตามพระราชบัญญัตินี้มีอำนาจ สั่งลงโทษผู้นั้นหรือสั่งให้ผู้นั้นออกจากราชการตามกฎหมายว่าด้วยระเบียบข้าราชการพลเรือน ที่ใช้อยู่ในขณะนั้น ส่วนการสอบสวน การพิจารณาและการดำเนินการเพื่อลงโทษหรือให้ออก จากราชการ ให้ดำเนินการตามพระราชบัญญัตินี้... ระเบียบสำนักนายกรัฐมนตรีว่าด้วยการพัสดุ พ.ศ. ๒๕๓๕ ข้อ ๗๑ กำหนดว่า คณะกรรมการตรวจรับพัสดุ มีหน้าที่ดังนี้... (๒) ตรวจรับพัสดุ ให้ถูกต้องครบถ้วนตามหลักฐานที่ตกลงกันไว้... ... ในกรณีที่เห็นว่าพัสดุที่ส่งมอบ มีรายละเอียด ไม่เป็นไปตามข้อกำหนดในสัญญาหรือข้อตกลง ให้รายงานหัวหน้าส่วนราชการผ่านหัวหน้าเจ้าหน้าที่ พัสดุ เพื่อทราบหรือสั่งการ แล้วแต่กรณี\n\nข้อเท็จจริงปรากฏว่า ขณะผู้ฟ้องคดีดำรงตำแหน่งปลัดอำเภอ (เจ้าพนักงาน ปกครอง 5) ที่ทำการปกครองอำเภอพญาเม็งราย จังหวัดเชียงราย มีหน้าที่เป็นผู้ช่วยเหลือ นายอำเภอ และมีอำนาจบังคับบัญชาข้าราชการฝ่ายบริหารส่วนภูมิภาคซึ่งสังกัดกระทรวง ทบวง กรมนั้น ในอำเภอนั้น ตามมาตรา ๖๓ แห่งพระราชบัญญัติระเบียบบริหารราชการแผ่นดิน พ.ศ. ๒๕๓๔ ที่ทำการปกครองอำเภอพญาเม็งรายได้รับการสนับสนุนเงินงบประมาณประจำปี พ.ศ. ๒๕๔๘ โดยนายอำเภอพญาเม็งรายได้แต่งตั้งให้ผู้ฟ้องคดีเป็นประธานกรรมการตรวจรับพัสดุ จำนวน ๒ โครงการ คือ โครงการจัดทำเวทีประชาคมหมู่บ้านเพื่อเสริมสร้างความเข้มแข็ง ให้ชุมชนอำเภอพญาเม็งราย จังหวัดเชียงราย และโครงการส่งเสริมการเกษตรแบบครบวงจร สำหรับโครงการจัดทำเวทีประชาคมหมู่บ้านเพื่อเสริมสร้างความเข้มแข็งให้ชุมชนอำเภอพญาเม็งราย จังหวัดเชียงราย ได้รับเงินงบประมาณและเงินอุดหนุนจากองค์การบริหารส่วนตำบลในอำเภอพญา เม็งราย ๕ อบต. เป็นเงิน ๙๐,๐๐๐ บาท โดยมีผู้ฟ้องคดีเป็นผู้เสนอโครงการซึ่งมีวัตถุประสงค์เพื่อให้ราษฎร มีส่วนร่วมในการป้องกันและเฝ้าระวังปัญหายาเสพติดในพื้นที่ ๕ ตำบล ๖๙ หมู่บ้าน ระหว่างวันที่ ๑๕ ธันวาคม ๒๕๔๗ ถึงวันที่ ๓๑ มกราคม ๒๕๔๘ ต่อมา ผู้ฟ้องคดีได้มีหนังสือ ไม่ระบุวันที่ เดือนมกราคม ๒๕๕๘ ขออนุมัติเปลี่ยนแปลงกำหนดการจัดทำเวทีประชาคมหมู่บ้านฯ โดยจัดประชุม เวทีประชาคมหมู่บ้านในวันที่ ๑๗ - ๑๙ มกราคม ๒๕๕๘ ณ หอประชุมพญาเม็งราย โครงการดังกล่าว มีการจัดซื้อจัดจ้างรวม ๓ รายการ คือ (๑) วัสดุแบบพิมพ์อบรมสร้างความเข้มแข็งให้ชุมชน และกระดาษถ่ายเอกสาร จำนวน ๑๐ รีม รีมละ ๑๐๐ บาท เป็นเงิน ๑,๐๐๐ บาท จากนางเจรุมาศ อนุสถิตวัฒนา ผู้ขาย (๒) จ้างเหมาจัดทำอาหาร อาหารว่าง และเครื่องดื่ม สำหรับผู้เข้าอบรม ในวันที่ ๑๗ ๑๙ มกราคม ๒๕๔๘ จำนวน ๕๙ หมู่บ้าน หมู่บ้านละ ๒๐ คน คนละ ๕๐ บาท เป็นเงิน ๕๔,๐๐๐ บาท จากนายสว่าง วรรณสอน ผู้รับจ้าง (๓) จ้างเหมาจัดสถานที่และเช่าเครื่องเสีย จัดทำเวทีประชาคม ในวันที่ ๑๗ ๑๙ มกราคม ๒๕๕๘ เป็นเงิน ๔,๐๐๐ บาท และจัดหาโต๊ะ เก้าอี้ สำหรับวิทยากรและผู้เข้าอบรม จำนวน ๕๐๐ ตัว พร้อมโต๊ะหมู่บูชา แอมป์พาวเวอร์ และ อุปกรณ์อื่นๆ เป็นเงิน ๒๑,๐๐๐ บาท รวมเป็นเงิน ๓๐,๐๐๐ บาท จากนายสุวนันท์ ไชยานันตา ผู้รับจ้าง รวมเป็นเงิน ๙๐,๐๐๐ บาท แต่จากรายงานการไต่สวนของผู้ถูกฟ้องคดีที่ ๑ ปรากฏว่า พยานบุคคลซึ่งเป็นผู้ปกครองท้องที่อำเภอพญาเม็งรายหลายรายต่างให้ถ้อยคำสอดคล้องกันว่า อำเภอพญาเม็งรายดำเนินการจัดประชุมประชาคม ณ หอประชุมหมู่บ้าน เพียงครึ่งวัน โดยไม่มี การจัดเลี้ยงอาหารกลางวันแก่ผู้เข้าอบรม และในวันที่ ๑๗ - ๑๙ มกราคม ๒๕๕๘ ไม่มีการจัด เวทีประชาคมหมู่บ้าน ณ หอประชุมอำเภอพญาเม็งราย แต่อย่างใด มีเพียงการประชุมประชาคม ของหมู่ที่ 5 ตำบลตาดควัน ในวันที่ ๑๗ ธันวาคม ๒๕๔๗ และการประชุมประชาคมของหมู่ที่ ๒ ตำบลแม่ต่ำ ในวันที่ ๓๐ ธันวาคม ๒๕๕๗ ตามบันทึกรายงานการตรวจ ลงวันที่ ๑๗ ธันวาคม ๒๕๕๗ และวันที่ ๓๐ ธันวาคม ๒๕๔๗ เท่านั้น ประกอบกับนายสุวนันท์ ไชยานันตา ผู้รับจ้างเหมา จัดสถานที่และเช่าเครื่องเสียงจัดทำเวทีประชาคม ให้ถ้อยคำว่า นายสุวนันท์เป็นอาสาสมัครรักษา ดินแดนอำเภอพญาเม็งรายอยู่ภายใต้การบังคับบัญชาโดยตรงของผู้ฟ้องคดี ผู้ฟ้องคดีได้สั่งการให้ นายสุวนันท์ลงลายมือชื่อเป็นผู้รับจ้างทั้งที่ทราบอยู่แล้วว่านายสุวนันท์ไม่มีอาชีพในการจัดสถานที่ หรือมีเครื่องเสียงให้เช่า แม้นายสุวนันท์จะได้จัดหาโต๊ะ เก้าอี้ ลำโพงขนาดใหญ่ ลำโพงขนาดเล็ก และไมโครโฟน เพื่อใช้จัดสถานที่ในวันที่ ๑๖ มกราคม ๒๕๕๘ จริง แต่นายสุวนันท์ไม่ทราบว่า ในวันที่ ๑๗ ๑๙ มกราคม ๒๕๕๘ มีการจัดประชุมหรือไม่ และไม่ทราบว่ามีการจัดหาเครื่องเสียง ชุดใหญ่พร้อมแอมป์พาวเวอร์หรือไม่ โดยนายสุวนันท์ไม่ได้รับค่าจ้างแต่อย่างใด อีกทั้ง ในการ เบิกจ่ายค่าจ้างเหมาจัดทำอาหาร อาหารว่าง และเครื่องดื่ม ก็ไม่ปรากฏรายละเอียดในหลักฐาน การเบิกจ่าย และนายสว่าง วรรณสอน ผู้รับจ้าง ก็ไม่มีอาชีพในการจัดทำอาหาร แต่เป็นอาสาสมัคร รักษาดินแดนอำเภอพญาเม็งรายซึ่งอยู่ภายใต้การบังคับบัญชาโดยตรงของผู้ฟ้องคดีเช่นเดียวกับ นายสุวนันท์ เห็นได้ว่า ผู้ฟ้องคดีซึ่งได้รับแต่งตั้งให้เป็นประธานกรรมการตรวจรับพัสดุ มีหน้าที่ ตรวจรับพัสดุให้ถูกต้องครบถ้วนตามหลักฐานที่ตกลงกันไว้ กรณีที่เห็นว่าพัสดุที่ส่งมอบมีรายละเอียดไม่เป็นไปตามข้อกำหนดในสัญญาหรือข้อตกลง ให้รายงานหัวหน้าส่วนราชการ ผ่านหัวหน้าเจ้าหน้าที่พัสดุเพื่อทราบหรือสั่งการ แล้วแต่กรณี ตามข้อ ๗๑ ของระเบียบสำนัก นายกรัฐมนตรีว่าด้วยการพัสดุ พ.ศ. ๒๕๓๕ แต่ผู้ฟ้องคดีกลับไม่ตรวจรับพัสดุตามโครงการจัดทำ เวทีประชาคมหมู่บ้านฯ ทั้งยังลงนามรับรองเป็นหลักฐานว่ามีการดำเนินการจัดซื้อจัดจ้างถูกต้อง ครบถ้วน เพื่อเสนอให้มีการเบิกจ่ายเงิน อันเป็นการไม่ปฏิบัติตามข้อ ๗๑ ของระเบียบสำนัก นายกรัฐมนตรีดังกล่าว\n\nส่วนโครงการส่งเสริมการเกษตรแบบครบวงจร ได้รับจัดสรรเงินงบประมาณ จำนวน ๓๘๔,๐๐๐ บาท มีการจัดซื้อวัสดุอุปกรณ์ส่งเสริมเกษตรธรรมชาติแบบครบวงจร จากนายธนกร ไชยวรรณ์ จำนวน 5 ครั้ง สำหรับหมู่ที่ ๓ ตำบลเม็งราย หมู่ที่ ๙ ตำบลไม้ยา หมู่ที่ ๙ ตำบลแม่ต่ำ หมู่ที่ ๑๓ ตำบลตาดควัน หมู่ที่ ๑๔ ตำบลไม้ยา และหมู่ที่ ๑๙ ตำบลแม่เปา ครั้งละ ๑๑ รายการ ประกอบด้วยเครื่องสกัดยาฆ่าแมลง เครื่องบดดิน ถังพ่นยา ถั่วฝักยาว ผักบุ้งจีน แตงกวา คะน้า กวางตุ้งใบ ผักชี ผักขึ้นฉ่าย และบวบเหลี่ยม ตามใบสั่งซื้อเลขที่ ๗/๒๕๔๘ ๑๒/๒๕๔๘ ๑๔ กุมภาพันธ์ ๒๕๕๘ แต่ปรากฏว่าชาวบ้านหมู่ที่ ๓ ตำบลเม็งราย ได้รับวัสดุไม่ครบ ทั้ง ลงวันที่ ๑๑ รายการ หมู่ที่ ๓ ตำบลตาดควัน ได้รับวัสดุไม่ครบทั้ง ๑๑ รายการ หมู่ที่ ๙ ตำบลไม้ยา ได้รับเฉพาะเครื่องสกัดยาฆ่าแมลงและเครื่องบดดิน หมู่ที่ 4 ตำบลแม่ต่ำ ได้รับเฉพาะเครื่องสกัด ยาฆ่าแมลงและเครื่องบดดิน หมู่ที่ ๑๔ ตำบลไม้ยา ได้รับเฉพาะเครื่องสกัดยาฆ่าแมลงและเครื่องบดดิน และหมู่ที่ ๑๙ ตำบลแม่เปา ได้รับเฉพาะเครื่องสกัดยาฆ่าแมลงและเครื่องบดดิน ชาวบ้านต่างได้รับ พัสดุไม่ตรงตามที่ขออนุมัติและได้รับพัสดุไม่ครบถ้วน แม้ผู้ฟ้องคดีจะไม่มีอำนาจหน้าที่ในการ แจกจ่ายพัสดุให้กับชาวบ้าน แต่ผู้ฟ้องคดีซึ่งเป็นประธานกรรมการตรวจรับพัสดุ มีหน้าที่ตรวจ รับพัสดุให้ถูกต้องครบถ้วนตามหลักฐานที่ตกลงกันไว้ กรณีที่เห็นว่าพัสดุที่ส่งมอบมีรายละเอียด ไม่เป็นไปตามข้อกำหนดในสัญญาหรือข้อตกลง ให้รายงานหัวหน้าส่วนราชการผ่านหัวหน้า เจ้าหน้าที่พัสดุเพื่อทราบหรือสั่งการ แล้วแต่กรณี ตามข้อ ๗๑ ของระเบียบสำนักนายกรัฐมนตรี ว่าด้วยการพัสดุ พ.ศ. ๒๕๓๕ กลับลงนามยืนยันว่ามีการตรวจรับพัสดุครบถ้วน เพื่อเสนออนุมัติ เบิกจ่ายเงิน ทั้งที่ผู้ฟ้องคดีไม่ได้ตรวจรับพัสดุตามโครงการส่งเสริมการเกษตรแบบครบวงจรจริง อันเป็นการไม่ปฏิบัติตามข้อ ๗๑ ของระเบียบสำนักนายกรัฐมนตรีดังกล่าว นอกจากนี้ ผู้ฟ้องคดี รับว่าทั้งโครงการจัดทำเวทีประชาคมหมู่บ้านฯ และโครงการส่งเสริมการเกษตรแบบครบวงจร นายบุญยัง เรือนกูล นายอำเภอพญาเม็งราย และนางวัลลภาภรณ์ เรือนปาง เสมียนตราอำเภอ พญาเม็งราย ได้รวบรวมเอกสารที่เกี่ยวข้องเสนอผู้ฟ้องคดีให้ลงนามในฐานะประธานกรรมการ ตรวจรับพัสดุ ผู้ฟ้องคดีได้ลงนามรับรองเป็นหลักฐานว่ามีการดำเนินการจัดซื้อจัดจ้างถูกต้อง ครบถ้วน เพื่อเสนอให้มีการเบิกจ่ายเงินงบประมาณจำนวน ๙๐,๐๐๐ บาท และจำนวน ๓๘๔,๐๐๐ บาท อันเป็นการเอื้อให้บุคคลอื่นได้รับผลประโยชน์จากงบประมาณดังกล่าว\n\nที่ผู้ฟ้องคดีอ้างว่า ผู้ฟ้องคดีไม่มีความเป็นอิสระในการบริหาร ต้องตกอยู่ภายใต้การสั่งการของ นายบุญยัง ผู้ฟ้องคดีไม่มีเจตนาทุจริต ไม่ปรากฏว่าผู้ฟ้องคดีมีทรัพย์สินเพิ่มมากขึ้นผิดปกติ หรือมีการได้ประโยชน์ในลักษณะทรัพย์สิน หรือประโยชน์อื่นใดจากการดำเนินโครงการ ผู้ฟ้องคดี ไม่ได้กระทำการทุจริตต่อหน้าที่ นั้น เห็นว่า เมื่อพิจารณาประวัติการรับราชการของผู้ฟ้องคดี ในช่วงก่อนที่ผู้ฟ้องคดีจะได้รับการแต่งตั้งให้ดำรงตำแหน่งปลัดอำเภอ ที่ทำการปกครองพญาเม็งราย ผู้ฟ้องคดีดำรงตำแหน่งปลัดอำเภอ (เจ้าพนักงานปกครอง ๓) ตั้งแต่วันที่ ๒๖ ธันวาคม ๒๕๓๗ และรับราชการตำแหน่งดังกล่าวมาแล้วในหลายท้องที่ของจังหวัดเชียงราย ผู้ฟ้องคดีย่อมเป็น ข้าราชการผู้มีความรู้และประสบการณ์ในการปฏิบัติหน้าที่ในตำแหน่งนั้นเป็นอย่างดี เมื่อผู้ฟ้องคดี ได้รับแต่งตั้งให้เป็นประธานกรรมการตรวจรับพัสดุ ผู้ฟ้องคดีจึงมีหน้าที่ต้องตรวจรับพัสดุให้ถูกต้อง ครบถ้วนตามข้อ ๗๑ ของระเบียบสำนักนายกรัฐมนตรีว่าด้วยการพัสดุ พ.ศ. ๒๕๓๕ แต่ผู้ฟ้องคดี กลับยอมลงชื่อในเอกสารการตรวจรับพัสดุและการจ้างตามที่ นายบุญยัง นำมาให้โดยมิได้ทักท้วง หรือเสนอความเห็นให้มีการทบทวนการกระทำที่ไม่ถูกต้องแต่อย่างใด แม้ไม่ปรากฏว่าผู้ฟ้องคดี ได้จะได้รับเงินจากการเบิกจ่ายงบประมาณดังกล่าวโดยตรง แต่การกระทำของผู้ฟ้องคดียังเป็น การเอื้อประโยชน์ให้กับบุคคลอื่นได้รับเงินหรือประโยชน์ที่มีควรได้ ข้อกล่าวอ้างของผู้ฟ้องคดี ฟังไม่ขึ้น จึงเห็นได้ว่า พฤติการณ์ของผู้ฟ้องคดีเป็นการปฏิบัติหรือละเว้นการปฏิบัติหน้าที่ราชการ โดยมิชอบ เพื่อให้ตนเองหรือผู้อื่นได้ประโยชน์ที่มีควรได้ เป็นการทุจริตต่อหน้าที่ราชการ ปฏิบัติหน้าที่ราชการโดยจงใจไม่ปฏิบัติตามกฎหมาย ระเบียบของทางราชการ มติคณะรัฐมนตรี หรือนโยบายของรัฐบาล อันเป็นเหตุให้เสียหายแก่ราชการอย่างร้ายแรง และกระทำการอื่นใด อันได้ชื่อว่าเป็นผู้ประพฤติชั่วอย่างร้ายแรง ซึ่งเป็นความผิดวินัยอย่างร้ายแรง ตามมาตรา ๘๒ เมื่อวันที่ ๘ วรรคสาม มาตรา ๘๕ วรรคสอง และมาตรา ๙๘ วรรคสอง แห่งพระราชบัญญัติระเบียบ ข้าราชการพลเรือน พ.ศ. ๒๕๓๕ ประกอบมาตรา ๑๓๓ แห่งพระราชบัญญัติระเบียบข้าราชการ พลเรือน พ.ศ. ๒๕๕๑ ตามที่ผู้ถูกฟ้องคดีที่ ๑ ชี้มูลความผิด ดังนั้น การที่ผู้ถูกฟ้องคดีที่ ๒ มีคำสั่งกรมการปกครอง ที่ ๑๔๐๐/๒๕๖๓ ลงวันที่ ๕ สิงหาคม ๒๕๖๓ ลงโทษไล่ผู้ฟ้องคดี ออกจากราชการ จึงชอบด้วยกฎหมายแล้ว และการที่ผู้ถูกฟ้องคดีที่ ๓ วินิจฉัยอุทธรณ์ของผู้ฟ้องคดี โดยอาศัยข้อเท็จจริงและข้อกฎหมายเดียวกันแล้วมีคำวินิจฉัยเรื่องดำที่ ๖๓๑๐๑๖๖ เรื่องแดง ที่ ๑๐๔๘๑๖๕ ลงวันที่ ๕ กรกฎาคม ๒๕๖๕ ให้ยกอุทธรณ์ของผู้ฟ้องคดี จึงชอบด้วยกฎหมายเช่นกัน\n\nในส่วนของคำขอท้ายคำฟ้องที่ผู้ฟ้องคดีมีคำขอให้เพิกถอนมติของผู้ถูกฟ้องคดีที่ ๑ ในการประชุมครั้งที่ ๕/๒๕๖๓ เมื่อวันที่ 4 มกราคม ๒๕๖๒ ที่ชี้มูลความผิดทางวินัย อย่างร้ายแรงผู้ฟ้องคดี นั้น เห็นว่า แม้มติของผู้ถูกฟ้องคดีที่ ๑ ในการประชุมครั้งที่ ๕/๒๕๖๓ 4 มกราคม ๒๕๖๓ ที่ชี้มูลความผิดทางวินัยอย่างร้ายแรงผู้ฟ้องคดีฐานทุจริตต่อหน้าที่ ราชการ จะมีผลผูกพันให้ผู้ถูกฟ้องคดีที่ ๒ ต้องมีคำสั่งตามมติดังกล่าว แต่ก็เป็นเพียงส่วนหนึ่งของการพิจารณาทางปกครองเพื่อออกคำสั่งทางปกครองเท่านั้น ยังไม่มีผลเป็นการสร้าง นิติสัมพันธ์ขึ้นระหว่างบุคคลในอันที่จะก่อ เปลี่ยนแปลง โอน สงวน ระงับ หรือมีผลกระทบ ต่อสถานภาพของสิทธิหรือหน้าที่ของบุคคล อันจะถือได้ว่าเป็นคำสั่งทางปกครองตามมาตรา ๕ แห่งพระราชบัญญัติวิธีปฏิบัติราชการทางปกครอง พ.ศ. ๒๕๓๙ แต่อย่างใด ผู้ฟ้องคดีจึงมิใช่ ผู้ได้รับความเดือดร้อนหรือเสียหายจากมติดังกล่าวที่จะมีสิทธิฟ้องขอให้ศาลมีคำพิพากษาหรือ คำสั่งเพิกถอนมติของผู้ถูกฟ้องคดีที่ ๑ ในการประชุมครั้งที่ ๕/๒๕๖๓ เมื่อวันที่ 4 มกราคม ๒๕๖๒ ที่ชี้มูลความผิดทางวินัยอย่างร้ายแรงผู้ฟ้องคดีได้\n\nกรณีที่ผู้ฟ้องคดีอ้างว่า มติคณะรัฐมนตรีแจ้งเวียนตามหนังสือสำนักเลขาธิการ คณะรัฐมนตรีที่ นร ๐๒๐๕/ว ๒๓๔ ลงวันที่ ๒๔ ธันวาคม ๒๕๓๖ ที่ระบุว่า การให้ลงโทษ ผู้กระทำผิดวินัยฐานทุจริตต่อหน้าที่ราชการควรลงโทษเป็นไล่ออกจากราชการการ เป็นเพียง การแนะนำเท่านั้น การลงโทษผู้ฟ้องคดีจึงเป็นดุลพินิจของผู้ถูกฟ้องคดีที่ ๒ และผู้ถูกฟ้องคดีที่ ๓ ย่อมมีอำนาจในการวินิจฉัยฐานความผิดทางวินัยแก่ผู้ฟ้องคดีเสียใหม่ได้ นั้น เห็นว่า แม้มติ คณะรัฐมนตรีดังกล่าวมิได้มีสถานะเป็นกฎหมายหรือกฎอันจะมีผลผูกพันให้ผู้ถูกฟ้องคดีที่ ๒ ต้องปฏิบัติตาม แต่มติดังกล่าวเป็นการกำหนดนโยบายหรือแนวปฏิบัติในการใช้ดุลพินิจ สั่งลงโทษผู้กระทำผิดในเรื่องที่กำหนดไว้ เพื่อให้การลงโทษเป็นไปด้วยความเหมาะสมกับกรณี ความผิด มีมาตรฐานเดียวกัน และเป็นไปในทางเดียวกัน ดังนั้น ในการพิจารณาสั่งลงโทษทางวินัย อย่างร้ายแรงฐานทุจริตต่อหน้าที่ราชการ ผู้บังคับบัญชาต้องนำมติดังกล่าวมาประกอบการพิจารณา ใช้ดุลพินิจด้วย ส่วนกระบวนการพิจารณาอุทธรณ์ของผู้ถูกฟ้องคดีที่ ๓ มิใช่การพิจารณาเฉพาะ รายงานการสอบสวนทางวินัยเท่านั้น แต่ยังให้โอกาสผู้ถูกกล่าวหาพิสูจน์หลักฐานอื่น รวมทั้ง มีอำนาจสอบสวนเพิ่มเติมได้ ผู้ถูกฟ้องคดีที่ ๓ จึงมีอำนาจพิจารณาอุทธรณ์ตามหลักเกณฑ์และ วิธีการที่กำหนดไว้ตามอำนาจหน้าที่และอาจพิจารณาแตกต่างจากคำสั่งลงโทษของผู้ถูกฟ้องคดีที่ ๒ ได้ ข้ออ้างของผู้ฟ้องคดีในประเด็นนี้จึงฟังไม่ขึ้น\n\nกรณีที่ผู้ฟ้องคดีอ้างว่า คดีหมายเลขดำที่ อท ๑๓๕, ที่ อท ๗๘, ๑๓๖/๒๕๖๒ และคดีหมายเลขแดง ๗๗/๒๕๖๓ ของศาลอาญาคดีทุจริตและประพฤติมิชอบภาค ๕ นั้น ผู้ฟ้องคดียังใช้สิทธิ ทางศาลในชั้นอุทธรณ์และฎีกาอยู่ คดีจึงยังไม่ถึงที่สุด และที่ผู้ถูกฟ้องคดีที่ ๑ ให้การเพิ่มเติมว่า พยานหลักฐานตามที่ได้นำสืบในชั้นศาลและในชั้นไต่สวนของผู้ถูกฟ้องคดีที่ ๑ เห็นได้ชัดว่า ผู้ฟ้องคดีได้กระทำความผิดตามที่ถูกกล่าวหา และการกระทำของผู้ฟ้องคดีจึงเป็นความผิดตาม คำพิพากษาของศาลอาญาคดีทุจริตและประพฤติมิชอบภาค ๕ นั้น เห็นว่า ในการพิจารณา พิพากษาคดีปกครองเกี่ยวกับการลงโทษทางวินัย ไม่มีกฎหมายบัญญัติบังคับไว้ให้ศาลปกครอง จำต้องถือข้อเท็จจริงตามที่ปรากฏในคำพิพากษาคดีส่วนอาญา ดังนั้น การพิจารณาคดีปกครอง\n\nเกี่ยวกับการลงโทษทางวินัยในกรณีสืบเนื่องจากผู้ถูกฟ้องคดีที่ ที่ ๑ ๑ ชี้มูลว่าผู้ฟ้องคดีกระทำการ อันมีมูลความผิดฐานทุจริตต่อหน้าที่ ซึ่งเป็นความผิดทางวินัยและทางอาญานั้น แม้การดำเนินการ ทางวินัยและการดำเนินคดีส่วนอาญาจะได้อาศัยสำนวนการสอบสวนของผู้ถูกฟ้องคดีที่ ๑ เป็นหลัก และคดีส่วนอาญาได้มีคำพิพากษาถึงที่สุดแล้วหรือไม่ก็ตาม ศาลก็หาจำต้องถือข้อเท็จจริง ตามที่ปรากฏในคำพิพากษาคดีส่วนอาญาแต่อย่างใดไม่ ข้ออ้างของผู้ฟ้องคดีในประเด็นนี้ จึงฟังไม่ขึ้นเช่นกัน\n\nพิพากษายกฟ้อง" + } +} \ No newline at end of file diff --git a/public/tmpl/T12_X.docx b/public/tmpl/T12_X.docx new file mode 100755 index 0000000000000000000000000000000000000000..93478ec322f4076cff47b6c196e8194edb4c53a4 GIT binary patch literal 154674 zcmeFYQZCiJr?sM+wJ5JwU(47w}A2ME6jxT1c zTscEQ8W;ox01N;E004jx;7D*_a0w6qKpPqW02u%RNL$$6&c)QuMPJ3!!PHro&coJ* zpa=wrq5uHsU;O_a{|E2Dbee4Z5(7fmt;AROg!*zvw**9CnfKly1hpbbot}3R))(oV zi~HwoV=`M!nv1fc(5n(a#)z-z*6)kl_ zi1LsJIO-NqQ}yu_dh`k#0Gi}=gOPR7U7T}@bpwzz{y?eJbunrRp7C&S0>3EmmlL1= zaH%^;oLLI*y3;+NRU@N{Pmg>(cM?7zmRip(mb2Pu%M6%pAiPx&cG}VFa1;a>zN83* zn6zN!tbMLmOWKAVuBlQRZ^tU2erXsmWTz8{LHTDA9Dx<+Xluli=KAvJt&fV1XJ@0N zCZ8?y!1<+?^x5+Utq46JV{Q$rseYqSH}5Y(_+J9g&uy$a$Sc<;Kzg~cbJ=Emk;VDj z{lQ*=O{@X`e&pO+4;}>4K@qixn3!Csr7MQVqjTb+-sS8aGtEe;N53-p~#ZJg=p{^R+7HQ@i?#Q$Gj zy(VeUYM22=;5PU>c%fHua}c9gj^22F3u6NuLR(S>d28Kj?dO|keH}>m+)#XKaVdG; z%Q;8PbuUHt4l7L+HmVnL^<8gR`?JRbAT_k7l+k0=;Q%K4*GB{cjHEIqE zEb%QOV`c#Pm`dd3xE$J&h;nY(n54Nl℞lHN%g&Ad_)r)k;F!8@!5Fv@Z;|YZlWF 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

``{)s^vG6throeyZ%U=eu7AG{kSkVTa>-?4a2l% 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

Nl{!O(0N> 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 literal 0 HcmV?d00001 diff --git a/public/tmpl/T_no_warning.json b/public/tmpl/T_no_warning.json new file mode 100755 index 0000000..59a8b65 --- /dev/null +++ b/public/tmpl/T_no_warning.json @@ -0,0 +1,51 @@ +{ + "info": { + "ticket_data": "{\"fid\":\"T3_P2_IN_XXX\",\"tid\":\"T_X\",\"uid\":\"oom\",\"uname\":\"สรวิชญ์\"}" + }, + "dc": { + "Interpleader": false, + "CommandTo": true, + "SendTo": true, + "AdditionCommand" : false, + "Warning":false, + + "docname":"คำสั่งเรียก\nให้ทำคำให้การเพิ่มเติม", + "doctype":"(ต. ๓)", + "blackno": "คดีหมายเลขดำที่", + "redno": "คดีหมายเลขแดงที่", + + "court": "ศาลปกครองนครราชสีมา", + "doctitle":"คำสั่งถึง", + "pname":"สมหญิง", + + "courtname":"ศาลปกครองสุพรรณบุรี", + "courtno":"๓๓๓ หมู่ ๖ ตำบลสนามชัย อำเภอเมืองสุพรรณบุรี", + "courttam":"จังหวัดสุพรรณบุรี ๗๒๐๐๐", + "courttel":"กลุ่มออกหมาย โทรศัพท์ ๐๓๕ ๔๓๒ ๓๓๓ ต่อ ๑๐๐๑,๒๒๐๓", + "courttel2":"สอบถามติดตามคดี ขอคำปรึกษาแนะนำ โทรสายด่วน ๑๓๕๕", + "pname2":"ชื่อของ pname2" + }, + "cc": { + "docname":"คำสั่งเรียกพยานบุคคล", + "blackyear": "๙๐/๒๕๖๙", + "redyear": "xxx", + "curdate": "วันที่ เดือน สิงหาคม พุทธศักราช ๒๕๖๙ ", + "commto" : "นายสมชาย จรดปลายเท้า", + "addrno" : "3/77 xxx yyy ทุ่งสองห้อง,หลักสี่, จังหวัดกรุงเทพฯ ", + "groupname":"ผู้อำนวยการโรงเรียน", + + "accuser": "นายธีรพล ร่มเริง", + "acc1": "ผู้ฟ้องคดี", + "accused": "รร", + "acc2": "ผู้ถูกฟ้องคดี", + "interpleader": "สมชาย ใจดี", + "acc3": "ผู้ร้องสอด", + "detail1": "ตุลาการเจ้าของสำนวนได้พิจารณาคำฟ้อง และเอกสารประกอบคำฟ้องแล้ว เห็นว่าคดีนี้เป็นคดีพิพาทซึ่งไม่อยู่ในอำนาจศาลปกครองสูงสุด\n\nผู้ฟ้องเป็นผู้มีสิทธิฟ้องคดี คดีนี้มีกฎหมายกำหนดให้ต้องดำเนินการแก้ไข ความเดือดร้อนหรือเสียหายก่อนฟ้องคดี ", + "sendto" : "นายสมศักดิ์ ฟักมี", + + "warning": "หากผู้ถูกฟ้องคดีไม่ปฏิบัติตามคำสั่งศาลภายในระยะเวลาที่กำหนด ศาลจะถือว่า ผู้ถูกฟ้องคดียอมรับข้อเท็จจริงตามข้อหาของผู้ฟ้องคดี", + "detail2":"คำสั่งเพิ่มเติมคือ bla bla", + "remarkhead":"หัวของ Remark", + "remark":"This is remark Text bla bla " + } +} \ No newline at end of file diff --git a/public/tmpl/index.html b/public/tmpl/index.html new file mode 100644 index 0000000..e47e08c --- /dev/null +++ b/public/tmpl/index.html @@ -0,0 +1,20 @@ + + + + + + Document + + +

Template & Data

+ +
    +
  1. T12_X + (T12_b2_IN, T12_FD155_3s-long) +
  2. +
  3. T_X + (TF_no_warning_P2, T_no_warning, TS_P2_IN.json) +
  4. +
+ + \ No newline at end of file From 98b370c925d13aceec1f01683be8194306d23514 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Mon, 26 Oct 2026 06:29:19 +0700 Subject: [PATCH 08/21] feat(docver): add access token endpoint and Bearer auth on all endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - POST /api/documents/token issues JWT HS256 (claims userid/username/fid/template) after x-api-key check against new env DOCVER_API_KEY (fail-closed, timing-safe, uniform 401) - all docver endpoints now require Authorization: Bearer, verified with env DOCVER_TOKEN_SECRET (TTL via DOCVER_TOKEN_TTL_SECONDS, default 3600); secret unset = 401 on verify / 500 on issue - identity (userId/userName) + templateId extracted from token claims instead of request payload; removed from multipart form (legacy fields now 400) - lib/docver-token.ts: hand-rolled JWT via node:crypto only — alg pinned to HS256 (anti alg-confusion), timing-safe compare with both sides hashed, exp enforced, identical 401 for all bearer failure modes - openapi spec: /api/documents/token path + DocverBearer scheme; PLAN-DOCVER.md / CLAUDE.md / .env.example updated - tests: new tests/lib/docver-token.test.ts + adjusted parseVersionUploadForm tests (258 pass, no DB env needed) - package-lock.json: sync minio (^8.0.7) that was missing from the earlier docver commit Co-Authored-By: Claude Code --- .env.example | 9 + CLAUDE.md | 11 +- .../[versionId]/download-link/route.ts | 5 + app/api/documents/[docId]/versions/route.ts | 23 +- app/api/documents/token/route.ts | 47 +++ docs/PLAN-DOCVER.md | 60 +++- lib/document-versioning.ts | 40 +-- lib/docver-token.ts | 208 ++++++++++++ lib/openapi.ts | 137 +++++++- package-lock.json | 194 +++++++++--- tests/lib/document-versioning.test.ts | 28 +- tests/lib/docver-token.test.ts | 297 ++++++++++++++++++ wordaddin/services/versioningService.ts | 4 + 13 files changed, 932 insertions(+), 131 deletions(-) create mode 100644 app/api/documents/token/route.ts create mode 100644 lib/docver-token.ts create mode 100644 tests/lib/docver-token.test.ts diff --git a/.env.example b/.env.example index 0a4a54b..d9988b6 100644 --- a/.env.example +++ b/.env.example @@ -18,3 +18,12 @@ MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin # MINIO_BUCKET=docver # DOCVER_LINK_TTL_SECONDS=900 + +# docver token (2026-10-22): API key ของ POST /api/documents/token (header x-api-key) +# ไม่ตั้ง/ว่าง = ปฏิเสธทุก request (fail closed) — ตั้งค่ายาวและสุ่ม เช่น 32+ ตัวอักษร +# (ห้าม log ค่า key ดิบ — เทียบแบบ timing-safe ทุกครั้ง) +DOCVER_API_KEY= +# secret เซ็น/ตรวจ JWT HS256 ของทุก endpoint /api/documents/* (Authorization: Bearer) +# ไม่ตั้ง/ว่าง = ออก token ไม่ได้ (500) และทุก endpoint ตอบ 401 (fail closed) — ห้าม log ค่า secret +DOCVER_TOKEN_SECRET= +# DOCVER_TOKEN_TTL_SECONDS=3600 diff --git a/CLAUDE.md b/CLAUDE.md index c1656e7..20c6558 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,7 +21,7 @@ 1. **Non-Destructive:** ไฟล์ต้นฉบับ (PDF/รูปภาพ) ไม่ถูกแก้ไขเด็ดขาด — annotation เก็บใน DB เป็นพิกัดเท่านั้น (export PDF ของ F8/N3 สร้างไฟล์ใหม่จากสำเนาในหน่วยความจำ — อ่าน `public/` อย่างเดียว ห้ามเขียนทับ; อัปโหลด N4 เขียนไฟล์**ใหม่**ที่ `public/note/uploads/` เท่านั้น ห้ามแตะไฟล์เดิม) 2. **พิกัดทุกอย่างใน DB เป็น % ของขนาดหน้า** — `rects`, `paths`, `position`, `strokeWidth` (% ของ width), `yOffset` (% ของ height) — แปลง px⇄% ผ่าน `utils/coordinates.ts` เท่านั้น และก่อนบันทึก HIGHLIGHT ต้องรวบ rects ด้วย `mergeRectsByLine` เหลือ 1 rect ต่อบรรทัด 3. **ลำดับ layer ตาม PLAN.md §1:** canvas (`pointer-events: none` เสมอ) → highlight-layer (`none` เสมอ, `mix-blend-mode: multiply`) → textLayer → InkLayer (SVG) → comment pins — สลับ pointer-events ตาม tool ปัจจุบัน -4. **เขียนผ่าน Server Actions เท่านั้น** (`app/actions/annotations.ts`) และ**หลังเขียน DB สำเร็จทุกครั้ง ต้อง publish event ผ่าน `lib/annotate-events.ts` + `revalidatePath` หน้า viewer และ summary ของ fileId นั้น** (helper `revalidateFilePages` — สั่ง browser ทิ้ง Router Cache ฝั่ง client, `force-dynamic` ปิดแค่ฝั่ง server ไม่พอ: ไม่งั้นกด back แล้วคลิกเข้าไฟล์ใหม่ยังได้ initial data เก่าจนต้อง hard refresh) / **อ่าน + sync ผ่าน SSE hook** (`hooks/useAnnotationRealtime.ts` เปิด `EventSource` ไป `/api/note/annotations/stream?fileId=`) — event เป็น DTO (camelCase + ISO string) จาก server ตรง ๆ — **ยกเว้น `registerExternalFile` (N8)** ที่เขียน row `files` ระหว่าง render ของหน้า `/note` และ `/note/annotate` (entry point ทั้งสองของสัญญาลิงก์ `?fileUrl=`) แล้ว `redirect` ออกทันที (หน้า `force-dynamic` + รายการไฟล์ไม่ได้ sync ผ่าน SSE — ไม่ต้อง publish event/`revalidatePath`) · เช่นกัน **กลุ่ม docver `app/api/documents/**`** (จัด version เอกสาร — `docs/PLAN-DOCVER.md`) เขียนตาราง `documents`/`document_versions` ผ่าน route handler ไม่ใช่ Server Action (ผู้เรียกเป็นระบบภายนอก stand-alone บอก userId/userName เองใน request ไม่มี session ของแอป note) — ไม่มีหน้า note ใดอ่านตารางนั้น จึงไม่ต้อง publish event/`revalidatePath` +4. **เขียนผ่าน Server Actions เท่านั้น** (`app/actions/annotations.ts`) และ**หลังเขียน DB สำเร็จทุกครั้ง ต้อง publish event ผ่าน `lib/annotate-events.ts` + `revalidatePath` หน้า viewer และ summary ของ fileId นั้น** (helper `revalidateFilePages` — สั่ง browser ทิ้ง Router Cache ฝั่ง client, `force-dynamic` ปิดแค่ฝั่ง server ไม่พอ: ไม่งั้นกด back แล้วคลิกเข้าไฟล์ใหม่ยังได้ initial data เก่าจนต้อง hard refresh) / **อ่าน + sync ผ่าน SSE hook** (`hooks/useAnnotationRealtime.ts` เปิด `EventSource` ไป `/api/note/annotations/stream?fileId=`) — event เป็น DTO (camelCase + ISO string) จาก server ตรง ๆ — **ยกเว้น `registerExternalFile` (N8)** ที่เขียน row `files` ระหว่าง render ของหน้า `/note` และ `/note/annotate` (entry point ทั้งสองของสัญญาลิงก์ `?fileUrl=`) แล้ว `redirect` ออกทันที (หน้า `force-dynamic` + รายการไฟล์ไม่ได้ sync ผ่าน SSE — ไม่ต้อง publish event/`revalidatePath`) · เช่นกัน **กลุ่ม docver `app/api/documents/**`** (จัด version เอกสาร — `docs/PLAN-DOCVER.md`) เขียนตาราง `documents`/`document_versions` ผ่าน route handler ไม่ใช่ Server Action (ผู้เรียกเป็นระบบภายนอก stand-alone ไม่มี session ของแอป note — identity userId/userName และ templateId แกะจาก Bearer token ที่ขอจาก `POST /api/documents/token` ผ่าน `lib/docver-token.ts` ไม่รับจาก request payload อีกต่อไป) — ไม่มีหน้า note ใดอ่านตารางนั้น จึงไม่ต้อง publish event/`revalidatePath` 5. **Timestamp ข้าม server→client→SSE เป็น ISO string เสมอ** (DTO types) — ห้ามส่ง Date object ตรง ๆ 6. **pdfjs-dist ต้อง `dynamic import()` ภายใน useEffect เท่านั้น** — ห้าม import top-level ใน module scope ที่ server evaluate และต้องตั้ง `GlobalWorkerOptions.workerSrc = withBasePath("/pdf.worker.min.mjs")` (postinstall `scripts/patch-pdf-worker.mjs` คัดลอกไฟล์ไป `public/note/` **แล้ว patch จุดเดียว: ถอด `if (scaledDim)` รอบ `textChunk.prevTransform = getCurrentTextTransform()` ให้อัปเดตทุก glyph** — pdf.js เดิมข้าม glyph กว้าง 0 อย่างสระ/วรรณยุกต์ไทย ทำให้ charSpacing ของ glyph เหล่านั้นสะสมเป็น gap จน `addFakeSpaces` แทรก " " กลางคำ "ฉะนั้ น" — bug ของ pdf.js ที่ upstream ยังไม่แก้; การ patch ทำบนโค้ด minified ด้วย pattern จำเพาะ ถ้าอัปเกรด pdfjs-dist แล้ว pattern ไม่ตรง postinstall จะ **fail ดัง ๆ โดยตั้งใจ** กัน patch หลุดเงียบ — test กัน regression: `tests/scripts/patch-pdf-worker.test.ts` + fixture `tests/fixtures/thai_letterspacing.pdf` — Docker build ต้องใช้ script นี้ด้วย ไม่ใช่ copyFileSync เปล่า ดู `docker/web.Dockerfile`) — ห้ามใช้ `new URL(..., import.meta.url)` 7. **InkLayer ใช้ SVG แบบไม่มี viewBox** — render ด้วย px จริง แปลง % → px จากขนาดที่ ResizeObserver วัด (viewBox จะทำให้ความหนาเส้นบิดเบี้ยว) @@ -35,13 +35,13 @@ ``` docker-compose.yml ← dev dependencies: `db` (judes-note-db — ชื่อ container/วอลุ่มเดียวกับคำสั่ง docker run เดิมใน README migrate ได้เลย, POSTGRES_DB ecourt-note) + `minio` (judes-note-minio — object storage ของกลุ่ม docver; โฮสต์แมพ 19000 API/19001 console เพราะ 9000/9001 ชน container โปรเจกต์อื่นในเครื่อง dev, image จาก quay.io ทางการ; bucket `docver` ไม่ต้องสร้างเอง app สร้างตอน first use ผ่าน ensureBucket) -docs/PLAN-DOCVER.md ← แผนกลุ่ม API docver: จัด version เอกสาร .docx+.pdf ต่อ docId (ห้ามซ้ำ) เก็บ object ใน MinIO + ผู้อัปโหลดต่อ version — contract 3 endpoint, ดีไซน์ตาราง, env MINIO_* ทั้งหมด +docs/PLAN-DOCVER.md ← แผนกลุ่ม API docver: จัด version เอกสาร .docx+.pdf ต่อ docId (ห้ามซ้ำ) เก็บ object ใน MinIO + ผู้อัปโหลดต่อ version — contract 4 endpoint (เพิ่ม POST /token ขอ access_token — Bearer บังคับทุก endpoint), ดีไซน์ตาราง, env MINIO_* + DOCVER_API_KEY/DOCVER_TOKEN_SECRET/DOCVER_TOKEN_TTL_SECONDS ทั้งหมด scripts/patch-pdf-worker.mjs ← postinstall: คัดลอก pdf.worker.min.mjs จาก node_modules ไป public/note/ + patch fake-space ไทย (invariant #6 — เจอ pattern ≠ 1 จุดหลังอัปเกรด pdfjs-dist = exit 1 ดัง ๆ) scripts/copy-swagger-ui.mjs ← postinstall ตัวที่สอง: คัดลอก swagger-ui-dist (bundle+css) ไป public/note/swagger-ui/ สำหรับหน้า Swagger UI ที่ GET /api/swagger (intranet ไม่ใช้ CDN — หาไฟล์ไม่เจอหลังอัปเกรด = exit 1 ดัง ๆ เหมือน patch-pdf-worker; Docker build รัน script นี้ด้วย ดู docker/web.Dockerfile) scripts/fetch-office-js.mjs ← postinstall ตัวที่สาม: ดาวน์โหลด Office.js runtime **ฉบับ CDN ล่าสุด** (appsforoffice.microsoft.com — ชุดไฟล์เฉพาะ Word: loader + host files ราย platform + legacy 15.x + o15apptofilemappingtable/microsoftajax + office_strings.js ต่อ locale en-us/th-th บังคับ) ไป public/wordaddin/office-js/ ให้ taskpane โหลด same-origin — npm `@microsoft/office-js` เลิกอัปเดตที่ 1.1.110 (~2018) ไม่มี `Document.close`/enum `CloseBehavior` ที่ runtime ใหม่+`@types/office-js` มี; ไฟล์หลักหาย/ตรวจไม่เจอ `"Document.close"`+`CloseBehavior` ใน word-win32-16.01.js หลังโหลด = exit 1 ดัง ๆ (ไฟล์ปลายทาง gitignore — ดาวน์โหลดใหม่ทุกครั้ง; ติดตั้งต้องออกเน็ตถึง CDN ของ Microsoft ส่วน runtime ตัวระบบ intranet ล้วน) db/schema.ts ← ตาราง + JSONB types + DTO + mapper toDTO db/index.ts ← postgres.js + drizzle (DATABASE_URL → container judes-note-db, prepare:false) -middleware.ts ← บังคับ login ทุกหน้า — ไม่มี cookie oacCase ที่ parse ได้ = redirect /note/login (path ที่ตัด basePath แล้ว: หน้าขึ้นต้น /note/… ส่วน API ขึ้นต้น /api/note/… — ยกเว้น note/login, api/note/*, api/template/* (กลุ่ม docx template เปิดกว้างให้ทางศาล/ผู้ทดสอบภายนอกยิงได้เลย — ไม่แตะ DB), api/documents/* (กลุ่ม docver จัด version เอกสาร — stand-alone ผู้เรียกบอก userId/userName เอง ไม่มี session, docs/PLAN-DOCVER.md), api/swagger/* (หน้า Swagger UI), wordaddin/* (taskpane ของ Word add-in — auth mock ของตัวเอง), note/uploads/*, note/samples, note/swagger-ui/*, note/pdf.worker.min.mjs, _next, fonts, favicon และ **root "/" เอง** (matcher ใช้ `.+` — root เป็น redirect สู่ /wordaddin ซึ่ง public ตามคำขอผู้ใช้ 2026-10-14, ลิงก์ e-Court N8 ที่ root ยังถูกคุมผ่าน /note ที่รับส่งต่อ)) +middleware.ts ← บังคับ login ทุกหน้า — ไม่มี cookie oacCase ที่ parse ได้ = redirect /note/login (path ที่ตัด basePath แล้ว: หน้าขึ้นต้น /note/… ส่วน API ขึ้นต้น /api/note/… — ยกเว้น note/login, api/note/*, api/template/* (กลุ่ม docx template เปิดกว้างให้ทางศาล/ผู้ทดสอบภายนอกยิงได้เลย — ไม่แตะ DB), api/documents/* (กลุ่ม docver จัด version เอกสาร — stand-alone ไม่มี session ใช้ auth ของกลุ่มเอง: ขอ access_token ผ่าน x-api-key แล้วแนบ Bearer ทุก endpoint, docs/PLAN-DOCVER.md), api/swagger/* (หน้า Swagger UI), wordaddin/* (taskpane ของ Word add-in — auth mock ของตัวเอง), note/uploads/*, note/samples, note/swagger-ui/*, note/pdf.worker.min.mjs, _next, fonts, favicon และ **root "/" เอง** (matcher ใช้ `.+` — root เป็น redirect สู่ /wordaddin ซึ่ง public ตามคำขอผู้ใช้ 2026-10-14, ลิงก์ e-Court N8 ที่ root ยังถูกคุมผ่าน /note ที่รับส่งต่อ)) lib/annotate-events.ts ← pub/sub in-process (actions publish → SSE route subscribe) lib/server-pdf.ts ← path guard อ่าน PDF จาก public/note/ (export ใช้) lib/session.ts ← getSessionUser — identity จาก cookie oacCase (mock login e-Court) @@ -50,7 +50,8 @@ lib/remote-file.ts ← N8 ดึงไฟล์รีโมตจาก allow lib/external-api-auth.ts ← N10 auth ของ /api/note/external/* — requireExternalApiKey เทียบ header `x-api-key` กับ env `EXTERNAL_API_KEY` ด้วย SHA-256 + crypto.timingSafeEqual (env ไม่ตั้ง/ว่าง = 401 fail closed ทุก request; ข้อความ 401 อันเดียวกันทั้งไม่ใส่/key ผิด กัน enumerate; ห้าม log ค่า key/header ดิบ) + externalApiError สร้าง Response ผิดพลาด JSON {"error": ไทย} ใช้ร่วมทุก route ของ N10 — **module นี้ห้าม import อะไรที่ลาก @/db มาด้วย** (db/index.ts throw ตอน import เมื่อไม่มี DATABASE_URL — unit test tests/lib/external-api-auth.test.ts ต้องรันได้โดยไม่แตะ DB) lib/external-api.ts ← N10 ส่วนกลางของ route — parseQueryBody (validate JSON body ของ /api/note/external/documents: รับเฉพาะ fileId/typeId/filter, ต้องมี fileId หรือ typeId อย่างน้อยหนึ่งอย่าง — ส่งทั้งคู่ได้ = AND เหลือเฉพาะเอกสารเดียวที่ตรงทั้งสองเงื่อนไข, คำขอผู้ใช้ 2026-09-25 (รอบเดียวกันเคยเป็น OR แล้วเปลี่ยนหลังทดสอบจริง), filter รับเฉพาะ type ∈ bookmarks|annotations ไม่ส่ง = ทั้งสอง + userId (string|array กรองตามเจ้าของรายการ annotations+bookmarks) + annotationTypes (ค่า enum annotation_type เฉพาะ annotations ค่าอื่น = 400 — เดิมชื่อ types เปลี่ยน 2026-09-25) — คืน discriminated mode "file" (fileId + typeId อาจมาด้วย) / "type" (typeId เดี่ยว)) + loadDocumentBundles(fileRows, parts) (bulk: inArray ทุกไฟล์ในชุด query เดียว = 3 query ต่อ request ไม่ว่ากลุ่มกี่ไฟล์ แทนยิง 3 query ต่อไฟล์พร้อมกันทั้งกลุ่ม, code review 2026-09-25 — กรอง userIds/annotationTypes ใน SQL; คืน bundle ยุบชั้น: field เอกสาร + annotations + bookmarks ระดับเดียวกัน ไม่มี object document ซ้อน; ต่อรายการมี `url` deep link F12 กลับหน้า `/annotate/[id]` — annotation `?ann=` / bookmark `?page=<หน้า 1-based>` — URL เต็ม: origin จาก request ผู้เรียก (route อ่าน x-forwarded-proto/host แล้ว fallback host ตรง ส่งเข้าทาง parameter origin) + basePath ผ่าน withBasePath, คำขอผู้ใช้ 2026-09-25) + type ExternalDocumentDTO (externalFileId เป็น null สำหรับไฟล์ url:-fallback/อัปโหลด N4 ในกลุ่ม) lib/openapi.ts ← OpenAPI 3.0 spec ของ API ทดสอบ (กลุ่ม template + N10 + docver — 2026-10-06) — แหล่งเดียวของ Swagger UI: GET /api/template inline ลง HTML ตรง ๆ ไม่มี endpoint spec แยก; URL ใน paths เป็น path เต็ม /api/… (servers เติมแค่ basePath ตาม invariant #12) — แก้ contract ของ API ต้องมาแก้ไฟล์นี้ให้ตรงด้วย -lib/document-versioning.ts ← docver pure validation (ห้าม import อะไรที่ลาก @/db หรือ minio มาด้วย — unit test tests/lib/document-versioning.test.ts ต้องรันได้เหมือน lib/external-api-auth.ts): parseDocId (charset [A-Za-z0-9._:-] 1–200), parseVersionUploadForm (allowlist field + userId/userName บังคับ + fields JSON ≤100 keys + message ≤2048), isDocxBytes/isPdfBytes (magic bytes), parseDownloadFormat/parseVersionIdParam, contentDispositionValue (ASCII fallback + filename*=UTF-8'') + docverError สร้าง Response JSON {"error": ไทย} ใช้ร่วมทุก route ของกลุ่ม +lib/document-versioning.ts ← docver pure validation (ห้าม import อะไรที่ลาก @/db หรือ minio มาด้วย — unit test tests/lib/document-versioning.test.ts ต้องรันได้เหมือน lib/external-api-auth.ts): parseDocId (charset [A-Za-z0-9._:-] 1–200), parseVersionUploadForm (allowlist field — identity ไม่รับจาก form แล้ว แกะจาก token ผ่าน lib/docver-token.ts + fields JSON ≤100 keys + message ≤2048), isDocxBytes/isPdfBytes (magic bytes), parseDownloadFormat/parseVersionIdParam, contentDispositionValue (ASCII fallback + filename*=UTF-8'') + docverError สร้าง Response JSON {"error": ไทย} ใช้ร่วมทุก route ของกลุ่ม +lib/docver-token.ts ← docver token + auth ของกลุ่ม /api/documents/* (2026-10-22 — pure module ห้าม import อะไรที่ลาก @/db หรือ minio มาด้วย เหมือน lib/document-versioning.ts; unit test tests/lib/docver-token.test.ts ต้องรันได้โดยไม่ต้องตั้ง env): issueDocverToken/verifyDocverToken (JWT HS256 มือเขียน node:crypto ล้วน — บังคับ alg=HS256 กัน alg-confusion, timingSafeEqual หลัง hash ทั้งสองฝั่ง, exp บังคับ) + requireDocverApiKey (header x-api-key เทียบ env DOCVER_API_KEY — fail closed 401 ไม่ตั้ง/ว่าง, 401 เดียวกันทั้งไม่ใส่/ผิด กัน enumerate, ห้าม log ค่า key/header ดิบ) + requireDocverBearer (Authorization: Bearer → claims {userid, username, fid, template} — secret จาก env DOCVER_TOKEN_SECRET ไม่ตั้ง = 401, ทุกกรณี 401 ข้อความเดียวกัน) + parseTokenRequestBody (allowlist userid/username/fid/template — fid = fileId ของ e-Court เก็บเป็น claim ยังไม่ถูก operation ใดอ่าน) lib/docver-store.ts ← docver server-side MinIO + DB (env MINIO_* อ่านตอนเรียกไม่ใช่ module load; client + cached ensureBucket เก็บบน globalThis แบบ db/index.ts): object key `docver//document.docx|pdf` (uuid ล้วน ไม่มี input ผู้ใช้), putVersionObjects (docx ก่อน — pdf ล้ม = ลบ docx คืนแล้วโยนต่อ), removeVersionObjects (best-effort), presignVersionUrl (TTL env DOCVER_LINK_TTL_SECONDS), insertVersionRecord (**tx เดียว**: upsert documents ON CONFLICT DO NOTHING → SELECT id FOR UPDATE → max(versionNumber)+1 → insert — จ่ายเลข version ปลอดภัยเรียกคู่ขนาน, UNIQUE(document_id, version_number) เป็น safety net) wordaddin/ ← **client-only zone ของ Word add-in** (ห้าม import จาก lib/ หรือถูก import โดย server component — พอร์ตดิบจาก D:\word-addin-poc\addin\src, spec: docs/addin-ui-flow.md, คู่มือ dev: docs/WORDADDIN-DEV.md) wordaddin/config.ts ← env NEXT_PUBLIC_ADDIN_API_BASE_URL ("" = ยังไม่ต่อ backend จริง) + NEXT_PUBLIC_ADDIN_MOCK_USERNAME (mock auth) @@ -82,7 +83,7 @@ app/api/note/annotations/export/route.ts ← export PDF ฝัง annotation app/api/note/external/documents/route.ts ← N10 API ภายนอกเส้นเดียว (POST + JSON body {fileId และ/หรือ typeId, filter:{type, userId, annotationTypes}} — auth ผ่าน requireExternalApiKey ก่อนแตะ DB, ถาม fileId = ค้น files.external_id = "ecourt:" ไม่เจอ 404 แม้ส่ง typeId มาด้วย / ถาม typeId = ทุกไฟล์ในกลุ่ม กลุ่มว่าง = 200 documents:[] / ส่งทั้งคู่ = AND กรอง externalId+typeId ใน query เดียว เหลือเอกสารเดียว ไม่ตรงคู่ = 404 แยกสาเหตุในข้อความ (2026-09-25 เดิมเคยเป็น OR แล้วเป็น 400); response รูปเดียว {documents:[bundle…]} ผ่าน loadDocumentBundles ทั้งสอง mode; ครอบ try/catch ทุก DB call — ล้ม = log ฝั่ง server + 500 JSON ตาม contract ไม่ปล่อยเป็น 500 ของ framework; read-only ไม่ publish SSE ไม่ revalidatePath — snapshot ณ ตอนเรียก) app/api/swagger/route.ts ← **GET /api/swagger = หน้า Swagger UI** (2026-10-06 — document ของ API ทุกกลุ่ม: spec inline จาก lib/openapi.ts + asset จาก public/note/swagger-ui/ ของ postinstall scripts/copy-swagger-ui.mjs, persistAuthorization เก็บ x-api-key ไว้ใช้ข้าม reload — เปิดกว้างไม่ต้อง login) app/api/template/{test,extract,render}/route.ts ← กลุ่ม API docx template ของ wordaddin (WIP — GET test ตรวจ service / POST extract ดึงค่า Content Control / POST render = template(.docx)+data(JSON ไฟล์หรือ field dataJson) คืน .docx ใหม่; logic รวมอยู่ app/api/template/lib-tmpl.ts เป็นแชร์ไลบรารี pizzip+xpath; ไฟล์ทดสอบ/โน้ตอยู่ app/api/template/test/ — ไม่แตะ DB, URL /docedit/api/template/…) -app/api/documents/[docId]/versions/route.ts + [versionId]/download-link/route.ts ← **กลุ่ม API docver จัด version เอกสาร** (docs/PLAN-DOCVER.md — URL /docedit/api/documents/…, ไม่มี auth เปิดกว้างเหมือน api/template): POST versions = multipart `file`(.docx บังคับ ≤100MB เช็ค size ก่อน buffer → 413)+`pdf`(optional ไฟล์เปล่า = ไม่มี)+userId/userName+fields/message/clientVersionId/templateId/fileName → สร้างเอกสารอัตโนมัติถ้า docId ใหม่, MinIO ก่อน DB (DB ล้มหลัง put = เก็บกวาด object แบบ N4) → `{hasPdf, versionId, versionNumber}` (เลขจาก server เป็นค่าจริง) / GET versions = DocumentVersionDTO[] ใหม่→เก่า (ไม่เคยมี docId = 404) / GET download-link?format=docx|pdf = presigned URL อายุสั้น (เช็ค docId+versionId คู่กันกันโยน id ข้ามเอกสาร; ขอ pdf ที่ version ไม่มี = 404) — carve-out invariant #4: ไม่ publish SSE ไม่ revalidatePath +app/api/documents/token/route.ts + [docId]/versions/route.ts + [versionId]/download-link/route.ts ← **กลุ่ม API docver จัด version เอกสาร** (docs/PLAN-DOCVER.md — URL /docedit/api/documents/…, auth ของกลุ่มเอง 2026-10-22: POST token ขอ access_token ด้วย header `x-api-key` + env `DOCVER_API_KEY` (lib/docver-token.ts — fail closed 401 ไม่ตั้ง/ว่าง เหมือน N10) แล้ว **endpoint ที่เหลือทุกตัวบังคับ `Authorization: Bearer`** JWT HS256 เซ็นด้วย env `DOCVER_TOKEN_SECRET` (TTL `DOCVER_TOKEN_TTL_SECONDS` default 3600) — identity userid/username + templateId แกะจาก claims ไม่รับจาก request อีกต่อไป; ส่ง userId/userName/templateId ใน form = 400 ไม่รู้จัก field): POST versions = multipart `file`(.docx บังคับ ≤100MB เช็ค size ก่อน buffer → 413)+`pdf`(optional ไฟล์เปล่า = ไม่มี)+fields/message/clientVersionId/fileName → สร้างเอกสารอัตโนมัติถ้า docId ใหม่, MinIO ก่อน DB (DB ล้มหลัง put = เก็บกวาด object แบบ N4) → `{hasPdf, versionId, versionNumber}` (เลขจาก server เป็นค่าจริง) / GET versions = DocumentVersionDTO[] ใหม่→เก่า (ไม่เคยมี docId = 404) / GET download-link?format=docx|pdf = presigned URL อายุสั้น (เช็ค docId+versionId คู่กันกันโยน id ข้ามเอกสาร; ขอ pdf ที่ version ไม่มี = 404) — carve-out invariant #4: ไม่ publish SSE ไม่ revalidatePath app/note/uploads/[name]/route.ts ← เสิร์ฟไฟล์อัปโหลดจาก public/note/uploads/ (N4 — Next production เห็นไฟล์ static เฉพาะที่มีตอน startup ไฟล์ที่เขียน runtime ต้องเสิร์ฟผ่าน route) app/note/page.tsx ← รายการไฟล์จัด section ตามกลุ่ม type_id + "ไม่มีกลุ่ม" ท้ายสุด (N9 — server component) app/note/groups/[typeId]/page.tsx ← N9 หน้าเอกสารในกลุ่ม (server component force-dynamic — files where type_id orderBy createdAt, ไม่มี = 404; ~~หมายเหตุ "แสดงเฉพาะเอกสารที่เคยเปิดในระบบนี้"~~ ถอดออกตามคำขอผู้ใช้ 2026-09-24; **redesign 2026-09-24 ตามคำขอผู้ใช้: ไม่มี navbar** — explorer 2 คอลัมน์ ตัวกรองซ้าย + list เดียวรวม bookmark+annotation ทั้งกลุ่มไม่แยกไฟล์: query ทั้งหมดฝั่ง server แล้วส่ง `GroupFileSummary[]` + `GroupListItem[]` (เรียง ไฟล์ตาม createdAt → หน้า → bookmark ก่อน annotation → createdAt) ให้ `GroupExplorer` กรองใน memory, แถวรายการ deep link `?ann=`/`?page=` เหมือนหน้าสรุป F12 + ไฟล์ที่มาต้น meta line) diff --git a/app/api/documents/[docId]/versions/[versionId]/download-link/route.ts b/app/api/documents/[docId]/versions/[versionId]/download-link/route.ts index 0b91a6c..b35fd0a 100644 --- a/app/api/documents/[docId]/versions/[versionId]/download-link/route.ts +++ b/app/api/documents/[docId]/versions/[versionId]/download-link/route.ts @@ -1,12 +1,14 @@ // PLAN-DOCVER.md §1 — GET /api/documents/{docId}/versions/{versionId}/download-link?format=docx|pdf // คืน presigned URL อายุสั้น (env DOCVER_LINK_TTL_SECONDS) ให้ UI เปิดในเบราว์เซอร์จริง // ของผู้ใช้ — เช็คทั้ง docId และ versionId พร้อมกันกันโยน id ข้ามเอกสาร +// auth: Authorization: Bearer (access_token จาก POST /api/documents/token — ทุก endpoint ของกลุ่ม) import { docverError, parseDocId, parseDownloadFormat, parseVersionIdParam, } from "@/lib/document-versioning"; +import { requireDocverBearer } from "@/lib/docver-token"; import { findVersionForDoc, presignVersionUrl } from "@/lib/docver-store"; export const runtime = "nodejs"; @@ -16,6 +18,9 @@ export async function GET( request: Request, { params }: { params: { docId: string; versionId: string } }, ) { + const auth = requireDocverBearer(request); + if (!auth.ok) return auth.response; + const docId = parseDocId(params.docId); if (!docId.ok) return docverError(docId.error, 400); const versionId = parseVersionIdParam(params.versionId); diff --git a/app/api/documents/[docId]/versions/route.ts b/app/api/documents/[docId]/versions/route.ts index d022100..f2e8edb 100644 --- a/app/api/documents/[docId]/versions/route.ts +++ b/app/api/documents/[docId]/versions/route.ts @@ -1,6 +1,8 @@ // PLAN-DOCVER.md §1 — POST อัปโหลด version ใหม่ + GET history ของ /api/documents/{docId}/versions -// กลุ่ม API stand-alone ไม่มี auth (เปิดกว้างเหมือน api/template — intranet) — ผู้เรียกบอก -// identity เองผ่าน field userId/userName เพราะไม่มี session ร่วมกับใคร +// กลุ่ม API stand-alone ไม่มี session — auth ด้วย access_token ของกลุ่มนี้: ขอที่ +// POST /api/documents/token (ยืนยัน x-api-key กับ env DOCVER_API_KEY) แล้วแนบ +// Authorization: Bearer ทุก request — identity (userid/username) และ templateId +// แกะจาก claims ของ token ไม่รับจาก multipart form อีก (lib/docver-token.ts) import { createHash, randomUUID } from "node:crypto"; import { toDocumentVersionDTO } from "@/db/schema"; @@ -14,6 +16,7 @@ import { parseVersionUploadForm, sanitizeFilePartName, } from "@/lib/document-versioning"; +import { requireDocverBearer } from "@/lib/docver-token"; import { findDocumentByDocId, insertVersionRecord, @@ -27,11 +30,14 @@ export const runtime = "nodejs"; export const dynamic = "force-dynamic"; /** POST — อัปโหลด version ใหม่ (เอกสารสร้างแบบ implicit ถ้าเป็น version แรก) - * ลำดับ: validate text field ก่อนแตะไฟล์ → cap ขนาดก่อน buffer → magic bytes → + * ลำดับ: Bearer token → validate text field ก่อนแตะไฟล์ → cap ขนาดก่อน buffer → magic bytes → * putObject MinIO → insert DB (tx) — DB ล้ม = เก็บกวาด object (pattern N4) * response = UploadVersionResult เป๊ะ โดย versionNumber เป็นค่า authoritative * จาก server (client เดา latest+1 แล้วแก้เองเมื่ออัปโหลดชนกัน) */ export async function POST(request: Request, { params }: { params: { docId: string } }) { + const auth = requireDocverBearer(request); + if (!auth.ok) return auth.response; + const docId = parseDocId(params.docId); if (!docId.ok) return docverError(docId.error, 400); @@ -114,14 +120,14 @@ export async function POST(request: Request, { params }: { params: { docId: stri contentHash, message: parsed.form.message, fields: parsed.form.fields, - templateId: parsed.form.templateId, + templateId: auth.claims.template, fileName: docxDisplayName, docxObjectKey: keys.docx, pdfObjectKey: pdfBytes ? keys.pdf : null, docxSize: docxBytes.length, pdfSize: pdfBytes?.length ?? null, - userId: parsed.form.userId, - userName: parsed.form.userName, + userId: auth.claims.userid, + userName: auth.claims.username, }); } catch (err) { // DB ล้มหลังเขียน object — เก็บกวาดก่อนตอบ กัน object กำพร้าไม่มี row ชี้ @@ -142,7 +148,10 @@ export async function POST(request: Request, { params }: { params: { docId: stri /** GET — history ของเอกสาร (ใหม่ → เก่า) · docId ที่ยังไม่เคยมี version = 404 * (สัญญาณ REST ตรง ๆ แบบ N10 — UI ฝั่ง add-in แสดง error + ปุ่มลองใหม่ ไม่พัง) */ -export async function GET(_request: Request, { params }: { params: { docId: string } }) { +export async function GET(request: Request, { params }: { params: { docId: string } }) { + const auth = requireDocverBearer(request); + if (!auth.ok) return auth.response; + const docId = parseDocId(params.docId); if (!docId.ok) return docverError(docId.error, 400); diff --git a/app/api/documents/token/route.ts b/app/api/documents/token/route.ts new file mode 100644 index 0000000..7593e4f --- /dev/null +++ b/app/api/documents/token/route.ts @@ -0,0 +1,47 @@ +// PLAN-DOCVER.md §auth — POST /api/documents/token ออก access_token ของกลุ่ม docver +// ยืนยันด้วย header "x-api-key" เทียบ env DOCVER_API_KEY (fail closed เมื่อ env ไม่ +// ตั้ง/ว่าง) — token เป็น JWT HS256 เซ็นด้วย env DOCVER_TOKEN_SECRET อายุตาม env +// DOCVER_TOKEN_TTL_SECONDS (default 3600) แล้วนำไปแนบ Authorization: Bearer กับ +// endpoint อื่นของกลุ่ม (lib/docver-token.ts) · segment คงที่ "token" ไม่ชน [docId] +// (Next ให้ static ชนะ) และ middleware ยกเว้น api/documents/* อยู่แล้ว +import { docverError } from "@/lib/document-versioning"; +import { + issueDocverToken, + parseTokenRequestBody, + requireDocverApiKey, +} from "@/lib/docver-token"; + +export const runtime = "nodejs"; +export const dynamic = "force-dynamic"; + +export async function POST(request: Request) { + const apiKeyFail = requireDocverApiKey(request); + if (apiKeyFail !== null) return apiKeyFail; + + let raw: unknown; + try { + raw = await request.json(); + } catch { + return docverError("body ต้องเป็น JSON", 400); + } + const parsed = parseTokenRequestBody(raw); + if (!parsed.ok) return docverError(parsed.error, 400); + + // ความผิด config ฝั่ง server ไม่ใช่ความผิดของผู้เรียก — ไม่ตั้ง = 500 (ห้าม log ค่า secret) + const secret = process.env.DOCVER_TOKEN_SECRET?.trim(); + if (secret === undefined || secret === "") { + return docverError("DOCVER_TOKEN_SECRET ยังไม่ถูกตั้งค่า", 500); + } + const ttlRaw = Number(process.env.DOCVER_TOKEN_TTL_SECONDS); + const ttlSeconds = Number.isFinite(ttlRaw) && ttlRaw > 0 ? Math.floor(ttlRaw) : 3600; + + const accessToken = issueDocverToken(parsed.claims, secret, ttlSeconds); + return new Response( + JSON.stringify({ + access_token: accessToken, + token_type: "Bearer", + expires_in: ttlSeconds, + }), + { headers: { "content-type": "application/json; charset=utf-8" } }, + ); +} diff --git a/docs/PLAN-DOCVER.md b/docs/PLAN-DOCVER.md index 771669a..b5f9e5c 100644 --- a/docs/PLAN-DOCVER.md +++ b/docs/PLAN-DOCVER.md @@ -1,6 +1,7 @@ # PLAN-DOCVER — Document Versioning API (stand-alone) -> วันที่ตกลงแผน: 2026-10-20 · สถานะ: implement รอบแรก (API ก่อน — ยังไม่ต่อ wordaddin) +> วันที่ตกลงแผน: 2026-10-20 · เพิ่ม auth ด้วย access_token: 2026-10-22 · สถานะ: implement +> รอบแรก (API ก่อน — ยังไม่ต่อ wordaddin) ## 0. บริบท/เป้าหมาย @@ -12,9 +13,14 @@ optional/best-effort ตาม contract ของ add-in — `pdf.size === 0` = ไม่มี, มี `hasPdf` บอกใน response) - เก็บ `userId`, `userName`, `createdAt` ต่อ version เพื่อรู้ว่าใครอัปโหลด -- **endpoint รับ `userId`/`userName` ใน request** เพราะเป็นระบบ stand-alone ไม่มี session - ร่วมกับใคร (ผู้เรียกคือ add-in ใน Word หรือระบบภายนอก — identity ส่งมาแบบ plain field) -- **ไม่มี auth** — เปิดกว้างเหมือนกลุ่ม `api/template` (intranet) · ไม่มี CORS +- **identity (`userId`/`userName`/`templateId`) แกะจาก Bearer token** (2026-10-22 — เดิมส่ง + plain field ใน multipart): ขอ token ที่ `POST /api/documents/token` ด้วย header + `x-api-key` แล้วแนบ `Authorization: Bearer ` กับทุก request — ระบบ stand-alone + ไม่มี session ร่วมกับใคร (ผู้เรียกคือ add-in ใน Word หรือระบบภายนอก) token จึงเป็น + พาหะ identity เดียว +- **auth ทุก endpoint ของกลุ่มนี้** = Bearer JWT HS256 เซ็นด้วย `DOCVER_TOKEN_SECRET` + (ออก token ผ่าน `x-api-key` เทียบ env `DOCVER_API_KEY` — pattern `lib/external-api-auth.ts` + ของ N10) · ไม่มี CORS ข้อตกลงกับผู้ใช้ (2026-10-20): ทำ API ก่อน **ไม่แต้ wordaddin รอบนี้** — contract ตรง surface ที่ mock คงไว้เป๊ะ วันหน้าสลับของจริงแค่ตั้ง `NEXT_PUBLIC_ADDIN_API_BASE_URL` แล้วก้อบ body @@ -28,23 +34,48 @@ Contract อ้างอิง: `docs/addin-ui-flow.md` §7 + `wordaddin/service ## 1. Endpoint (URL เต็ม = `/docedit/api/documents/…` — prefix ใหม่ ได้รับยกเว้นจาก middleware เหมือน `api/template`) +### `POST /api/documents/token` — ขอ access_token (2026-10-22) + +auth: header `x-api-key` เทียบ env `DOCVER_API_KEY` (ไม่ตั้ง/ว่าง = 401 fail closed · +ไม่ใส่/ผิด = 401 ข้อความเดียวกันเป๊ะ กัน enumerate) — ตรวจก่อนอ่าน body เสมอ + +JSON body (allowlist — key อื่น = 400 กัน typo): + +| field | type | หมายเหตุ | +|---|---|---| +| `userid` | string | บังคับ — trim 1–100 (claim `userid` → column `user_id`) | +| `username` | string | บังคับ — trim 1–200 (claim `username` → column `user_name`) | +| `fid` | integer | optional — **fileId ของ e-Court** เก็บเป็น claim อย่างเดียว ยังไม่มี operation ใดอ่าน/บังคับ (ตารางของกลุ่มนี้คีย์ด้วย `docId` ไม่มี column fileId) | +| `template` | string | optional ≤100 (claim `template` → column `template_id` ตอน upload) | + +- env `DOCVER_TOKEN_SECRET` ไม่ตั้ง/ว่าง = **500** "DOCVER_TOKEN_SECRET ยังไม่ได้ตั้งค่า" + (ความผิด config ฝั่ง server ไม่ใช่ความผิดของผู้เรียก — และห้าม log ค่า secret) +- response 200: `{ "access_token": string, "token_type": "Bearer", "expires_in": number }` + — JWT HS256 stateless มี `iat`/`exp` (TTL env `DOCVER_TOKEN_TTL_SECONDS` default 3600) + · verify บังคับ alg=HS256 (กัน alg-confusion) และเทียบ signature แบบ timing-safe +- **endpoint อื่นของกลุ่มนี้ทั้งหมดต้องแนบ `Authorization: Bearer `** — + ไม่มี header / รูปผิด / signature ผิด / หมดอายุ / secret ไม่ตั้ง = 401 ข้อความไทย + เดียวกันทุกกรณี (ไม่เปิดให้ใช้ endpoint เป็น oracle รู้สาเหตุ) + ### `POST /api/documents/{docId}/versions` — อัปโหลด version ใหม่ +auth: `Authorization: Bearer` (ดู /token ด้านบน) — identity/templateId ของ row ใหม่ +มาจาก claims ของ token ไม่รับจาก form อีกต่อไป + multipart/form-data: | field | type | หมายเหตุ | |---|---|---| | `file` | File (.docx) | บังคับ — ≤ 100MB, magic bytes `PK\x03\x04` | | `pdf` | File (.pdf) | optional — `size === 0` = ถือว่าไม่มี (add-in แนบ blob เปล่าได้) | -| `userId` | string | **บังคับ** (stand-alone) — trim 1–100 | -| `userName` | string | **บังคับ** (stand-alone) — trim 1–200 | | `clientVersionId` | string | optional ≤100 — GUID ที่ client generate เอง | | `fields` | string (JSON) | optional — map `{ [sdtTag]: text }` (≤100 keys, value ≤10,000) | | `message` | string | optional — commit message ≤2048 (trim ว่าง = NULL) | -| `templateId` | string | optional ≤100 | | `fileName` | string | optional — ชื่อไฟล์ดั้งเดิม (ผ่าน `decodeUploadFilename` แล้ว) | -- field ที่ไม่อยู่ในตาราง = **400 กัน typo เงียบ ๆ** (pattern `parseQueryBody` ของ N10) +- field ที่ไม่อยู่ในตาราง = **400 กัน typo เงียบ ๆ** (pattern `parseQueryBody` ของ N10) — + **breaking change 2026-10-22:** `userId`/`userName`/`templateId` ถอดออกจาก form แล้ว + (ส่งมา = 400) identity/template มาจาก Bearer token ของ request นั้นแทน - เอกสาร (`documents` row) สร้าง **แบบ implicit** ตอน upload version แรก — ไม่มี endpoint "สร้างเอกสาร" แยก (ตาม flow ของ add-in ที่ generate documentId เองแล้วอัปโหลดทันที) - response 200: `{ "hasPdf": boolean, "versionId": string, "versionNumber": number }` @@ -88,10 +119,11 @@ response: `DocumentVersionDTO[]` เรียง **ใหม่ → เก่า | เหตุ | สถานะ | |---|---| -| รูป docId/versionId/format ผิด · field ไม่รู้จัก · ไม่ใช่ multipart · magic bytes ผิด | 400 | +| รูป docId/versionId/format ผิด · field ไม่รู้จัก (รวม userId/userName/templateId เก่า) · ไม่ใช่ multipart · magic bytes ผิด · body ขอ /token ผิดกติกา | 400 | +| `x-api-key` ไม่ตั้ง/ไม่ใส่/ผิด (ที่ /token) · Bearer ขาด/เน่า/หมดอายุ/secret ไม่ตั้ง (ทุก endpoint อื่น) | 401 | | ไฟล์เกิน 100MB (เช็ค `file.size` ก่อน buffer เสมอ — route ไม่มี `bodySizeLimit` คุม) | 413 | | เอกสาร/version ไม่มี · ขอ pdf ที่ version ไม่มี | 404 | -| MinIO env ไม่ตั้ง/ล้ม · DB ล้ม (หลังเก็บกวาด object กำพร้า) | 500 (log เต็มฝั่ง server + ข้อความกลาง ๆ ไทย) | +| MinIO env ไม่ตั้ง/ล้ม · DB ล้ม (หลังเก็บกวาด object กำพร้า) · `DOCVER_TOKEN_SECRET` ไม่ตั้ง (ที่ /token) | 500 (log เต็มฝั่ง server + ข้อความกลาง ๆ ไทย) | ## 2. ตาราง (db/schema.ts) @@ -156,6 +188,9 @@ read-only ของ N10 แต่ตัวนี้เขียนได้) | `MINIO_ACCESS_KEY` / `MINIO_SECRET_KEY` | — บังคับ | dev compose = `minioadmin` | | `MINIO_BUCKET` | `docver` | app สร้างเองตอน first use (`ensureBucket` — จับ race ข้าม process) | | `DOCVER_LINK_TTL_SECONDS` | `900` | อายุ presigned URL | +| `DOCVER_API_KEY` | — บังคับ | API key ของ `POST /api/documents/token` (header `x-api-key`) — ไม่ตั้ง/ว่าง = 401 fail closed · เทียบ timing-safe · ห้าม log ค่าดิบ | +| `DOCVER_TOKEN_SECRET` | — บังคับ | secret เซ็น/ตรวจ JWT HS256 — ไม่ตั้ง: /token = 500 · endpoint อื่น = 401 · ห้าม log ค่าดิบ | +| `DOCVER_TOKEN_TTL_SECONDS` | `3600` | อายุ access_token (วินาที) | `putObject` แนบ `Content-Type` + `Content-Disposition` (ชื่อไฟล์ไทยผ่าน `filename*=UTF-8''` best-effort + ASCII fallback `document.docx`/`document.pdf`) — presigned GET ได้ชื่อไฟล์สวย @@ -166,13 +201,16 @@ best-effort + ASCII fallback `document.docx`/`document.pdf`) — presigned GET - `db/schema.ts` — ตาราง + `DocumentVersionDTO`/`toDocumentVersionDTO` - `lib/document-versioning.ts` — pure validation (ห้าม import `@/db`/`minio` — unit test ได้ โดยไม่ต้องตั้ง env อะไรเลย ตาม pattern `lib/external-api-auth.ts`) +- `lib/docver-token.ts` — JWT HS256 issue/verify + `requireDocverApiKey`/`requireDocverBearer` + + `parseTokenRequestBody` (pure เหมือนกัน — ไม่ import `@/db`/`minio`) - `lib/docver-store.ts` — MinIO client (เก็บบน `globalThis` ใน dev แบบ `db/index.ts`) + `ensureBucket` + put/remove/presign + `insertVersionRecord` (tx) + query helpers +- `app/api/documents/token/route.ts` — POST ขอ access_token (x-api-key) - `app/api/documents/[docId]/versions/route.ts` — POST + GET - `app/api/documents/[docId]/versions/[versionId]/download-link/route.ts` — GET - `docker-compose.yml` — db (ชื่อ/วอลุ่มเดิมกับ `docker run` ใน README) + minio (โฮสต์ 19000 API / 19001 console — ข้างใน container 9000/9001; image จาก quay.io) -- `tests/lib/document-versioning.test.ts` — unit test ส่วน pure +- `tests/lib/document-versioning.test.ts` + `tests/lib/docver-token.test.ts` — unit test ส่วน pure ## 8. Out of scope รอบนี้ diff --git a/lib/document-versioning.ts b/lib/document-versioning.ts index 9973286..ebb55fe 100644 --- a/lib/document-versioning.ts +++ b/lib/document-versioning.ts @@ -23,16 +23,15 @@ const DOC_ID_RE = /^[A-Za-z0-9._:-]{1,200}$/; const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; -/** field ที่รับใน multipart ของ POST …/versions — อะไรนอกนี้ = 400 กัน typo เงียบ ๆ */ +/** field ที่รับใน multipart ของ POST …/versions — อะไรนอกนี้ = 400 กัน typo เงียบ ๆ + * (userId/userName/templateId ถอดออกแล้ว — identity/template มาจาก Bearer token + * ผ่าน lib/docver-token.ts ส่ง field เก่ามา = 400 ตาม convention) */ const FORM_FIELDS = [ "file", "pdf", - "userId", - "userName", "clientVersionId", "fields", "message", - "templateId", "fileName", ] as const; @@ -160,11 +159,8 @@ export function parseFieldsJson(raw: string): } export type ParsedVersionUploadForm = { - userId: string; - userName: string; clientVersionId: string | null; message: string | null; - templateId: string | null; /** ชื่อไฟล์ดั้งเดิมจาก field "fileName" (แยกจากชื่อของ File เอง — add-in ส่งมาเอง * เพราะ desktop Word ได้ชื่อจาก path จริง) — strip path + cap แล้ว */ fileName: string | null; @@ -172,7 +168,8 @@ export type ParsedVersionUploadForm = { }; /** ตรวจ text field ทั้งหมดของ POST …/versions (file/pdf เช็คที่ route เพราะต้องอ่าน - * size ก่อน buffer) — userId/userName บังคับเพราะระบบ stand-alone ไม่มี session */ + * size ก่อน buffer) — identity (userId/userName) กับ templateId ไม่รับใน form + * แล้ว: route แกะจาก Bearer token ผ่าน lib/docver-token.ts ก่อนเรียกตรงนี้ */ export function parseVersionUploadForm(form: FormData): | { ok: true; form: ParsedVersionUploadForm } | DocverError { @@ -187,36 +184,12 @@ export function parseVersionUploadForm(form: FormData): } } - const userId = formString(form, "userId"); - if (!userId.ok) return userId; - if (userId.value === null) { - return { ok: false, error: "ต้องระบุ userId (ระบบ stand-alone — บอกผู้อัปโหลดเอง)" }; - } - if (userId.value.length > 100) { - return { ok: false, error: "userId ยาวเกิน 100 ตัวอักษร" }; - } - - const userName = formString(form, "userName"); - if (!userName.ok) return userName; - if (userName.value === null) { - return { ok: false, error: "ต้องระบุ userName (ระบบ stand-alone — บอกผู้อัปโหลดเอง)" }; - } - if (userName.value.length > 200) { - return { ok: false, error: "userName ยาวเกิน 200 ตัวอักษร" }; - } - const clientVersionId = formString(form, "clientVersionId"); if (!clientVersionId.ok) return clientVersionId; if (clientVersionId.value && clientVersionId.value.length > 100) { return { ok: false, error: "clientVersionId ยาวเกิน 100 ตัวอักษร" }; } - const templateId = formString(form, "templateId"); - if (!templateId.ok) return templateId; - if (templateId.value && templateId.value.length > 100) { - return { ok: false, error: "templateId ยาวเกิน 100 ตัวอักษร" }; - } - const message = formString(form, "message"); if (!message.ok) return message; if (message.value && message.value.length > DOCVER_MESSAGE_MAX) { @@ -238,11 +211,8 @@ export function parseVersionUploadForm(form: FormData): return { ok: true, form: { - userId: userId.value, - userName: userName.value, clientVersionId: clientVersionId.value, message: message.value, - templateId: templateId.value, fileName: sanitizeDisplayFileName(rawFileName.value), fields, }, diff --git a/lib/docver-token.ts b/lib/docver-token.ts new file mode 100644 index 0000000..2b48bb3 --- /dev/null +++ b/lib/docver-token.ts @@ -0,0 +1,208 @@ +// PLAN-DOCVER.md §auth — access_token ของกลุ่ม /api/documents (document versioning) +// - POST /api/documents/token ออก JWT HS256 ให้ผู้เรียกที่ยืนยัน header "x-api-key" +// กับ env DOCVER_API_KEY (fail closed เมื่อ env ไม่ตั้ง/ว่าง — pattern เดียวกับ +// lib/external-api-auth.ts ของ N10: hash SHA-256 ทั้งสองค่า + timingSafeEqual, +// ข้อความ 401 ของ "ไม่ใส่ key" กับ "key ผิด" เป็นอันเดียวกัน, ห้าม log ค่าดิบ) +// - token เซ็นด้วย env DOCVER_TOKEN_SECRET (มือเขียน HS256 ผ่าน node:crypto ล้วน — +// ไม่เพิ่ม dependency) แล้วทุก endpoint อื่นของกลุ่มบังคับ Authorization: Bearer — +// identity (userid/username) กับ templateId แกะจาก claims ไม่รับจาก payload +// - claims "fid" = fileId ของ e-Court — ตาราง docver คีย์ด้วย docId (ไม่มีคอลัมน์ +// fileId ตัวเลข) จึงเก็บเป็น claim ไว้ก่อน ยังไม่มี operation ใดอ่าน/บังคับ +// +// ห้าม import อะไรที่ลาก @/db หรือ minio มาด้วย — unit test (tests/lib/) ต้องรันได้ +// โดยไม่ต้องตั้ง DATABASE_URL/MINIO_* (pattern เดียวกับ lib/external-api-auth.ts) +import { createHash, createHmac, timingSafeEqual } from "node:crypto"; + +import { docverError, type DocverError } from "@/lib/document-versioning"; + +/** claims ของ access_token docver — โครงสร้างตามผู้เรียกกำหนด (e-Court) */ +export type DocverTokenClaims = { + userid: string; + username: string; + fid: number | null; + template: string | null; +}; + +function base64url(input: string): string { + return Buffer.from(input, "utf8").toString("base64url"); +} + +function decodeJsonSegment(segment: string): Record | null { + let text: string; + try { + text = Buffer.from(segment, "base64url").toString("utf8"); + } catch { + return null; + } + try { + const parsed = JSON.parse(text) as unknown; + if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) { + return null; + } + return parsed as Record; + } catch { + return null; + } +} + +/** ออก JWT HS256 (base64url 3 segment) — payload = claims + iat/exp (วินาที) */ +export function issueDocverToken( + claims: DocverTokenClaims, + secret: string, + ttlSeconds: number, +): string { + const header = base64url(JSON.stringify({ alg: "HS256", typ: "JWT" })); + const iat = Math.floor(Date.now() / 1000); + const payload = base64url( + JSON.stringify({ ...claims, iat, exp: iat + ttlSeconds }), + ); + const signature = createHmac("sha256", secret) + .update(`${header}.${payload}`) + .digest("base64url"); + return `${header}.${payload}.${signature}`; +} + +/** claims ที่ถอดได้จาก payload ที่ตรวจ signature ผ่านแล้ว — รูปร่างผิด = invalid + * (กัน token ที่เซ็นถูกแต่ claims ไม่ครบ/ผิด type หลุดไปใช้ต่อ) */ +function claimsFromPayload(payload: Record): DocverTokenClaims | null { + const { userid, username, fid, template } = payload; + if (typeof userid !== "string" || userid === "") return null; + if (typeof username !== "string" || username === "") return null; + if (fid !== null && !(typeof fid === "number" && Number.isInteger(fid))) return null; + if (template !== null && typeof template !== "string") return null; + return { userid, username, fid, template }; +} + +/** ตรวจ token: 3 segment → alg บังคับ HS256 (กัน alg-confusion เช่น "none") → + * เทียบ signature แบบ timing-safe (hash ทั้งสองฝั่งก่อน — digest ยาวเท่ากันเสมอ) → + * exp ต้องยังไม่ผ่านมาแล้ว → claims ต้องครบรูปร่าง */ +export function verifyDocverToken( + token: string, + secret: string, +): { ok: true; claims: DocverTokenClaims } | { ok: false } { + const segments = token.split("."); + if (segments.length !== 3) return { ok: false }; + const [headerSegment, payloadSegment, signatureSegment] = segments; + + const header = decodeJsonSegment(headerSegment); + if (header === null || header.alg !== "HS256") return { ok: false }; + + const expected = createHmac("sha256", secret) + .update(`${headerSegment}.${payloadSegment}`) + .digest(); + // ถอด base64url กลับเป็น byte ดิบของ signature ก่อน — แล้ว hash ทั้งสองฝั่ง + // (SHA-256 = 32 byte เท่ากันเสมอ แม้ segment จะเป็นขยะ decode ได้สั้น/ว่าง — + // timingSafeEqual จึงไม่ throw เรื่องความยาวต่างกัน) + const provided = createHash("sha256") + .update(Buffer.from(signatureSegment, "base64url")) + .digest(); + const expectedHash = createHash("sha256").update(expected).digest(); + if (!timingSafeEqual(provided, expectedHash)) return { ok: false }; + + const payload = decodeJsonSegment(payloadSegment); + if (payload === null) return { ok: false }; + const exp = payload.exp; + if (typeof exp !== "number" || !Number.isFinite(exp)) return { ok: false }; + if (exp <= Math.floor(Date.now() / 1000)) return { ok: false }; + + const claims = claimsFromPayload(payload); + if (claims === null) return { ok: false }; + return { ok: true, claims }; +} + +/** auth ของ POST /api/documents/token — mirror requireExternalApiKey (N10) แต่ + * env เป็น DOCVER_API_KEY และตอบผ่าน docverError ของกลุ่มนี้ + * คืน null เมื่อผ่าน / Response 401 เมื่อไม่ผ่าน */ +export function requireDocverApiKey(request: Request): Response | null { + // เทียบกับค่า trim แล้ว — กัน trailing newline จาก K8s secret / docker env_file + const envKey = process.env.DOCVER_API_KEY?.trim(); + if (envKey === undefined || envKey === "") { + return docverError("ระบบยังไม่ได้ตั้งค่า API key", 401); + } + const provided = request.headers.get("x-api-key"); + if (provided === null || provided === "") { + return docverError("API key ไม่ถูกต้อง", 401); + } + const providedHash = createHash("sha256").update(provided).digest(); + const envHash = createHash("sha256").update(envKey).digest(); + return timingSafeEqual(providedHash, envHash) + ? null + : docverError("API key ไม่ถูกต้อง", 401); +} + +/** 401 เดียวสำหรับทุกกรณี — ไม่แยกสาเหตุ (ไม่มี header / รูปผิด / signature ผิด / + * หมดอายุ / secret ไม่ตั้ง) กันใช้ endpoint เป็น oracle สำรวจ */ +const BEARER_DENIED = "โทเคนไม่ถูกต้องหรือหมดอายุ"; + +/** บังคับ Authorization: Bearer ของกลุ่ม docver — เรียกเป็นอย่างแรกของ + * ทุก endpoint ก่อน parse path param · env DOCVER_TOKEN_SECRET ไม่ตั้ง = ไม่มี + * token ใดผ่าน (fail closed — ไม่ใช่ 500 เพราะไม่ใช่ความผิดของผู้เรียก) */ +export function requireDocverBearer(request: Request): + | { ok: true; claims: DocverTokenClaims } + | { ok: false; response: Response } { + const denied = () => ({ ok: false, response: docverError(BEARER_DENIED, 401) }) as const; + const secret = process.env.DOCVER_TOKEN_SECRET?.trim(); + if (secret === undefined || secret === "") return denied(); + const header = request.headers.get("authorization"); + if (header === null) return denied(); + const match = /^Bearer\s+(\S+)$/i.exec(header); + if (match === null) return denied(); + const verified = verifyDocverToken(match[1], secret); + if (!verified.ok) return denied(); + return { ok: true, claims: verified.claims }; +} + +/** body ของ POST /api/documents/token — strict allowlist กัน typo เงียบ ๆ ตาม + * convention ของกลุ่ม (key อื่น = 400) · trim-ว่าง = ถือว่าไม่ส่ง */ +const TOKEN_BODY_FIELDS = ["userid", "username", "fid", "template"] as const; + +export function parseTokenRequestBody(raw: unknown): + | { ok: true; claims: DocverTokenClaims } + | DocverError { + if (raw === null || typeof raw !== "object" || Array.isArray(raw)) { + return { ok: false, error: "body ต้องเป็น JSON object" }; + } + const record = raw as Record; + for (const key of Object.keys(record)) { + if (!(TOKEN_BODY_FIELDS as readonly string[]).includes(key)) { + return { + ok: false, + error: `ไม่รู้จัก field '${key}' (รับเฉพาะ ${TOKEN_BODY_FIELDS.join(", ")})`, + }; + } + } + + const rawUserid = record.userid; + if (typeof rawUserid !== "string" || rawUserid.trim() === "") { + return { ok: false, error: "ต้องระบุ userid (string)" }; + } + const userid = rawUserid.trim(); + if (userid.length > 100) { + return { ok: false, error: "userid ยาวเกิน 100 ตัวอักษร" }; + } + + const rawUsername = record.username; + if (typeof rawUsername !== "string" || rawUsername.trim() === "") { + return { ok: false, error: "ต้องระบุ username (string)" }; + } + const username = rawUsername.trim(); + if (username.length > 200) { + return { ok: false, error: "username ยาวเกิน 200 ตัวอักษร" }; + } + + const rawFid = record.fid ?? null; + if (rawFid !== null && !(typeof rawFid === "number" && Number.isInteger(rawFid))) { + return { ok: false, error: "fid ต้องเป็นตัวเลขจำนวนเต็ม" }; + } + + const rawTemplate = record.template ?? null; + if (rawTemplate !== null && typeof rawTemplate !== "string") { + return { ok: false, error: "template ต้องเป็น string" }; + } + const template = rawTemplate?.trim() || null; + if (template !== null && template.length > 100) { + return { ok: false, error: "template ยาวเกิน 100 ตัวอักษร" }; + } + + return { ok: true, claims: { userid, username, fid: rawFid, template } }; +} diff --git a/lib/openapi.ts b/lib/openapi.ts index 4cd07d3..c211ba3 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -39,8 +39,10 @@ export const openApiSpec = { name: "documents", description: "จัด version เอกสาร (docver — docs/PLAN-DOCVER.md): เอกสารเดียวต่อ docId แต่ละ " + - "version เก็บ .docx + .pdf ใน MinIO พร้อมผู้อัปโหลด — ไม่มี auth (stand-alone): " + - "ผู้เรียกบอก userId/userName เองใน multipart", + "version เก็บ .docx + .pdf ใน MinIO พร้อมผู้อัปโหลด — auth ด้วย access_token " + + "ของกลุ่มนี้: ขอที่ POST /api/documents/token (ยืนยัน x-api-key กับ env " + + "DOCVER_API_KEY) แล้วแนบ Authorization: Bearer ทุก request — identity/template " + + "แกะจาก claims ของ token ไม่รับใน multipart form อีก", }, { name: "Judge's Note", @@ -225,6 +227,59 @@ export const openApiSpec = { }, }, }, + "/api/documents/token": { + post: { + tags: ["documents"], + summary: "ขอ access_token ของกลุ่ม documents (docver)", + description: + "ออก access_token ใช้กับ endpoint อื่นของกลุ่ม documents เท่านั้น — token เป็น " + + "JWT HS256 ฝัง claims {userid, username, fid, template} ตามที่ส่งมา อายุตาม env " + + "DOCVER_TOKEN_TTL_SECONDS (default 3600 วินาที) · identity (userId/userName) " + + "และ templateId ตอนอัปโหลด version แกะจาก claims ของ token นี้", + security: [{ ApiKeyAuth: [] }], + requestBody: { + required: true, + content: { + "application/json": { + schema: { $ref: "#/components/schemas/TokenRequest" }, + examples: { + "ครบทุก claim": { + value: { + userid: "123", + username: "Sorawit Bholsithi", + fid: 3, + template: "T12", + }, + }, + "ขั้นต่ำ (fid/template เป็นทางเลือก)": { + value: { userid: "123", username: "Sorawit Bholsithi" }, + }, + }, + }, + }, + }, + responses: { + 200: { + description: "access_token พร้อมใช้ (แนบเป็น Authorization: Bearer ทุก request ถัดไป)", + content: { + "application/json": { + schema: { $ref: "#/components/schemas/TokenResponse" }, + }, + }, + }, + 400: { $ref: "#/components/responses/BadRequest" }, + 401: { + description: "ไม่ได้ส่ง header x-api-key หรือค่าไม่ถูกต้อง (env DOCVER_API_KEY)", + content: { + "application/json": { + schema: { $ref: "#/components/schemas/Error" }, + }, + }, + }, + 500: { $ref: "#/components/responses/ServerError" }, + }, + }, + }, "/api/documents/{docId}/versions": { post: { tags: ["documents"], @@ -233,7 +288,10 @@ export const openApiSpec = { "สร้างเอกสารอัตโนมัติเมื่อ docId ยังไม่เคยมี (docId ห้ามซ้ำ) แล้วเก็บไฟล์ " + ".docx (+ .pdf ถ้าแนบมา) ลง MinIO พร้อมผู้อัปโหลด — versionNumber ที่ server จ่าย " + "คือค่าจริงเสมอ (client ที่เลขไม่ตรงต้องแก้ XML part ของตัวเอง) · pdf เป็น " + - "optional: ไม่แนบ/ไฟล์เปล่า = version นี้ไม่มีสำเนา PDF (ดู hasPdf ใน response)", + "optional: ไม่แนบ/ไฟล์เปล่า = version นี้ไม่มีสำเนา PDF (ดู hasPdf ใน response) · " + + "ผู้อัปโหลด (userId/userName) และ templateId แกะจาก Bearer token ไม่รับใน form " + + "(ส่ง field เหล่านั้นมา = 400)", + security: [{ DocverBearer: [] }], parameters: [ { name: "docId", @@ -249,7 +307,7 @@ export const openApiSpec = { "multipart/form-data": { schema: { type: "object", - required: ["file", "userId", "userName"], + required: ["file"], properties: { file: { type: "string", @@ -262,14 +320,6 @@ export const openApiSpec = { description: "ไฟล์ .pdf ของ version นี้ (optional — ไม่แนบหรือไฟล์เปล่า = ไม่มี)", }, - userId: { - type: "string", - description: "รหัสผู้อัปโหลด (ระบบ stand-alone ไม่มี session)", - }, - userName: { - type: "string", - description: "ชื่อผู้อัปโหลด (แสดงใน version history)", - }, clientVersionId: { type: "string", description: "GUID ที่ client generate ก่อนเรียก (เก็บไว้สืบสวน ไม่ใช่ identity)", @@ -283,10 +333,6 @@ export const openApiSpec = { type: "string", description: "commit message ของ version (≤ 2048 ตัวอักษร)", }, - templateId: { - type: "string", - description: "รหัส template ที่ใช้สร้างเอกสาร (ถ้ามี)", - }, fileName: { type: "string", description: @@ -307,6 +353,7 @@ export const openApiSpec = { }, }, 400: { $ref: "#/components/responses/BadRequest" }, + 401: { $ref: "#/components/responses/DocverUnauthorized" }, 413: { description: "ไฟล์แนบใหญ่เกิน 100MB", content: { @@ -324,6 +371,7 @@ export const openApiSpec = { description: "version ทุกตัวของ docId นี้ พร้อมผู้อัปโหลด (userId/userName), เวลา, commit " + "message, ค่า fields และ hasPdf — ใช้เขียน UI version history ของ add-in", + security: [{ DocverBearer: [] }], parameters: [ { name: "docId", @@ -346,6 +394,7 @@ export const openApiSpec = { }, }, 400: { $ref: "#/components/responses/BadRequest" }, + 401: { $ref: "#/components/responses/DocverUnauthorized" }, 404: { description: "ไม่พบเอกสารนี้ในระบบ (ยังไม่เคยอัปโหลด version)", content: { @@ -366,6 +415,7 @@ export const openApiSpec = { "คืน URL อายุสั้น (env DOCVER_LINK_TTL_SECONDS, default 900 วินาที) ให้เปิดใน " + "เบราว์เซอร์/Word ของผู้ใช้ — URL อ้าง endpoint MinIO ตรง ๆ จึงต้องเป็นโฮสต์ที่ " + "ผู้ใช้เข้าถึงได้ · ขอ pdf ใน version ที่ไม่มีสำเนา = 404", + security: [{ DocverBearer: [] }], parameters: [ { name: "docId", @@ -399,6 +449,7 @@ export const openApiSpec = { }, }, 400: { $ref: "#/components/responses/BadRequest" }, + 401: { $ref: "#/components/responses/DocverUnauthorized" }, 404: { description: "ไม่พบ version นี้ของเอกสาร หรือขอ format=pdf ใน version ที่ไม่มีสำเนา PDF", @@ -420,7 +471,17 @@ export const openApiSpec = { in: "header", name: "x-api-key", description: - "ค่าเดียวกับ env EXTERNAL_API_KEY ของ server — กดปุ่ม Authorize ด้านบนเพื่อใส่ครั้งเดียวแล้วใช้ทุก request", + "x-api-key ของ N10 = env EXTERNAL_API_KEY · ของการขอ token กลุ่ม documents = env " + + "DOCVER_API_KEY (คนละค่ากับของ N10 — กดปุ่ม Authorize ใส่ครั้งเดียวแล้วใช้ได้ทุก " + + "request ของกลุ่มที่ key ตรง)", + }, + DocverBearer: { + type: "http", + scheme: "bearer", + bearerFormat: "JWT", + description: + "access_token จาก POST /api/documents/token — แนบเป็น Authorization: Bearer " + + "กับ endpoint อื่นของกลุ่ม documents ทุกตัว", }, }, responses: { @@ -434,6 +495,15 @@ export const openApiSpec = { }, }, }, + DocverUnauthorized: { + description: + "ไม่ได้แนบ Authorization: Bearer หรือ token ไม่ถูกต้อง/หมดอายุ (กลุ่ม documents — ข้อความเดียวกันทุกสาเหตุ)", + content: { + "application/json": { + schema: { $ref: "#/components/schemas/Error" }, + }, + }, + }, ServerError: { description: "เกิดข้อผิดพลาดของระบบ (เช่นไฟล์ .docx ไม่ถูกต้อง / DB ล้ม)", content: { @@ -451,6 +521,39 @@ export const openApiSpec = { error: { type: "string" }, }, }, + TokenRequest: { + type: "object", + description: "claims ที่จะฝังใน access_token ของกลุ่ม documents — key อื่น = 400", + required: ["userid", "username"], + properties: { + userid: { type: "string", description: "รหัสผู้ใช้ (บังคับ, ≤ 100 ตัวอักษร)" }, + username: { type: "string", description: "ชื่อผู้ใช้ (บังคับ, ≤ 200 ตัวอักษร)" }, + fid: { + type: "integer", + nullable: true, + description: + "fileId ของ e-Court — เก็บเป็น claim ไว้ก่อน ยังไม่ถูก operation ของ docver อ่าน", + }, + template: { + type: "string", + nullable: true, + description: + "รหัส template ที่ใช้สร้างเอกสาร — กลายเป็น templateId ตอนอัปโหลด version", + }, + }, + }, + TokenResponse: { + type: "object", + description: "access_token ของกลุ่ม documents (JWT HS256 อายุสั้น)", + properties: { + access_token: { type: "string", description: "JWT พร้อมใช้แนบเป็น Bearer" }, + token_type: { type: "string", example: "Bearer" }, + expires_in: { + type: "integer", + description: "อายุวินาที (env DOCVER_TOKEN_TTL_SECONDS, default 3600)", + }, + }, + }, ExtractResponse: { type: "object", properties: { diff --git a/package-lock.json b/package-lock.json index 748a8c7..32740b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@xmldom/xmldom": "^0.9.12", "drizzle-orm": "^0.45.2", "lucide-react": "^1.42.0", + "minio": "^8.0.7", "next": "^14.2.35", "pdf-lib": "^1.17.1", "pdfjs-dist": "^4.10.38", @@ -3902,15 +3903,13 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-3.1.0.tgz", "integrity": "sha512-LsS/DjHr+uDM647Gru/cA8+J3a3HfhttwCKLyuoyN7yXTFCxKBtSgMQBvrW9yNPa2/zDRUNuGPaH5QUFoa4arQ==", - "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/nodable" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", @@ -4670,15 +4669,13 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.1.tgz", "integrity": "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==", - "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/apache-arrow": { "version": "21.2.0", @@ -4771,6 +4768,12 @@ "node": ">=12" } }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, "node_modules/async-hook-jl": { "version": "1.7.6", "resolved": "https://registry.npmjs.org/async-hook-jl/-/async-hook-jl-1.7.6.tgz", @@ -5015,6 +5018,15 @@ "readable-stream": "^3.4.0" } }, + "node_modules/block-stream2": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/block-stream2/-/block-stream2-2.1.0.tgz", + "integrity": "sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==", + "license": "MIT", + "dependencies": { + "readable-stream": "^3.4.0" + } + }, "node_modules/body-parser": { "version": "1.20.8", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.8.tgz", @@ -5133,6 +5145,12 @@ "node": ">=8" } }, + "node_modules/browser-or-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-2.1.1.tgz", + "integrity": "sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==", + "license": "MIT" + }, "node_modules/browserslist": { "version": "4.28.9", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.9.tgz", @@ -5193,6 +5211,15 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -5845,6 +5872,15 @@ "dev": true, "license": "MIT" }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -7029,6 +7065,12 @@ "node": ">= 0.6" } }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -7240,7 +7282,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.1.tgz", "integrity": "sha512-pIM/1n3ntFXKYrUZwW7QCK0gAW7XY+wzj1YMIV3tLDvPj/V+zTGJK5e3/4WJfwj0qWw2ElNXiTixda/R+3YSug==", - "dev": true, "funding": [ { "type": "github", @@ -7248,7 +7289,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "path-expression-matcher": "^1.6.2", "xml-naming": "^0.3.0" @@ -7258,7 +7298,6 @@ "version": "5.11.2", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.11.2.tgz", "integrity": "sha512-R9iDuNrQYeQut46cn2r2wHKn4HYzVDvDm5J1wW+koZewykv0yuO2HChTYeZtrULyHIDM9cj9TUXloCsueCQUog==", - "dev": true, "funding": [ { "type": "github", @@ -7266,7 +7305,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@nodable/entities": "^3.0.1", "fast-xml-builder": "^1.2.0", @@ -7343,6 +7381,15 @@ "node": ">=8" } }, + "node_modules/filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/finalhandler": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", @@ -8009,9 +8056,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/ini": { "version": "1.3.8", @@ -8317,15 +8362,13 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-2.0.2.tgz", "integrity": "sha512-HgbIHPBH0KHHCcjLfGsCvhtPTVxjaAZlXjwdz7/GQC40SjSe4sfQsar8J5VFo8JOSbarkpV0OLG95bbaNd9aAQ==", - "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/is-wsl": { "version": "2.2.0", @@ -8909,9 +8952,7 @@ "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", - "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.includes": { "version": "4.3.0", @@ -9145,9 +9186,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -9156,9 +9195,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "license": "MIT", - "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -9208,6 +9245,39 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minio": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/minio/-/minio-8.0.7.tgz", + "integrity": "sha512-E737MgufW8CeQAsTAtnEMrxZ9scMSf29kkhZoXzDTKj/Jszzo2SfeZUH9wbDQH2Rsq6TCtl/yQL0+XdVKZansQ==", + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.4", + "block-stream2": "^2.1.0", + "browser-or-node": "^2.1.1", + "buffer-crc32": "^1.0.0", + "eventemitter3": "^5.0.1", + "fast-xml-parser": "^5.3.4", + "ipaddr.js": "^2.0.1", + "lodash": "^4.17.21", + "mime-types": "^2.1.35", + "query-string": "^7.1.3", + "stream-json": "^1.8.0", + "through2": "^4.0.2", + "xml2js": "^0.5.0 || ^0.6.2" + }, + "engines": { + "node": "^16 || ^18 || >=20" + } + }, + "node_modules/minio/node_modules/ipaddr.js": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.5.0.tgz", + "integrity": "sha512-aq+t5NAc+cS6rZQQVWC2x98CPqGtKKTMDd4Gaodv0wShnItdKg/51djkGJ1hqH+Oy0ivDftCbSLCQob8zso01w==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/minipass": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", @@ -10930,7 +11000,6 @@ "version": "1.6.2", "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz", "integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==", - "dev": true, "funding": [ { "type": "github", @@ -10938,7 +11007,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=14.0.0" } @@ -11412,6 +11480,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/query-string": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", + "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", + "license": "MIT", + "dependencies": { + "decode-uri-component": "^0.2.2", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -11528,9 +11614,7 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, "license": "MIT", - "peer": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -11783,7 +11867,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -11798,8 +11881,7 @@ "url": "https://feross.org/support" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/safer-buffer": { "version": "2.1.2", @@ -11812,7 +11894,6 @@ "version": "1.6.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", - "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=11.0.0" @@ -12230,6 +12311,15 @@ "source-map": "^0.6.0" } }, + "node_modules/split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/stack-chain": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/stack-chain/-/stack-chain-1.3.7.tgz", @@ -12263,6 +12353,21 @@ "dev": true, "license": "MIT" }, + "node_modules/stream-chain": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", + "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==", + "license": "BSD-3-Clause" + }, + "node_modules/stream-json": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.9.1.tgz", + "integrity": "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==", + "license": "BSD-3-Clause", + "dependencies": { + "stream-chain": "^2.2.5" + } + }, "node_modules/streamroller": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", @@ -12325,13 +12430,20 @@ "node": ">=10.0.0" } }, + "node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, "license": "MIT", - "peer": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -12392,7 +12504,6 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.5.1.tgz", "integrity": "sha512-UM4nTLWzQiDBseVxUGxN0wJzIk0UU89Qi7pKXz9qISo+lhviJikTuikBsK/oFRlpvYHvSdz0TVoLyQ+sEpYwxA==", - "dev": true, "funding": [ { "type": "github", @@ -12400,7 +12511,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "anynum": "^1.0.1" } @@ -12742,6 +12852,15 @@ "node": ">=0.8" } }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "license": "MIT", + "dependencies": { + "readable-stream": "3" + } + }, "node_modules/tinybench": { "version": "6.1.4", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.1.4.tgz", @@ -13123,7 +13242,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, "license": "MIT" }, "node_modules/utils-merge": { @@ -13575,7 +13693,6 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.3.0.tgz", "integrity": "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==", - "dev": true, "funding": [ { "type": "github", @@ -13583,7 +13700,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=16.0.0" } @@ -13592,7 +13708,6 @@ "version": "0.5.0", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", - "dev": true, "license": "MIT", "dependencies": { "sax": ">=0.6.0", @@ -13606,7 +13721,6 @@ "version": "11.0.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "dev": true, "license": "MIT", "engines": { "node": ">=4.0" diff --git a/tests/lib/document-versioning.test.ts b/tests/lib/document-versioning.test.ts index 635fb84..7a4182d 100644 --- a/tests/lib/document-versioning.test.ts +++ b/tests/lib/document-versioning.test.ts @@ -112,11 +112,11 @@ describe("parseFieldsJson", () => { }); describe("parseVersionUploadForm", () => { + // identity (userId/userName) กับ templateId ถอดออกจาก form แล้ว — route แกะจาก + // Bearer token ผ่าน lib/docver-token.ts (เคสของ token อยู่ docver-token.test.ts) function baseForm(): FormData { const form = new FormData(); form.append("file", new File([Buffer.from("PK\x03\x04")], "draft.docx")); - form.append("userId", "u0001"); - form.append("userName", "สมชาย ใจดี"); return form; } @@ -124,17 +124,13 @@ describe("parseVersionUploadForm", () => { const form = baseForm(); form.append("clientVersionId", " 11111111-1111-4111-8111-111111111111 "); form.append("message", " "); - form.append("templateId", "tmpl-main"); form.append("fields", '{"blackno":"1"}'); form.append("fileName", "C:\\Users\\judge\\คำสั่งเรียก.docx"); const result = parseVersionUploadForm(form); ok(result); expect(result.form).toEqual({ - userId: "u0001", - userName: "สมชาย ใจดี", clientVersionId: "11111111-1111-4111-8111-111111111111", message: null, - templateId: "tmpl-main", fileName: "คำสั่งเรียก.docx", fields: { blackno: "1" }, }); @@ -148,22 +144,22 @@ describe("parseVersionUploadForm", () => { if (!result.ok) expect(result.error).toContain("oops"); }); - it("ตกเมื่อขาด userId หรือ userName (stand-alone บอกผู้อัปโหลดเอง)", () => { - const noUser = baseForm(); - noUser.delete("userId"); - expect(parseVersionUploadForm(noUser).ok).toBe(false); - const noName = baseForm(); - noName.delete("userName"); - expect(parseVersionUploadForm(noName).ok).toBe(false); + it("ส่ง userId/userName/templateId ใน form = 400 ไม่รู้จัก field (identity มาจาก token)", () => { + for (const legacy of ["userId", "userName", "templateId"]) { + const form = baseForm(); + form.append(legacy, "x"); + const result = parseVersionUploadForm(form); + expect(result.ok).toBe(false); + if (!result.ok) expect(result.error).toContain(legacy); + } }); it("ตกเมื่อ text field ถูกส่งมาเป็นไฟล์", () => { const form = baseForm(); - form.delete("userId"); - form.append("userId", new File(["x"], "u.txt")); + form.append("message", new File(["x"], "m.txt")); const result = parseVersionUploadForm(form); expect(result.ok).toBe(false); - if (!result.ok) expect(result.error).toContain("userId"); + if (!result.ok) expect(result.error).toContain("message"); }); it(`message ยาว ${DOCVER_MESSAGE_MAX} ผ่าน แต่เกิน = ตก`, () => { diff --git a/tests/lib/docver-token.test.ts b/tests/lib/docver-token.test.ts new file mode 100644 index 0000000..98ff74a --- /dev/null +++ b/tests/lib/docver-token.test.ts @@ -0,0 +1,297 @@ +import { createHmac } from "node:crypto"; +import { afterEach, describe, expect, it } from "vitest"; + +import { + issueDocverToken, + parseTokenRequestBody, + requireDocverApiKey, + requireDocverBearer, + verifyDocverToken, + type DocverTokenClaims, +} from "@/lib/docver-token"; + +// docver token (PLAN-DOCVER.md §auth) — validation/auth ล้วนของ /api/documents/*: +// ไฟล์นี้ตั้งใจไม่แตะ module ใดที่ลาก @/db หรือ minio มาด้วย (pattern เดียวกับ +// external-api-auth.test.ts — vitest ไม่โหลด .env จึงต้องรันได้โดยไม่ต้องตั้ง +// DATABASE_URL/MINIO_*) + +const SECRET = "test-secret-docver"; +const CLAIMS: DocverTokenClaims = { + userid: "123", + username: "Sorawit Bholsithi", + fid: 3, + template: "T12", +}; + +function ok(result: { ok: boolean } & T): asserts result is { ok: true } & T { + expect(result.ok).toBe(true); +} + +// --- env save/restore — require* อ่าน env ตอนเรียก ไม่ใช่ตอน module load +const HAD_API_KEY = Object.prototype.hasOwnProperty.call(process.env, "DOCVER_API_KEY"); +const ORIG_API_KEY = process.env.DOCVER_API_KEY; +const HAD_SECRET = Object.prototype.hasOwnProperty.call(process.env, "DOCVER_TOKEN_SECRET"); +const ORIG_SECRET = process.env.DOCVER_TOKEN_SECRET; + +afterEach(() => { + if (HAD_API_KEY) process.env.DOCVER_API_KEY = ORIG_API_KEY; + else delete process.env.DOCVER_API_KEY; + if (HAD_SECRET) process.env.DOCVER_TOKEN_SECRET = ORIG_SECRET; + else delete process.env.DOCVER_TOKEN_SECRET; +}); + +function reqWithKey(key?: string): Request { + return new Request("http://localhost/docedit/api/documents/token", { + headers: key === undefined ? {} : { "x-api-key": key }, + }); +} + +function reqWithBearer(authorization: string | null): Request { + return new Request("http://localhost/docedit/api/documents/doc1/versions", { + headers: authorization === null ? {} : { authorization }, + }); +} + +async function bodyOf(res: Response): Promise<{ error: string }> { + expect(res.headers.get("content-type")).toBe("application/json; charset=utf-8"); + return (await res.json()) as { error: string }; +} + +/** เซ็น payload ใด ๆ ด้วย SECRET ผ่านมือ — ใช้ประกอบเคส claims ผิดรูปที่ issueDocverToken สร้างเองไม่ได้ */ +function signPayload(payload: Record): string { + const header = Buffer.from(JSON.stringify({ alg: "HS256", typ: "JWT" }), "utf8").toString( + "base64url", + ); + const body = Buffer.from(JSON.stringify(payload), "utf8").toString("base64url"); + const signature = createHmac("sha256", SECRET) + .update(`${header}.${body}`) + .digest("base64url"); + return `${header}.${body}.${signature}`; +} + +describe("issueDocverToken / verifyDocverToken", () => { + it("issue → verify ได้ claims ครบทุกตัว (userid/username/fid/template)", () => { + const verified = verifyDocverToken(issueDocverToken(CLAIMS, SECRET, 60), SECRET); + ok(verified); + expect(verified.claims).toEqual(CLAIMS); + }); + + it("claims ทางเลือก null ทั้งคู่ (fid/template ไม่ส่ง) ก็ roundtrip ได้", () => { + const verified = verifyDocverToken( + issueDocverToken({ userid: "u1", username: "ชื่อ", fid: null, template: null }, SECRET, 60), + SECRET, + ); + ok(verified); + expect(verified.claims).toEqual({ userid: "u1", username: "ชื่อ", fid: null, template: null }); + }); + + it("แก้ payload แล้วใช้ signature เดิม (tampered) หรือ secret ผิด = ไม่ผ่าน", () => { + const token = issueDocverToken(CLAIMS, SECRET, 60); + const [header, , signature] = token.split("."); + const tamperedPayload = Buffer.from( + JSON.stringify({ ...CLAIMS, userid: "999" }), + "utf8", + ).toString("base64url"); + expect(verifyDocverToken(`${header}.${tamperedPayload}.${signature}`, SECRET).ok).toBe(false); + expect(verifyDocverToken(token, "wrong-secret").ok).toBe(false); + }); + + it("header alg อื่น (none / HS512) = ไม่ผ่าน แม้จะเซ็นด้วย secret ถูก (กัน alg-confusion)", () => { + const body = Buffer.from( + JSON.stringify({ ...CLAIMS, iat: 0, exp: Math.floor(Date.now() / 1000) + 60 }), + "utf8", + ).toString("base64url"); + for (const alg of ["none", "HS512"]) { + const header = Buffer.from(JSON.stringify({ alg, typ: "JWT" }), "utf8").toString("base64url"); + const signature = createHmac("sha256", SECRET) + .update(`${header}.${body}`) + .digest("base64url"); + expect(verifyDocverToken(`${header}.${body}.${signature}`, SECRET).ok).toBe(false); + } + }); + + it("token หมดอายุ (ttl ติดลบ) หรือไม่มี exp = ไม่ผ่าน", () => { + expect(verifyDocverToken(issueDocverToken(CLAIMS, SECRET, -10), SECRET).ok).toBe(false); + const noExp = signPayload({ ...CLAIMS, iat: Math.floor(Date.now() / 1000) }); + expect(verifyDocverToken(noExp, SECRET).ok).toBe(false); + }); + + it("claims ไม่ครบรูปร่าง (แม้เซ็นถูก) = ไม่ผ่าน — กัน token เซ็นเองหลุดไปใช้", () => { + const now = Math.floor(Date.now() / 1000) + 60; + for (const bad of [ + { username: "n", fid: null, template: null, iat: 0, exp: now }, // ไม่มี userid + { userid: "u", fid: null, template: null, iat: 0, exp: now }, // ไม่มี username + { userid: "", username: "n", fid: null, template: null, iat: 0, exp: now }, // userid ว่าง + { userid: "u", username: "n", fid: "3", template: null, iat: 0, exp: now }, // fid เป็น string + ]) { + expect(verifyDocverToken(signPayload(bad), SECRET).ok).toBe(false); + } + }); + + it("รูป token ผิด (ไม่ใช่ 3 segment / segment เป็น base64 ปกติไม่ใช่ base64url) = ไม่ผ่าน", () => { + expect(verifyDocverToken("not-a-jwt", SECRET).ok).toBe(false); + expect(verifyDocverToken("a.b.c.d", SECRET).ok).toBe(false); + // base64 ปกติมี + / ซึ่ง base64url decode แล้วได้ byte ผิด → header/payload พัง + expect(verifyDocverToken("???.???.???", SECRET).ok).toBe(false); + }); +}); + +describe("requireDocverApiKey", () => { + it("env ไม่ตั้ง / ว่าง / ช่องว่างล้วน = 401 fail closed ทุกกรณี", async () => { + for (const value of [undefined, "", " "]) { + if (value === undefined) delete process.env.DOCVER_API_KEY; + else process.env.DOCVER_API_KEY = value; + const res = requireDocverApiKey(reqWithKey("test-key")); + expect(res?.status).toBe(401); + expect(await bodyOf(res!)).toEqual({ error: "ระบบยังไม่ได้ตั้งค่า API key" }); + } + }); + + it("ไม่ใส่ header กับ key ผิด ต้องได้ 401 ข้อความเดียวกันเป๊ะ (กัน enumerate)", async () => { + process.env.DOCVER_API_KEY = "test-key"; + const missing = requireDocverApiKey(reqWithKey()); + const wrong = requireDocverApiKey(reqWithKey("not-the-key")); + expect(missing?.status).toBe(401); + expect(wrong?.status).toBe(401); + expect(await bodyOf(missing!)).toEqual(await bodyOf(wrong!)); + }); + + it("key ตรง = ผ่าน (คืน null) · env มี whitespace รอบ ๆ ก็ trim ให้เทียบได้", () => { + process.env.DOCVER_API_KEY = "test-key"; + expect(requireDocverApiKey(reqWithKey("test-key"))).toBeNull(); + process.env.DOCVER_API_KEY = " test-key "; + expect(requireDocverApiKey(reqWithKey("test-key"))).toBeNull(); + }); +}); + +describe("requireDocverBearer", () => { + it("ทุกกรณีพ้นสภาพ (ไม่มี header / ไม่ใช่ Bearer / token เน่า / หมดอายุ / secret ไม่ตั้ง) = 401 ข้อความเดียวกัน", async () => { + const deniedBodies = new Set(); + const cases: { label: string; secret: string | undefined; authorization: string | null }[] = [ + { label: "ไม่มี Authorization", secret: SECRET, authorization: null }, + { label: "scheme อื่น (Basic)", secret: SECRET, authorization: "Basic dXNlcjpwYXNz" }, + { label: "Bearer ว่างเปล่า", secret: SECRET, authorization: "Bearer " }, + { label: "token เน่า", secret: SECRET, authorization: "Bearer garbage.token.here" }, + { + label: "token หมดอายุ", + secret: SECRET, + authorization: `Bearer ${issueDocverToken(CLAIMS, SECRET, -10)}`, + }, + { + label: "เซ็นด้วย secret ผิด", + secret: SECRET, + authorization: `Bearer ${issueDocverToken(CLAIMS, "other-secret", 60)}`, + }, + { label: "secret ไม่ตั้ง (fail closed)", secret: undefined, authorization: null }, + { + label: "secret ไม่ตั้งแม้จะมี token ถูก", + secret: undefined, + authorization: `Bearer ${issueDocverToken(CLAIMS, SECRET, 60)}`, + }, + ]; + for (const testCase of cases) { + if (testCase.secret === undefined) delete process.env.DOCVER_TOKEN_SECRET; + else process.env.DOCVER_TOKEN_SECRET = testCase.secret; + const auth = requireDocverBearer(reqWithBearer(testCase.authorization)); + expect(auth.ok, testCase.label).toBe(false); + if (!auth.ok) { + expect(auth.response.status, testCase.label).toBe(401); + deniedBodies.add(JSON.stringify(await bodyOf(auth.response))); + } + } + expect(deniedBodies.size).toBe(1); // ข้อความ 401 เดียวกันหมด — ไม่ใช้ endpoint เป็น oracle + }); + + it("Bearer ที่ verify ผ่าน = ได้ claims · คำว่า bearer พิมพ์เล็กก็รับ (case-insensitive)", () => { + process.env.DOCVER_TOKEN_SECRET = SECRET; + const token = issueDocverToken(CLAIMS, SECRET, 60); + const auth = requireDocverBearer(reqWithBearer(`Bearer ${token}`)); + ok(auth); + expect(auth.claims).toEqual(CLAIMS); + const lower = requireDocverBearer(reqWithBearer(`bearer ${token}`)); + ok(lower); + expect(lower.claims).toEqual(CLAIMS); + }); + + it("env มี whitespace รอบ secret ก็ trim แล้ว verify ได้", () => { + process.env.DOCVER_TOKEN_SECRET = ` ${SECRET} `; + const auth = requireDocverBearer( + reqWithBearer(`Bearer ${issueDocverToken(CLAIMS, SECRET, 60)}`), + ); + ok(auth); + expect(auth.claims).toEqual(CLAIMS); + }); +}); + +describe("parseTokenRequestBody", () => { + it("ครบทุก field = ได้ claims (trim ค่า) · fid/template ไม่ส่ง = null", () => { + const full = parseTokenRequestBody({ + userid: " 123 ", + username: " Sorawit Bholsithi ", + fid: 3, + template: " T12 ", + }); + ok(full); + expect(full.claims).toEqual({ userid: "123", username: "Sorawit Bholsithi", fid: 3, template: "T12" }); + + const minimal = parseTokenRequestBody({ userid: "123", username: "Sorawit" }); + ok(minimal); + expect(minimal.claims).toEqual({ userid: "123", username: "Sorawit", fid: null, template: null }); + }); + + it("template ส่งมาเป็นช่องว่าง = null (ถือว่าไม่ส่ง)", () => { + const result = parseTokenRequestBody({ userid: "123", username: "n", template: " " }); + ok(result); + expect(result.claims.template).toBeNull(); + }); + + it("ไม่ใช่ object (array / string / null) = 400", () => { + for (const raw of [[], "text", null]) { + const result = parseTokenRequestBody(raw); + expect(result.ok).toBe(false); + if (!result.ok) expect(result.error).toBe("body ต้องเป็น JSON object"); + } + }); + + it("key อื่นนอก allowlist = 400 ไม่รู้จัก field (กัน typo เงียบ ๆ)", () => { + const result = parseTokenRequestBody({ userid: "1", username: "n", oops: 1 }); + expect(result.ok).toBe(false); + if (!result.ok) expect(result.error).toContain("oops"); + }); + + it("ขาด userid หรือ username / ส่งมาไม่ใช่ string = 400 ข้อความเฉพาะ field", () => { + for (const raw of [ + { username: "n" }, + { userid: " ", username: "n" }, + { userid: 123, username: "n" }, + { userid: "1" }, + { userid: "1", username: "" }, + ]) { + const result = parseTokenRequestBody(raw); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toMatch(/ต้องระบุ (userid|username)/); + } + } + }); + + it("fid ไม่ใช่ integer (string / ทศนิยม) หรือ template ไม่ใช่ string = 400", () => { + for (const raw of [ + { userid: "1", username: "n", fid: "3" }, + { userid: "1", username: "n", fid: 3.5 }, + { userid: "1", username: "n", template: 12 }, + ]) { + expect(parseTokenRequestBody(raw).ok).toBe(false); + } + }); + + it("ความยาวเกิน cap: userid > 100 / username > 200 / template > 100 = 400", () => { + expect(parseTokenRequestBody({ userid: "a".repeat(101), username: "n" }).ok).toBe(false); + expect( + parseTokenRequestBody({ userid: "1", username: "a".repeat(201) }).ok, + ).toBe(false); + expect( + parseTokenRequestBody({ userid: "1", username: "n", template: "a".repeat(101) }).ok, + ).toBe(false); + }); +}); diff --git a/wordaddin/services/versioningService.ts b/wordaddin/services/versioningService.ts index 54dd519..d3576fc 100644 --- a/wordaddin/services/versioningService.ts +++ b/wordaddin/services/versioningService.ts @@ -5,6 +5,10 @@ * - คงชื่อ/ลายเซ็น uploadVersion/getVersions/getDownloadLink/newClientVersionId ไว้เป๊ะ — * วันปลด mock: ก้อบ body ของต้นฉบับกลับมาได้เลย * - ต้นฉบับมีอีก 2 ตัวที่ไม่มี caller ใน UI จึงไม่พอร์ต: buildDownloadUrl, downloadVersion + * - **วันปลด mock ห้ามส่ง userId/userName/templateId ใน FormData** — contract ของ + * POST /api/documents/{docId}/versions ถอด 3 field นี้ออกแล้ว (ส่งมา = 400): + * server แกะ identity/template จาก Bearer token (ออกที่ POST /api/documents/token + * ด้วย x-api-key) ที่ apiClient.ts แนบให้อยู่แล้ว — templateId เด้งเป็น claim "template" * - **metadata ของ version ถูก persist ลง localStorage** (record เล็ก ๆ ต่อ documentId) — * ไม่งั้นปิดเอกสาร (taskpane ปิดตาม) แล้วเปิดใหม่ history ว่างเปล่า ทั้งที่ metadata * การ track อยู่ในไฟล์จริง ทำให้ทดสอบวง FR-18 ปิด→เปิดใหม่ไม่ได้; blob ของไฟล์จริง From 0b2c9e6343cbf5c4f9bcbf1215bb7f6ca0a3a229 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Mon, 26 Oct 2026 10:05:01 +0700 Subject: [PATCH 09/21] feat(docver): derive docId from fid claim and drop [docId] URL segment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docId of every docver operation now comes from the Bearer token: the fid claim (e-Court fileId) is used directly as docId; segment [docId] removed from URLs — POST/GET /api/documents/versions and GET /api/documents/versions/{versionId}/download-link - fid is required and must be a string (trim 1-200): token issuance rejects missing/blank/non-string fid with 400, verify rejects fid-less tokens with 401 - parseDocId removed from lib/document-versioning.ts (dead code, no callers) - openapi spec: paths renamed, docId path params removed, fid required + string - PLAN-DOCVER.md / CLAUDE.md / wordaddin versioningService comment updated - tests: fid required/string cases added, parseDocId describe removed (256 pass) Co-Authored-By: Claude Code --- CLAUDE.md | 8 +- .../[versionId]/download-link/route.ts | 13 ++-- .../documents/{[docId] => }/versions/route.ts | 23 +++--- docs/PLAN-DOCVER.md | 35 +++++---- lib/document-versioning.ts | 22 +----- lib/docver-token.ts | 20 +++-- lib/openapi.ts | 74 +++++++------------ tests/lib/document-versioning.test.ts | 23 ------ tests/lib/docver-token.test.ts | 55 +++++++++----- wordaddin/services/versioningService.ts | 7 +- 10 files changed, 122 insertions(+), 158 deletions(-) rename app/api/documents/{[docId] => }/versions/[versionId]/download-link/route.ts (76%) rename app/api/documents/{[docId] => }/versions/route.ts (91%) diff --git a/CLAUDE.md b/CLAUDE.md index 20c6558..f5ccb40 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,7 +21,7 @@ 1. **Non-Destructive:** ไฟล์ต้นฉบับ (PDF/รูปภาพ) ไม่ถูกแก้ไขเด็ดขาด — annotation เก็บใน DB เป็นพิกัดเท่านั้น (export PDF ของ F8/N3 สร้างไฟล์ใหม่จากสำเนาในหน่วยความจำ — อ่าน `public/` อย่างเดียว ห้ามเขียนทับ; อัปโหลด N4 เขียนไฟล์**ใหม่**ที่ `public/note/uploads/` เท่านั้น ห้ามแตะไฟล์เดิม) 2. **พิกัดทุกอย่างใน DB เป็น % ของขนาดหน้า** — `rects`, `paths`, `position`, `strokeWidth` (% ของ width), `yOffset` (% ของ height) — แปลง px⇄% ผ่าน `utils/coordinates.ts` เท่านั้น และก่อนบันทึก HIGHLIGHT ต้องรวบ rects ด้วย `mergeRectsByLine` เหลือ 1 rect ต่อบรรทัด 3. **ลำดับ layer ตาม PLAN.md §1:** canvas (`pointer-events: none` เสมอ) → highlight-layer (`none` เสมอ, `mix-blend-mode: multiply`) → textLayer → InkLayer (SVG) → comment pins — สลับ pointer-events ตาม tool ปัจจุบัน -4. **เขียนผ่าน Server Actions เท่านั้น** (`app/actions/annotations.ts`) และ**หลังเขียน DB สำเร็จทุกครั้ง ต้อง publish event ผ่าน `lib/annotate-events.ts` + `revalidatePath` หน้า viewer และ summary ของ fileId นั้น** (helper `revalidateFilePages` — สั่ง browser ทิ้ง Router Cache ฝั่ง client, `force-dynamic` ปิดแค่ฝั่ง server ไม่พอ: ไม่งั้นกด back แล้วคลิกเข้าไฟล์ใหม่ยังได้ initial data เก่าจนต้อง hard refresh) / **อ่าน + sync ผ่าน SSE hook** (`hooks/useAnnotationRealtime.ts` เปิด `EventSource` ไป `/api/note/annotations/stream?fileId=`) — event เป็น DTO (camelCase + ISO string) จาก server ตรง ๆ — **ยกเว้น `registerExternalFile` (N8)** ที่เขียน row `files` ระหว่าง render ของหน้า `/note` และ `/note/annotate` (entry point ทั้งสองของสัญญาลิงก์ `?fileUrl=`) แล้ว `redirect` ออกทันที (หน้า `force-dynamic` + รายการไฟล์ไม่ได้ sync ผ่าน SSE — ไม่ต้อง publish event/`revalidatePath`) · เช่นกัน **กลุ่ม docver `app/api/documents/**`** (จัด version เอกสาร — `docs/PLAN-DOCVER.md`) เขียนตาราง `documents`/`document_versions` ผ่าน route handler ไม่ใช่ Server Action (ผู้เรียกเป็นระบบภายนอก stand-alone ไม่มี session ของแอป note — identity userId/userName และ templateId แกะจาก Bearer token ที่ขอจาก `POST /api/documents/token` ผ่าน `lib/docver-token.ts` ไม่รับจาก request payload อีกต่อไป) — ไม่มีหน้า note ใดอ่านตารางนั้น จึงไม่ต้อง publish event/`revalidatePath` +4. **เขียนผ่าน Server Actions เท่านั้น** (`app/actions/annotations.ts`) และ**หลังเขียน DB สำเร็จทุกครั้ง ต้อง publish event ผ่าน `lib/annotate-events.ts` + `revalidatePath` หน้า viewer และ summary ของ fileId นั้น** (helper `revalidateFilePages` — สั่ง browser ทิ้ง Router Cache ฝั่ง client, `force-dynamic` ปิดแค่ฝั่ง server ไม่พอ: ไม่งั้นกด back แล้วคลิกเข้าไฟล์ใหม่ยังได้ initial data เก่าจนต้อง hard refresh) / **อ่าน + sync ผ่าน SSE hook** (`hooks/useAnnotationRealtime.ts` เปิด `EventSource` ไป `/api/note/annotations/stream?fileId=`) — event เป็น DTO (camelCase + ISO string) จาก server ตรง ๆ — **ยกเว้น `registerExternalFile` (N8)** ที่เขียน row `files` ระหว่าง render ของหน้า `/note` และ `/note/annotate` (entry point ทั้งสองของสัญญาลิงก์ `?fileUrl=`) แล้ว `redirect` ออกทันที (หน้า `force-dynamic` + รายการไฟล์ไม่ได้ sync ผ่าน SSE — ไม่ต้อง publish event/`revalidatePath`) · เช่นกัน **กลุ่ม docver `app/api/documents/**`** (จัด version เอกสาร — `docs/PLAN-DOCVER.md`) เขียนตาราง `documents`/`document_versions` ผ่าน route handler ไม่ใช่ Server Action (ผู้เรียกเป็นระบบภายนอก stand-alone ไม่มี session ของแอป note — identity userId/userName, templateId และ docId (จาก claim fid) แกะจาก Bearer token ที่ขอจาก `POST /api/documents/token` ผ่าน `lib/docver-token.ts` ไม่รับจาก request payload อีกต่อไป) — ไม่มีหน้า note ใดอ่านตารางนั้น จึงไม่ต้อง publish event/`revalidatePath` 5. **Timestamp ข้าม server→client→SSE เป็น ISO string เสมอ** (DTO types) — ห้ามส่ง Date object ตรง ๆ 6. **pdfjs-dist ต้อง `dynamic import()` ภายใน useEffect เท่านั้น** — ห้าม import top-level ใน module scope ที่ server evaluate และต้องตั้ง `GlobalWorkerOptions.workerSrc = withBasePath("/pdf.worker.min.mjs")` (postinstall `scripts/patch-pdf-worker.mjs` คัดลอกไฟล์ไป `public/note/` **แล้ว patch จุดเดียว: ถอด `if (scaledDim)` รอบ `textChunk.prevTransform = getCurrentTextTransform()` ให้อัปเดตทุก glyph** — pdf.js เดิมข้าม glyph กว้าง 0 อย่างสระ/วรรณยุกต์ไทย ทำให้ charSpacing ของ glyph เหล่านั้นสะสมเป็น gap จน `addFakeSpaces` แทรก " " กลางคำ "ฉะนั้ น" — bug ของ pdf.js ที่ upstream ยังไม่แก้; การ patch ทำบนโค้ด minified ด้วย pattern จำเพาะ ถ้าอัปเกรด pdfjs-dist แล้ว pattern ไม่ตรง postinstall จะ **fail ดัง ๆ โดยตั้งใจ** กัน patch หลุดเงียบ — test กัน regression: `tests/scripts/patch-pdf-worker.test.ts` + fixture `tests/fixtures/thai_letterspacing.pdf` — Docker build ต้องใช้ script นี้ด้วย ไม่ใช่ copyFileSync เปล่า ดู `docker/web.Dockerfile`) — ห้ามใช้ `new URL(..., import.meta.url)` 7. **InkLayer ใช้ SVG แบบไม่มี viewBox** — render ด้วย px จริง แปลง % → px จากขนาดที่ ResizeObserver วัด (viewBox จะทำให้ความหนาเส้นบิดเบี้ยว) @@ -50,8 +50,8 @@ lib/remote-file.ts ← N8 ดึงไฟล์รีโมตจาก allow lib/external-api-auth.ts ← N10 auth ของ /api/note/external/* — requireExternalApiKey เทียบ header `x-api-key` กับ env `EXTERNAL_API_KEY` ด้วย SHA-256 + crypto.timingSafeEqual (env ไม่ตั้ง/ว่าง = 401 fail closed ทุก request; ข้อความ 401 อันเดียวกันทั้งไม่ใส่/key ผิด กัน enumerate; ห้าม log ค่า key/header ดิบ) + externalApiError สร้าง Response ผิดพลาด JSON {"error": ไทย} ใช้ร่วมทุก route ของ N10 — **module นี้ห้าม import อะไรที่ลาก @/db มาด้วย** (db/index.ts throw ตอน import เมื่อไม่มี DATABASE_URL — unit test tests/lib/external-api-auth.test.ts ต้องรันได้โดยไม่แตะ DB) lib/external-api.ts ← N10 ส่วนกลางของ route — parseQueryBody (validate JSON body ของ /api/note/external/documents: รับเฉพาะ fileId/typeId/filter, ต้องมี fileId หรือ typeId อย่างน้อยหนึ่งอย่าง — ส่งทั้งคู่ได้ = AND เหลือเฉพาะเอกสารเดียวที่ตรงทั้งสองเงื่อนไข, คำขอผู้ใช้ 2026-09-25 (รอบเดียวกันเคยเป็น OR แล้วเปลี่ยนหลังทดสอบจริง), filter รับเฉพาะ type ∈ bookmarks|annotations ไม่ส่ง = ทั้งสอง + userId (string|array กรองตามเจ้าของรายการ annotations+bookmarks) + annotationTypes (ค่า enum annotation_type เฉพาะ annotations ค่าอื่น = 400 — เดิมชื่อ types เปลี่ยน 2026-09-25) — คืน discriminated mode "file" (fileId + typeId อาจมาด้วย) / "type" (typeId เดี่ยว)) + loadDocumentBundles(fileRows, parts) (bulk: inArray ทุกไฟล์ในชุด query เดียว = 3 query ต่อ request ไม่ว่ากลุ่มกี่ไฟล์ แทนยิง 3 query ต่อไฟล์พร้อมกันทั้งกลุ่ม, code review 2026-09-25 — กรอง userIds/annotationTypes ใน SQL; คืน bundle ยุบชั้น: field เอกสาร + annotations + bookmarks ระดับเดียวกัน ไม่มี object document ซ้อน; ต่อรายการมี `url` deep link F12 กลับหน้า `/annotate/[id]` — annotation `?ann=` / bookmark `?page=<หน้า 1-based>` — URL เต็ม: origin จาก request ผู้เรียก (route อ่าน x-forwarded-proto/host แล้ว fallback host ตรง ส่งเข้าทาง parameter origin) + basePath ผ่าน withBasePath, คำขอผู้ใช้ 2026-09-25) + type ExternalDocumentDTO (externalFileId เป็น null สำหรับไฟล์ url:-fallback/อัปโหลด N4 ในกลุ่ม) lib/openapi.ts ← OpenAPI 3.0 spec ของ API ทดสอบ (กลุ่ม template + N10 + docver — 2026-10-06) — แหล่งเดียวของ Swagger UI: GET /api/template inline ลง HTML ตรง ๆ ไม่มี endpoint spec แยก; URL ใน paths เป็น path เต็ม /api/… (servers เติมแค่ basePath ตาม invariant #12) — แก้ contract ของ API ต้องมาแก้ไฟล์นี้ให้ตรงด้วย -lib/document-versioning.ts ← docver pure validation (ห้าม import อะไรที่ลาก @/db หรือ minio มาด้วย — unit test tests/lib/document-versioning.test.ts ต้องรันได้เหมือน lib/external-api-auth.ts): parseDocId (charset [A-Za-z0-9._:-] 1–200), parseVersionUploadForm (allowlist field — identity ไม่รับจาก form แล้ว แกะจาก token ผ่าน lib/docver-token.ts + fields JSON ≤100 keys + message ≤2048), isDocxBytes/isPdfBytes (magic bytes), parseDownloadFormat/parseVersionIdParam, contentDispositionValue (ASCII fallback + filename*=UTF-8'') + docverError สร้าง Response JSON {"error": ไทย} ใช้ร่วมทุก route ของกลุ่ม -lib/docver-token.ts ← docver token + auth ของกลุ่ม /api/documents/* (2026-10-22 — pure module ห้าม import อะไรที่ลาก @/db หรือ minio มาด้วย เหมือน lib/document-versioning.ts; unit test tests/lib/docver-token.test.ts ต้องรันได้โดยไม่ต้องตั้ง env): issueDocverToken/verifyDocverToken (JWT HS256 มือเขียน node:crypto ล้วน — บังคับ alg=HS256 กัน alg-confusion, timingSafeEqual หลัง hash ทั้งสองฝั่ง, exp บังคับ) + requireDocverApiKey (header x-api-key เทียบ env DOCVER_API_KEY — fail closed 401 ไม่ตั้ง/ว่าง, 401 เดียวกันทั้งไม่ใส่/ผิด กัน enumerate, ห้าม log ค่า key/header ดิบ) + requireDocverBearer (Authorization: Bearer → claims {userid, username, fid, template} — secret จาก env DOCVER_TOKEN_SECRET ไม่ตั้ง = 401, ทุกกรณี 401 ข้อความเดียวกัน) + parseTokenRequestBody (allowlist userid/username/fid/template — fid = fileId ของ e-Court เก็บเป็น claim ยังไม่ถูก operation ใดอ่าน) +lib/document-versioning.ts ← docver pure validation (ห้าม import อะไรที่ลาก @/db หรือ minio มาด้วย — unit test tests/lib/document-versioning.test.ts ต้องรันได้เหมือน lib/external-api-auth.ts): parseVersionUploadForm (allowlist field — identity ไม่รับจาก form แล้ว แกะจาก token ผ่าน lib/docver-token.ts + fields JSON ≤100 keys + message ≤2048), isDocxBytes/isPdfBytes (magic bytes), parseDownloadFormat/parseVersionIdParam, contentDispositionValue (ASCII fallback + filename*=UTF-8'') + docverError สร้าง Response JSON {"error": ไทย} ใช้ร่วมทุก route ของกลุ่ม (parseDocId ถอดแล้ว 2026-10-26 — docId ไม่มาจาก path param) +lib/docver-token.ts ← docver token + auth ของกลุ่ม /api/documents/* (2026-10-22 — pure module ห้าม import อะไรที่ลาก @/db หรือ minio มาด้วย เหมือน lib/document-versioning.ts; unit test tests/lib/docver-token.test.ts ต้องรันได้โดยไม่ต้องตั้ง env): issueDocverToken/verifyDocverToken (JWT HS256 มือเขียน node:crypto ล้วน — บังคับ alg=HS256 กัน alg-confusion, timingSafeEqual หลัง hash ทั้งสองฝั่ง, exp บังคับ) + requireDocverApiKey (header x-api-key เทียบ env DOCVER_API_KEY — fail closed 401 ไม่ตั้ง/ว่าง, 401 เดียวกันทั้งไม่ใส่/ผิด กัน enumerate, ห้าม log ค่า key/header ดิบ) + requireDocverBearer (Authorization: Bearer → claims {userid, username, fid, template} — secret จาก env DOCVER_TOKEN_SECRET ไม่ตั้ง = 401, ทุกกรณี 401 ข้อความเดียวกัน) + parseTokenRequestBody (allowlist userid/username/fid/template — **fid = fileId ของ e-Court (string, บังคับ 2026-10-26)** เป็น docId ของทุก operation ตรง ๆ · verify ไม่รับ token ที่ไม่มี fid) lib/docver-store.ts ← docver server-side MinIO + DB (env MINIO_* อ่านตอนเรียกไม่ใช่ module load; client + cached ensureBucket เก็บบน globalThis แบบ db/index.ts): object key `docver//document.docx|pdf` (uuid ล้วน ไม่มี input ผู้ใช้), putVersionObjects (docx ก่อน — pdf ล้ม = ลบ docx คืนแล้วโยนต่อ), removeVersionObjects (best-effort), presignVersionUrl (TTL env DOCVER_LINK_TTL_SECONDS), insertVersionRecord (**tx เดียว**: upsert documents ON CONFLICT DO NOTHING → SELECT id FOR UPDATE → max(versionNumber)+1 → insert — จ่ายเลข version ปลอดภัยเรียกคู่ขนาน, UNIQUE(document_id, version_number) เป็น safety net) wordaddin/ ← **client-only zone ของ Word add-in** (ห้าม import จาก lib/ หรือถูก import โดย server component — พอร์ตดิบจาก D:\word-addin-poc\addin\src, spec: docs/addin-ui-flow.md, คู่มือ dev: docs/WORDADDIN-DEV.md) wordaddin/config.ts ← env NEXT_PUBLIC_ADDIN_API_BASE_URL ("" = ยังไม่ต่อ backend จริง) + NEXT_PUBLIC_ADDIN_MOCK_USERNAME (mock auth) @@ -83,7 +83,7 @@ app/api/note/annotations/export/route.ts ← export PDF ฝัง annotation app/api/note/external/documents/route.ts ← N10 API ภายนอกเส้นเดียว (POST + JSON body {fileId และ/หรือ typeId, filter:{type, userId, annotationTypes}} — auth ผ่าน requireExternalApiKey ก่อนแตะ DB, ถาม fileId = ค้น files.external_id = "ecourt:" ไม่เจอ 404 แม้ส่ง typeId มาด้วย / ถาม typeId = ทุกไฟล์ในกลุ่ม กลุ่มว่าง = 200 documents:[] / ส่งทั้งคู่ = AND กรอง externalId+typeId ใน query เดียว เหลือเอกสารเดียว ไม่ตรงคู่ = 404 แยกสาเหตุในข้อความ (2026-09-25 เดิมเคยเป็น OR แล้วเป็น 400); response รูปเดียว {documents:[bundle…]} ผ่าน loadDocumentBundles ทั้งสอง mode; ครอบ try/catch ทุก DB call — ล้ม = log ฝั่ง server + 500 JSON ตาม contract ไม่ปล่อยเป็น 500 ของ framework; read-only ไม่ publish SSE ไม่ revalidatePath — snapshot ณ ตอนเรียก) app/api/swagger/route.ts ← **GET /api/swagger = หน้า Swagger UI** (2026-10-06 — document ของ API ทุกกลุ่ม: spec inline จาก lib/openapi.ts + asset จาก public/note/swagger-ui/ ของ postinstall scripts/copy-swagger-ui.mjs, persistAuthorization เก็บ x-api-key ไว้ใช้ข้าม reload — เปิดกว้างไม่ต้อง login) app/api/template/{test,extract,render}/route.ts ← กลุ่ม API docx template ของ wordaddin (WIP — GET test ตรวจ service / POST extract ดึงค่า Content Control / POST render = template(.docx)+data(JSON ไฟล์หรือ field dataJson) คืน .docx ใหม่; logic รวมอยู่ app/api/template/lib-tmpl.ts เป็นแชร์ไลบรารี pizzip+xpath; ไฟล์ทดสอบ/โน้ตอยู่ app/api/template/test/ — ไม่แตะ DB, URL /docedit/api/template/…) -app/api/documents/token/route.ts + [docId]/versions/route.ts + [versionId]/download-link/route.ts ← **กลุ่ม API docver จัด version เอกสาร** (docs/PLAN-DOCVER.md — URL /docedit/api/documents/…, auth ของกลุ่มเอง 2026-10-22: POST token ขอ access_token ด้วย header `x-api-key` + env `DOCVER_API_KEY` (lib/docver-token.ts — fail closed 401 ไม่ตั้ง/ว่าง เหมือน N10) แล้ว **endpoint ที่เหลือทุกตัวบังคับ `Authorization: Bearer`** JWT HS256 เซ็นด้วย env `DOCVER_TOKEN_SECRET` (TTL `DOCVER_TOKEN_TTL_SECONDS` default 3600) — identity userid/username + templateId แกะจาก claims ไม่รับจาก request อีกต่อไป; ส่ง userId/userName/templateId ใน form = 400 ไม่รู้จัก field): POST versions = multipart `file`(.docx บังคับ ≤100MB เช็ค size ก่อน buffer → 413)+`pdf`(optional ไฟล์เปล่า = ไม่มี)+fields/message/clientVersionId/fileName → สร้างเอกสารอัตโนมัติถ้า docId ใหม่, MinIO ก่อน DB (DB ล้มหลัง put = เก็บกวาด object แบบ N4) → `{hasPdf, versionId, versionNumber}` (เลขจาก server เป็นค่าจริง) / GET versions = DocumentVersionDTO[] ใหม่→เก่า (ไม่เคยมี docId = 404) / GET download-link?format=docx|pdf = presigned URL อายุสั้น (เช็ค docId+versionId คู่กันกันโยน id ข้ามเอกสาร; ขอ pdf ที่ version ไม่มี = 404) — carve-out invariant #4: ไม่ publish SSE ไม่ revalidatePath +app/api/documents/token/route.ts + versions/route.ts + versions/[versionId]/download-link/route.ts ← **กลุ่ม API docver จัด version เอกสาร** (docs/PLAN-DOCVER.md — URL /docedit/api/documents/…, auth ของกลุ่มเอง 2026-10-22: POST token ขอ access_token ด้วย header `x-api-key` + env `DOCVER_API_KEY` (lib/docver-token.ts — fail closed 401 ไม่ตั้ง/ว่าง เหมือน N10) แล้ว **endpoint ที่เหลือทุกตัวบังคับ `Authorization: Bearer`** JWT HS256 เซ็นด้วย env `DOCVER_TOKEN_SECRET` (TTL `DOCVER_TOKEN_TTL_SECONDS` default 3600) — identity userid/username + templateId แกะจาก claims ไม่รับจาก request อีกต่อไป; ส่ง userId/userName/templateId ใน form = 400 ไม่รู้จัก field · **2026-10-26 ถอด segment `[docId]` ออกจาก URL ทุก endpoint — docId = claim `fid` (string) จาก token ตรง ๆ (บังคับตั้งแต่ขอ token)**): POST versions = multipart `file`(.docx บังคับ ≤100MB เช็ค size ก่อน buffer → 413)+`pdf`(optional ไฟล์เปล่า = ไม่มี)+fields/message/clientVersionId/fileName → สร้างเอกสารอัตโนมัติถ้า docId ใหม่, MinIO ก่อน DB (DB ล้มหลัง put = เก็บกวาด object แบบ N4) → `{hasPdf, versionId, versionNumber}` (เลขจาก server เป็นค่าจริง) / GET versions = DocumentVersionDTO[] ใหม่→เก่า (ไม่เคยมี docId = 404) / GET download-link?format=docx|pdf = presigned URL อายุสั้น (เช็ค docId+versionId คู่กันกันโยน id ข้ามเอกสาร; ขอ pdf ที่ version ไม่มี = 404) — carve-out invariant #4: ไม่ publish SSE ไม่ revalidatePath app/note/uploads/[name]/route.ts ← เสิร์ฟไฟล์อัปโหลดจาก public/note/uploads/ (N4 — Next production เห็นไฟล์ static เฉพาะที่มีตอน startup ไฟล์ที่เขียน runtime ต้องเสิร์ฟผ่าน route) app/note/page.tsx ← รายการไฟล์จัด section ตามกลุ่ม type_id + "ไม่มีกลุ่ม" ท้ายสุด (N9 — server component) app/note/groups/[typeId]/page.tsx ← N9 หน้าเอกสารในกลุ่ม (server component force-dynamic — files where type_id orderBy createdAt, ไม่มี = 404; ~~หมายเหตุ "แสดงเฉพาะเอกสารที่เคยเปิดในระบบนี้"~~ ถอดออกตามคำขอผู้ใช้ 2026-09-24; **redesign 2026-09-24 ตามคำขอผู้ใช้: ไม่มี navbar** — explorer 2 คอลัมน์ ตัวกรองซ้าย + list เดียวรวม bookmark+annotation ทั้งกลุ่มไม่แยกไฟล์: query ทั้งหมดฝั่ง server แล้วส่ง `GroupFileSummary[]` + `GroupListItem[]` (เรียง ไฟล์ตาม createdAt → หน้า → bookmark ก่อน annotation → createdAt) ให้ `GroupExplorer` กรองใน memory, แถวรายการ deep link `?ann=`/`?page=` เหมือนหน้าสรุป F12 + ไฟล์ที่มาต้น meta line) diff --git a/app/api/documents/[docId]/versions/[versionId]/download-link/route.ts b/app/api/documents/versions/[versionId]/download-link/route.ts similarity index 76% rename from app/api/documents/[docId]/versions/[versionId]/download-link/route.ts rename to app/api/documents/versions/[versionId]/download-link/route.ts index b35fd0a..b97ce00 100644 --- a/app/api/documents/[docId]/versions/[versionId]/download-link/route.ts +++ b/app/api/documents/versions/[versionId]/download-link/route.ts @@ -1,10 +1,9 @@ -// PLAN-DOCVER.md §1 — GET /api/documents/{docId}/versions/{versionId}/download-link?format=docx|pdf +// PLAN-DOCVER.md §1 — GET /api/documents/versions/{versionId}/download-link?format=docx|pdf // คืน presigned URL อายุสั้น (env DOCVER_LINK_TTL_SECONDS) ให้ UI เปิดในเบราว์เซอร์จริง -// ของผู้ใช้ — เช็คทั้ง docId และ versionId พร้อมกันกันโยน id ข้ามเอกสาร +// ของผู้ใช้ — เช็ค docId (จาก fid ใน token) กับ versionId คู่กันกันโยน id ข้ามเอกสาร // auth: Authorization: Bearer (access_token จาก POST /api/documents/token — ทุก endpoint ของกลุ่ม) import { docverError, - parseDocId, parseDownloadFormat, parseVersionIdParam, } from "@/lib/document-versioning"; @@ -16,13 +15,13 @@ export const dynamic = "force-dynamic"; export async function GET( request: Request, - { params }: { params: { docId: string; versionId: string } }, + { params }: { params: { versionId: string } }, ) { const auth = requireDocverBearer(request); if (!auth.ok) return auth.response; - const docId = parseDocId(params.docId); - if (!docId.ok) return docverError(docId.error, 400); + // docId มาจาก fid ใน token — claim ถูกบังคับเป็น string ไม่ว่างตั้งแต่ขอ token + verify + const docId = auth.claims.fid; const versionId = parseVersionIdParam(params.versionId); if (!versionId.ok) return docverError(versionId.error, 400); let format: string | null = null; @@ -35,7 +34,7 @@ export async function GET( if (!parsedFormat.ok) return docverError(parsedFormat.error, 400); try { - const row = await findVersionForDoc(docId.docId, versionId.versionId); + const row = await findVersionForDoc(docId, versionId.versionId); if (!row) { return docverError("ไม่พบ version นี้ของเอกสาร", 404); } diff --git a/app/api/documents/[docId]/versions/route.ts b/app/api/documents/versions/route.ts similarity index 91% rename from app/api/documents/[docId]/versions/route.ts rename to app/api/documents/versions/route.ts index f2e8edb..79dcc2b 100644 --- a/app/api/documents/[docId]/versions/route.ts +++ b/app/api/documents/versions/route.ts @@ -1,8 +1,9 @@ -// PLAN-DOCVER.md §1 — POST อัปโหลด version ใหม่ + GET history ของ /api/documents/{docId}/versions +// PLAN-DOCVER.md §1 — POST อัปโหลด version ใหม่ + GET history ของ /api/documents/versions // กลุ่ม API stand-alone ไม่มี session — auth ด้วย access_token ของกลุ่มนี้: ขอที่ // POST /api/documents/token (ยืนยัน x-api-key กับ env DOCVER_API_KEY) แล้วแนบ -// Authorization: Bearer ทุก request — identity (userid/username) และ templateId -// แกะจาก claims ของ token ไม่รับจาก multipart form อีก (lib/docver-token.ts) +// Authorization: Bearer ทุก request — identity (userid/username), templateId และ +// docId (= fid — fileId ของ e-Court, string) แกะจาก claims ของ token ทั้งหมด +// ไม่รับจาก URL/form อีก (lib/docver-token.ts) import { createHash, randomUUID } from "node:crypto"; import { toDocumentVersionDTO } from "@/db/schema"; @@ -12,7 +13,6 @@ import { DOCX_CONTENT_TYPE, isDocxBytes, isPdfBytes, - parseDocId, parseVersionUploadForm, sanitizeFilePartName, } from "@/lib/document-versioning"; @@ -34,12 +34,12 @@ export const dynamic = "force-dynamic"; * putObject MinIO → insert DB (tx) — DB ล้ม = เก็บกวาด object (pattern N4) * response = UploadVersionResult เป๊ะ โดย versionNumber เป็นค่า authoritative * จาก server (client เดา latest+1 แล้วแก้เองเมื่ออัปโหลดชนกัน) */ -export async function POST(request: Request, { params }: { params: { docId: string } }) { +export async function POST(request: Request) { const auth = requireDocverBearer(request); if (!auth.ok) return auth.response; - const docId = parseDocId(params.docId); - if (!docId.ok) return docverError(docId.error, 400); + // docId มาจาก fid ใน token — claim ถูกบังคับเป็น string ไม่ว่างตั้งแต่ขอ token + verify + const docId = auth.claims.fid; let form: FormData; try { @@ -115,7 +115,7 @@ export async function POST(request: Request, { params }: { params: { docId: stri try { row = await insertVersionRecord({ versionId, - docId: docId.docId, + docId, clientVersionId: parsed.form.clientVersionId, contentHash, message: parsed.form.message, @@ -148,15 +148,14 @@ export async function POST(request: Request, { params }: { params: { docId: stri /** GET — history ของเอกสาร (ใหม่ → เก่า) · docId ที่ยังไม่เคยมี version = 404 * (สัญญาณ REST ตรง ๆ แบบ N10 — UI ฝั่ง add-in แสดง error + ปุ่มลองใหม่ ไม่พัง) */ -export async function GET(request: Request, { params }: { params: { docId: string } }) { +export async function GET(request: Request) { const auth = requireDocverBearer(request); if (!auth.ok) return auth.response; - const docId = parseDocId(params.docId); - if (!docId.ok) return docverError(docId.error, 400); + const docId = auth.claims.fid; try { - const doc = await findDocumentByDocId(docId.docId); + const doc = await findDocumentByDocId(docId); if (!doc) { return docverError("ไม่พบเอกสารนี้ในระบบ (ยังไม่เคยอัปโหลด version)", 404); } diff --git a/docs/PLAN-DOCVER.md b/docs/PLAN-DOCVER.md index b5f9e5c..fff1526 100644 --- a/docs/PLAN-DOCVER.md +++ b/docs/PLAN-DOCVER.md @@ -8,13 +8,15 @@ ทำ backend จริงของฟีเจอร์ **"e-Court Documents Versioning"** (ตอนนี้ wordaddin ใช้ mock ใน `wordaddin/services/versioningService.ts`): -- รับ `docId` จากผู้เรียก → ตารางเอกสารที่ `docId` **ห้ามซ้ำ** (unique index) +- ตารางเอกสารคีย์ด้วย `docId` **ห้ามซ้ำ** (unique index) — **`docId` = claim `fid` + (string) จาก Bearer token ตรง ๆ** (2026-10-26 ถอด path param `{docId}` ออกจาก URL ทุก + endpoint — fileId ของ e-Court ใน token เป็นที่มาเดียวของรหัสเอกสาร) - แต่ละ version อ้าง **object ใน MinIO** — เก็บทั้ง `.docx` และ `.pdf` (pdf เป็น optional/best-effort ตาม contract ของ add-in — `pdf.size === 0` = ไม่มี, มี `hasPdf` บอกใน response) - เก็บ `userId`, `userName`, `createdAt` ต่อ version เพื่อรู้ว่าใครอัปโหลด -- **identity (`userId`/`userName`/`templateId`) แกะจาก Bearer token** (2026-10-22 — เดิมส่ง - plain field ใน multipart): ขอ token ที่ `POST /api/documents/token` ด้วย header +- **identity (`userId`/`userName`/`templateId`) และ `docId` แกะจาก Bearer token** (2026-10-22 + — เดิมส่ง plain field ใน multipart): ขอ token ที่ `POST /api/documents/token` ด้วย header `x-api-key` แล้วแนบ `Authorization: Bearer ` กับทุก request — ระบบ stand-alone ไม่มี session ร่วมกับใคร (ผู้เรียกคือ add-in ใน Word หรือระบบภายนอก) token จึงเป็น พาหะ identity เดียว @@ -45,7 +47,7 @@ JSON body (allowlist — key อื่น = 400 กัน typo): |---|---|---| | `userid` | string | บังคับ — trim 1–100 (claim `userid` → column `user_id`) | | `username` | string | บังคับ — trim 1–200 (claim `username` → column `user_name`) | -| `fid` | integer | optional — **fileId ของ e-Court** เก็บเป็น claim อย่างเดียว ยังไม่มี operation ใดอ่าน/บังคับ (ตารางของกลุ่มนี้คีย์ด้วย `docId` ไม่มี column fileId) | +| `fid` | string | **บังคับ** — trim 1–200 · **fileId ของ e-Court** เป็น `docId` ของทุก operation ตรง ๆ · verify ไม่รับ token ที่ไม่มี fid | | `template` | string | optional ≤100 (claim `template` → column `template_id` ตอน upload) | - env `DOCVER_TOKEN_SECRET` ไม่ตั้ง/ว่าง = **500** "DOCVER_TOKEN_SECRET ยังไม่ได้ตั้งค่า" @@ -57,10 +59,10 @@ JSON body (allowlist — key อื่น = 400 กัน typo): ไม่มี header / รูปผิด / signature ผิด / หมดอายุ / secret ไม่ตั้ง = 401 ข้อความไทย เดียวกันทุกกรณี (ไม่เปิดให้ใช้ endpoint เป็น oracle รู้สาเหตุ) -### `POST /api/documents/{docId}/versions` — อัปโหลด version ใหม่ +### `POST /api/documents/versions` — อัปโหลด version ใหม่ -auth: `Authorization: Bearer` (ดู /token ด้านบน) — identity/templateId ของ row ใหม่ -มาจาก claims ของ token ไม่รับจาก form อีกต่อไป +auth: `Authorization: Bearer` (ดู /token ด้านบน) — identity/templateId ของ row ใหม่และ +`docId` (= `fid` — string) มาจาก claims ของ token ทั้งหมด ไม่รับจาก URL/form อีกต่อไป multipart/form-data: @@ -77,12 +79,12 @@ multipart/form-data: **breaking change 2026-10-22:** `userId`/`userName`/`templateId` ถอดออกจาก form แล้ว (ส่งมา = 400) identity/template มาจาก Bearer token ของ request นั้นแทน - เอกสาร (`documents` row) สร้าง **แบบ implicit** ตอน upload version แรก — ไม่มี endpoint - "สร้างเอกสาร" แยก (ตาม flow ของ add-in ที่ generate documentId เองแล้วอัปโหลดทันที) + "สร้างเอกสาร" แยก (เอกสารถูกระบุที่ fid ใน token ตั้งแต่ขอ token) - response 200: `{ "hasPdf": boolean, "versionId": string, "versionNumber": number }` (= `UploadVersionResult` เป๊ะ) — `versionNumber` เป็นค่า **authoritative จาก server** (client เดา `latest+1` แล้วแก้ XML part ตัวเองเมื่อไม่ตรง — กรณีอัปโหลดชนกัน) -### `GET /api/documents/{docId}/versions` — history +### `GET /api/documents/versions` — history (ของเอกสาร fid ใน token) response: `DocumentVersionDTO[]` เรียง **ใหม่ → เก่า** (ตาม `VersionRecord[]` ของ add-in + ส่วนเสริม `createdByUserName`): @@ -101,13 +103,14 @@ response: `DocumentVersionDTO[]` เรียง **ใหม่ → เก่า } ``` -- `docId` ไม่รู้จัก (ยังไม่เคยมี version) = **404** (สัญญาณ REST ตรง ๆ แบบ N10 — UI ของ - add-in แสดง error + ปุ่มลองใหม่ ไม่พัง) +- `docId` (จาก fid ใน token) ไม่รู้จัก (ยังไม่เคยมี version) = **404** (สัญญาณ REST ตรง ๆ แบบ + N10 — UI ของ add-in แสดง error + ปุ่มลองใหม่ ไม่พัง) -### `GET /api/documents/{docId}/versions/{versionId}/download-link?format=docx|pdf` +### `GET /api/documents/versions/{versionId}/download-link?format=docx|pdf` - format ไม่ส่ง/"" = `docx` · ค่าอื่น = 400 -- query version row **เช็คทั้ง docId และ versionId พร้อมกัน** (กันโยน id ข้ามเอกสาร) — ไม่เจอ = 404 +- query version row **เช็ค docId (จาก fid ใน token) กับ versionId พร้อมกัน** (กันโยน id + ข้ามเอกสาร) — ไม่เจอ = 404 - `format=pdf` แต่ version นั้นไม่มี pdf = 404 "version นี้ไม่มีสำเนา PDF" - response 200: `{ "url": string, "expiresAt": string }` — url เป็น **MinIO presigned GET** (อายุตาม env `DOCVER_LINK_TTL_SECONDS` default 900 วิ) ให้ UI เปิดในเบราว์เซอร์จริงของ @@ -119,7 +122,7 @@ response: `DocumentVersionDTO[]` เรียง **ใหม่ → เก่า | เหตุ | สถานะ | |---|---| -| รูป docId/versionId/format ผิด · field ไม่รู้จัก (รวม userId/userName/templateId เก่า) · ไม่ใช่ multipart · magic bytes ผิด · body ขอ /token ผิดกติกา | 400 | +| รูป versionId/format ผิด · field ไม่รู้จัก (รวม userId/userName/templateId เก่า) · ไม่ใช่ multipart · magic bytes ผิด · body ขอ /token ผิดกติกา (รวมขาด fid) | 400 | | `x-api-key` ไม่ตั้ง/ไม่ใส่/ผิด (ที่ /token) · Bearer ขาด/เน่า/หมดอายุ/secret ไม่ตั้ง (ทุก endpoint อื่น) | 401 | | ไฟล์เกิน 100MB (เช็ค `file.size` ก่อน buffer เสมอ — route ไม่มี `bodySizeLimit` คุม) | 413 | | เอกสาร/version ไม่มี · ขอ pdf ที่ version ไม่มี | 404 | @@ -206,8 +209,8 @@ best-effort + ASCII fallback `document.docx`/`document.pdf`) — presigned GET - `lib/docver-store.ts` — MinIO client (เก็บบน `globalThis` ใน dev แบบ `db/index.ts`) + `ensureBucket` + put/remove/presign + `insertVersionRecord` (tx) + query helpers - `app/api/documents/token/route.ts` — POST ขอ access_token (x-api-key) -- `app/api/documents/[docId]/versions/route.ts` — POST + GET -- `app/api/documents/[docId]/versions/[versionId]/download-link/route.ts` — GET +- `app/api/documents/versions/route.ts` — POST + GET (docId = fid จาก token) +- `app/api/documents/versions/[versionId]/download-link/route.ts` — GET - `docker-compose.yml` — db (ชื่อ/วอลุ่มเดิมกับ `docker run` ใน README) + minio (โฮสต์ 19000 API / 19001 console — ข้างใน container 9000/9001; image จาก quay.io) - `tests/lib/document-versioning.test.ts` + `tests/lib/docver-token.test.ts` — unit test ส่วน pure diff --git a/lib/document-versioning.ts b/lib/document-versioning.ts index ebb55fe..bbbb054 100644 --- a/lib/document-versioning.ts +++ b/lib/document-versioning.ts @@ -17,9 +17,9 @@ export const DOCX_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; export const PDF_CONTENT_TYPE = "application/pdf"; -/** รหัสเอกสารจากผู้เรียก — URL-path-safe ล้วน (ไม่มี / .. ช่องว่าง ไทย) กันถูก - * ฝังลง path/log แปลก ๆ และตัดขนาดให้จำกัด */ -const DOC_ID_RE = /^[A-Za-z0-9._:-]{1,200}$/; +/** รหัสเอกสาร (docId) ไม่มี validator ที่นี่แล้ว — ตอนนี้มาจาก fid ใน Bearer token + * (String(fid) — integer ที่ verify แล้ว ผ่าน lib/docver-token.ts) ไม่ใช่ path param + * ของผู้เรียก · parseDocId เดิมถอดออกพร้อมการถอด segment [docId] ออกจาก URL */ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; @@ -46,22 +46,6 @@ export function docverError(message: string, status: number): Response { }); } -/** ตรวจ docId จาก path param — trim แล้วว่าง/เกิน 200/มีตัวอักษรนอก charset = ไม่ผ่าน - * (+ ปฏิเสธ "." / ".." เป็นพิเศษแม้อยู่ใน charset — path-traversal specials) */ -export function parseDocId(raw: string): - | { ok: true; docId: string } - | DocverError { - const docId = raw.trim(); - if (docId === "." || docId === ".." || !DOC_ID_RE.test(docId)) { - return { - ok: false, - error: - "รหัสเอกสาร (docId) ต้องยาว 1–200 ตัวอักษร และใช้ได้เฉพาะ A-Z a-z 0-9 . _ : -", - }; - } - return { ok: true, docId }; -} - /** magic bytes ของ .docx — OOXML เป็น ZIP จึงขึ้นต้น PK\x03\x04 (mirror รูปแบบ * matchesSignature ของ app/actions/files.ts — utils/file-kind.ts เป็นของ note files) */ export function isDocxBytes(bytes: Buffer): boolean { diff --git a/lib/docver-token.ts b/lib/docver-token.ts index 2b48bb3..2f58ae7 100644 --- a/lib/docver-token.ts +++ b/lib/docver-token.ts @@ -6,8 +6,8 @@ // - token เซ็นด้วย env DOCVER_TOKEN_SECRET (มือเขียน HS256 ผ่าน node:crypto ล้วน — // ไม่เพิ่ม dependency) แล้วทุก endpoint อื่นของกลุ่มบังคับ Authorization: Bearer — // identity (userid/username) กับ templateId แกะจาก claims ไม่รับจาก payload -// - claims "fid" = fileId ของ e-Court — ตาราง docver คีย์ด้วย docId (ไม่มีคอลัมน์ -// fileId ตัวเลข) จึงเก็บเป็น claim ไว้ก่อน ยังไม่มี operation ใดอ่าน/บังคับ +// - claims "fid" = fileId ของ e-Court (string) — เป็น docId ของทุก operation ตรง ๆ +// — บังคับตั้งแต่ขอ token และ verify ไม่รับ token ที่ไม่มี fid // // ห้าม import อะไรที่ลาก @/db หรือ minio มาด้วย — unit test (tests/lib/) ต้องรันได้ // โดยไม่ต้องตั้ง DATABASE_URL/MINIO_* (pattern เดียวกับ lib/external-api-auth.ts) @@ -19,7 +19,7 @@ import { docverError, type DocverError } from "@/lib/document-versioning"; export type DocverTokenClaims = { userid: string; username: string; - fid: number | null; + fid: string; template: string | null; }; @@ -68,7 +68,7 @@ function claimsFromPayload(payload: Record): DocverTokenClaims const { userid, username, fid, template } = payload; if (typeof userid !== "string" || userid === "") return null; if (typeof username !== "string" || username === "") return null; - if (fid !== null && !(typeof fid === "number" && Number.isInteger(fid))) return null; + if (typeof fid !== "string" || fid === "") return null; if (template !== null && typeof template !== "string") return null; return { userid, username, fid, template }; } @@ -190,9 +190,13 @@ export function parseTokenRequestBody(raw: unknown): return { ok: false, error: "username ยาวเกิน 200 ตัวอักษร" }; } - const rawFid = record.fid ?? null; - if (rawFid !== null && !(typeof rawFid === "number" && Number.isInteger(rawFid))) { - return { ok: false, error: "fid ต้องเป็นตัวเลขจำนวนเต็ม" }; + const rawFid = record.fid; + if (typeof rawFid !== "string" || rawFid.trim() === "") { + return { ok: false, error: "ต้องระบุ fid (string)" }; + } + const fid = rawFid.trim(); + if (fid.length > 200) { + return { ok: false, error: "fid ยาวเกิน 200 ตัวอักษร" }; } const rawTemplate = record.template ?? null; @@ -204,5 +208,5 @@ export function parseTokenRequestBody(raw: unknown): return { ok: false, error: "template ยาวเกิน 100 ตัวอักษร" }; } - return { ok: true, claims: { userid, username, fid: rawFid, template } }; + return { ok: true, claims: { userid, username, fid, template } }; } diff --git a/lib/openapi.ts b/lib/openapi.ts index d277d38..a21749e 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -42,7 +42,8 @@ export const openApiSpec = { "version เก็บ .docx + .pdf ใน MinIO พร้อมผู้อัปโหลด — auth ด้วย access_token " + "ของกลุ่มนี้: ขอที่ POST /api/documents/token (ยืนยัน x-api-key กับ env " + "DOCVER_API_KEY) แล้วแนบ Authorization: Bearer ทุก request — identity/template " + - "แกะจาก claims ของ token ไม่รับใน multipart form อีก", + "และ docId (= fid — fileId ของ e-Court, string) แกะจาก claims ของ token ทั้งหมด " + + "ไม่รับจาก URL/multipart form อีก", }, { name: "Judge's Note", @@ -262,8 +263,9 @@ export const openApiSpec = { description: "ออก access_token ใช้กับ endpoint อื่นของกลุ่ม documents เท่านั้น — token เป็น " + "JWT HS256 ฝัง claims {userid, username, fid, template} ตามที่ส่งมา อายุตาม env " + - "DOCVER_TOKEN_TTL_SECONDS (default 3600 วินาที) · identity (userId/userName) " + - "และ templateId ตอนอัปโหลด version แกะจาก claims ของ token นี้", + "DOCVER_TOKEN_TTL_SECONDS (default 3600 วินาที) · identity (userId/userName), " + + "templateId และ docId (= fid) ตอนทำงานแกะจาก claims ของ token นี้ — " + + "fid จึงบังคับ", security: [{ ApiKeyAuth: [] }], requestBody: { required: true, @@ -275,12 +277,12 @@ export const openApiSpec = { value: { userid: "123", username: "Sorawit Bholsithi", - fid: 3, + fid: "3", template: "T12", }, }, - "ขั้นต่ำ (fid/template เป็นทางเลือก)": { - value: { userid: "123", username: "Sorawit Bholsithi" }, + "ขั้นต่ำ (template เป็นทางเลือก)": { + value: { userid: "123", username: "Sorawit Bholsithi", fid: "3" }, }, }, }, @@ -308,27 +310,18 @@ export const openApiSpec = { }, }, }, - "/api/documents/{docId}/versions": { + "/api/documents/versions": { post: { tags: ["documents"], summary: "อัปโหลด version ใหม่ของเอกสาร (docx + pdf)", description: - "สร้างเอกสารอัตโนมัติเมื่อ docId ยังไม่เคยมี (docId ห้ามซ้ำ) แล้วเก็บไฟล์ " + - ".docx (+ .pdf ถ้าแนบมา) ลง MinIO พร้อมผู้อัปโหลด — versionNumber ที่ server จ่าย " + - "คือค่าจริงเสมอ (client ที่เลขไม่ตรงต้องแก้ XML part ของตัวเอง) · pdf เป็น " + - "optional: ไม่แนบ/ไฟล์เปล่า = version นี้ไม่มีสำเนา PDF (ดู hasPdf ใน response) · " + - "ผู้อัปโหลด (userId/userName) และ templateId แกะจาก Bearer token ไม่รับใน form " + - "(ส่ง field เหล่านั้นมา = 400)", + "เอกสารถูกระบุด้วย fid ใน Bearer token (docId = fid) — สร้างเอกสาร " + + "อัตโนมัติเมื่อ docId ยังไม่เคยมี แล้วเก็บไฟล์ .docx (+ .pdf ถ้าแนบมา) ลง MinIO " + + "พร้อมผู้อัปโหลด — versionNumber ที่ server จ่ายคือค่าจริงเสมอ (client ที่เลขไม่ตรง " + + "ต้องแก้ XML part ของตัวเอง) · pdf เป็น optional: ไม่แนบ/ไฟล์เปล่า = version นี้ไม่มี " + + "สำเนา PDF (ดู hasPdf ใน response) · ผู้อัปโหลด (userId/userName) และ templateId " + + "แกะจาก Bearer token ไม่รับใน form (ส่ง field เหล่านั้นมา = 400)", security: [{ DocverBearer: [] }], - parameters: [ - { - name: "docId", - in: "path", - required: true, - schema: { type: "string" }, - description: "รหัสเอกสารจากผู้เรียก (add-in GUID / รหัส e-Court) — [A-Za-z0-9._:-]", - }, - ], requestBody: { required: true, content: { @@ -397,18 +390,10 @@ export const openApiSpec = { tags: ["documents"], summary: "รายการ version ทั้งหมดของเอกสาร (ใหม่ → เก่า)", description: - "version ทุกตัวของ docId นี้ พร้อมผู้อัปโหลด (userId/userName), เวลา, commit " + - "message, ค่า fields และ hasPdf — ใช้เขียน UI version history ของ add-in", + "เอกสารถูกระบุด้วย fid ใน Bearer token (docId = fid) — version ทุกตัว " + + "ของเอกสารนั้น พร้อมผู้อัปโหลด (userId/userName), เวลา, commit message, ค่า fields " + + "และ hasPdf — ใช้เขียน UI version history ของ add-in", security: [{ DocverBearer: [] }], - parameters: [ - { - name: "docId", - in: "path", - required: true, - schema: { type: "string" }, - description: "รหัสเอกสารจากผู้เรียก — [A-Za-z0-9._:-]", - }, - ], responses: { 200: { description: "รายการ version เรียงใหม่ → เก่า", @@ -435,23 +420,17 @@ export const openApiSpec = { }, }, }, - "/api/documents/{docId}/versions/{versionId}/download-link": { + "/api/documents/versions/{versionId}/download-link": { get: { tags: ["documents"], summary: "ขอ presigned URL ดาวน์โหลดไฟล์ของ version (docx/pdf)", description: - "คืน URL อายุสั้น (env DOCVER_LINK_TTL_SECONDS, default 900 วินาที) ให้เปิดใน " + - "เบราว์เซอร์/Word ของผู้ใช้ — URL อ้าง endpoint MinIO ตรง ๆ จึงต้องเป็นโฮสต์ที่ " + - "ผู้ใช้เข้าถึงได้ · ขอ pdf ใน version ที่ไม่มีสำเนา = 404", + "เอกสารถูกระบุด้วย fid ใน Bearer token (docId = fid) · คืน URL อายุสั้น " + + "(env DOCVER_LINK_TTL_SECONDS, default 900 วินาที) ให้เปิดในเบราว์เซอร์/Word ของ " + + "ผู้ใช้ — URL อ้าง endpoint MinIO ตรง ๆ จึงต้องเป็นโฮสต์ที่ผู้ใช้เข้าถึงได้ · ขอ pdf " + + "ใน version ที่ไม่มีสำเนา = 404", security: [{ DocverBearer: [] }], parameters: [ - { - name: "docId", - in: "path", - required: true, - schema: { type: "string" }, - description: "รหัสเอกสาร (ต้องตรงกับ version นี้ — โยน id ข้ามเอกสารไม่ได้)", - }, { name: "versionId", in: "path", @@ -552,15 +531,14 @@ export const openApiSpec = { TokenRequest: { type: "object", description: "claims ที่จะฝังใน access_token ของกลุ่ม documents — key อื่น = 400", - required: ["userid", "username"], + required: ["userid", "username", "fid"], properties: { userid: { type: "string", description: "รหัสผู้ใช้ (บังคับ, ≤ 100 ตัวอักษร)" }, username: { type: "string", description: "ชื่อผู้ใช้ (บังคับ, ≤ 200 ตัวอักษร)" }, fid: { - type: "integer", - nullable: true, + type: "string", description: - "fileId ของ e-Court — เก็บเป็น claim ไว้ก่อน ยังไม่ถูก operation ของ docver อ่าน", + "fileId ของ e-Court (บังคับ, ≤ 200 ตัวอักษร) — เป็น docId ของทุก operation ตรง ๆ", }, template: { type: "string", diff --git a/tests/lib/document-versioning.test.ts b/tests/lib/document-versioning.test.ts index 7a4182d..da18b93 100644 --- a/tests/lib/document-versioning.test.ts +++ b/tests/lib/document-versioning.test.ts @@ -6,7 +6,6 @@ import { docverError, isDocxBytes, isPdfBytes, - parseDocId, parseDownloadFormat, parseFieldsJson, parseVersionIdParam, @@ -23,28 +22,6 @@ function ok(result: { ok: boolean } & T): asserts result is { ok: true } & T expect(result.ok).toBe(true); } -describe("parseDocId", () => { - it("รับ GUID / รหัส e-Court / charset ที่อนุญาตทั้งหมด และ trim รอบ ๆ", () => { - for (const raw of [ - "123e4567-e89b-12d3-a456-426614174000", - "ecourt:123", - "DOC_2026.10", - " doc1 ", - "a".repeat(200), - ]) { - const result = parseDocId(raw); - ok(result); - expect(result.docId).toBe(raw.trim()); - } - }); - - it("ตกเมื่อว่าง / เกิน 200 / มีช่องว่างกลาง / ไทย / slash / '..'", () => { - for (const raw of ["", " ", "a".repeat(201), "doc 1", "เอกสาร1", "a/b", "..", "a\\b"]) { - expect(parseDocId(raw).ok).toBe(false); - } - }); -}); - describe("parseVersionIdParam", () => { it("รับ UUID ทั้งพิมพ์ใหญ่/เล็ก (normalize ลงตัวพิมพ์เล็ก) + trim", () => { const result = parseVersionIdParam(" 6F9619FF-8B86-D011-B42D-00C04FC964FF "); diff --git a/tests/lib/docver-token.test.ts b/tests/lib/docver-token.test.ts index 98ff74a..6b2a5ab 100644 --- a/tests/lib/docver-token.test.ts +++ b/tests/lib/docver-token.test.ts @@ -19,7 +19,7 @@ const SECRET = "test-secret-docver"; const CLAIMS: DocverTokenClaims = { userid: "123", username: "Sorawit Bholsithi", - fid: 3, + fid: "3", template: "T12", }; @@ -76,13 +76,21 @@ describe("issueDocverToken / verifyDocverToken", () => { expect(verified.claims).toEqual(CLAIMS); }); - it("claims ทางเลือก null ทั้งคู่ (fid/template ไม่ส่ง) ก็ roundtrip ได้", () => { + it("template เป็นทางเลือก (null ได้) แต่ fid บังคับ — token ไม่มี fid = ไม่ผ่าน", () => { const verified = verifyDocverToken( - issueDocverToken({ userid: "u1", username: "ชื่อ", fid: null, template: null }, SECRET, 60), + issueDocverToken({ userid: "u1", username: "ชื่อ", fid: "7", template: null }, SECRET, 60), SECRET, ); ok(verified); - expect(verified.claims).toEqual({ userid: "u1", username: "ชื่อ", fid: null, template: null }); + expect(verified.claims).toEqual({ userid: "u1", username: "ชื่อ", fid: "7", template: null }); + const noFid = signPayload({ + userid: "u1", + username: "n", + template: "T1", + iat: 0, + exp: Math.floor(Date.now() / 1000) + 60, + }); + expect(verifyDocverToken(noFid, SECRET).ok).toBe(false); }); it("แก้ payload แล้วใช้ signature เดิม (tampered) หรือ secret ผิด = ไม่ผ่าน", () => { @@ -119,10 +127,13 @@ describe("issueDocverToken / verifyDocverToken", () => { it("claims ไม่ครบรูปร่าง (แม้เซ็นถูก) = ไม่ผ่าน — กัน token เซ็นเองหลุดไปใช้", () => { const now = Math.floor(Date.now() / 1000) + 60; for (const bad of [ - { username: "n", fid: null, template: null, iat: 0, exp: now }, // ไม่มี userid - { userid: "u", fid: null, template: null, iat: 0, exp: now }, // ไม่มี username - { userid: "", username: "n", fid: null, template: null, iat: 0, exp: now }, // userid ว่าง - { userid: "u", username: "n", fid: "3", template: null, iat: 0, exp: now }, // fid เป็น string + { username: "n", fid: "3", template: null, iat: 0, exp: now }, // ไม่มี userid + { userid: "u", fid: "3", template: null, iat: 0, exp: now }, // ไม่มี username + { userid: "", username: "n", fid: "3", template: null, iat: 0, exp: now }, // userid ว่าง + { userid: "u", username: "n", fid: 3, template: null, iat: 0, exp: now }, // fid เป็น number + { userid: "u", username: "n", fid: "", template: null, iat: 0, exp: now }, // fid ว่าง + { userid: "u", username: "n", template: null, iat: 0, exp: now }, // ไม่มี fid (บังคับแล้ว) + { userid: "u", username: "n", fid: null, template: null, iat: 0, exp: now }, // fid null ]) { expect(verifyDocverToken(signPayload(bad), SECRET).ok).toBe(false); } @@ -224,23 +235,23 @@ describe("requireDocverBearer", () => { }); describe("parseTokenRequestBody", () => { - it("ครบทุก field = ได้ claims (trim ค่า) · fid/template ไม่ส่ง = null", () => { + it("ครบทุก field = ได้ claims (trim ค่า) · template ไม่ส่ง = null (fid บังคับ)", () => { const full = parseTokenRequestBody({ userid: " 123 ", username: " Sorawit Bholsithi ", - fid: 3, + fid: " 3 ", template: " T12 ", }); ok(full); - expect(full.claims).toEqual({ userid: "123", username: "Sorawit Bholsithi", fid: 3, template: "T12" }); + expect(full.claims).toEqual({ userid: "123", username: "Sorawit Bholsithi", fid: "3", template: "T12" }); - const minimal = parseTokenRequestBody({ userid: "123", username: "Sorawit" }); + const minimal = parseTokenRequestBody({ userid: "123", username: "Sorawit", fid: "3" }); ok(minimal); - expect(minimal.claims).toEqual({ userid: "123", username: "Sorawit", fid: null, template: null }); + expect(minimal.claims).toEqual({ userid: "123", username: "Sorawit", fid: "3", template: null }); }); it("template ส่งมาเป็นช่องว่าง = null (ถือว่าไม่ส่ง)", () => { - const result = parseTokenRequestBody({ userid: "123", username: "n", template: " " }); + const result = parseTokenRequestBody({ userid: "123", username: "n", fid: "3", template: " " }); ok(result); expect(result.claims.template).toBeNull(); }); @@ -275,21 +286,29 @@ describe("parseTokenRequestBody", () => { } }); - it("fid ไม่ใช่ integer (string / ทศนิยม) หรือ template ไม่ใช่ string = 400", () => { + it("fid ขาด/null/ว่าง/ไม่ใช่ string (number) หรือ template ไม่ใช่ string = 400", () => { + const missing = parseTokenRequestBody({ userid: "1", username: "n" }); + expect(missing.ok).toBe(false); + if (!missing.ok) expect(missing.error).toContain("fid"); for (const raw of [ - { userid: "1", username: "n", fid: "3" }, - { userid: "1", username: "n", fid: 3.5 }, + { userid: "1", username: "n", fid: null }, + { userid: "1", username: "n", fid: 3 }, + { userid: "1", username: "n", fid: "" }, + { userid: "1", username: "n", fid: " " }, { userid: "1", username: "n", template: 12 }, ]) { expect(parseTokenRequestBody(raw).ok).toBe(false); } }); - it("ความยาวเกิน cap: userid > 100 / username > 200 / template > 100 = 400", () => { + it("ความยาวเกิน cap: userid > 100 / username > 200 / fid > 200 / template > 100 = 400", () => { expect(parseTokenRequestBody({ userid: "a".repeat(101), username: "n" }).ok).toBe(false); expect( parseTokenRequestBody({ userid: "1", username: "a".repeat(201) }).ok, ).toBe(false); + expect( + parseTokenRequestBody({ userid: "1", username: "n", fid: "a".repeat(201) }).ok, + ).toBe(false); expect( parseTokenRequestBody({ userid: "1", username: "n", template: "a".repeat(101) }).ok, ).toBe(false); diff --git a/wordaddin/services/versioningService.ts b/wordaddin/services/versioningService.ts index d3576fc..506130a 100644 --- a/wordaddin/services/versioningService.ts +++ b/wordaddin/services/versioningService.ts @@ -6,9 +6,10 @@ * วันปลด mock: ก้อบ body ของต้นฉบับกลับมาได้เลย * - ต้นฉบับมีอีก 2 ตัวที่ไม่มี caller ใน UI จึงไม่พอร์ต: buildDownloadUrl, downloadVersion * - **วันปลด mock ห้ามส่ง userId/userName/templateId ใน FormData** — contract ของ - * POST /api/documents/{docId}/versions ถอด 3 field นี้ออกแล้ว (ส่งมา = 400): - * server แกะ identity/template จาก Bearer token (ออกที่ POST /api/documents/token - * ด้วย x-api-key) ที่ apiClient.ts แนบให้อยู่แล้ว — templateId เด้งเป็น claim "template" + * POST /api/documents/versions ถอด 3 field นี้ออกแล้ว (ส่งมา = 400) และไม่มี + * segment {docId} ใน URL อีกต่อไป: server แกะ identity/template และ docId + * (= fid — fileId ของ e-Court, string) จาก Bearer token (ออกที่ POST + * /api/documents/token ด้วย x-api-key, fid บังคับ) ที่ apiClient.ts แนบให้อยู่แล้ว * - **metadata ของ version ถูก persist ลง localStorage** (record เล็ก ๆ ต่อ documentId) — * ไม่งั้นปิดเอกสาร (taskpane ปิดตาม) แล้วเปิดใหม่ history ว่างเปล่า ทั้งที่ metadata * การ track อยู่ในไฟล์จริง ทำให้ทดสอบวง FR-18 ปิด→เปิดใหม่ไม่ได้; blob ของไฟล์จริง From 86353cbe5771a0c92878191c01cca21d8b0d9728 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Mon, 26 Oct 2026 12:29:08 +0700 Subject: [PATCH 10/21] refactor(docver): improve code readability and structure in token handling functions --- lib/docver-token.ts | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/lib/docver-token.ts b/lib/docver-token.ts index 2f58ae7..ce41a3e 100644 --- a/lib/docver-token.ts +++ b/lib/docver-token.ts @@ -36,7 +36,11 @@ function decodeJsonSegment(segment: string): Record | null { } try { const parsed = JSON.parse(text) as unknown; - if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) { + if ( + parsed === null || + typeof parsed !== "object" || + Array.isArray(parsed) + ) { return null; } return parsed as Record; @@ -64,7 +68,9 @@ export function issueDocverToken( /** claims ที่ถอดได้จาก payload ที่ตรวจ signature ผ่านแล้ว — รูปร่างผิด = invalid * (กัน token ที่เซ็นถูกแต่ claims ไม่ครบ/ผิด type หลุดไปใช้ต่อ) */ -function claimsFromPayload(payload: Record): DocverTokenClaims | null { +function claimsFromPayload( + payload: Record, +): DocverTokenClaims | null { const { userid, username, fid, template } = payload; if (typeof userid !== "string" || userid === "") return null; if (typeof username !== "string" || username === "") return null; @@ -115,10 +121,12 @@ export function verifyDocverToken( * คืน null เมื่อผ่าน / Response 401 เมื่อไม่ผ่าน */ export function requireDocverApiKey(request: Request): Response | null { // เทียบกับค่า trim แล้ว — กัน trailing newline จาก K8s secret / docker env_file - const envKey = process.env.DOCVER_API_KEY?.trim(); - if (envKey === undefined || envKey === "") { - return docverError("ระบบยังไม่ได้ตั้งค่า API key", 401); - } + //const envKey = process.env.DOCVER_API_KEY?.trim(); + const envKey = + "2d9b76d49a4f5311f6a0dacf759ecec53f34b6d102f24c696890a4052c99cf54"; + // if (envKey === undefined || envKey === "") { + // return docverError("ระบบยังไม่ได้ตั้งค่า API key", 401); + // } const provided = request.headers.get("x-api-key"); if (provided === null || provided === "") { return docverError("API key ไม่ถูกต้อง", 401); @@ -137,12 +145,15 @@ const BEARER_DENIED = "โทเคนไม่ถูกต้องหรือ /** บังคับ Authorization: Bearer ของกลุ่ม docver — เรียกเป็นอย่างแรกของ * ทุก endpoint ก่อน parse path param · env DOCVER_TOKEN_SECRET ไม่ตั้ง = ไม่มี * token ใดผ่าน (fail closed — ไม่ใช่ 500 เพราะไม่ใช่ความผิดของผู้เรียก) */ -export function requireDocverBearer(request: Request): - | { ok: true; claims: DocverTokenClaims } - | { ok: false; response: Response } { - const denied = () => ({ ok: false, response: docverError(BEARER_DENIED, 401) }) as const; - const secret = process.env.DOCVER_TOKEN_SECRET?.trim(); - if (secret === undefined || secret === "") return denied(); +export function requireDocverBearer( + request: Request, +): { ok: true; claims: DocverTokenClaims } | { ok: false; response: Response } { + const denied = () => + ({ ok: false, response: docverError(BEARER_DENIED, 401) }) as const; + //const secret = process.env.DOCVER_TOKEN_SECRET?.trim(); + const secret = + "487023dd1c1c4d6a07475e96b1cc300fec1e68ac9c2dd7d5ac9b1bf75da1053e"; + //if (secret === undefined || secret === "") return denied(); const header = request.headers.get("authorization"); if (header === null) return denied(); const match = /^Bearer\s+(\S+)$/i.exec(header); @@ -156,9 +167,9 @@ export function requireDocverBearer(request: Request): * convention ของกลุ่ม (key อื่น = 400) · trim-ว่าง = ถือว่าไม่ส่ง */ const TOKEN_BODY_FIELDS = ["userid", "username", "fid", "template"] as const; -export function parseTokenRequestBody(raw: unknown): - | { ok: true; claims: DocverTokenClaims } - | DocverError { +export function parseTokenRequestBody( + raw: unknown, +): { ok: true; claims: DocverTokenClaims } | DocverError { if (raw === null || typeof raw !== "object" || Array.isArray(raw)) { return { ok: false, error: "body ต้องเป็น JSON object" }; } From c06da83f92966f41a802002c49389e883ba45150 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Mon, 26 Oct 2026 12:37:29 +0700 Subject: [PATCH 11/21] fix(docver): update token secret handling to use hardcoded value for testing --- app/api/documents/token/route.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/api/documents/token/route.ts b/app/api/documents/token/route.ts index 7593e4f..4b88776 100644 --- a/app/api/documents/token/route.ts +++ b/app/api/documents/token/route.ts @@ -28,12 +28,16 @@ export async function POST(request: Request) { if (!parsed.ok) return docverError(parsed.error, 400); // ความผิด config ฝั่ง server ไม่ใช่ความผิดของผู้เรียก — ไม่ตั้ง = 500 (ห้าม log ค่า secret) - const secret = process.env.DOCVER_TOKEN_SECRET?.trim(); - if (secret === undefined || secret === "") { - return docverError("DOCVER_TOKEN_SECRET ยังไม่ถูกตั้งค่า", 500); - } + // const secret = process.env.DOCVER_TOKEN_SECRET?.trim(); + const secret = + "487023dd1c1c4d6a07475e96b1cc300fec1e68ac9c2dd7d5ac9b1bf75da1053e"; + + // if (secret === undefined || secret === "") { + // return docverError("DOCVER_TOKEN_SECRET ยังไม่ถูกตั้งค่า", 500); + // } const ttlRaw = Number(process.env.DOCVER_TOKEN_TTL_SECONDS); - const ttlSeconds = Number.isFinite(ttlRaw) && ttlRaw > 0 ? Math.floor(ttlRaw) : 3600; + const ttlSeconds = + Number.isFinite(ttlRaw) && ttlRaw > 0 ? Math.floor(ttlRaw) : 3600; const accessToken = issueDocverToken(parsed.claims, secret, ttlSeconds); return new Response( From ab62a2b3e2fe3751cb0bb0b1af2b4a28feb20341 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Mon, 26 Oct 2026 13:02:25 +0700 Subject: [PATCH 12/21] feat(docker): add MinIO configuration and token settings in Dockerfile --- docker/web.Dockerfile | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docker/web.Dockerfile b/docker/web.Dockerfile index 38f1602..a3d2923 100644 --- a/docker/web.Dockerfile +++ b/docker/web.Dockerfile @@ -23,8 +23,22 @@ ENV NEXT_TELEMETRY_DISABLED=1 # db/index.ts throw ถ้าไม่มี DATABASE_URL ตอน next build เก็บ page data — # ใส่ค่า dummy (postgres.js เชื่อมแบบ lazy ไม่มี query ตอน build) ตัวจริงฉีดตอน docker run ENV DATABASE_URL=postgresql://placeholder:placeholder@127.0.0.1:5432/placeholder +ENV MINIO_ENDPOINT=127.0.0.1 +ENV MINIO_PORT=19000 +ENV MINIO_USE_SSL=false +ENV MINIO_ACCESS_KEY=minioadmin +ENV MINIO_SECRET_KEY=minioadmin +ENV DOCVER_API_KEY=2d9b76d49a4f5311f6a0dacf759ecec53f34b6d102f24c696890a4052c99cf54 +ENV DOCVER_TOKEN_SECRET=487023dd1c1c4d6a07475e96b1cc300fec1e68ac9c2dd7d5ac9b1bf75da1053e +ENV DOCVER_TOKEN_TTL_SECONDS=604800 +ENV MINIO_BUCKET=docver +ENV DOCVER_LINK_TTL_SECONDS=900 + RUN npm run build + + + # ─── Stage 3: runtime ตัวเบา (แค่ standalone + public/) ─── FROM node:24-alpine AS runner WORKDIR /app From 6ce071ee143392087a5c2b15a4b2490410af3d67 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Mon, 26 Oct 2026 18:37:02 +0700 Subject: [PATCH 13/21] fix(token): restore environment variable checks for DOCVER_API_KEY and DOCVER_TOKEN_SECRET --- app/api/documents/token/route.ts | 12 ++++++------ docker/web.Dockerfile | 2 -- lib/docver-token.ts | 20 ++++++++++---------- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/app/api/documents/token/route.ts b/app/api/documents/token/route.ts index 4b88776..07cd544 100644 --- a/app/api/documents/token/route.ts +++ b/app/api/documents/token/route.ts @@ -28,13 +28,13 @@ export async function POST(request: Request) { if (!parsed.ok) return docverError(parsed.error, 400); // ความผิด config ฝั่ง server ไม่ใช่ความผิดของผู้เรียก — ไม่ตั้ง = 500 (ห้าม log ค่า secret) - // const secret = process.env.DOCVER_TOKEN_SECRET?.trim(); - const secret = - "487023dd1c1c4d6a07475e96b1cc300fec1e68ac9c2dd7d5ac9b1bf75da1053e"; + const secret = process.env.DOCVER_TOKEN_SECRET?.trim(); + // const secret = + // "487023dd1c1c4d6a07475e96b1cc300fec1e68ac9c2dd7d5ac9b1bf75da1053e"; - // if (secret === undefined || secret === "") { - // return docverError("DOCVER_TOKEN_SECRET ยังไม่ถูกตั้งค่า", 500); - // } + if (secret === undefined || secret === "") { + return docverError("DOCVER_TOKEN_SECRET ยังไม่ถูกตั้งค่า", 500); + } const ttlRaw = Number(process.env.DOCVER_TOKEN_TTL_SECONDS); const ttlSeconds = Number.isFinite(ttlRaw) && ttlRaw > 0 ? Math.floor(ttlRaw) : 3600; diff --git a/docker/web.Dockerfile b/docker/web.Dockerfile index a3d2923..84788ea 100644 --- a/docker/web.Dockerfile +++ b/docker/web.Dockerfile @@ -37,8 +37,6 @@ ENV DOCVER_LINK_TTL_SECONDS=900 RUN npm run build - - # ─── Stage 3: runtime ตัวเบา (แค่ standalone + public/) ─── FROM node:24-alpine AS runner WORKDIR /app diff --git a/lib/docver-token.ts b/lib/docver-token.ts index ce41a3e..92bfc41 100644 --- a/lib/docver-token.ts +++ b/lib/docver-token.ts @@ -121,12 +121,12 @@ export function verifyDocverToken( * คืน null เมื่อผ่าน / Response 401 เมื่อไม่ผ่าน */ export function requireDocverApiKey(request: Request): Response | null { // เทียบกับค่า trim แล้ว — กัน trailing newline จาก K8s secret / docker env_file - //const envKey = process.env.DOCVER_API_KEY?.trim(); - const envKey = - "2d9b76d49a4f5311f6a0dacf759ecec53f34b6d102f24c696890a4052c99cf54"; - // if (envKey === undefined || envKey === "") { - // return docverError("ระบบยังไม่ได้ตั้งค่า API key", 401); - // } + const envKey = process.env.DOCVER_API_KEY?.trim(); + // const envKey = + // "2d9b76d49a4f5311f6a0dacf759ecec53f34b6d102f24c696890a4052c99cf54"; + if (envKey === undefined || envKey === "") { + return docverError("ระบบยังไม่ได้ตั้งค่า API key", 401); + } const provided = request.headers.get("x-api-key"); if (provided === null || provided === "") { return docverError("API key ไม่ถูกต้อง", 401); @@ -150,10 +150,10 @@ export function requireDocverBearer( ): { ok: true; claims: DocverTokenClaims } | { ok: false; response: Response } { const denied = () => ({ ok: false, response: docverError(BEARER_DENIED, 401) }) as const; - //const secret = process.env.DOCVER_TOKEN_SECRET?.trim(); - const secret = - "487023dd1c1c4d6a07475e96b1cc300fec1e68ac9c2dd7d5ac9b1bf75da1053e"; - //if (secret === undefined || secret === "") return denied(); + const secret = process.env.DOCVER_TOKEN_SECRET?.trim(); + // const secret = + // "487023dd1c1c4d6a07475e96b1cc300fec1e68ac9c2dd7d5ac9b1bf75da1053e"; + if (secret === undefined || secret === "") return denied(); const header = request.headers.get("authorization"); if (header === null) return denied(); const match = /^Bearer\s+(\S+)$/i.exec(header); From a3d14fd58dc09c55a51e7f523f52c6966ea62761 Mon Sep 17 00:00:00 2001 From: oom Date: Tue, 27 Oct 2026 01:47:08 +0700 Subject: [PATCH 14/21] Add generate API, Prototype for developer, Clean data --- app/api/template/download-docx/route.ts | 108 +++++ app/api/template/extract/route.ts | 46 +- app/api/template/generate/route.ts | 101 +++++ app/api/template/lib-tmpl.ts | 518 ----------------------- app/api/template/render/route.ts | 124 ++---- app/api/template/test/T2.json | 29 -- app/api/template/test/T3.docx | Bin 152943 -> 0 bytes app/api/template/test/Test.docx | Bin 158334 -> 0 bytes app/api/template/test/make-protected.mjs | 298 ------------- app/api/template/test/note.md | 127 ------ app/api/template/test/output-T3.docx | Bin 154797 -> 0 bytes app/api/template/test/output.docx | Bin 108079 -> 0 bytes app/api/template/test/test.http | 43 -- app/tmpl/page.tsx | 100 +++++ components/utils/DependentDropdown.tsx | 91 ++++ lib/lib-utils.ts | 4 +- lib/openapi.ts | 504 +++++++++++++++++++--- middleware.ts | 2 +- public/tmpl/T12_FD155_3s-long.json | 2 +- public/tmpl/T12_b2_IN.json | 2 +- public/tmpl/T12_b2_IN.out.json | 51 --- public/tmpl/TF_no_warning_P2.json | 2 +- public/tmpl/TS_P2_IN.json | 2 +- public/tmpl/T_X.docx | Bin 157789 -> 157535 bytes public/tmpl/T_no_warning.json | 4 +- public/tmpl/index.html | 20 - 26 files changed, 911 insertions(+), 1267 deletions(-) create mode 100644 app/api/template/download-docx/route.ts create mode 100644 app/api/template/generate/route.ts delete mode 100644 app/api/template/lib-tmpl.ts delete mode 100644 app/api/template/test/T2.json delete mode 100644 app/api/template/test/T3.docx delete mode 100644 app/api/template/test/Test.docx delete mode 100644 app/api/template/test/make-protected.mjs delete mode 100644 app/api/template/test/note.md delete mode 100644 app/api/template/test/output-T3.docx delete mode 100644 app/api/template/test/output.docx delete mode 100644 app/api/template/test/test.http create mode 100644 app/tmpl/page.tsx create mode 100644 components/utils/DependentDropdown.tsx delete mode 100644 public/tmpl/T12_b2_IN.out.json delete mode 100644 public/tmpl/index.html diff --git a/app/api/template/download-docx/route.ts b/app/api/template/download-docx/route.ts new file mode 100644 index 0000000..13f8725 --- /dev/null +++ b/app/api/template/download-docx/route.ts @@ -0,0 +1,108 @@ +// ตัวอย่างรองรับค่าจาก Frontend ที่เป็น Vanilla JavaScript และ Classic Framework +// โดย ผ่าน Form หรือ การกดลิงค์(GET) เปลี่ยนหน้าเพื่อดาว์นโหลดไฟล์ +// จะมีข้อมูล info.ticket_data ที่สมบูรณ์แล้ว({fid,tid,uid,uname})แล้วเเรียก /api/template/generate เพื่อสร้างเอกสาร docx +import { NextRequest, NextResponse } from 'next/server'; +import path from 'path'; +import fs from 'fs/promises'; + +const DOCEDIT_BASE_URL = process.env.DOCEDIT_BASE_URL??""; +const EXTERNAL_API_KEY = process.env.EXTERNAL_API_KEY??""; +const TMPL_PATH = process.env.TMPL_PATH??""; +if (!DOCEDIT_BASE_URL||!EXTERNAL_API_KEY||!TMPL_PATH) { + console.error('Missing variable DOCEDIT_BASE_URL, EXTERNAL_API_KEY or TMPL_PATH') +} + + +async function processRequest(fid: string,tid:string,uid:string,uname:string):Promise> +{ + // console.log("processRequest get:",fid,tid,uid,uname) + // console.log(process.cwd()) + const filePath = path.join(TMPL_PATH,`${fid}.json`); + try { + if (!fid||!tid||!uid||!uname) { + return NextResponse.json({ error: 'Missing fid, tid, uid or uname' }, { status: 400 }); + } + + // อ่านไฟล์ JSON จำลองการอ่านจากฐานข้อมูล + const rawJson = await fs.readFile(filePath, 'utf-8'); + const jsonData = JSON.parse(rawJson); + if (!jsonData || typeof jsonData !== 'object' ) { + return NextResponse.json({error:'Invalid data format'}, { status: 400 }); + } + + //!! ตรวจสอบสิทธิ์ผู้ใช้ uid ว่ามีสิทธิ์เข้าถึงไฟล์ fid หรือไม่เพื่อกันการการยิง API ตรงๆ ไม่ผ่าน Frontend!! + + // ข้อมูลจำเป็นต้องมี prop นี้เสมอถ้าไม่มีให้สร้าง jsonData.info.ticket_data + if(!Object.hasOwn(jsonData, 'info')) + jsonData['info']={} + jsonData.info['ticket_data'] = JSON.stringify({fid,tid,uid,uname}) + // sign เองตรงนี้ให้อยู่ในรูปแบบ data.info={ticket_data,HmacSha256,Ed25519} หรือว่างไว้ให้ docedit สร้างให้ + + //ยิงไปหาระบบ Docedit เพื่อเอา Stream + // console.log(`fetch ${DOCEDIT_BASE_URL}/api/template/generate`) + const docxResponse = await fetch(`${DOCEDIT_BASE_URL}/api/template/generate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-api-key': EXTERNAL_API_KEY, + }, + body: JSON.stringify(jsonData), + }); + + // เช็ค Content-Type และ Status ก่อน + const contentType = docxResponse.headers.get('content-type') || ''; + + if (!docxResponse.ok || contentType.includes('text/html')) { + const errorText = await docxResponse.text(); + console.error('download-docx:Invalid docx Response:', errorText); + + return NextResponse.json( + {error:`Error from docedit generate: Expected docx stream but received ${contentType} (Status ${docxResponse.status})`}, + { status: 502 } + ); + } + + // 2. ดึง ReadableStream โดยตรงจาก Response ของระบบ + const stream = docxResponse.body; + + // 3. ส่ง Binary Stream กลับไปยัง Browser พร้อม Header กำหนดประเภทไฟล์ + const download_file = `${fid}_${uid}_xxx` + return new NextResponse(stream as unknown as BodyInit, { + status: 200, + headers: { + 'Content-Type': + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'Content-Disposition': `attachment; filename="${encodeURIComponent(download_file)}.docx"`, + 'Cache-Control': 'no-store, max-age=0', + }, + }); + } catch (error:any) { + if ( error.code === 'ENOENT') { + return NextResponse.json( + { error: `Data not found: ${filePath}`}, + { status: 404 } + ); + } + console.error(error) + return NextResponse.json({error:'Internal Server Error'}, { status: 500 }); + } +} + +export async function GET(request: NextRequest) +{ + const searchParams = request.nextUrl.searchParams; + const fid = searchParams.get('fid') || ''; + const tid = searchParams.get('tid') || ''; + const uid = searchParams.get('uid') || ''; + const uname = searchParams.get('uname') || ''; + return await processRequest(fid,tid,uid,uname) +} + +export async function POST(request: NextRequest) { + const formData = await request.formData(); + const fid = String(formData.get('fid')) + const tid = String(formData.get('tid')) + const uid = String(formData.get('uid')) + const uname = String(formData.get('uname')) + return await processRequest(fid,tid,uid,uname) +} \ No newline at end of file diff --git a/app/api/template/extract/route.ts b/app/api/template/extract/route.ts index e723f35..12108b7 100644 --- a/app/api/template/extract/route.ts +++ b/app/api/template/extract/route.ts @@ -1,8 +1,7 @@ import { NextResponse } from "next/server"; import { extractContentControls, extractBookmark, templateContentControls, setCustomDocProps, getDocProps,templateDynamicContents } from "@/lib/lib-tmpl"; -import { verifyDataEd25519, verifyHmacSha256 } from "@/lib/lib-utils"; - -export const runtime = "nodejs"; +import { verifyEd25519, verifyHmacSha256 } from "@/lib/lib-utils"; +// export const runtime = "nodejs"; const DOCX_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; @@ -15,27 +14,14 @@ export async function POST(request: Request) { try { const formData = await request.formData(); const file = formData.get("file"); - const TEST_SECRET = String(formData.get("secret"))??""; - const TEST_PUBLIC_KEY_HEX = String(formData.get("public_key"))??""; + const TEST_SECRET = String(formData.get("secret") ?? ""); + const TEST_PUBLIC_KEY_HEX = String(formData.get("public_key") ?? ""); - - if (!(file instanceof File)) { - return NextResponse.json( - { - success: false, - error: 'Missing DOCX file in field "file".', - }, - { status: 400 }, - ); - } - - if ( - file.type !== DOCX_CONTENT_TYPE && - !file.name.toLowerCase().endsWith(".docx") + if (!(file instanceof File) || + (file.type !== DOCX_CONTENT_TYPE && !file.name.toLowerCase().endsWith(".docx") ) ) { return NextResponse.json( { - success: false, error: 'Field "file" must be a .docx file.', }, { status: 400 }, @@ -45,7 +31,6 @@ export async function POST(request: Request) { if (file.size > MAX_UPLOAD_BYTES) { return NextResponse.json( { - success: false, error: 'Field "file" exceeds the 100MB size limit.', }, { status: 400 }, @@ -62,35 +47,30 @@ export async function POST(request: Request) { const bookmark = extractBookmark(buffer) const properties = getDocProps(buffer) let verify: Record ={} - const ticket_data = String(properties?.custom?.ticket_data) + const ticket_data = properties?.custom?.ticket_data const HmacSha256 = String(properties?.custom?.HmacSha256) const Ed25519 = String(properties?.custom?.Ed25519) - - if(ticket_data){ + + if(ticket_data && typeof ticket_data === 'string'){ const ticketDataObj= JSON.parse(ticket_data) const unix_timestamp = Number(ticketDataObj.exp)*1000 if(unix_timestamp){ verify['exp'] = new Date(unix_timestamp).toLocaleString("th-TH", { timeZone: "Asia/Bangkok" }) } if(TEST_SECRET && HmacSha256){ - verify['verifyHmacSha256'] = verifyHmacSha256(ticket_data,TEST_SECRET,HmacSha256) + verify['HmacSha256'] = verifyHmacSha256(ticket_data,TEST_SECRET,HmacSha256) } if(TEST_PUBLIC_KEY_HEX&& Ed25519){ - verify["Ed25519"]=verifyDataEd25519(ticket_data,Ed25519,TEST_PUBLIC_KEY_HEX) + verify["Ed25519"]=verifyEd25519(ticket_data,Ed25519,TEST_PUBLIC_KEY_HEX) } } - let returnData : Record = { - success: true, - cc,bookmark,properties,verify - } - + + let returnData : Record = {cc,bookmark,properties,verify} return NextResponse.json(returnData); } catch (error) { console.error(error); - return NextResponse.json( { - success: false, error: error instanceof Error ? error.message diff --git a/app/api/template/generate/route.ts b/app/api/template/generate/route.ts new file mode 100644 index 0000000..d5394d2 --- /dev/null +++ b/app/api/template/generate/route.ts @@ -0,0 +1,101 @@ +// สร้าง docx จาก json +// - json.info.ticket_data จะมี tid ให้โหลดไฟล์ template นี้มาใช้ +// - สร้าง signature/hash จาก json.info.ticket_data + +import { NextRequest, NextResponse } from 'next/server'; +import {readFile} from 'fs/promises'; +import path from 'path'; +import { applyTemplate } from '../render/route' +import { DocumentTemplate, templateContentControls, setCustomDocProps, templateDynamicContents } from "@/lib/lib-tmpl"; +const EXTERNAL_API_KEY = process.env.EXTERNAL_API_KEY || 'secret-api-key-1234'; +const TMPL_PATH = process.env.TMPL_PATH || '' +const PRIVATE_KEY_HEX = process.env.PRIVATE_KEY_HEX ||'' +const SECRET = process.env.SECRET || '' + +if (EXTERNAL_API_KEY || !TMPL_PATH || !PRIVATE_KEY_HEX || !SECRET) + console.log("api/template/generate: require EXTERNAL_API_KEY, TMPL_PATH and PRIVATE_KEY_HEX") + +export async function POST(request: NextRequest) { + // 1. ตรวจสอบ API Key จาก HTTP Header (x-api-key) + const apiKey = request.headers.get('x-api-key'); + + if (!apiKey || apiKey !== EXTERNAL_API_KEY) { + return NextResponse.json( + { error: 'Unauthorized: Invalid API Key' }, + { status: 401 } + ); + } + + try { + // รับข้อมูล JSON Payload + const templateData:DocumentTemplate = (await request.json()); + console.log(templateData) + // ต้องมี templateData.info.ticket_data เป็น json string เสมอ + if (!templateData || !templateData.info || + !templateData.info.ticket_data || + typeof templateData.info.ticket_data !== "string" + ) { + return NextResponse.json( + { + error: "template/generate json.info.ticket_data not avaliable", + }, + { status: 400 }, + ); + } + const ticketObj = JSON.parse(templateData.info.ticket_data) + const tid = String(ticketObj.tid) + const fid = String(ticketObj.fid) + const uid = String(ticketObj.uid) + if(!tid||!fid||!uid) + { + return NextResponse.json( + { + error: "template/generate require json.info.ticket_data.(fid|tid|uid)", + }, + { status: 400 }, + ); + } + + + //เปิดไฟล์ template + const tmplPath = path.join(TMPL_PATH, `${tid}.docx`); + let templateBuffer: Buffer = await readFile(tmplPath) + + const output = await applyTemplate(templateBuffer, templateData, SECRET, PRIVATE_KEY_HEX); + + // สร้าง ReadableStream ของไฟล์ .docx + // ในโค้ดจริง ส่วนนี้จะดึง Binary Buffer จาก Library เช่น docx, docxtemplater หรืออ่านจาก File System + // const encoder = new TextEncoder(); + + const docxStream = new ReadableStream({ + start(controller) { + controller.enqueue(output); + controller.close(); + }, + }); + + // 4. ส่ง Return เป็น Response พร้อม Stream และ Headers กำหนด MIME type ของ MS Word + const dlFileName = encodeURIComponent(`${tid}_${fid}_${uid}XX`) + return new NextResponse(docxStream, { + status: 200, + headers: { + 'Content-Type': + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'Content-Disposition': `attachment; filename="${dlFileName}.docx"`, + 'Cache-Control': 'no-store, max-age=0', + }, + }); + } catch (error:any) { + if ( error.code === 'ENOENT') { + return NextResponse.json( + { error: 'NotFound: Invalid template ID or incorrect storage path'}, + { status: 404 } + ); + } + console.error('Error generating docx:', error); + return NextResponse.json( + { error: 'Failed to generate document payload'}, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/app/api/template/lib-tmpl.ts b/app/api/template/lib-tmpl.ts deleted file mode 100644 index 2c15f52..0000000 --- a/app/api/template/lib-tmpl.ts +++ /dev/null @@ -1,518 +0,0 @@ -import PizzaZip from "pizzip"; -import { - DOMParser, - XMLSerializer, - type Document as XmlDocument, - type Element as XmlElement, - type Node as XmlNode, - type Attr as XmlAttr, -} from "@xmldom/xmldom"; -import xpath from "xpath"; - -export interface ExtractedData { - [tagName: string]: string; -} - -const W_NS = - "http://schemas.openxmlformats.org/wordprocessingml/2006/main"; - -const asXpathNode = (node: XmlNode): globalThis.Node => - node as unknown as globalThis.Node; - -function createSelector() { - return xpath.useNamespaces({ w: W_NS }); -} - -/** - * อ่าน Content Control (w:sdt) จาก DOCX Buffer - * ปัจจุบันอ่านจาก word/document.xml ตาม logic ของโค้ดต้นฉบับ - */ -export function extractContentControls( - docxBuffer: Buffer, -): ExtractedData { - const zip = new PizzaZip(docxBuffer); - const docXmlFile = zip.file("word/document.xml"); - - if (!docXmlFile) { - throw new Error("Invalid docx file: word/document.xml not found."); - } - - const parser = new DOMParser(); - const doc = parser.parseFromString(docXmlFile.asText(), "text/xml"); - const select = createSelector(); - const result: ExtractedData = {}; - - const sdtNodes = select( - "//w:sdt", - asXpathNode(doc), - ) as unknown as XmlElement[]; - - sdtNodes.forEach((sdt) => { - const tagAttr = select( - "./w:sdtPr/w:tag/@w:val", - asXpathNode(sdt), - true, - ) as unknown as XmlAttr | undefined; - - const tagName = tagAttr?.value; - - if (!tagName) return; - - const sdtContent = select( - "./w:sdtContent", - asXpathNode(sdt), - true, - ) as unknown as XmlElement | undefined; - - if (!sdtContent) { - result[tagName] = ""; - return; - } - - const nodes = select( - ".//w:p | .//w:t | .//w:tab | .//w:br | .//w:noBreakHyphen | .//w:softHyphen | .//w:sym", - asXpathNode(sdtContent), - ) as unknown as XmlElement[]; - - let extractedText = ""; - - nodes.forEach((node) => { - switch (node.nodeName) { - case "w:p": - if ( - extractedText.length > 0 && - !extractedText.endsWith("\n") - ) { - extractedText += "\n"; - } - break; - - case "w:t": - extractedText += node.textContent || ""; - break; - - case "w:tab": - extractedText += "\t"; - break; - - case "w:br": - extractedText += "\n"; - break; - - case "w:noBreakHyphen": - extractedText += "\u2011"; - break; - - case "w:softHyphen": - extractedText += "\u00ad"; - break; - - case "w:sym": { - const charAttr = node.getAttribute("w:char"); - if (charAttr) { - const charCode = Number.parseInt(charAttr, 16); - extractedText += String.fromCharCode(charCode); - } - break; - } - } - }); - - result[tagName] = extractedText; - }); - - return result; -} - -type SdtControlType = "plain" | "rich" | "other"; -type SdtStructure = "block" | "inline"; - -function detectSdtControlType( - sdt: XmlElement, - select: xpath.XPathSelect, -): { type: SdtControlType; multiLine: boolean } { - const sdtPr = select( - "./w:sdtPr", - asXpathNode(sdt), - true, - ) as unknown as XmlElement | undefined; - - if (!sdtPr) { - return { type: "other", multiLine: false }; - } - - const textEl = select( - "./w:text", - asXpathNode(sdtPr), - true, - ) as unknown as XmlElement | undefined; - - if (textEl) { - const multiLineAttr = select( - "./@w:multiLine", - asXpathNode(textEl), - true, - ) as unknown as XmlAttr | undefined; - - return { - type: "plain", - multiLine: - multiLineAttr?.value === "1" || - multiLineAttr?.value === "true", - }; - } - - const richTextEl = select( - "./w:richText", - asXpathNode(sdtPr), - true, - ) as unknown as XmlElement | undefined; - - if (richTextEl) { - return { type: "rich", multiLine: true }; - } - - const otherTypeTags = [ - "w:date", - "w:dropDownList", - "w:comboBox", - "w:checkbox", - "w:citation", - "w:group", - "w:docPartObj", - "w:docPartList", - ]; - - for (const tag of otherTypeTags) { - const found = select( - `./${tag}`, - asXpathNode(sdtPr), - true, - ) as unknown as XmlElement | undefined; - - if (found) { - return { type: "other", multiLine: false }; - } - } - - return { type: "rich", multiLine: true }; -} - -function detectSdtStructure( - sdtContent: XmlElement, - select: xpath.XPathSelect, -): SdtStructure { - const directP = select( - "./w:p", - asXpathNode(sdtContent), - ) as unknown as XmlElement[]; - - return directP.length > 0 ? "block" : "inline"; -} - -function buildRunChildren( - doc: XmlDocument, - text: string, -): XmlElement[] { - const nodes: XmlElement[] = []; - const tokens = text.split(/(\t|\n)/); - - tokens.forEach((token) => { - if (token === "") return; - - if (token === "\t") { - nodes.push(doc.createElementNS(W_NS, "w:tab")); - } else if (token === "\n") { - nodes.push(doc.createElementNS(W_NS, "w:br")); - } else { - const t = doc.createElementNS(W_NS, "w:t"); - t.setAttribute("xml:space", "preserve"); - t.textContent = token; - nodes.push(t); - } - }); - - if (nodes.length === 0) { - const t = doc.createElementNS(W_NS, "w:t"); - t.setAttribute("xml:space", "preserve"); - t.textContent = ""; - nodes.push(t); - } - - return nodes; -} - -function buildRun( - doc: XmlDocument, - text: string, - rPr?: XmlElement, -): XmlElement { - const run = doc.createElementNS(W_NS, "w:r"); - - if (rPr) { - run.appendChild(rPr.cloneNode(true)); - } - - buildRunChildren(doc, text).forEach((node) => - run.appendChild(node), - ); - - return run; -} - -function insertInlineContent( - doc: XmlDocument, - sdtContent: XmlElement, - select: xpath.XPathSelect, - text: string, - allowLineBreak: boolean, -): void { - const safeText = allowLineBreak - ? text - : text.replace(/\n/g, " "); - - const existingRuns = select( - "./w:r", - asXpathNode(sdtContent), - ) as unknown as XmlElement[]; - - const firstRPr = - existingRuns.length > 0 - ? (select( - "./w:rPr", - asXpathNode(existingRuns[0]), - true, - ) as unknown as XmlElement | undefined) - : undefined; - - existingRuns.forEach((r) => { - if (r.parentNode) r.parentNode.removeChild(r); - }); - - sdtContent.appendChild( - buildRun(doc, safeText, firstRPr), - ); -} - -function insertBlockContent( - doc: XmlDocument, - sdtContent: XmlElement, - select: xpath.XPathSelect, - text: string, - allowMultiParagraph: boolean, -): void { - const allParagraphs = select( - "./w:p", - asXpathNode(sdtContent), - ) as unknown as XmlElement[]; - - if (allParagraphs.length === 0) { - // ป้องกัน template ที่มีโครงสร้างผิดปกติ - const p = doc.createElementNS(W_NS, "w:p"); - sdtContent.appendChild(p); - allParagraphs.push(p); - } - - const firstP = allParagraphs[0]; - - const firstPPr = select( - "./w:pPr", - asXpathNode(firstP), - true, - ) as unknown as XmlElement | undefined; - - const firstRun = select( - "./w:r", - asXpathNode(firstP), - true, - ) as unknown as XmlElement | undefined; - - const firstRPr = firstRun - ? (select( - "./w:rPr", - asXpathNode(firstRun), - true, - ) as unknown as XmlElement | undefined) - : undefined; - - allParagraphs.forEach((p) => { - if (p !== firstP && p.parentNode) { - p.parentNode.removeChild(p); - } - }); - - const existingRuns = select( - "./w:r", - asXpathNode(firstP), - ) as unknown as XmlElement[]; - - existingRuns.forEach((r) => { - if (r.parentNode) r.parentNode.removeChild(r); - }); - - if (!allowMultiParagraph) { - firstP.appendChild(buildRun(doc, text, firstRPr)); - return; - } - - text.split("\n").forEach((line, index) => { - let targetP: XmlElement; - - if (index === 0) { - targetP = firstP; - } else { - targetP = doc.createElementNS(W_NS, "w:p"); - - if (firstPPr) { - targetP.appendChild(firstPPr.cloneNode(true)); - } - - sdtContent.appendChild(targetP); - } - - const run = doc.createElementNS(W_NS, "w:r"); - - if (firstRPr) { - run.appendChild(firstRPr.cloneNode(true)); - } - - buildRunChildren(doc, line).forEach((node) => - run.appendChild(node), - ); - - targetP.appendChild(run); - }); -} - -function insertFallbackContent( - sdtContent: XmlElement, - select: xpath.XPathSelect, - text: string, -): void { - const textNodes = select( - ".//w:t", - asXpathNode(sdtContent), - ) as unknown as XmlElement[]; - - if (textNodes.length === 0) return; - - textNodes[0].textContent = text; - - for (let i = 1; i < textNodes.length; i++) { - textNodes[i].textContent = ""; - } -} - -export type TemplateData = Record; - -function normalizeValue(value: unknown): string { - if (value === null || value === undefined) { - return ""; - } - - if (typeof value === "string") { - return value; - } - - if ( - typeof value === "number" || - typeof value === "boolean" || - typeof value === "bigint" - ) { - return String(value); - } - - // รองรับ JSON ที่ส่ง array/object มา แต่ Content Control ต้องการ text - return JSON.stringify(value); -} - -/** - * นำ JSON data ไปแทนค่า Content Control ใน DOCX template - * คืนค่าเป็น Buffer ของ DOCX ที่สร้างเสร็จแล้ว - */ -export function templateContentControls( - templateBuffer: Buffer, - data: TemplateData, -): Buffer { - const zip = new PizzaZip(templateBuffer); - const docXmlFile = zip.file("word/document.xml"); - - if (!docXmlFile) { - throw new Error("Invalid docx file: word/document.xml not found."); - } - - const parser = new DOMParser(); - const doc = parser.parseFromString(docXmlFile.asText(), "text/xml"); - const select = createSelector(); - - const sdtNodes = select( - "//w:sdt", - asXpathNode(doc), - ) as unknown as XmlElement[]; - - sdtNodes.forEach((sdt) => { - const tagAttr = select( - "./w:sdtPr/w:tag/@w:val", - asXpathNode(sdt), - true, - ) as unknown as XmlAttr | undefined; - - const tagName = tagAttr?.value; - - if (!tagName || !Object.prototype.hasOwnProperty.call(data, tagName)) { - return; - } - - const newValue = normalizeValue(data[tagName]); - - const sdtContent = select( - "./w:sdtContent", - asXpathNode(sdt), - true, - ) as unknown as XmlElement | undefined; - - if (!sdtContent) return; - - const { type: controlType, multiLine } = - detectSdtControlType(sdt, select); - - const structure = detectSdtStructure(sdtContent, select); - - if (controlType === "other") { - insertFallbackContent(sdtContent, select, newValue); - return; - } - - if (structure === "inline") { - const allowLineBreak = - controlType === "plain" ? multiLine : true; - - insertInlineContent( - doc, - sdtContent, - select, - newValue, - allowLineBreak, - ); - } else { - insertBlockContent( - doc, - sdtContent, - select, - newValue, - controlType === "rich", - ); - } - }); - - const serializer = new XMLSerializer(); - zip.file( - "word/document.xml", - serializer.serializeToString(doc), - ); - - return zip.generate({ - type: "nodebuffer", - compression: "DEFLATE", - }); -} diff --git a/app/api/template/render/route.ts b/app/api/template/render/route.ts index 8f04c9f..9bb8d08 100644 --- a/app/api/template/render/route.ts +++ b/app/api/template/render/route.ts @@ -1,73 +1,49 @@ import { NextResponse } from "next/server"; -import { DocumentTemplate, templateContentControls, setCustomDocProps, getDocProps, templateDynamicContents } from "@/lib/lib-tmpl"; -import { generateHmacSha256, verifyHmacSha256, signDataEd25519, verifyDataEd25519, sortObjectKeys } from '@/lib/lib-utils'; - +import { DocumentTemplate, templateContentControls, setCustomDocProps, templateDynamicContents } from "@/lib/lib-tmpl"; +import { generateHmacSha256, signEd25519, sortObjectKeys } from '@/lib/lib-utils'; export const runtime = "nodejs"; const DOCX_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; - // cap เดียวกับ N4 (app/actions/files.ts) — route นี้เปิดกว้างให้ภายนอกยิงได้ // ต้องเช็คขนาดก่อนอ่านเข้าหน่วยความจำ กัน DoS (bodySizeLimit ครอบเฉพาะ Server Actions) const MAX_UPLOAD_BYTES = 100 * 1024 * 1024; // 100MB +const TICKET_SEC = Number(process.env.TICKET_SEC||'300') export async function POST(request: Request) { try { const formData = await request.formData(); - const template = formData.get("template"); const dataFile = formData.get("data"); const dataJson = formData.get("dataJson"); - const TEST_SECRET = (typeof formData.get("secret") === "string") ? - String(formData.get("secret")) : "super_secret"; - const TEST_PRIVATE_KEY_HEX = (typeof formData.get("private_key") === "string") ? - String(formData.get("private_key")) : "8f2077b57b39399b6a7828f35da03a63ade8e3a36a6bf437b091a8bdb3d18ee1"; + const TEST_SECRET = String(formData.get("secret")||"") + const TEST_PRIVATE_KEY_HEX = String(formData.get("private_key")||"") - console.log(`secret:${TEST_SECRET}`) - console.log(`private:${TEST_PRIVATE_KEY_HEX}`) - // ----------------------------- // ตรวจสอบ Template - // ----------------------------- - if (!(template instanceof File)) { return NextResponse.json( { - success: false, error: 'Missing DOCX file in field "template".', }, { status: 400 }, ); } - if ( - template.type !== DOCX_CONTENT_TYPE && - !template.name.toLowerCase().endsWith(".docx") + if (template.type !== DOCX_CONTENT_TYPE && + !template.name.toLowerCase().endsWith(".docx") && + template.size > MAX_UPLOAD_BYTES ) { return NextResponse.json( { - success: false, - error: 'Field "template" must be a .docx file.', + error: 'Field "template" must be a .docx file and not exceeds the 100MB size limit.', }, { status: 400 }, ); } - if (template.size > MAX_UPLOAD_BYTES) { - return NextResponse.json( - { - success: false, - error: 'Field "template" exceeds the 100MB size limit.', - }, - { status: 400 }, - ); - } - // ----------------------------- // อ่าน JSON - // ----------------------------- - let templateData: DocumentTemplate; - // JSON ผิด syntax เป็นความผิดของฝั่งผู้เรียก — คืน 400 ไม่ใช่ 500 // (catch กลางท้ายฟังก์ชันสงวนไว้ให้ error ของระบบเท่านั้น) try { @@ -78,13 +54,11 @@ export async function POST(request: Request) { if (dataFile.size > MAX_UPLOAD_BYTES) { return NextResponse.json( { - success: false, error: 'Field "data" exceeds the 100MB size limit.', }, { status: 400 }, ); } - // กรณี upload data.json const text = await dataFile.text(); @@ -92,7 +66,6 @@ export async function POST(request: Request) { } else { return NextResponse.json( { - success: false, error: 'Missing JSON data. Send either "data" file or "dataJson" field.', }, @@ -102,14 +75,12 @@ export async function POST(request: Request) { } catch (error) { return NextResponse.json( { - success: false, error: `Invalid JSON data (${error instanceof Error ? error.message : "parse error" }).`, }, { status: 400 }, ); } - // JSON ต้องเป็น object if ( templateData === null || @@ -118,7 +89,6 @@ export async function POST(request: Request) { ) { return NextResponse.json( { - success: false, error: "JSON data must be an object.", }, { status: 400 }, @@ -132,34 +102,64 @@ export async function POST(request: Request) { let templateBuffer: Buffer = Buffer.from( await template.arrayBuffer(), ); + const output = await applyTemplate(templateBuffer,templateData,TEST_SECRET,TEST_PRIVATE_KEY_HEX); + // ----------------------------- + // ส่ง DOCX กลับ + // ----------------------------- + if(templateData?.info?.ticket_data) + return new NextResponse(output as BodyInit, { + status: 200, + headers: { + "Content-Type": DOCX_CONTENT_TYPE, + "Content-Disposition": + 'attachment; filename="generated-document.docx"', + "Content-Length": String(output.length), + }, + }); + } catch (error) { + console.error(error); + + return NextResponse.json( + { + + error: + error instanceof Error + ? error.message + : "Internal Server Error", + }, + { status: 500 }, + ); + } +} + +export async function applyTemplate(templateBuffer:Buffer,templateData:DocumentTemplate, + secret:string,private_key:string):Promise> +{ //Calculate hash const originalTicket_data = templateData?.info?.ticket_data //expect valid json string // console.log("templateData",templateData?.info?.ticket_data) if (originalTicket_data && typeof originalTicket_data === 'string') { try { - - const ttlSeconds: number = 300 const originalTicketObj = JSON.parse(originalTicket_data)//sort and add expire to ticket data if (originalTicketObj && typeof originalTicketObj === 'object') { console.log("Found valid ticket data: hash it") // คำนวณค่า expire (ปัจจุบัน + จำนวนวินาทีที่ต้องการให้หมดอายุ) // หาร 1000 เพราะต้องการหน่วยวินาที (sec) ไม่ใช่มิลลิวินาที (ms) - const expireTimestamp = Math.floor(Date.now() / 1000) + ttlSeconds; + const expireTimestamp = Math.floor(Date.now() / 1000) + TICKET_SEC; const fullObj = { ...originalTicketObj, exp: expireTimestamp }; const sortedObj = sortObjectKeys(fullObj); // แปลงกลับเป็น JSON string เพื่อนำไปใช้ส่งและทำ Hash const ticket_data = JSON.stringify(sortedObj); // console.log("check secret:",TEST_SECRET,TEST_PRIVATE_KEY_HEX,ticket_data) - const HmacSha256 = generateHmacSha256(ticket_data, TEST_SECRET) + const HmacSha256 = generateHmacSha256(ticket_data, secret) - const Ed25519 = signDataEd25519(ticket_data, TEST_PRIVATE_KEY_HEX) + const Ed25519 = signEd25519(ticket_data, private_key) templateBuffer = setCustomDocProps(templateBuffer, { ticket_data, HmacSha256, Ed25519 }) } - } catch (error) { + } catch (error) { //just skip console.error("Set custom property fail",error); - } } @@ -173,36 +173,6 @@ export async function POST(request: Request) { console.log("Apply Content Control") templateBuffer = templateContentControls(templateBuffer, templateData.cc); } + return templateBuffer - const output = templateBuffer; - - // ----------------------------- - // ส่ง DOCX กลับ - // ----------------------------- - - return new NextResponse(output as BodyInit, { - status: 200, - headers: { - "Content-Type": DOCX_CONTENT_TYPE, - - "Content-Disposition": - 'attachment; filename="generated-document.docx"', - - "Content-Length": String(output.length), - }, - }); - } catch (error) { - console.error(error); - - return NextResponse.json( - { - success: false, - error: - error instanceof Error - ? error.message - : "Internal Server Error", - }, - { status: 500 }, - ); - } } \ No newline at end of file diff --git a/app/api/template/test/T2.json b/app/api/template/test/T2.json deleted file mode 100644 index 799c36f..0000000 --- a/app/api/template/test/T2.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "notname": "คำสั่งเรียก ให้ทำคำให้การ", - "nottype": "(ต. ๑)", - "blackno": "คดีหมายเลขดำที่", - "blackyear": "๑๐/๒๕๖๙", - "redno": "คดีหมายเลขแดงที่", - "redyear": "", - "court": "ศาลปกครองสุพรรณบุรี", - "curdate": "วันที่ ๗ เดือน สิงหาคม พุทธศักราช ๒๕๖๙", - "accuser": "นายธัญวิสิษฐ์ ศรแก้ว", - "acc1": "ผู้ฟ้องคดี", - "accused": "เจ้าคุณมด ที่ ๑ กับพวกรวม ๓ คน", - "acc2": "ผู้ถูกฟ้องคดี", - "nottitle": "คำสั่งถึง", - "commto": "เจ้าคุณมด", - "groupname": "ผู้ถูกฟ้องคดีที่ ๑", - "detail1": "ด้วยผู้ฟ้องคดีได้ยื่นฟ้องผู้ถูกฟ้องคดีต่อศาล มีข้อความตามที่ปรากฏ ในระบบงานคดีปกครองอิเล็กทรอนิกส์\nฉะนั้น ให้ ท่านทำคำให้การแก้คำฟ้อง พร้อมด้วยพยานหลักฐาน ๑ ชุด ต่อศาลภายใน ๓๐ วัน นับแต่วันที่ได้รับคำสั่งนี้", - "pname": "นางนพเก้า เถื่อนชัย", - "courtname": "ศาลปกครองสุพรรณบุรี", - "courtno": "๓๓๓ หมู่ ๖ ตำบลสนามชัย อำเภอเมืองสุพรรณบุรี", - "courttam": "จังหวัดสุพรรณบุรี ๗๒๐๐๐", - "courttel": "กลุ่มออกหมาย โทรศัพท์ ๐๓๕ ๔๓๒ ๓๓๓ ต่อ ๑๐๐๑,๒๒๐๓", - "courttel2": "สอบถามติดตามคดี ขอคำปรึกษาแนะนำ โทรสายด่วน “๑๓๕๕”", - "sendto": "เจ้าคุณมด", - "addrno": "เลขที่ - หมู่ที่ - ตรอก/ซอย - ถนน - ตำบล/แขวง - อำเภอ/เขต - จังหวัด - โทรศัพท์ -", - "remarkhead": "", - "remark": "", - "warning": "หากผู้ถูกฟ้องคดีไม่ปฏิบัติตามคำสั่งศาลภายในระยะเวลาที่กำหนด ศาลจะถือว่าผู้ถูกฟ้องคดียอมรับข้อเท็จจริงตามข้อหาของผู้ฟ้องคดี" -} \ No newline at end of file diff --git a/app/api/template/test/T3.docx b/app/api/template/test/T3.docx deleted file mode 100644 index 7573a456d7c4fd07c5925ff5d544b488d407d6d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 152943 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 z z3#zDGJ%5)k)0F;V$CiW77wZ!;&!+Mu5?qVd&Q6L)m}rvO4~sCp`P8Vgw`TZ2*pOn4 zvRL8UAYGElvYX6`;^QwkLnPK$M7>e`HdC8tBM1({dcEpKO1NiWQEV1P=6&ZkS9I`k zZ<&m;CW%h!nX0^tPVB!L;60ZY&E>>%lHqJ$r64e z>hW0uE(LUK;SA!*J(V6AZ}`$-jI700f${U%)to*hoo9bKp(->0cvcqu#>f8LN$*l) zvI;*v6o0tvtlDJgrA>58GT$(iGwd80r)Zv%+UXNx-tJx(-Eh1ml}B>|e9%=gVt3!A zyIo#@PP{(nI0R`nj}-5XH*6See=lYcu5}tAFjV`O~+(Z~IR^wi$1)s@X5=P@0G^3J}wu3tIcyp_TUTgKJuJ zCp!9aUeD&Iv8O$0*OpI)4(k_>tlXHb9bA8W=39=~5?<}cUT41SbgkYT0%JTfY_;ZB zrsAz%q5bdk?ccu-=ba!pfW}A>8O3(T2w9D%#2)T2b~MHl=IYBYPi z`1x$~^8~?x(kvI2_K^e;r}(Z)s>O)y{qfcz0iskcb`E<2DN@pxXIQ@+e47r+g2s#EZ#1*Wyl@(;jhq{jX>Bu$dpUTP*n)X=+6*ybt*H`@I8<3 z!}M;e2${E}LLXOjjz+`o;WFLooAf|5Wsjk1CpJ9CnWFG!l1C9YxorrP!2t_eVW>0R zt#c?=--#^#m?hDWDxtvDBg9eQd+QTALGL-1#H^`Ro0`eJqU3o;#t>{LP2YO{2rtqm zCVd7XzUTJ{1)z7X7!%jj;185#&`-Bk-gpu5sE_^5#yw0QyRo}bG34}gqDthgLEGjP z#_{=2O3&6m5hA3iDTgBj`%@EGOGGrw$Civ8bQ9Wd#`#VJH9h=b&{&WhnKTw#@-Wdc ziDb0n+o{j|Uj$?G=}1pia?>b8tbZ z>N84Q%h}S$A`V1rd}U148=Dn(t6sk<7+}v zJb7^Ix=R-S%}&a;S2BpwEz&@y$F?$Zg2RT3tb?Ti9WSrVvAskoFudjT8;YNCYZZLB zU3g2lHnL$9hP&P`7#F>jdnt86DV@YU)>bliBL0t3mX55ZOv$PyVl-Ra$3ai4CbnjnX! z;*3Cr+_qM_*(WZVd}1g*L1Ou+sgkWZ zGIk|M@Cz?fp?*Sl9s$q8-NwF{r7Nz zTh829TA9vb6<%K#H_1B+FO1l8Eb`qK<+FD`U3xs`o29%*~p7MQmtEP%sSEtb?B+yynCet->-R^!2 ztuJSTiy~WSci$=h=`@IN_@vNdFohlTIWhhiBOZIFej6zsIj2&48wpxO$Z%F1R+w8p zOVuWvllaUYUonU)li|bQcaW~pS0_Drb8<2~EUGchXYXvmn*tZH_xRRUk|IIyf}W+ya8*^{HkYrxHENtKPIFMF1G4q@mfYN3>VBD+sJr9RbU z{q$OLUbxu^Ba1Te{4@PfbLaUlaMLxBmZt$ONGHn2QbYl)JQP0lt$kA8HVCv2Yu-yb7kYH17HQ??mrUfv9<)Lbz z3IO-h2){Xl(+mK|Sl?U|d>$Simb@u^2ZrzibZmk_28J*n2-ejH1pq)giqKFs4(9yk7FIi#K(S;3Oq#f&rdwjC}RX> zebz6|Q%a9+-ei@%n!B8j;$@TRkL_5>VXkoveR=VXsJ#1eWHY<_B-4eBwD!s(QLjQH zNi&<%K}}0#h9A@hvTr{#h4>qN&HPf_UHeK(nH5 zJ~A}5exNnF@+9tQN_~-MQrmKIjX?|N_?UaW-<6(^2!)wRs!czgvccQ(2^@a;opr%E z4o1k{I{j1h6$uHCB<8X2eWuU3+>@nMUArJhs5@SG&f}rHFrJ|P{u8gIu^y?4!LQ5@ z_YrcRa|d^#8J=7pn)VKvb2=;9BenP{oFJt`&(!|idGgX_38`3YJ$L>{9%Txnv*WnE zpk<;S%t58PkTP0#U=GyvA1kun%+)*80d#&)7a4jg;I}6tjC|Uhz%aF2kgH0kQ^RBmNvb>XOB`>Lxq28?IJxL9m zI7&BNokBW$85ITNyPG%~xi=*O3 z@lMZ9GEY0e5GN7{8U43(KY0_l#uPbUWdM&uS$Ld$?>--x{b0cQ#zZiqKs|A*(|sXQ z_w)US_lC;NONXGRWH9J;^|hxyVVJFCaIh~$PFXg@6vxLVA5?|;myAuWwaLOa@|9#?~QN-hXtNNF?PB9 z)VWRdPY{dKftx9jGO`JEPMRm>Tx7Y-PMD6^k?xJ5|Qs(8e_U|Ovu6+r+x$za3^?vDdF`SVCKG(zNhi#)DQ%=@U zDckz;| z2e&Seu+gVDQ?XR3f!vP8h-+0R9Z6EU$t(k8SP~UX)enJ(m7C2q!epps zNaF-Sc|iFf3<;vx&SQ=dh+nOrzOegL9e1o8_WIIhEuT{XSsB49goxAF>UFUsF=4$OC7VJO zV<$gi12+1UWkq3W*hwk4oA^fYMd@vwrxbv)M@mj_sBJ-<^MfESg=@{Hs?#M%TnzIk zQo8Hjc6o*rLsA>Pmo_-J<^c(9his?Ja!A#{ zpBQ&;{IEjfP6FLKomZ^uZxTCNi#ZO5WbmIwz@HGA^p-w7k=cAxXzrUs5Sgpap*U%Q z8@+xPk=~A^$7)xZLE2-hfyZ^W&;id6BRw2KE64hLrwemx7BdPxyz4r!u zwB~TjiCmLx@a@ZDQJ3|vv?p9rxrvdrL~16aS{sV;L8R@JBxYV0Zm=VB*l}El)}pZD z^Is>Iy+0kp*+rD)290?GJc$}$>oQi#6fkOv(T_4qzpuZkTY3%MX4^ugN|U~H`OT)8 z4olU8#VedI9Qp6V%^W)&-zK7bQ&lEdfpZC3!auVw?Ud`*-q7d3U zIyL=E#;f?xb|}eRm!l6e8KZa$UJ^O-3r*(dt1V5TUD=+JS(fQbPK4T%BG~dxrP{Uz@*|sTTq5w@hBUeDq8R6~X=4 zp6gT}>gyt`zABzTYa?$&`h1cQA6i4{>3B)(JbmVm--FoW?$PbxO@FUKH&<7=_Lyw_ z!5w$iczC&~Zly=|*-`1L zQu*lETG_aNs_WjQ_5a+EO6D4_B_)uLlv=#*sbwIka_0FXiSy|e-TuRTa(B8IU5L!h zXHsHB9iO$^E&G2f}Js%$oUmAH~ZJ*b&ED9h~o1djoe#<&#Bg@0}-xoY5DzfG+)7uTF zo$*fQu+2UlNw61&qd!2cM~#fCb1sxcG%4mb^ogKNaPaC{m~eb_h6J!$URvC@^J^}v zxUEC&p+B(rVA&EnB&BloId3Y_9T#&!j!8n5^Y% zPa@l$&o6GKE(_vTrmt(CGQLMx4L62_=$|NDN2*H;K<~d`XFAQS7dDsfz3pviGs#6p z{RaO@=fyjo_8AW5T{`um*v59JZC0Sf7H+(!sS1dbl(UJr$ebKlhFW9rw(!wc=ue#^ zJ6C04aCW{NcZ)hb`;Gq2SL^_67ESAnk_4{>QtsDb-FGiW7;a*-L|H#IJ6(CHKK6@j z8yB~)gdDqo7dehg%(riR_p3TPSn#XhJNlGYruH{(z2>5OOHWQCWCm3fVQY+QF7AXn zov%`u7|?hPy_u(i(@0K1|D{=YXtxulKor=bu$_sgmp7xP81YzAH~b#XS|grVB(c`z zkAy)_aXNJfL|C|ms;v{nxn+H7qhl<;5y&4xFB=O#>*CmS>t$AavXz$Oh#){_0~Xe4 zbz4PUXMD@loV&WRGj(~ZGnSw3IJH%irDV#+!Heb4?M%-C>}0jit)4b(nq60aw!C_# zNz4k%I)kT=EJyh^T#;{HVVLMfbdy*k+}+LKA`g?rMa0@$%hSYSzQ8-V{nv}HKh}|0 zr>>HDw$LU;`pLb{WEISD5tx)je!IwaA=}&gX|HZA?xO12^zH&ryUsa zk+7pEndj9uamcl8y)?zw#m{8KMauFT2JftW6Bm4Spc4NLZMGhqQy_;$=>GabRXx>1 zP8I1yn~4&O6(IqGQ%PeN&(Lf7UmhLFCNRGe`62Z}rTN$4*+$_W-p|*B2i zlcph2U6E+$QmZ&$ z`{tC*%iIwK37BmA-BaP4R3eJd>2BLf*2G-kFpcQP^m$)X{PZD=F}t`ueuUc(ocIF> zO+$^lYEJQ(A2j*phfozTn_jpL0WcpL5bJOK5Mn~!0Z5O-(ZebH7R_)kcK|Rj0Qf;f zJOCF$0bm>v5X|l<2m^p|IAHckxj-0yhP@%Y-*Lc&eTeAM?Hhy4nJWnnHH(4BHD2e=>XmmCn5UvfaW zf0qLa`9T9B1pP@HFZGxlpaVWJ^>FkAYwj~ZbR>bJx&Z(=`~r|1#Q?X{7`(%A;0!>7 zhlhucM}&`0L{3ORNKQ>kL_|tWM@dOdNl8aebaZ`x97q244k0EaBqkvyBOxK9A|WB6 z!rVxxjKP-!VW;mAuw{t;X6=Bg2Ofl=%s{ULyj(xVax>sAW$r9 z99%qn0zy#ve@uh`P}q+|G5~@FfkCmL*tj@&*f1h-Fp(UF#YQ2Dt!m;-$sQOdhC_Af z#(gyoPHNL;7jcQ8_&#-9t_-u4sa4ltNt&D03EUc)=5CrTMQAD7ev1#twVE@n10mDv zT0B|Bi4QKf4SxJuQu};pX2a4wH0es|!}j6XO=)c_kFc}Zx9U1Z=C;TIC=}!t>xd^@ z9Bj!W7T827us{w1*(tHb;x18PIA}KQTM_4UnF@-}P&cD?4VGBt!oe_r%PrZ`4>D0i z>!xWTg+_k($-+P8faezr;Px3J*io6}fHJUQo`K-vB6vWNSKya3T`S=cB|Ds_WeZfOvoA-@Z z!R1fORJmq$Ut(@$L~@pL4L{XiLM6v)UZ7YA+cO)Ty`;oIzoLXtPo3BAwpTuvmGy$4 zc?`gv%r1Ku@!F^p-um{FQ_d<=@mOp4`ZrV4x(DYKqPRF5{FfwG`a~<663Pl<6#Iki ziQ?FbjMB(5a!v&p6@S)dld12K_a&b#xb;>(^Sbhe*W1s#kVg^4rB3mj-jvsAo)y*S z)jiU59b4s?r7#1TKa$+&u-U)Nz~T|&JPYnG#N8@CjnxsYtLrk>#IV@=Hp;wTeRzWZ5THB+a*9iOF5qB2+9o!A ze+ax)4mSlPRLB&_DkEqfk|bnh^$w;L_fIHk?^DB7DTvaj>W5US9(=i{{;nZe*KPJ} za4*GmTRU#2u(0eeX*0407fC3Tu)?W1v!35A^x9SNKC_wk?vwb%DV-{bP9>+zPBX8! z69IGS8QDGwd3ckzR8aGkV=*a@w2xG3cG!O;99zwVr-yU7MpPVBJBJbv?QOsQ_Ew<( zhY)h8>MBA%hjTgIP2=JrFl?p|+9x{e!ok+5==#{!E%Um%x+aoOlKT|vJMWjXQM-I^ zZir8qXFXfNBf+j|yZ+|EC?~l>8dXe$dbnqG6_6ml#aSBJ9#1m8)6oAO=i=r!v(vax zC4BjFU(g7BCUu<$0cenHWA(Y4uL-3JG}OG$kKW8C@7d9J~Rt7nXJ2^9SV-E*5 zEEtI_r#}|o2wV3@=$W$LpSv9}GLM*C?K*V`CbE9 z$_Ljna;0_F@$*a4SVUsau9$4tJis5+AeChMws27Pt%OV5xF&9Yg{mK+vv|J>C!vMP zFc-F?z8}jj2HHoV2S^QULS~198y18b!^`n`E}YuQx~_=4vC@Q#XhDSsMIfi6+;yZ} zV58*mjcJ&uJ3HnslL`tQV&N*P$WRO zq|KSlnB3;0)b80KC$)1e&jW}_&f-XVyL_mDh<{9bqOb-z_5Jc!VXkDQtX^pSY&0(3U=8W-r z{cVPfHfCOl6LxFjYldlA_^9dykn>y zDJ~g(BOl?T|LEG>R%~@@4)|&&!^Q6hJ1)+wmJUB>#clj@|6x)^bLL$;n06nEcYzPA zXwWu>kDEA7JJ@!l(_Z0qa+V4P1YBylkl6Oww`r&&5m$1E`Q=bU3p`lRH+qUo)b$00 z+k+Js^eXSwPm~H2QWr_iY-6z_IP28#JQ=a|sX{^tl+@@xtb_{OBQ4#z)hE*@6TU0o55p7M?Zggyq4*{&K-pDY&0&^R;g1hiu ztQbv-4#VLK5~GoLnsMR`+r=fX?^NyMM6+jp&V1N__;T&>rguo-$KsMjCs)_Tpi)pr z$3l%93k6oau3EI*7w0h(Q-qFYuX76Hx*5rxRV)gbRZ}=K$qm&(%cy7gd|{UOa+jWH zlTyuOINa+e--&G??F-)(R>s;3QjUtf*U2SL1SGu0)ALaey@s32(%mb(m+_TnK_)A! zbL9S0dL>z;1r5sEC$03=ABUZ`?~;)AE^aI`27XLne_u; zbol1*Gx^tFqbPA95kN)_VSt4hdjS9~6T}vPF>-1De1ao3@c$Tx>j;Oe!+~}NUF*Lw z{!BKjX(7V?D8JlM+6;IH*RqaAKHzWT1q6Bdqm4q)0U>A*h#49J#=&Sa7Zl1DGd2Pc z;16vg{X>pNreGKYQ}+$R#7U3xnt_LUKnyi6G$s!w`Isg$$k5pjX|8E)4uXzIea~PV zghIKSA<-cLMlRm2U?LG<35(W#cvWD1)#l9{@^kjK#Rdt4?z3>5CRm^ zCFp0w)Fas7XGA^7U;S6aAN?z$@8TQ$GsVyY9r`n(>F2BQGXmD>FVbqRJ|5pI;<%Oo zaGg*c3>?$$ydn zg8tv69n}SfG^V419K|pl7Pt(KxgEttFd+a6IZnm!jln%m!#j$7Z^2L%Fai90Ps7Yn zf2YHMiNc)0+p*l3C;$VoFduN@90oXpy$cfTZw$at5a0u-gZ)kj7(xU3V8{df2m+RX zIDp~kdxON#_<8*X`$_j_A|~(~!cX{j<}q>q5MN9$iKze_7gUJ9Tkzksepj?8n08b( zm?rbnEz^Cs%NfA_$G& z%EI*sLWKnU0fd7J^78Qd+4pLiVW=A&!9W_s&LL=&Hqsv%#hL_C=dJd;9^5#1-l5YmN*@BZ76z z4fKC>gLpr{e?Z3fLExrj=E+1P-GDLPtYxYfd7b2 z=;EP{@N(X0C;8K4bwQTAAcZ|07L)`g8mpf z(7*DR|K%_L%U}MNzx*$M`CtC>zx?HY`OE+Ef6@PPG-hD~hbG{*U+@FL3|dG5Rlp6L z9Jqj!2RP`k@C3tX@DqF#3IH)E{|N>hoNAZ>QW%2Aqom^*3NE-pcT~pV6uF@%8XX`f zEbJdFF6cq=ZBzB&EQwM}~`uh>8h|fd5j0q7rhF zQgUL_@b4EpSXz*qyPTxukk^rS29BW!dLBa1itHe#$0GOz{3QFQb@c<$&Gjc<05};qD%{Og z80m||{A7JFSg)cO@sEfG{8+dAndJ|pejP3nwuNwNBXD8Zmx3fs31RQu(FPJ*$ZP9x7p!8bs%>%)d5Qe z*%SXAj!;(zoq_INzF-1o!#y0KsV*fdDmZfddRo@aM=z>Bk7@uq@(ZUkAi~Bv3}ql49xt{+Qt@+`wCo+ z#Rpf6!G%|6*WdsHGcC-f6>ulEI{fI=HYRd>fv%v<;o~LV0YM#OIQ&P#fAdN18W03- z#()@u_~LG$mjeu+0mHsw=l~2Hvx-LL;)4l8F-yQyK_G)*m<|*6I1XbL&8S?C!z!4t zo1Z_p;0nd?9N^~Xh6%TU;d7xOn1xuFJ{V34^+JY$;XyFW=^Ntb1%@%Z0jT_t&fwxO z7G{Nx6ODBB1j8a=m?+5H6x_{$r2wwa5qbO;cKI!g23JHuTtFQa5OK6d2gM-c; z8MqEI%omA93mStCE9W3L(4FKL;Orj(0LL<8asd>7H$guFAK(7V#NY7#s~|?V$LZ@P zM>>Phy!-|GmGc)2bralI0PehirT>DthX6QFKn5W}hYH*hv@&q34ybN_H!sX|5bovw2N%q56#m6*zfr&hkL?-^P!=Bm zRE|ObSuYg;`}`8Xq9g=h_Se7^$WOiL<6DBRTL7?PojbOBFbt+2-T!cdrh!ptu-DNx z1wh@@9PSzt6ncz;g?V6u`*lbGDu5nf0XP6&aLNOYf}DVeL1Z9G5KV|a#1vu!ae{b2{2?KbC`bY%1(E^DgWQ0WL+(TB zAL-h@^{ zA3~d<-OwTEBy1V+mr(W9eX7V7X!iV#Q#kV&!3#VLilZ!y3Sv!dk&Pz$V6K!ajj5hmF9t z#`eGt!%o7!f?bMTi~SsX2zv&569)%}28Rbn8b=4m2FDvG5+@a>0OuY~6HXt_G|mPt zE-pRp30y^7BV1=(G;R`ZE^Z}mBW^$L4DL1_As#E97@iiMEuKGKJl++&+j!6L`tj!Q z_VCH@x$x!ijqu&@Bk5H!9tX*AU|!!*0J z%(SYsNZRwXcWL`+x9Aw@ROpa&$#nPVhUj+b;q;pHKJ=I9>*(JxU@`DBm@tGfTw{37 zu*68kD9`B1n9NwsIK~8J;%72tiexHcddalG%)+eA9Kf8*+{(PfLc^lM;?0u9@{DDH zm6BD7)r&QYwUPA`9NfJR_l4)c+u$o~Ol&%AA#69;Ua{@5^RQd6$FtvKf5$<42{7`;Leoy{<{@xSN6H+I@uP8jxCx9&=E8s71Q(#1pNKjQU zRIoyDN{B{CUnpLvPUwp;r?9Q?CE*U?0})9PUy)*waZw6UUD0^a$D(Uu{9>+R`C^0O z#Nt}wvEq-!S0zqJASJFzj7m~U8c3d%Y?0iTl9mdRx+}FH%_;39eN}oyhDyd*CROHz zEVitgY^>~4*&R74Ika50+_Jo&ypQ~C`FRB%1*Af;!n7j0qO;-+#YrW&l9SSPrAcKr zWoPA^${$oXRNPcbRpwOrRJ~OzRllf-s0FFjs%@#ufj^<$qyf{=)=1WP1^yP5g=U`S zdo6Y?53LHV6>UlFNbM#aY#lwFOFAP6W`rxE9I>J+tsAY|rbnb_rkAJpL7!hgNFV%> zKm%QaOoO+ET!#LJbw+>@!YI?|oiUGbpz#wE91~-ce3MyIQPU{X4l_zKN3+{zo91ff zY3Ab=ycQuAEtaH~_Lk+ATUHuY8CH|l!q(B&-8Kw1UN(_i&DcxY zC)p1>fInUD(0P*Kr0>ZFM^Z;;#~LSWCo8AhPT!o3oNqdBxFB4vx~#aWyJovCx~aHj zxh)_Skr~K2cSZLM_jwORk4%pRPi4<5o=aZpUU^=t-a6ie-djFKKDT@heXV@&`QiCF z`PKVV`1|;`p;%DisJ?&`0Z9RqfpUSD16P9df=YuS!4AQX&{W`H>SYLTNMguTs8VQt z=ysT8SWP&2xPSP|2>yuk5p$=sPL)JrMY=||M6pH1MNLJkM&F2m#JI$?#B#)*iJdvE zeYz};AkHVQCtf5zBmQfGRl?&l%x6xYnNHMBtVkkB3Q8I|D}T1|9M(CnbFa>epU*vi zknEP+l_HXICFNVHYiid8(F-{j4llZ2e3d4Zb}bzz-9LT!lJcdp4DyUq8Pk~tnGdtz zS;<*nFFRiDydrU>Fq{XKs4#w>;0hp?tOcdsmsRp1-~tF@H1 z$qyk9BOb2QdDcxnvU${3uUp^pSpISC6QL)!p0Yj7Z=h~Sdq(i=%(KJBQ;i!<0ZmKI z9?jD&jxFP@)~$nWCT*{t>pg$buGQY!q1w^-Lh;4ZPPxwdE}5>nZmI5vFC|~r_DJ^B zzLI+NuvfbG(QDb)kNXt*p7ksDHxFnGv=1T%yN3*i`i3orM@Q^OCP&>y=im6gSse=< z+ZzXeaOZ9EJF<6I-ZQ{hKARr_Icjdys(}6GyM^GI65D0i~kQ|!=hYgpK z9l#T%QWfK%#y3%;apn}KH4TiTbGgK&e&hZMw?q&DeRH2A&y<-|{A$Kc4f9}4Eop66 zUJFYrhU%974{L4-=*%<7qMFu$X+9bE^$>npIUR&9I9T)W^zsf33y(OJm~{5s`Q)t2 zSF&?*^NLGKZo7WsRHrT;y5l*o8EAt5y!vJ+1#fdv_d-- zF99BEq;u6+y;;pINpCKt>Bd74oRF!7#_(%_46&3xlX%4NIv+z;@dM1U#>>a&8hN#? z++}_qZTxkvQODXd{9I1iqZgy|+p-87uZZ)x<@KF!7IygMbZxy)CFk9K+%@)T_m9U9 zC#EMJZ=Kxw(lFAncQ0f)X7OF*Ux0th`iMPz{g<--Q*1T%u39RfxI882f;=T{=PPA=Sam@CNG^@wLY1UmRVdm4w=@uTs-GVMwwDD#;$^6;&dykFt@`DSAuCzdFyS?ZvBc^PU*q3005x!k)q4$@qt zcUQLL*$*;m4EgeC4~)MDi=XAZ6sz~>5U|OKZD0lGwC=WDZ8QA0jSqnbz2H~GG>BkG z755;g_vz(U4{&a~vwqHffq~7}S;h#Ls%f$ITVKC&2rQlCR$j))6L>ai)>ZjYR&d&@ zM>eE;;l(PXd!!&?x#q^+{#y)!2e%2*cG-0ff%$tNRAFE_N?@2`zlTrRQ9y1m_Jy%T zaoQ%PE{z_%xoY7^wL@UTxUYTLTqLNtJ~-@^!o5#-+*(D?5> zBNZQ>Tcx6k6!tubL*>n{&HqbWuV{GOMYK8zkUdK$6Fi%k*b*HF=JzpL#_Neo^-Dll<6_Z z;h(#3l=As56~X(52U~!LS(m@s?b3Df#$sB|0s`?&?6tKym!eAw;p1?(>HWJWxDJ64 zGmv_T10(e(~*87PBsD zUj@^AFvkCH?LTt-H=CCm`vjTGd>yf|pn|Pbw!U~v>6Ys7c6FNtRJx|s@{4R!S1Dh4BPujbfenE4@W9zNZ4rvO(FMhEpp(3 z6c3NhApj}!Mlzi`qkn(>>wX78;ZesHd+QB-Zl(L4w}nC>=h$fM3z5oru;0@L>0Fx} zB~M|vnsWbJB^kfnzQ?7Hjo&gs+qiWu)RIKr=VIygw0l=>V0uY7*&b+4{elf=lEy@{ zk|tLBMWy%s#T+r+8hKye^8@cIdGgT;0UCv0`O`oXLDW8C!t|bhXDWei+B6}StbJJ` zhd=}_nB92h`Lz2XAfI+K;}FO$Ia9caRnh_SmhV0`N`JP3m4S7Uum_)YiQ|&hQ7>v> zwq&ngIO|?{8f z)(WNyhDvFS)gaS0S@9T>@LBw<_1qxx53H+Mn3pldsp6)a9>l?kiz zO>b4c&P26!H_;vf=CmMX-L?!_a|Oc;4#J8eTuatLu?##+t5*KAn)GdtHh9F7JjB^BZMvyDL#+nDEY7#fV zzBg@C32Z{zot&An88$37bnmWddAP(r&&h}eHQNd*0^|Q3*7O6ejIPpUQm{8_Nk9Jgb06&H#+LOPMQvz4!3a^Uz0o6yIJHktgzC$ z!4dcs&I-~-{KMB6rV;;7+xY)gPm}(R{ug?`nO)^8>0vR7jxeo4(GP^Jz7K%C5T=}w9#7#MrZUgqnAWW5H(~=VU*~- zCL}t8AyE@01<{2>)I=l^B{3w~*d&M<-+G+0&vo|M`+V=I@AqAozn*!XweEG7-(A+d zmbr+fIi|pf7l?BOcXN5Uz% zBd%Se^ECvx#s&1rddF|p7a6tg-CBGk6LW2LKHtf4-jVkv6=ento&|$BWre}WhTu@hVVnGVSCuIDT%*6RF4UxIWcZ?gM&Q%= z1K0%!;%=z}51RPboxqT+cUHDqDMm@+i8Z~K@RK`s{rzHE7`L_a7EJ_hrf^94 zZ{GJEjo{2Ld^T$4kmrU%I7oyIomx)T97gTmhKSVzigZtkz}Y;w#wq&nOasP}Xn!k` z8W38LhCRmDADnZw-Z$}OHVH@W4#FS6jERb1193#ADvB!=1;2PN1&MFUszX9-It&Wn z%@3PypAfR@=6gFWz1ufc#x6xhZUD=>#u!q+xC(`E*{L0^+9GogWSMWT-L00OTFIF}<8H#ba8hVu1DEg60+P!1R zw7IDRnFiZosTEqJHfY4#fa8CdO0RMZy7%HJob^v}vzwX-T8_4-yL8s?09Uwm?JJcQ+s z2K1SIinU^|t-je$%uo=KCdplDMEZ1SWJ3L)^=WdCt`G#rlH5Ntk;Yv%45jERQugeE zNKov4JqxAuGvnDrXLg{W2OR)E*-5k@*3P-;nG8b`B^($uDlx z?7kAR&-e~Q8~j>eO`cW!{|G#mGA z$TMmwO{Wz&jKn#@pXv=fol0>!BB_=(`jLPAYo%ExsV@4^=CAq(L}+n%o#W4F@Pais^OKHJgP~A!cUs20R^(1^%jj9Ps4y`{s+kLsKA+Q~eWve^ zM8Qdrcw}F26mXOscbDsfyJ?b%Iv&4Mi_tR^ZS$@S}AFm^$WJaHC&eNn{% zMo|PsU~S`H!s{(*9D?u1A~?&$zvXuCUA@7afX+frLheDn_)QhL;LxHt&Tib(iX*a! zhI5%mShI8yZWk?edT;IxF#m59g1aUdMW+&)gs_hjU2?k1v}8M*FwXbM&hLMq7)x6v1d4GUQtO64|gIl{*L+O;Qa!fJL3Z(PFcoh(OLlrbcRBx}55)hGC_5jj2&}&v=_6F(A9yubgUj zmwzSnar@r2`VgUL_9Iru8Ymh4j|$Gm>{5c+*E#`1gF;ZOS-Q6RY#yO>b;xoFawroX zmyl?x#@{ziJEtf6sn>tGTV!<`5JTU$Nns;i^(M2gDLaqNBX43uEl$645t zCBVNG#PgqN)ZLdX+ zx;;Usll{}BzMVZ*@5HAEpcMP z#-}p3L`S>{Id4|gBf#%5N-tk{eDcAsrd+pr97C|EyBJZIGf6mcKYg;%eHR*mS#+^T z+#w9_VPBO7PqRle3VT>QF;-T+LtDH7DT}qrouN}k2q{ju&jYRBqvPd_f|)i$$NVswRb)n>W%ubb7M6n_>_nfmhpNUET;nbO`O z(6P~Hk~J1DbeGQ+t*GqKfIuQVD1d5ePmsYcl64H;IO535;};ka9|J+St8!w+z*0?P z4l^2M@stUX%fOqd@VQJl#el^VM#+pqS`s0>mw9RZSXfwHA$!Pby}x>5BmjOR90jU< zTPNEaPQrhIM(VjzZgt+fmN9tU`Z)tXhxf9_eNZ2Bcl|~JTwo19(MQ6Il^?*)4hbH> zdTYSa$LK9Y`bKjG*px1PYjT(lqBpVD|Fq9;5cKC-_`BN|mf+=8xa&Ow>4p5&)#XgB zIDK^HBJGmwgPYtZ`ue^Efq8M^pGB$V`Z)9I{s=~!C3nYOzJT{`6dNlc8W zr?L%M)SeSl;FV7V@X5WQR*^`RKOs7Iz{Om846CBLqzCKq+=3-TPt>s+ap*}SW~yY! z!x$AdbjX~3dF>rrz^;@x@B7b+K!XUDBV8@e-p`l^gn>%k!8hgQ>@g zx8niE6)eTYvljJY$?p0TD`db|v*C*-&2T?1H7#^vIXqXbekKj#YsvfOl>sa37?=|~AziL-KB~s9IzoZQ89hgo+ArfhYQg3v zj4wEq%GsK_b(jcKA<8E^NPZAgN(M%?(&y|i&#|OhoXR?nJAcC@ir4Dp>BLtx`f=Un z)irF3Q1)o{YUb*GONNAjE=HlZ_d)b8hLfwN&vAw^+_cin+szh z+hJ*>N)uss*-O(%#{7JZ*z(ik-OlX;7#|@0G(e2MKw`rSb1Jkcy)H_@4DoWXG>0VZ zzNJQtcmhq-d`37&U0)zM&Fs@Nd5g%*gd1IdW($ul6;6ax}IDd6O zRY~zL{t+GKd}Cl#f+rBb|1yLh{4ebNhRo!SST69(W02J`3sDHq{P;1^eOe-F9vH+5 z_sDsPODB8_hczp9yfcbf-1jnxt$&I+ZUb&Q}PsE+R#e#1H*`Qzd3d!w50WhIY8GhZ4Gsf#YxWlxDE!I|x7W}Hgfz{J_=TQv-x=)HbfkIbs~xB_I2^sJEO^l*5@+oQWFDc9f^>85&uRsnEMda^zPFa_r&yV zpKD#c2e7p2)IZfawC=4(*#;u#(S1f=0@f-t4HsiBbPfRS>Ofb*!N|^11AhUFfI*YE zc1}-)eQ(&dHfr53WR3|~J6BJH1fgvzI`1H!~Gj2rr9Yzho?MTf0qJHk{GN+Rx^3r`l~Gq2%|?yAmtj=+*TJ zg*@(zZDzv6xk0xEu>+VLgcvj-8A(14K0#YmRdGxK1QwqdQY=KbL%{u3yff{$|) zdg%A#bHtW%Vq*F}B=pMn%-y`=iWf?=t|pG3_VCcp5*^zE;5CV;7G7CZIIoGJY=w&+ZnveNtB1gHt zr*%M&3>2PfdA+Tus=c1~ll0Yu3p!>i^T?voZNt8?^>3}mF4D^ELxZYmEZ1BO!_{~7 z$|y>IuzAo_bJ;jjKN?MOu2R~c0bt>+s57oXwf%N4vaDmj1FRSZDcD=`A+oFK*=~0i@2aVAH}5+E}($3J-Yu z;hesomF2_n*6p#purN~e$z#a*Ct$qH4w3R?dPg3oBwU!i*y8-7T-x*T+C)PX#+a`Q zf{knSBb@=!S>lG28o)mb0Bd!RwV`g_6*w zd^yr29xrZf!7t{?TM>+WN&*BvOX?p;EK5rg^T-$SW>@P7bup0YqQa1cp7>K})Mp>l zH#2$H*_pqfl$FWpnJT^L$nY@(as6pTL$Cjp~$jfVxf4S zN>0f8$Q<;m6$Zm<$%O(MrP?H@E6xl@`1fYi3i^?M>i}zuJuSWg(u!)}s)Ll`cmAJ*<65EW|0$^R@{)};7oXVXK8`@Z}u zlheSdU1oSOMnMb*c*8xl#Wko%-u1iuH>x zyIM&Pq(DvB4ib}RblLR?!MH5rU7;H-kwhER+fSLuCF-2?!z=>j|Cwm2=&Xd~Qp+@@ zMf~#U)Mt;N3*N6U>fFn&$%*E&+JLxzQ^7U#Th0OOy2aG3jLES69A>7=FsJ1Du{Y5X zcdmhwwJmt5YzD0;%<5@X?S-qXuErqsFPytVe*2P}EfA~bvVq?V6{ zmH8j@(AKfdDG^%4zWo@; zY$htW9uzf8AJK>_a_G2XGm4JN%zPodZ-rNW(_21in!a_U#}=ez?LsKa@?18X)onqB#83hv+^El_vBkO7uo>EUa}rtQA>Ul&Zw6l6n#`J z8bSHg7Yj8Lzn`kh00ILVzmBwY=E9xjxEwE>^^Ehea zT8+#-w{2^^O${ZJH5x!)|5Ig{zu%Yl-|Q(uNe;A^^$A&9<>oz}iRv~Aj7S&R#;$aJ zAp-ShG*!6IJfyIx&@|k)lsV+(g|vc{ei{kXQdH*J)p~jUb77({?@Ag=4)E0o9_e0c z{Xw=1$)Yd4f832QU6jS2fN*}&WHkMtoojV~xCXSv`meo%ow(20@;IZUd@FW0&LU6} z3J3dd&9d`k!RCjKf#^?cZdzQ4DN#Csk*B|mw50Q3rQZ0dbBCb{2(WTNxkd5PfN4gQ zH>VYoxKQwzuNqaRlTV`EI_GDq-3BPCG!|by=V*X2G|RL$N2qpA<}>13`Bv@TgP*i` z1uKKbmm02|o!r`9^{&bwdW*Bj4XAe->xa@7my(%}Lr}VW2RGujA{MkAI%b~k%~2zC z<87fkSJ9Mohmq7}vX zU*~Kx90nyvkZ;xdwB%DlG3A#6)YTow<@rU-^C^VLktGN+q$`k!8Sb@Wu3X|T9L!4@ z_HI|iMS`}+r%psNz9$~GG~Jv;qhIZXR8}PD(f!nv44P)GA96K6jDRHiZ#UMlVM0MA zHLcR#&PlGJS&TaRC}bMiW1o9;@~Y|_sy|jl+uqV09Fl9GJ8LKYdyF}GH_K+Lk9~h{E{a5PxG@+LUZW-D zDyihq{GGYT`iv}ZzORRHxdj8~IciKip((u-8luh+mZb?}Mk&FLLs>55$MkIiQk)fV zqJl*YVgi-v+S-PT-(EkjI=TOfVZ|JXyTa1{krG-FzE|TAdci@wp_ZjqFxSIAGwEt~ zYb^??1t_mOlk7i%N|jD7i$2z&f1>J*cIGoVehtHI{f?zTb>ku7n}@zEEBWTVnxwR{ zrYAQA#eK8#F2XakM;EwhLRNT{+@CfyP8x`0sLynqSlMV{;Y{U$vn=miPK90Ov#1<> zRgF9>b^XOC;lKQFDHi9&-8NMZJ2~ZRU}5mhi(+M9{k7G^HJo1wWkRh|%O-A$!a@==9j|W%w+v%j z-cRf7@TCLYqzpFPe25Z~f7p2bva*-^5WM)%XMBJZKW}b!d@0GsCN3%NyWX+~zL7Mr z?6&W-Pf`bbl+>X^Z@^{x8}&G24acZ3mRpCRwFgkP-((_MX&W^K4<+?t6Nmdb@34Lx z#n^mc%GYWR1D#+5XlDTsamHNj+EoW9C0Z^X7yS#@n}&pAn&@r4@30_Vl%^)*OYX-X zmNLY0Y(|7VwMrbgAIRT{Rnv^E#!X`I!*KS<=t-a zVad~|sp-p8@xS6_KK}kQ%^rwJ)|5WJ+3>8HEn}h0jFxlrgoQt?7WuM7ssQz062lG@ zTWjGoP@9-n7sGXKCKXf)>S6JkUiQN2#do7Kq_^1kG$974s3H;u=jFf!B*kERhMjMn zABuNaV`jJQ?o><_Uyjb`a9rfnn>UX$Uf#EUVWeq=OlXX(Rue(UEw*ay<6v(KOL#u#uYT+Zj^k6M#pCZwc8~hhJ@= zyDN-z@R$@fIgFgESuVA0v{1l0VrCCosBv@c1>ycAanB2Qv5QkdLw;5$k=1G|q?J6a z<7ys^1afnNfvY1b9d|Y9HQQ*r*n7cFMsK=`KDq^OF#E{dC0IQ`o8L07h3$ z1f_(WqzhgZ$!nL>ioXhr-n;T3CZLPY9MYmCrFTN|`o1!_qqt`Fg5n8NdFfLe=U_)& zH@d7UHlcv3@$;ic6Lqf&9(d(tDo!-dwZ+;Qz!BymPd|5Hm6Z0N#In-It+8>6bFviY zP}aBqRG8szr*#_Q-upcAkDvA+)}00m`%7^biivO+pEGC6A4$BayTTA%s=29e4!kNp zrMMaiB~u}EOB3`{%{{69<>xYi4BJ@h@0_OAge zL9Fd3v8TqSvYoDPIn9I4Mx^*m3b0WAT&>Ad`88;NFt6v017z8r~y`fcv7{;m!P$ ziM61JUm*&EAs~f`9kxRIIn7~m&Jvp+nwrq4a?1IsbH`^B=mUnv(Zq-%5K&BGGG@lR z?agM(3MuY>2}SDb-|q^0#fa^NtWNHsk??a3jE{q8tTsl^oH_EHZ+wngYN%a-@t!8} z)C}bO?fzDa|7&>k7h8!jsu=Ve?Y7NMidrN1KI*eqD=Kcw^a|Tg$lCEh**sm<5t-Nr zVLNV5L#ww1~uZ0~3nc1uvHSF`Qz{kFM>TX1GbYg}ec_D*7nt45HI?19?F8m&wktfAf2YX?QA@x|)V>v_AB`{HBZvBB z#;tU0n>L#eCO)F8C7ERgO7?npbe747g{yASW^QZy`PuNUy)}K!5_-Z0v;0xAq%I>~ z@SXR^kUSf{59}`3zW6ODiaZhi_d)~ZrH!0+2QOgzF_*X_8Q{|Xyh=zNkh?*ym;7~# zx(#ndkLqgby>o?H8^fLRSUTikGKhnIKl7oP_4L(JHF%5ZIW@{XVe_4MKB!b#`XKsA zv9K`@Mj}2-^Lq5wJQtA~238vk%XIHS%a;DsHvL z7f|zk8r(AkaN7~Jhjrb43|JsZMFLP7T;-mucqPlVS679A65Q!lkDgB&^WFAvtv?g!{C?QY> zFkIp*rL>C~gpxkZuM2O+KX};-NlMa<=*siWaLtl95@6H~9YqvmBq&(bPJ2H@hm$HQ zx&KLz5kv0(G%S*CfaH)2>Oat7tm*3y#BWO*dW%byI}CAGQ)h`-?`1RyO;&)`cM6~+ zb#42hQd0TeVvH3=#>^wm@3djwJn2)n4fpJ@Q1}vh2F&rFh+6-JG3kT+6!huJ1rW$I+6 zf4;@KZe_w|w`_cctwH~zHcHv?wckz~Xna@|S(offtuo2eXhRz;I;3xkeMG!uWu}%H zPH678>7RDPLM4E_GrmD|Bj)s)c5Cw%_(5ih{ZEYE>-5l)8kNpAHwt?C$Y_&*@w4%S>zWn4U=#$kh>IScxb} z@1f|1Ji-g0HUDuZ(D@&1ku+7g)53mLsU~k!$XV>e@geJ*{R>{z3T?Z29KsnH)h?*z)C%`-k^XA()dnk++9fCy z6#K|!tALE1Dj3Fc`&n|S$^?#d6P7Q`7-VY4*^sYQV-qxh_ugWi+~u^x6I`9F(n}w- z%|%o%S(xQB5)FR%Bf^0Q^WRN|j4z~mL`s?>G7-nAYo_fnyv$g!%H;GF+kGfE&kif> z&V4%eQqo543H%`fb410;#-3+ML&bhX(zg*Ku1MAmRpD0*@VLrnV`|dgV)zd>1{4`Q zI(>3Zfy0L9AYuz2wmzpnC5@hVW?5mFq+ngG9l~mT*&EUS*>HaLeKv59H~)E%2+T5E za1I^>n=v&UmJv6Ic2Qf9c3mhAIw$s}Uf85Jx=Z_Gc@*(FxhBm%g)bnIaL@EhjNW8D zMz)Pps7j!7=Fup#9kw1)_Om47hXdGRoB^p~;sCbo3ReD@_@f7G>C&YWQB~mda3^@{ zQu_4|&e2V_x+>d4l%9H>lzM9Qmq<{3rmE+mj68NYFk$r=ll7DZ~wU z?yZdOwd~bNy3s&Je$AFTE}y#3;5-mgpXd)h>D4Ih)!ERTE7eTZ4#APo$#F zH{&^dKIx1c?b#pk;5`+zm;Ozwb#epL!4OJAX;(dJhKDRq3@cV5goQ4QrK1W9;i|8= z2r$G7`kf%POPu1i$boppGfrI4QvyOErkGx`;eX(W%;Q>c6`2h?JAU%=U#IUR614uWtIIt<=kaTn~Pa)AJ0qwc@bXk)Q}1ehM{0LSXgNzb1;mFTMXzBcTBvAD33-P!P}5bL{42 z&1VHc#;~%~(b0JhQN?#Nf?D)H2kbCfcL!`t1pqF34u7bYx9HC` znlVQ!GB(v!bIyz{JKJ-U1#0mfGC#lkwOsc;MJfyelLxsIDrQB)3ay;Q1OwC|RZ zlcDsCz>|LW?ySANnD|sd?kk!xSKZ7ap*mbARpbH8pUm3rG#0$c$s$hoeI88 z*Q%1`rw?KoKCMbqh=RG>eEpdNP~8LdS_F>|`dDx^pS>ESsrj!vjiRcXPmIO``BYiY zH?yink=1TR;-5A{Zxf#m zAHasEkt7@%z#QbT9{>JP!?VU;WOFy}bANw4a!HQRI=}0la{B-lO9Ujp)|h!du_~FK zr$=`ZP0zhozl%}Fs8x?&0aW<%V2{UX6epw%Wy^)QCvDK`B#aX_T`r~ z#67vJr)F>_{Hdc^hNMyx3t1PXBUr1^9{rEtnyhjdGljXKX%2I^J`D~0xb*qXnZtM^ zZ<6EUWt5KPl%51Do!b=7lw56WUaUNTc;2R4o z%S}IqU3N~~qaYT4dk2=z43_>+F#CTF+Fk!cU3-*gTGjXUOU5Qteu&$uCTJ8S&P4Br zTWM$?z)~SbWM3$=(%zW*yfHRj${EB?!sTk9+VxTDKVLvGqbay41r!mX!NJ*g*%=%TPz z&=IhDnT0%qSm`n9P>X%Pk#xl_94#)A#p4=n{{@H%S9g4&=BZiG6-c*@DE* zSRShfL*APf;tz9&iaBxa zT0O#c8Q;73DRUMDuS^&ciso24#-^_tdFm?zQfHeRn&8E+9HJ3S!-yfg#Tm8inVb!g z7MQ5RfI-H_&q??!eEirI8j&HRem1HEkuhnS$DQJS5zvh~6@C%DbqURjt3r59DZ$&{ z&bLwKQr{|A3PicqAJ+X12_<(2MkrstK&dKDHiX?_ZLQ&U_|`cI1tVgOj?V=2%EVu6 z?s+bJ^|LA`;{31YkC6lYxXCe79#X-gzaMrGT*3o=tpeWp z`=07IAOSi0%pdiE+)jdNIhoGZ9B02N;Ucq&EF$7;`D5>YaWh{a0Z{)U+2Nm`63up5 z!${PlUHR0)J1BsFl)Z4!Y+aUsZbzK?jJy(MmR3aAUh&ZnC@qrwXF?b}PjuHH1Eu?T)FM zPIvpdkXaf~@oXjnm?uQ+uhbJb9?DP%kkwb3Z_b8nS94ElcVZUT;O@Dka>xx)>N>`B zb2DOj!DpYDE5PLr{L%CYLK#}zhs_Rge)WYMKIU{^NX|y3UXSY)uzGi}RuQQrE)^@JeAYWmV7TU^VRwewVed>*6H`ID-27ne})d!9O+%vs7|$a zTnAk6SaP7jq@H^0tYAd^YQe6&!N7orduEN!tRd>57TA9)#v%1nyC28X zZ?i$fvGEs(-*PXawin2y7!V|K*f~LUuWyhK`p1*Oad4&oip=WZXOX)5<9Vm3qLdo* zV|yjsBf@pV4>z<;b4O>Emu4`MWmYEWmv7G|6Fu4|r%4meov)x10##}n6N4W6myGCn zxGsIna6z#o$IvO$b>CCh2PaAn1IZJzVRT+_v8qV5dAli7ZxQ7^f-hAH-%1IvGXm7b z@jDp=&C#;`YE z%_+P}q)~1!FYJQZZP0xQcrk|D&xN3fVb?{Y0$W^;kGh9+vg&{T^O0U`gAgEGoA<=k ziUP4_PWK2_2}?h&EKR<^BuNfsqNX>g1Ui=;B0vt#jByxJGHOwRiejMFAKBs`TScsW zTG`;ySWkseUJlAkU+!9Rnv;~w>E~)pr{WJm&J>I_t{n9B)<)^diqSI{y~3RfHPV5` z5xhY=t|fb|TN-O`sauLYPWkN5jZZcP@Ry~g z_?+QEj}@o5BDvy^vqDT|>(x_fOhV(-WC~miP*1`HuQ``>R9=DYVE=0o4X?DqCAlt* zTA>lY;7{uYYD5EI2yxt=Cb_p(#cOWrHc#jvl6~n-BJ(VFY}L}&A!RN6TTF;9T#WjG zN;Aua;i|ElH!RQ5ea6~RL~#!go3T){RE0Dcw938qQ@@RlJy~<_yUNFThA-AqJ7=AM z2CV#4dN)P@MelsZC(V=IcQbV>?9HB>GUVN@h<_vZ-zmaB_~74bm6}AvhMb;NSrbt* zwEEyr_LCaiH(2UOTT)n11Qw25X{un^??%U|rFTST9{Sw3;bJn6L%aIX~k_C=N2*&DK;PEgFh7DtJ8b$^+ z8PGHng7(BET5K@MGS14lsMz#UI~QAPB93Ao2DKCr&V$$fu0tGdk8n0sk_dTpv%DT<;2VoEdxNh=)vT;^qeHN8?9*uw zHXPS@QqF~8^_2I{lM7QGTYWmp9mzVGf;=oZ=tQvHuiXd@{+!S~O9#4W{TnJtyDN1;K^lT0iG&myj0R<7}xlJJKO%h4w9=I-_= zJve}gmW#Z^aLUjQlAU+CE`?gmVwfjEV-{1iuQt;0-*h&m#;-g1-e^^w_u9L(Z5496 zxbzc*Wkg|5^-#4?g{HcO!P4BEO(ed9XgRsAwFJdPha0?){~l?-3Uc~bbwyD@m|G#C zB2QVjI)!{go|f7I%YOi?!G$V(Ji!_3m19jfD&~k()_oYtmS0N3ytK2#_E-bHmuZwAmgdQ>+(EY`5xhOx z)Ak6U`+>6ohRM%H#-zw_@85_yYRVy9yH$2uvWY~EF zvHKGc_FsY=2o-*{>Oa)YKlTyOC!POZE7BP1Q8V(IjVS-M3Cl)iR*ih0h?KCsI-Mwz zGUQ(*0IBBeYSznZ3%HYv^Ft=FAskL|GtoXe@knK6Nc&b@W`~o@us_sfgJXTu{%F~* zdXD+ng@qe^oKGi51(bvp5T^`tbyI7Sc??#N)j&709In56QM=i@N2oYUnZ!{ep5tm{ z&I2l2j;_h8+ywX~2n0^jwq0nJot6Jki+R*?ug9)+YQbNv5v1r#5t@LuiGPmOE7EVz zRJ~a0U-TfRS>bCKc7N{Cp=J1PzK0{dAG;dyWbLhR!;ZtFQLVT;l&Msossl{|W3EvI zOn&ia3C2!SLi*;}=O((rvPO?^x3@S|?aAx?%4sz-@^RFuXIqwl;Htl7*6+O2g#MMx z!0pfL1veOZEFzcM5M}fIb;CB_r+g*vg+U%qQ~A%jR9bxAus$Xdp)bIlRRwmFLho@b z(+!u#Jmi}Ql=Pj03>i8I_Q&`F8L<;QO{F}nQZGw|rMw(muJW|cj`t`O>1bVEoHaER zuJ4cDDxZ-AH9E6Ld0|8CS*-i%ljVC+K|ob}>GSbD7@AVmg=@!>f(=m_eBV?(9QPbI zNcj9XxvMN_R4FX{KH0&;aDE>m}@9y zVAZJ9v${qc{gnL(4eZ=%W)7*dggUv%05lL@{y2fK7P_U45;VN9w3K!6nIdL%NTiqp zT)@;>1^9IX3%Eh$SlQI0l;icpj^LAWDr@r*%SK&=?)4{3?h83wjda~tpq-gDB_W}l zZqEs(yv(KO%Z8If0=X8$Aw6o$owZM25YBaR&mErlA1eln_>J*q-@ZxLe)Y&k^^f7R z)k^pTkHnpZ6Hl<>8u$n2Z;GP#DrSgBC(B4w>%x? z?pVq;wX!|d^{=29J%B-+Hf=fMt}$gHH}Oo*o23Akb$SHGbcF>Gt&I+corGxR$TSiZ zyPSc?=ku9W@xz>3Vx3L&MkArFw|_9YIl@&Hvb%q4Jr@antMjl=Yy|K{7v*lEaZ2qW2IJptg9nJbQ;409#R7Qb9B&t|C?-QIeoZ z=G8#86i_?Z72LlB^z`s-#sA%<#Ae+vPrn zQt2E2u7-0#gRHjB3YTPTexky;iura56k`UtDY_N`x6EDsAzMA(sx)C$pV6mRnZs6H zo)Wm2H9k=d`sGUI)2r0@KQh5WAFP>mW7r8ku-j_-5x( z@6qw8@qw}meOjYchNSZi53{*9e882c=3tl{E_bAqm&!s-qI-3vo&Q>`Nq3ZOFasfQ zEf_f)4_?TP_#2v6PQ42YXu)%^Gf!^&d26Q$)!ZKf$7pkC!I=>Vq68sJ-;Nx9ASA@9 z_X6Al>QhPed93}UQl}(GGs9sYjjDtMzJqaimBTI^DguxW>ubUPCOG^Xx3OvB2w;!o z#L%FS?}U|4RrGuPaqMN+5~4pqKB&*s7L{z~u>yWXVrDRDuruWB%=FVR^uO{;agbu0!7o1>HN*j)1+nL=l& z<#fHpi*;Yfr&g-1Qf*<_QJjq6Bmt(QKYXv`aY>L9xPYgREco;Vj$G*fVrKpWGefL} zPW4naHJs%z^gIi?e*~gVQZjW@0^K;g;N{ow_J4-faoV5!g^cbdm*(6xPR0zD-x3U^ z)KA^X(^JdbvL>4IuDunIX}g*-YFBk*B@wZGA~Q5j&M#0(@r=VTQe+PXX;9oOr>N{! z!??AWU8%FgH8wZX*K?6lU;kv`hr8;4*5A09-xUvZ++ooRdnFRpB(ES2&6dJYi zvDEjwxRSJ+U(V%Sa5HqPK7Y+S6~k1SoGlc~U8oJE4aPiG-%q$N4`K$#(H`QJ`Gv{Y z+i7F{rRJp=Bc(z$Z=u8HVkpS@`!xR=j+OLW{!(hb{LCW#wbzV2HN1aQF=c~@N-cb> z^8-E?v<1RHWNUNtIs8p)d5e)QbdR46pSgl>khcZaeDX9h`C5I5VjtpCANN>WmYAhS z>deZ@yj(-464dRzCi3Ss{ol#B^_^GyqSvW1Tyf&h+?Fb2H~b`GyjOOt&%%E+@{i71#=UiuALeQMk0OZfHRC?vl7t*w z+VrC-xmL*+{LI){J<=19$M0MW>~pVKc~oSRat6iEMh{OL*T}eXw6YGV#0!))SSm@o z8Q&a{iy%{0VxydcvhaH@e*+z>$Mxe7xhz|k$Z#G44qdmFxCaF1xL5)i011%}c<&3V zrz-P9o>8W9rEgeo^Ue2UlAy8E!C0jMo}Lk3W2=RIU+4tzkm^kaUhaT+QMrof3$&i# z`wz+M@3Zn>Yb84M-OPH&OUjkYp$xw@hQ&-Xo)KTZe^{sG4Q!hP>?BYn=ZH7N(84|eMvgkw z1#V<4f>8kNwJWWzs-BuJ;@IL+jzedoNU5a-1BX`>ctH?#M!LCL;{K=}F1mW|^~wD_ z{R5b90=7V8uQGCvAOgNO-1FI=o~Sc2dTeTBcp2x#+yfUL^!9c+fIaxwKi!I~Ijo?w zv;O|-7-%hjnCAaA*o1VqHG9XENp(0hU8{tfUSmF%DC~=M6ZR!ZT|}w@b*H!P#^l^J zbe~Z-ML&AT7HpsT&fZ~U7VXq{c=EcmlRhzqL5~NaZ0t!i~3OZ)EUvVDfb zyH`X1F(+|SFOygKB#JdNetfm7_PEMnNdWdb#pzW6$boG(R=uh+<4MIurSJ-4mRk>g4;x1KcTRc2wM`*r;qp>{!_VF9#PZ)mgy@gZ7x@T9q-^|j;1A8-I z{#a=+&|gdBk7cHJw>(C`SdqslFD_(!y1|n_&Q@EC>}8kG+90GxJW3K**i<%~hP1eD z+Of&i4=K~)ZIhJvH@i&_U{zq6n=B#dwjH*DG?r*#(r}u6r_@*fag|m$bjfqb+l)E8 z6yIBTpQu}bV^8lYBp$#d704Tl*WDO)AX}Bq>>2m3B1t6NP4r+&zzgNVt;YMQ69y%h z5>u|bq9{dMA)L1R_S~l_tGz+REBxGu!l;B#7|ykVYIM6#`~Y@aS!ji-n+<>)bWO!? zyqwm9-9E>@yu^qO?^Un`ZYQjE02>rZhYs77K!Kpyt&7QSn+n@*d#o*HCfw$nxSVLNi&%;tVh$=e9i09?Sgq1CxKPDTspWq44)z+f>eVOP8WEj9M97&llcL zfjeAXfpmo*10FwkeY&%w-e=cleeBHuwjAB2iWJ%(f7^NZFBpE)WHnBGeR8BWwQTd2 zn9p)1-s2>HLIEq;)6F6Fj7Glf0EjUa4;+%XdHk53dUzFGach@(#+_BWYEkM?)SY#0 z{RUsvMziyl-IH3G^MOjFq6vC z?|vD-BbTKP2xa@U%oQJGZDHA(b-HCd-#WMLau(iGX<5)rdQg?-B((1_R)=TRu5PM6 zMm^m{OZEZ@)fRqN+WJk*0QuiWb2aqXGs`mxA7VLC+;g#dCr@=To9`TltS~aA-^%H6 z$o*hvbRnazp$0okAx$7mctA$i7vB}$Y}rZ^-&^% zdLB4Ug8QGr;PuaM$R9=Pf1PKyZ1oaQ`IV!&(+%ZuI-FGTBy{}GxoM}^wfMkgHa`DB611wucF()s{HxrL; z*mzMv=BE=wnq=699Gx{_*+0lBy6>khiCAY{my#LctVPhzcf7Ec7d`JPLeGu57O3|b z1rJxB>F_A6!gYJ5TuVwHF23j>TY}bwXX_whQA{KFjto)>9}x;{2s+KSL%GkUEPK}% z=Hv#rT)Ia-5N1}jE_|2@dY#V66KE!PHOA2Z!g8w1pYAzZLjpPcs&cD}o(N1!^SFsLkFFmV*0MUVCv#Q5 zk#7Ow4Q$$KNDre9e>!eG{IYe2peD1o{>%;81qeKB4vr<>%=$6nU6EO@NXC?$dAZ8> zmg$oxx534VXQ9s<{}#Xg37P)WT{IyRcLwRahR$oV)k{~y}EJD%$G5Byl6$jAz19eYJaNgWc-F*1)WyKGsNx*QbQ0qk(A1IeyP`?yXmSZ};Br@AvxMKfG?A^Z7jEz1Q=8*leSy5-3XmFo7Cju(&P*jU_x)I`VJ2c z4qS3B$ys=#YgVTrzl1uaoGw^uuw{C58uH-0+x6>HceUORv*B9;x4zhq7Hp|u^Hh_v z2(=;7Bev7@fr_-6Us9f1ZcfViHJ#yl?3#Y<{N3HVxtLVve}9@-olEXxIdJV{>phQ_ zw*j|z?U1~p7Liq$!sfKpPB(m)Xn)2BOu%sHU(JK>WlVK6f))Lv5op6cow~<7LEU4I znK_5YQrnJpa&#_!L|sEYMON{A7xT9xWcKu>0^bYH-AQ{npt_pw^V-k0#MUQ+d@9 zyGtaj%7YJ?2I0k>=E(0cDpTM;80tog<|JCUYQXz!$Sp|z^A@yP*%_By{ zG&OeZFEGv!_^k#BA`43OE!K-X1s3wjH6_+2!xp=dhI%qMDr3sl6>`&$ATUz#_Q&0u zi0$`>uF6?R7(6xTxqb;vH_NofYO#r^f+%7n{U>_+!AJVMg976# z)`mo;x<*{&>Wv2V*<_K)S3d_*MIwT!R@c@a&qvHZDJGh>hI0SxtzNYP?L2aw==zw+<*8{PI*?foV3)k zsTQtrM+Gn&^Ck2%aT<^yuL42-i>l41cdSG{yrkjvZ*sZZxgVV$&c~n@7yM+2(6kKA zUAY`@f5UB|LX`5gcb`<1ga0Q^)N8WUX=A3Milt^VN_s9Vf>D^4?|o&8XYmVi>H>}* z6n;a{D!}i5)^3e4YkIBEUr~V$d0A-cry~?|FT!Hx%#)l^M<07S-W1Rc)u1UWtGSs@ zLXyxjSfV!m`&H@64(zoXGU1xsl|eA}OCBt{-qMQKRK&d}{1|Cp#)v|dOJ1ax=ZTMAuf3K+!6dqrpE#5q96Z*%^5)IJ-VDNoH2W~F zEvP(uSc)<;)kKS?rD#VV`7DuBiuc>H%-e^8UeO;{7q+$9TZ@>NYf9%E>5@_>Cn>gi zOOaTQTs)vPbmw0A14^kl&TywJ&y%--z53JwlQw25B6)mz0)!OPrLX;Yxs*N+IAeGe3rUy1pNS*IcSX0&Py zOYWP6X=;LH0g^qC@3m<*%ogQe(r-fzQ;{Yh2b|yABzVU_dza>c9!5L-d#xE$L6Nxq za%^j%NZLs1_oOZ$L~eblkxnghw{6u52*U|=B6HD$s_}%D=hV_&>X|i7@)X1Bj?c#PJX)Lw*^y>iKJ)&Xl z$AZDf4@Td<7GttD;4n+JxQOR7&8HAGbRF5H;l6&$6TtzUB^_R^-@A@)iP}|2*lI~Q zPT@k~b?PuZhRts2k1NTB-a)WpF;WUc_ZdVAT&GVb(5UEDm+N#g#>cWiWhdnOG*<58 zYU+*4uEhzr$0gi2cINpCZujA>5eSs(Wtwkz2bnD6t4a)ue-gFtL99M&L{Zg=EaqEu z&!--;S5ihCZw_lS1{)42ef-&9LEf>{-s+0pwIWilwC?teF; z^giEG*jcV&Ah3LYr>y=yPC1MRPr^2>nMl4IreP+qa9U9h@7S%3nq}vgLN<`enOFS zS=vhVMMeV0mH5=kS^!R%a)V}zJ-IhF5BQVT%Sjst@|cnDMGt>YA1Zc~{eV`*j8m(1cFZaF6Is z&rKRV>;+}}klItPDX!k&4lFNBbBT`T+0Fp0^>>VE6kAwj*J_P% zHQ?v-uMZSz{#EVz*SHF4uz2RpR){7$!T2CdvNKNmtdGq^sZaI?<~3bSN2~> zc6RaNR%e`VdIgoRR(5c-*zA##^b7$PmhKjbn3D^J*YgG9!+VZfTgIF}2VH_{$(!%7 z0}@Wl%9qZ|W+~+4Q-xcks^zN#^Mm1GAL=L+Q+)I>Wi@@npjz2Hy#Oy<<|^uvY+K|a zXQ!c{MLl*hcD|J_9CEi07&zC4_jFNbKO; zU1A+AIGLWL%?~llo_3<$W#M?I&QM3f6_d9C9x;P8drale%3xgk24{Id9swNT@Xfx< zX;ap)T}*;*4f*PW&$+V_Qah6SDhyU3Q`rc&{r;-^x)nppkV;cl;TwrO)9uf%99Q?J zOKZhG%uIpU3#2`LLTtN=o|o$KaG&8oWsWGTlsH3OuWmEG`AHIDKS;hA0JzcQ0RDnO zij1eq2QY_IoEDK5tZujZ^)^J43+V-QUR|I*(u}DE#|>)&^2_f`E&6RsBJ^I0vhU2% z89#k-a6DbHmB3H?h5tQy(!D~>%WQOqk2a)iD{t4)h&^9ACPpkW)Ls)^rvezr>k=}z zy@&CyP4qRZq{l3dG~>#BiBLnDb#*COS^D@PdG zj-5z89(%L6TtY~RxKfa`faOh*J~*9ZH9^GHKr!$;re%EJ3HN482?=L{n;`m` zKxyRVBKKs;WF+W=7-Zr{?o-mE&v8!97L!|68Z|hCuEAhlILyhE^=;MCgsU9(33)E!P3QKr7T+YPV^eo5sh_HocoI zO_j5LYP?dfHQBGiT1t+TGxh7GF?XnHEkESex%j?BlcBLrNl6tdd)YrH_$C7&4u2}f zs#$e;#Rl~SHMwK2;Z+L6z+Fm*!@$yt%=`l#vhdZBqWFdd{&Ysu89lV~;rzHh`*BQb z(LM+hEhl-)j4o3Lxz$$RUFs+Mcx6`ZrG(ZKPX2~Y*f%^An*(gxV{=ANKRp31@mWmN zUfUPhnC41+o!3^J3P)RR4=m7`mu?cDpFb@lQ(eGST-+m8Ou!h~?u;OeAEs-P>r(Kf zn`MW9uyqnRfD34mQF78u4(-`mhYaPw2uc&Jl%r>8v&nhL3y@wR($z+n^S(&k!slJX z_DXQhJBZF_o}~TIS@U@t_z?r&qC2h<>3dv6@ai>U!QL2Y|;PP_;s)~(_Z+qD)B5~!w7U5{5 z$lT(6roO803nXkVF-Npu0#|3|bi9x{k)2H<@dMP<-~7fs-Zqt)aZbZ(v=h1#CTb59 zrToyN8vG9i8Ka{MncHwB*BFIvw@!_s;K@w&54hc*9u(^z zVD5hSDmo3Tp{-nL$9@m1T}|_L!R(2@IY{k~|pe8h5tn98+~FH3?Sp z^jG1Ze44oUR_LJc(Ijq*Q0KHXg^TGsT!zuqX%YsnQlO(Qw}~ixx84j6o@amqY!IB! z6P>5sy4}w!dc^YRJl|K1c-6&`E;uu15qlC=AQpQXc2Mgw$Z4~QDHIu%my6wg&4!@q zX(jhR@1|+u1+7Y!{}*D-5=D^`+obQ*Q7)E`SB4aPfV$5X;D4UI1$3T~<8;JJJ0GXP zQlm-xH6(Ks%HmRRpc34yUrRNco^`FlF_`{FJpNhmYC+!L@A zL~e^sW!P~1Y>UL%2ML#i9SOqD_%ms(ES)jMER%Buz4X%`g}|Z@D{krOFn+QSm$nj% zyL?EDf-jlDs|*;G5AKqC##Bafxy`Vsvz_d0E!mgsq)w{O+X|R9FexeTDs!G62$7-) zqGa)}w4v)X=T338!-mCRHb@}tEn{mX6_~;Bcf!s$&-9IsDuPRgJ8#ge;$wwucFUHR z*cTFwtH`HL;Grt=-h>*fn4USdyf9la>=tfn*pRo{uZ;@TX(Wp=Y6v|4yQ7ywj>c5a zO8J-JJI5joo~H7xpMu7>#vHiAtVuO|KRCTX`yD!d0NOu;^}iL^frj=TiX%q-glW30 z0(Fr47Ev+eTQ=1NS+`?grA$P?TGt;BAN%ccgJ+|T*ipRM%)^5No(71hyErHke#lh~ zd0X$?ek{gHJX-CZs$UsRL7HrVT2W}5P58cMybQT?POIapAL}({XZaS-z`e)zv6MKK z5_OR22|NihiWb2DDFJ2GPcwfDAGcD7zY}3ectd^Pp@_9xp~qo$lne3{r)BNJOt8gz zcd-UeU#rZ;@Qjd+hajeXcAb3p-(ZTj*M++rgtBDOs(}8jTb9)>W?0-uJnC~e#m{pR z@sb4YqJ^k`2N?FNt@KlP282xpl2mkl^W(GC%LxiHadl9_@;{5bf#zRcrz5oh*cf=0 zQ()5+Wl-MXD6w^(J^{IVbgD~=ie3F>P-3)x&$$@y)9wU03yU6FA&VL#C!ZBGog2mO z?DN4kdqg!`4}sCNrZy21F1EW(J=m@~moH-x+bZ3Ab{zIu6MTp3n}cs~0$lw2y2)=) z-+XKM*;Np6o{^gpM{9#`YFsC0G`2t5M8pzhH?D$CKodcz>0khF1pw7=^7yC5(l>L8 z@#EnZtLEw}$N_`%o%byzsmIET@6=v2^q^c|^LLb*;DJNsj9!<&j?XnYO)4 zs!25myX9qjlHB^XB@|?)!{6I!^WwLB?=-T$mpHFTp}18)Y}sn4Omsbu(dSVvxoPL5 z*X(v>SzSpv=H5x4eZ%Z}{%5J=yb&>nd6rjU?qTlN8{P+Sz0*0nUy>j`H*dQ#LS>Wd zO;Z#fD-k4w6~WvW;9okN;4SR~LWVP^ZMCF$Lqc*&?^Bi~*oQtcj4iuj+m9-po5JIi zl_4&=>{^i?CGv5?pLSr^Cv&!B3VKOQ4!-_F+&pf&J=A1z&{{7krA*O@+m&PE{4V)2 zC*M`cUF2&ebSqo9E5xGD63wV@1xqlh<;baZQ;(ZlPOY~bZq}LUX+?@K3OM+i4y^V zb9-2Ab?Bi1|J8P>WE97mR)+!Y5W%B}b^(ec~Vv)|sZ;Z`0oJ<-L5(c{QvBJg(D{;v8t z($no%_go$AeV_aJcxeaoCc>)Q;0qsMT}iOSZ@i#BYOUO~=LzN|p7PTTk!{p|<3Ar7CBe_<@G}oN%xp55mHBZ zsx!TLt1e~e>Hzhx-U}@N4dglRgfR>=OS? zG?D>Nb@NHu)dVU^v-B$#l=Nm-mC_sg=k2|SOkmmB*ZVf?d|Ow*Nt@9d2LUnd2ekO_ zGG^f*_OP*uDHiRSy415bI3zjW!`I)RTpz}$esuC#-1DO+4=qZw2kOPqw0aNH_4@^@ z#IleW!J@0#eQ%xfph$~4W`iloC7vmA#&BjYJz(8nrA7Tw-e6obpsjFx`93!~apRT-3_Bk;db+!ftxe@JrAY!|ZUUma?36 zJuAK!Tud=M#GimZnhh=v3^eCQSK{Sssg(yOCO`>C_T18b-shMV>lm6CzMb)I z*f^Kh5kgp&?mKZ576+kI3}4G(UWOQqEbtC4clzxXAttpP#t7+`Z= z7|fJ^zE}*uFa5MlE8YeBR zGH5(BBsyOjE~eS~a-6313oC>VG$-MbM)wyd&=uvJpl1^sM8BFVi?Pi7Gf0_)&FZ(G61yv4 z)}l)9PWp5MZaLMuxI1@VEvaps1K@W9Tm5@-`YJNY_N&xsBvw{5ZQ%~uDeI@&jE-Wa zL++$o=Yh(BwkT}TLwtUXbq&jM&o)|QrJMaqeCG1G6elovAuXF5gYR9gZM^8Zf=~oq z47(a=fkq37-iG+6@KttM)I#xCnxPrlWM^9YAIuyPS`*;!zB{K*x(p^*;21AZusDl7 zd1Q!Cak+NwlElG~o8@(ZiD=~;)K(HV4bgf{Q(+b(&OqAJGU_hm2A1UTN8;m@6#># z98P%ms)#igHYT^V$eO_aq*T+v_mc~fk9=a7@Ah{(cA>N` zLvn|{ML)M2k6ujN>U4pVRFsD53GJc;FP{&)0StrNW>>gu3a08K;^fh_9aXVW$dstv zv?!~f%uwzAV;jt%X~CQU*u@Vj7k%(EW;VaLl4?YMe{|xjE^s969tMPMpghW}0V~s~ zx`O@ix!VxWQ7-P*w~DMulR-@_Q;-uM8p^*!T0aoqFV_cR4$d1*-NA5qD4H&{lY}*P zX5y+`D{*5G56WP5rEltVKL+(u^UHULmz^U@A1yKR{d}_wf`a#Zy3~K-uH5b9AvSXo zZ$NMH7|&@T&@aa+urs}Du^o=(f!sg8ociU=)T{yZ?-AzF7moJ{**$d7qnGzv?-nAS zgQDAC8$+CE(_@ypY67(F12th60!s!;t!lH?O4y7Xu=Jnu z<_`eW^vSB>kWjJr90?TTI;%f(A;@5C7z=@oJ4>wuyCi^AoSdE{VHj+!EUWO7rbQaoiyy{_&zE2AdO*+Upf6*_psvB zoPF1eIr+~L!Iah7=GXxH?uA`D_3ug|uwfhI4kiB{yvyS|iR*YL4s81rTt8fLn6{*b zB}U%b2D?Y6IbzJzuR^+`qoWJ#Iu7+ql$d;G6Y){<#^U05C&S^b+#WPnb@XE18XzAp z)@@AqJNN)~gF4YVeIL}=lyT>-55q}+Z)~0Ji31NnGV*yMnC8-b-ea#R$K9^hZX@Lt zRMQAfNkm9>Xp%}+Y;qukf2eR1R=y%S@w-d1=5xG7IZbNa0FEuaj#~|*xT6pDU(4G> zxQ+x@`=!6UZ(MKOI{&J>Imx!l=b7fZUr25)SPo;zFA>Eu=hO<*A69v$+x17=(>vsr zy6;}Wa+k~CsxMud0arJ`a+`opQORZK0VnlUaQ+`PiX|VacKD*>)BI(U8h3hn>C$SV zK+M#<^ygVzjrXA(FgN4rThSZVyZP%X5~xQ?@${D)R2ptD`sue^7a}{8#(*S#kSKbe z*X?fIo#jXD4ZhhO3c5>$4+*#C3&Vh69v`3<<1%+fX)j^4(vqo$G`KbvY34wxgWg-= zRyuxgw@;JM3R6E|y8n2tXY7}Y1JzqVpB9qzwL+55BVcde-v?X+9ViLN5ylVgrDK-1 zre@RJ0TI^g3h&CEZUo#V_lVEe1*`w6>6az!HfpxeKTiLJ^n3{r%2_2`H6^~(r` zh@gpdfmRb+A-g3&3wB^91_!pLXzsFgl8$>{DTluf;pbOyvzJ>naot8viRAbW|e*#|E%Qx2ue{ zb<(d(CI|TB6Sq^43Ls}WjbptINg3;Q9F;dOyyRWn*Zzr z6@11E*DJn=cJz?&#vpT_`>q`VpGtDTqzBK*U>b=DhtzVtqu2w^6Gi@jIwH_JO$F-XOn zX<+2RIIO<3q<(?AH$kq!7%HLlFl%sk*^7+Sevf3uyvVT(YsMIk*|Qrc-Eh=a;^q7E zT=@5x3LKyaGvj2S<0sMy-s4+?3L*BP=a%&P^2~4s_8po|M4{wVTqCq6jR08(pd3oS zTsAx8zjhX_=#R6?)80g+41>+j-~W!ozXeOavix5k{Bq+fHPXk-o~-4`n6nl_jjOWN zPNaL$5R1u>QaG+l?N&7%%B|A0ewz6DNK`DTwXzZXm=4kVmC^I=Dw19{x(HMAyB5o7 zD1=&!E4gQ-4@=>^wu7vc2Ki#3tFKn}OO@C-oLuQHxV$IxuK2|sc}C;U!VJxh=OY@r zsy7iPP{YY%552vkRc|^xs!8w5ePBczyVH@YJk&W`e}ul_A_UUWWbcFb6B6)O#gB@1 zjj#_1X)+kA$S=|`&`|ryQ}*ui1m&=c!0o!wJ&SQluy|B9`{y;b;(UuR8&w_h-O7LP zGJddRC>xQBMwffg&EU+nhOP%~JGXk-o|%!E)=$Ua*4aV=L22S1R*RsycSs zjisfCa)5mXrg(~J0f^vmCAN2yNIcOMWhQQxs>~K?K2pykT!gL|5o9+Juu@{N>Zaxg zuCrq3Yl(b0cAwq$udu9Vo@EbTUV}i+Ap&|x`d_DNuoXZ7-GRDu+DW+A_?5$o-_gXI zvF0B#t&Qx@P(SYZ!~vz1~A|#4q)d8x_Mk2v)(kT0||9uzYa?=L)4+ zKm)MTIH*F&ckiA9)LeRS6Tx9qN>l@ND5K;~1dO-A6TK6}!`h@(_{E|EMpeF@|CYbj z^1fMHq;Z@Ko$C2dLSXY4{X)qu1MBEa(g&G(i%_MPqQ@n)2~8`K!Taj?tRb)10oZ@T zErtKJml<-&;rD3DlZQDN0(5>Gz-zz2tKsf~u{5?nzdY<9SI^R2jE;X?C$jC1Masj; zF5D9I`=QD7SI+u=EM~Bm9Fm?oJi7cim zP2M%OxSLQqd?-;O>mu!X|pknr%Aex3<>j)N{Fe zmUfBDC0fv+eA{APGf`oQV7?dM@9rBD8k}mM>NB%meK>suSdt0JBJhrKUuy`jR)8vo zr}9oRTWLgI(w7T>lUPBjdAM3u2rKN*M*9Z5ufuwJ_4E0Gn@vL!)v4uj=MjxQaE+JkXwmxneX#WLo{j{PGsoK zi+SL5ER74UY(;i?%XqNXeo&*-s)Q@PeiiU*HSYjGamJH0(m6@AjG`7qb;8 zzrLyZHkp+(6g9#e1kJBYAbetUm#bcrTkHHf(w$v!Y~w(F#(-5JWFz zg`$#oMQ*+WV{NG3I{z;0j)dg<1KFjmtVWo-q$4UTdlhRK zM#Vl-+dPKL(EW26e`SS%b7n_#UvHlW&xPcg2~ zrF2D-IL6s&Ukx)2fkNLw7!KCo|L#-$3)I4vQc{C57^le#{M!k&z>4}<2qr9rzKURZ z>hwpKUG_?)5AL}M|6VDv<${`i%)%*))u*{->wdvw&EG%>mlF1Hkv3Log?bQF2WDVD zC5OuG-L79XL$wUgDGlCtx<7lT-mNjb{!G~IHFtE$4Zd&&&^!wU+>40Yz0 zS_(u1A(n@pFBZ@UmX?ZRSF|{v@8jQ%WCF|GvUW-Tx!81%zCx0bsr;uLWU(66v%=Ojmz^0COg;fz-vDk95mDJOLY1F$zd|+#`U{Qair2iGF-`@E3nk3DI76a)(=Tz0c~R`+z%Cs;MRPEeYr1|?fi?x#Kvk46k!0Dqu72pr(2 z#2~=MqzH;NB#De0Nl)=^XaAjJ)X^nhtc=aN?xf#%LBSC(#LL%COEyUw|0K(z$os(7 z_c}qWa&KLgId`m~7IQyBH53R~q>w5`JC-vcL340k z1D#NEuR@BBi-&a|RP82zOB^DN$JP3$MJyhcRtn-}Wbfs^ioSse>9%KDgWnysILqGU z=FJc|ySzp)7u8_<7U{XD+X@QOM*|-}7n_p9PsXNkibCZy zzjN%x#?C!HGps}GsCXBV6^1JmLh z_)I(fS!&aGX<i?-?JL*vc>o12{(Nmh(71eff}#KKBMD!4TEa&{KoVg z!~9|8fnkEnnI2HKYZ#Je10-B2q!?&)qJbD;pO<@Yz41)1&W>btZ};Tu-wpw?-3GWJ zfd4hUG&VVwXv2*0`RpQ8^Q9{T!VvdMF#N`vztqXAYvaWYr<}w2|$VZrr%_j94J*wOIrtlE33(O5Innr|CF_&%GjX1yTkHIm&(0# zIfoXdHzMzad(rt6zxIM>nuZ^+(=JV5g(VS4i;a|E`u93|c*-_LD_u9fV}JCoI{oJ! z{>C4H8yMu1CQU1gC`*(c*~#(&dzs(RJ8D<_@c1}a%Id&paw8XCK$brzX|k{Y`{2YF z6{Mpl+|JAf1U4x-J66ijZ@CB0JqJSDQA;F)L}dcvVsx#GWPJ_g*EbA42Rr`CCq;^0 z?|G{63GMSI=RfXPqI<7W6uP6^qcz^^)2sJ5FcB;>BrBO;6AOWNECyX?qdSYzb}Lg; zZdFOMSEuGVPY#D2aO)mU#JB6+eW-4{a#%LlbcLNJBW!M1BbegW{x zHurN7!tHmcMskUy%3NhLMl)Z(;=){OYh``3K>8dH&HxOu#n@+Ih~yrghI&zWUv`FA zbQf-5DEQdS#C{88iABI&mDD67V;$Yj`wNNI3J+K^79HvNeg{aaR0zl>xMfA+(_3`3ZGyr}vk9DDgG>VPIMR>4HlbWX%#zI2blwqxtY#sigwXwEv` zC5owB;O!(F@O6@W(e30`P_bX-%)P|}TF#|8n%b#{2n8=hXp?kU*2o#r9B!+J`AJ>o z*hHkFT+c4U^GR8mix(8{+$^||6imgeHU-0khl_It))~@W!f-!E?GCA?f z9Uxuje*2T#zF9&4VL_10zKW1Re_#Z4-A)3Nf`dr@BpsGx2ug6yI>Dp8<{ud{;p)^^u zlUH9@9o1A9C33^~xBk$CE;PcU*2BVr59~wr>o{N8`WxlT2JwArGRFxLTXIcG9$F{X&_*=Nt5!TsdN_`~@D&|HT5QZ}k6D1q3N|e*aC}%=2zsl|DdFDx7WmDcr_yY`7(z z8e&H_*rSF4Kyj0}&iFHmE#IUgHyVcaXr&l3vBCZM9I zBq1Sdt3}OBX|$kqHl9W825Ojm?jZS3E8-V|i0{9?s(912<>NL(wOgqdu%$#51|(@P z@x9dK*akdj9EhT|-q2vzG}eEXPY-gs+5OMOndXfa0z|{S&ww2aH9|(rUL;8k;D&$W zTPuNvKIY2*E&F{hp?~19ufQR*@Lq5BkqbSE^B5sq58_Y4q=rPLfAwJ_4drb}v4Yb_z^mJtBZV zVy;Ea+&d+oq|X0GDZ%#~`c+cQaXe>fKC$p5;sQZm(2ud-q-$3H6uJ>oxU2Os7-;UR0{IIV1J388I+eY!_B4b#xl94%+28J(+AXp^ z8r*~GXPP?*`03X7IFfsd`E8$6%KXS#fA;8xTW^y%ZYPQ%Z?`JJmk$0pQCEtVL9 zL%6+d6_hd{m&FV&6a`c#@ZsillyF8n+Otk?L?JYEn-}g zcruy8wRPbT)B2DR*k;kAJ0K3xU{0(k2G#3X24JXd-(%#PrvHkA|M(#5z2hg(8e+vl z-xpWjKgpB|>~1i{6_`YK6Tw}*t;BW$c+&smPa*M5kbXte&ma6!G9NO}z)zT`%nO_d zB$nsl8jCLD?m?9P|KuI+H~TrHi1g>4#0|u5TQ3SfVplq21h@PYvh$*>InD9Kf|81| zn613_md;-;T1>mW7Ig^XR38nHP8_Ckf(juW)wwkJzM*Rq@ydp8mF!e+_yB+WfDMim z2>g9*NZ^zI`_3)@OnZ8gxO$f=_Kv|91TP+n^z?B{)j*{~1PKp<+p`s{e7U}=?*XlW zHugI=CEbv_$q5;q+?g{zpT|Lz;(ayKItq}^F_LoRCgMspY7?O{1#-4fV88p{m-KJM zycW^mvDA5@dTytH6X$1BDKls8X%~vO=s;eknjuKTmDVb^D}3pBvmT@#b&K7j#O$pLt^ z%isZz7kip+d1u94(8Zca_Njb&B}`vm^F=z*^!ei;CDD_2tO)?{$&tjj9eVd45AIQISp=;f8Mj-*F6J@Y3n+I3~-;s*;lG1j`o|7kn>da!4BO z$mWPQk1!^S%7+mX``kUTG^%UMT?(e%cmYhb8K0p}r+Op!{0ize7t`k_nwtpYSD>r# zF=!urETjMwAlABI18y-~BqM3n9`(wFlc<|Zx(z`oX^k&P@pts&B6OJOX-eA|BJ-bM zmKR*aIaBHwdU~CIEBl7Dy%kwA2M#cDB!3%UtJE6|O3!-KZ}^q@PxO!lEupk_1ecKgpxc zAs@3FsVCh>8TSMthGhisao=B z$|*_`_}$=NsUTn3#r}cE#xcUyo8hVG=?*9RBWWC{<&mRl>u0?~E!M|2wV2H}q~sUz=Jvu|C+m ziRf{bCEqh03cWt@>t|Y~3Qz;?iKKG9KMs^B{_9qMP|U5rlpoftwhh3>lFHNbaZm5f zkO^B8S_wzboLGLboDdS;k`83o&VIg+&?3tJ2!8s(HvYA3{^b)RkHR3=3c)z{AaQpV z&al&Gu9(_9-d4Bq1z>}+Afw$9bLdJ4cc$=Au*xmJ!ui|rH9l2|0Qlc3IP+%wyaXT0 zXt146a?XjSxZx4rr$@^hjyZZLC$|bvWdKioeVrMNC5EAr&P50`Dj}PIFd_O))_h(1 z@aqtztD-7H19t}HgsiOw?p*-KkV#SDVBfhNQ13=cZKurLM$3J?GL@-`&^WU=9kWrX zgL_;z3o<^(=GA{j%@7rHB9ndI zcDyu6B5|24QOy62P0IpT*=|UjD6UBC*_JfQfK3})ZI(~MW{?!180bHc{WPuVwA_ zT$}kO!u(4BA2?Q|yoor|3V<0V3nr3OvYI@=0_uYej3?g=84%eaA^*odp}%xDk8j{` zW$9e6Ik8CyKwYyZwL(6(9A8R2=?U~zV0w-L`w>>6s)?pB@SqZ^K+__ykV3ecANck! z`r#KJ?1rQ;aHjmi;K;C=;&}trI)X1S=)Dh@TUb~|aY8!ztiF$sUsy&Hm6cBK4UXL` z##_DVnT3%9Vvro4x+kPvI-JbrvDXa){HisO;oFMcg~ zXb55Io}RyZDn}pKi`+vJdYpB10H?^6=%4?tSn zpeU@!c6h6WP`L0c(w%L`=i?h!`FfNgpTUk>wO2hdRXWgSDgpO-RTD&l`8{WFdn_`N1 z=pvMuh4cohfqOKJSVY996I^>}Fc@YPd}&eu`JRq?u>YT%F!O1aI<-)A*>9~?Wd_y2 zV95cg-JY-YJrt5E`g_NeOB*jVWZi2UGnCQ1lXknc6DmU;Q%_a=LBaw-M7JR>5^aMB zRyM@PCKH)39I$+?(sNQ>D09R+H=Ab%x%Og+v#~C-LQ!=$rVTON=OzML z8Dc0LfvIZ@3v#X;&)KJXOv6W^hjO>{Y>HVZMre(_b^{WdB^aFyZYrK$ADL762Q}VI zhws+!`dpm8fU0N8AVSv7u$E!k&cdfFPAJQ9s+gOol(J(tIz(cTvEWy=uCWkTAJ~xq^f3bOX16pR{)HiRFaHob-87REh`PWzT>d z1@uIQ`@n*}Dzm?6xBu{f;!DZpp;sOq*?RTThdCO{z@Z1t)_P$n+CMVk#|O(4xg-wY zt(b)OOSQ)rCSPM(V0YrDAop=h%>e*me1EC2<85Y%R*+ygp8IT>C+=ZyryI3>n(_Kh zE}o}`bx>5y9f^UxO7jE3)egs$O#%ZP0`0-2a*!_IPXD8IMK6SLI#a}rKjQk#TdU|K zHlm%{h7Z64@wr*d_lZuk%4vCxZPnrTI4EW?44&H5opvAs3}Y+9I^2O3mBq!Um#Z|7 z?8&HeEU;vkL2P-`#}`W^OVY4#uKB#k90Aw7n0-#vDbDeS#cs?#f%&YzXWO3=*jM>I zC*SvK<4N;YopTud_->OP;rI(9;A);wFJ}5L7;?Ld9l?JBfpeqb@QCx`0vo=5 z{|wwl$o6SY_WncF>zfF=WM;nghd_GdbpfOu=CkVxS~0O+)quCRy(sJ*1&Rf7 z$G46A8fn`7Qu+>#rRM!I>h6_p0>{XnR9sY+0x(ct>jFtSe-Zqs5jlFsU;2a|y=_@Aw!`}b$N%*rbdaU&^Rlpk-3rFX)O=1s-)5Nc?T(w~O01fw} zkJ&jd3ZM<<@e&cXC z@kBiaZ_FFi$;(QCY88vxlA(hK-v)FENDA64mDf1Zej&$;!oX(!FYJZ9u@EDzNc?q) z0P{<;2_^a7yPg=!Q8u-tIGep3o7x7zx3Hxn~!>6V{iuLl2#qBWH zK_IiDJZX`+;4Xv4Lx6_%kieg?)bcMd-_Q;1&k1CG&j{Fodhw30Gt1&{xBQJsMJrP3N0R>AZmJ zS#AUv{rg1IzoYhtH_$#-8b%lQ^4~Oil)^WH+i^^sjwYtI;rox(Fdwja>S~7Lh?T6ivcv$4tqSij2 z-DP(}*Mzq`1NE}pEib8-`Z1H7$I}%ba*ZDp^gmaSTT6u#3+>ae*M{GQ#7Yem22&7) z!`7k2US{kq#j8uG4=A$PrbRjq>>bw&2c9NYRe|3sX#?MEB0fLP2WkGTHS5sXE5l9N z@NEPTHW80bZ6ad8t`;Z%_sN3l%|+&T*Waryd2Vlr5$1d~MBk*Fh9QL%h$k<|u(5-$HOKPeseNZA!%ULGuZ&+^b_Yug#ExH%+b#V>>(*T18l6S}cO3 z>h!my^dBvnudt!yDaSQE8q0lsZ=MqLNr_Ydmljoqf~n>Yg5~6wo@jGu8^lYUzz6lM zKa>>A{UFW-mN1wL;QoVa{Lkw5m-3Hf$pZ9x`0`Bo#Sh2MfRo?tBS?b}<2+z8br=WD zu-=}hom5dGa;G3uC9(YsuP>=3+L{QZO9Uk2e_q6|K}fTzM5k9L%aUfaV5guerMioN z1yyIU2TO>|L4bXPkS@kBK_Mf2kp>G&#XucgxEtzM5rNSFeNnrE54wN^Kn*7uAbxa^WRG;Z-ud zV`pNoq8CH%TV+Xg)!KA^Vb#@2Q9`i?zT24D>YfcEBoC52CcZU2$y?n-$jv5orAsB9 z!eC;rg4z=`Vc|iV(AJ^YeJKC=nvOAnaE5UGs1@Z56)K6i5`1<3grh?%)&DSp*m>3o~vn~uU zXr9+7IK*ip5jpG~G?NIhCi9gR|1!jurym;LX(n1n^)}dC-nQ4aAhPr0YUsyJ1i2hZ z(p39Wn}b-Xz_EfNuTpB=|r7ci$ zq&o1YQ*0Hw8B)YUbs{aER5%EK z{vikZm+k$>2OZCxv`#6Rnw>%on6iQ#8=T;n#BoLT9m9(6R0n%~)@~&{!aXMf44nt4 z(xPRj9iMR(Cq+u2*;}Vbin638>M|m0*~XHk?90s9NvK3c)=G>e6oo=1 z329>P@AaO${66Q_IejJP&*-hYZ++Jc7db)TN;X zq#X7xA^&~wQMqBXS_gk{3S^QJ{FQtswQK}AH+wT1;&r_}nx%Dac;P=SqE!yeX`4W* zeVho{#qW_Lrml}UQ4OtKdc?w$ENsRU*Ic@}PA3_QQi0OuR{mbBG0a_@4VjzItSxAD zABU5|)o{m@M8o*Nw=;pH7kH&b4I95HT-48t`#C zXn!ehwMnrgnA$GR8Y$Xn+=AsRz2^i$$%?A;AdbU5xkfuz#LBtV$LU$P6X;<8WEVTl zIUpI@WX*~Q(!O6hn=tC-@`*Wtg5mtTiLR$+K#ap=&E>5gNNp4v&<9BtsoLXP92e=P zP%c2AIG!!#-l})VV=ur(L`lvJ)e-(v{z$oNpT8Q_^AbSz}_f5IW4aA zx;!Q+BrhwFo#Pr^nmrZwYhC%UTAMg=f$RjNW=UMI!44^;NR^IdyP=q)vo~kXnz+lA z;Rk=my#NH%3gFJ)jh}RW=Mj(*HU-k;9!k6K^D2$fAEc2rwh?|kNyaIOGJ&Fo75ZcS z^@t-(nKvHTR^@7&WpigBXz6e2HY(Zg{=8wO+Y>MH zGFzhp1Y<@y-tKk1g}jN=&(xHw`#b4VzuF8MuOf`|?s@SSV>4wK7Yl)UEZZ0{Pg`3> z#P@{KF`x2Y$cC9uKIMn>f8cuxRN@!;;ML>d&thx*)mJlJEA#~<7`GGOyO1J{=%T;U z`EMk!;%U#=1oD22!jL6}Z$%89MoJ0GIT$3@BS}vNAHxYRE6o18C@eW5F{Jr`IHR=7;0th*2nR0Zu*q4XX2i7 zA_eBNB#m&p)3fWU03zZ7&V{VJe(WWVwQ#sQotM8_=~Ow+7JEp4k%cXGk7Qe&-{;{+ zQj*Gi9Ubyg)J@j2mwRZF(0jUVNOk9V_dPcZi=`9T$ErfXfMh~VP=U*&w90R2^&kAO zjF@XZFOI=ve^P9)SlBCL8v$7u7kwDNLV`gFLA0Ge&PCJ5OERn?)*%^j>>Y(TvF|et z{4FFJ?*Bx!pUy$r^YKkv4swl3<2{K6D+dVxT`E7nmnH! zrPH%5RS~UU3Gx{rY@IOX;^v-K%ZT}Cqb0~9LoSRPG~A7Wf~pBp(RyA_USsIfrrC4t zb6S0Lc*WVYOEegrKpN<8g(68o=5H)8qsn_`Z_EZY%}4gCyp3aaTleRjq_4?poWVfj zmH%OmhvA-W?r)J}t-bU>w+{Kvc4g5?iY~PF)@~~107G^rsV^QB_WZ^KS8TuTulkAo zo%x!%lto2sr85@`*uv=>@dF%jYnJC2N`bnR>~$JWg2GBbu8N z$656{QJY%>L2uoGtq2o*H+%M(d%Q`)K~GN8d+lTNUK7^?_PC5LfCwFU?da_v7W?l9 zQU2$;E{raa@Z|QR<0UWD^^C1wzgmgNNMHF{Z$PTxEDDWC++V_sH9V(Q4U_Mt^X2Kj z=XFF%a9jk6(~N%o`=deq(7x^O9`}e(7v{YP+xhf~1`~1ejY|WdY^#n630SC30%F?x zLI-D_0^=W7_>v6#xY zZH@LBAx_$@Lu49jk-wkP3c7e}ov=2z@J!-jGb)A!JBsRI1o}rEd5iYvc)DIo@?n;> zI_mvZ`^fIxST$HvZ4(cv4H+Qyu(7hdDQ_5iUgX4ep{ZU=UEO&6N|QI4e9id|cI76> z8M!?(^~L3<^pq+!{-@%YF?mJ3N3!zq7Avb;n08_@RP@V!wOL8gDW&#;IsIuIFq*c|!E#6Yl=5aU;G8#CyZZ|52OfcZIH10%Cqo zT5)AD!P9$D+wfN7F0(r|Ayy5tBU-0iyv9c(#rhO%tyID?t{*sY0^&MFpa${mnbf61&av7&U+k}hhtHkBf0aH4uc z>Sivj;b7u9e94`}1fj9sJuq{jvtEeA1#ZsiaiwYOLQ{*d9xB=b#jaBk9XLxW2hQpG zu`Xx8YSezGHBy2zq>TDYv;lwD3j7rW#)5N#ShL1TC=C?AAeDU7;wGB5pByxPGo zSoZ|+eiX(TXs9Q&5ngSBm;Uc1U4C#H5ZD^vs#2N@&O2PSwtDzcb@y3(E93HDB-J{^ z(-`$>hoMFs zfCEdxv=PgB?$rwqghT5yUfJIpS&@&!FbI*3X%%9wulkB%GY+H6d`<_)87JmU>(k2k z-zvPg2QgUaz3FAG1Zk-P@#PI8*7#b3gd1gt`3fHjVe`~f2nV6TGX9k*7z;lo#N~3v z&VZs;YrpnIuO?@ejjDVZs*yR;Brg8^PLcJCab8FrE>}L~W<;j$MUKH5Ar=Q)lgqOp zqZYioD*byB#9&{0V9wqRS6-b`=c(sabV^CR-Sv3>5pDz~fy_EqjRR6`E3!Z;PyliF zd$2&l3D-eSE<0&+iWnok1PnHV(soP=D;B#LcSRH@)MYwF*n0KrGvHa z&DqTKj!K1vNs9|g7+!VB>@k-4WN5hMds0S?3ap_jA7nfVJip^DT9fH|{exmK8LQ=3 z-L$Pl`2-scxr1LJv{);RIQ+7_$}r2+dUGV6GKn@*H}|$&BbiMgv>v}G{UF!>uD(%k zuZCT0P7rZL-_KAlz6co(UkR2WRU;MfpEW8i@g2xA(~HQpKp~M0a*XR6Szxcr!uHn_ zwNjnmSwj9N%TUAAPcHL)db+7Tl3tYE%9_-Mr`q8bqdxF=P#`uo2=GV4^>9~`*D|(W z6md8=sQL>A74iY~fU=EL!<>}u^1D&_###I8AAWcPnzRzo-3RIr>Lr1^LOk;q$U+?x zJ#}fJIXo;^fW_9kz4c|*Br|-2oQxDjGm8e|!G2RyXH9O@@~4x=;zwbHS?1zWH{nl( z@rW59Fe?FoSARXWzc-x~y3XK-xa*<8;tHcO`Q2@}Y1z@9eJu( zVh!Kv32%d~?5>hI-4grjmTFzRnIX_Yl2+Fip3L!W!visxweC;D(iI@EvUmrW+S%>jtQv zSiZI(yM>#yy03$>I%fp-RKV5aKb;O2enlUsyL?Tem(od1uq2IL?Q&=fvn%(a8ZluK zyae?ZMga%t^PMxKaaX5a20w}OwR92pQMBXOhmW}^^; z=@aFv^;p_dUW>YMIn}nEdE!e+gyK~w4RcI$^U;m~@!;{fPK#39RFp?G!tib}4)sLi z@GmgvF!88Lz{|%j{Fv1_A9EkyJf@=>JFi~v4h{uXj}pPWc)DRQ${2Q7G>lZi;J<0+ zNw`Px*;ePnr-RD@7NCWjBoFpu)}q_amho?q@ZrR z#5qWW5J2gF+^zV-AN%n=^WPWtVLTs(QM;4_WnlBxF_wz(fgE%mLMM{^|NRh|ECQ~j zug|t>T$y&gws!-_uy=M$Eyx40l+#Qhd;D8*VG7MJi{dh@4w86&o=~F zfDoO8+SeZ?Hh3O06VY3$jP_?Gwi~?}r{jqVwC#h;cxB-5lSo!~iN!4tRunjkz$x*L zMQJw07|(8~!nL3q_(CPxeHLp=(OT?^J(T*(TDqvyM<<&GgVHW9}X~i zXy}=ML>EK^)$M>q`^F1!DD5JDtg283igY;9IZhcB>v0B&yR9REv}+n)l)cRAysE!V~cUmV2{YE1Gs!OMs zta`F;?UCb+8cg<%m-6b8Z8k}Rc~-#xN^GkA`Ls;~kLQ-U=$^6xF*<%@4=zNciH(RG}!R-_^p5 zcQiUdH4i&pr>xQr0Pp$Rn|d;t2h`8^rLxv!9!WDe3ClKD-)Dchct-Nc7~~*S-=QBp zMb~+h*Z!J@jCHT^J`-o@EeR6|K{$n5vg^$bW#P+^3v>7rm!TXh(#BZJ&$~KhTftOo zG`4$+?nGfQ6`)Maq;GAGn+NXvtpQ?e_kd!9Ye_Ip#-9qt2z7BCnTzW+NGwUq_aALr z!}lYeXEl9&+I}$EMBr4kI2$1~$zQ=?GmD zkV=6Rb+nOiAv#jH7%R2``7L*F4zU=iVQmaA+Ncp0ZKMhAg3IECcv34W8X0-DD)}8h zA?lIQT{G?o(kp(vna!U)mml69{{uwka!OC+YT{C3?mT7YQr;ZAizzIw%4hwpb?oxf z+Yje!9uB8fuhw3wA^$bT9}JOv;byarSW2wE-xJu^^A~%!9>VN|h=C&MgwNpW*e|fZ z66b&5GgJ-VmErBAzkz;cPk5QzlXuEO+sIX~yMrQ{1Tlv!DQ#t1Uxtp@Acr25dI@Ad z7f!9MsG(y+K+&wy%Yko%9$6ALD?MQ%rgnTyt`y5%F1(jOnK-}nWpm(L92$?wI%Fjh zpom)2EZsU!#X8z?TYnHnb5L$Gnrel36-YT8ya*cjO8AvzT*N77n#?3|d_PdrBvN!(^~WC;#NyfI5q{HD^K@qb%pr~d8{1lf`O&yam9deM=O;bB zbkPyJ#sIz?YM?+rU6Z<0``Cjosy8u*t_V z9aGfJRbr!;$tWe>NkuAkN`j`RFhc-Be!ArkMtm%8Y=#B5@I|MzopLw2Z6w5ks*xi? z9@(*nXO)zFo2t}D3lL_~KQZYVxzfqH80Biq^c6-OafPtI^;tW7pNwN2;yfsuB&ZYY zo*XA>I(Terkg=L@1W|N!@)i@g?f+8<4DW9&QNK@RNCRx{%158?Z+hCgP|jymtCy-9 z7mwI@tU!F84rD*02=LMRJ@47MrSi;4Yu01d0~eT_%}S7r6B}oUP)6?ZH-$33W#(TF z^@n%T|MX4@o-wZNNC7+UY3_ATVRA2w35^|r;TSk0>mCINnFcYg?evo717YzcKdZ@= z_=;Z3(w;rhqM^`PBYc71=byY$!>cCWgLwZ zuxo%DE2R~sTmZ_VrMM%4RUO=s-|@}%e!HysnjIVO zmEQYC`x4dorto15$n<4YXmvY=IGf^iP6_}cGU|)nH?NQoo2+M9j6OWwdoWm#EcZhoY~^|wE>KgFK?Z*saoBDjVm7i`tl)8j+7#!6tp0tUo$#>O1X@t zy)_01x`uF*6f`&uw7GEB9&}V@w2#?INng?39aGtBN#&8%P*iH`bNA(J(#q3k(m2k| zj3XC9k7)pucYuT)a`D^M1g^?1*BNAF> zhYIn+*SlF_?@S9i&c;EzvmKMfz3(SnkJG2-_N~(2 zx(QU8ZfKk|Y0yS26&wF}(7ilCoeSTCzxSb!z>}?UZ*?fk0 z8wm4?)o-TkPY&ZZYWe*iemH61PpBgw$m=?|Aa+sj&6UWN^f!+^AItG(FowbN zM23iy3h5o1K!5+qbRgCH?rJCFc1u9Pk{UPlS3s}DhM!~N=F93UjFt5dc8^hk zsMniAnYdmX5gW;s@XY}p7hNpM8ri^M6|{A45i-D~H3zR&438$y^UIR?iciI^cktFQ zz86Ay{SHXFYi1S8qjs;#@8~LKJuTT1c=f?F=6uFwbg^Zij4BX2s!QJxrXJ#M!?RY9 zP$X7~3vOH#f24VpUw0#cn{9x=uI~kZHL(jpS(ZYYzpZ}Rl{szYc1~~#h~}uJ5;#_H zfbpw^CYW+gY;C#ml-r=zAsgP@7_NI~Ot5+7x+a*kBSO#nMtcgO+#&70&u2k`t@@zL z7k;}}? zVAQ8taHtQ>xN8R~32|*6Y(qJKs#C)7od(E7+sxPhf^xrI3j&~Gnhq=&RF}frB~UKTLyIt&K82eVn%y6r0L+S`5>yTvd7p%*lZcL1>UZ(=b?ry%`ccRyLKrpAT zdtpsjxFI|3XJ%%1=R|z^C(<-TM%LTO_mweMm#IUwAh){=ix zuD^6oz6<+*!k!VQyYer5lRgHwE>sCRUKdNao>0RG+g9a~%P z*-OyH_uQ}0sdXxw`pM?5fF)PcR zK$NB<=AlK}-z|j%wDC{${4T}A;%5uZ{7hSS=7nmSB7I`WB=SNX7`oF!5mRb}so7%S zH#j=t?gSv89REP6dq9hVDUil4&Dzh|Tp$|~D%X2~#-@YRGiFM`cX95YH(*B=FD7wM z%vlny?izsJpI9zaCAPC`ca_n&pc=(2Q0%~m>ewf%@}Ev_Z-o$Nfb1BaZ=0DK*bR3& z)hwQYj`hvw=Awn;nlR^<(zZL}=t#Sf&m7vZzOKIZjjB`phxZZ7`lw0dnJ3S;a?6BPE5_m{1+M;&RA@xo3sVyJzS>qn;`}U!LaU!6AG`IHZ}* zi9p&?Su)A$nD*MGBMU=b4_!V)R5>Q?P?FIUfG zPu1dhl?L$$xLkGWc^+lfL%(|}bXZrc_gcV{Ot-_wx+b8_tFc-&ZtlF3=OpF}4rf+m zE@V1Ayj)|Y9*2dEM3<9xG$cjr$G5h^uNZwAUTf>@aa~D{6rC2bkdB4|HMOkc3P|IY zv92^vdX{6X&3?$rj7Ien;`3<+3&mh!M?DmV(($7nhu?bT-tuKb_1MQ0YjgW9hPjo!{oO+8ZGIp-k$0r6loTLN!okJs&0j2{8<%a z>c)L>C+kZc=Tj}33W|OLr*?9QB~buS%*S(X+qhZ8W*5gL5I@Nzl@GKZ?{V--ZIptJlm1@H6=B8VL@0SjjyzC zLe+@E!F)CF7~w#45lH>n#E&4g4k;=6vC18`*5f7HGhb}dKu3yYL6jhZaemi+{s)VL zl?DZLI$L^C-8*MHT^ex2Lo8azrF5Rpe23)tYzzr+3@Fx~G;*$p5M9bo7XIzvRUa5n zX_mJ;DsdrRx5l^Ny?keHz5w~Q$KpO!_4J)$C`vTi0-2!S@c!Rx?zeaRt>lb9p@A9K z9Y?RGPsl==fW_)Fd&wh*ct5(%=@6;Fh>n?N-Z}oUzfw3g|B(5(gION@BLzU;w~%+5 zAYGnE6w=uqJG#0Yqu;ff%UBT_6)8zDCp;k4Sat@}{=TaQDat?O4f@Moi+P@lqMlDp zhEgTn@-_R6tcxCmyXgbu&_|braTRsF_&i@0W8b7C{)D!9rjN!6!_){HP4G>pmnt9HT=`y486juPZj;blV2M4%DN7(?Ds{pIuCAll zp~o3FF0gxwnp)rY6eE&b%U9qS3CZoL-C$Q>G+++`>8#@CSGGIh5326gsf@E_0!1t^6%qm2yMmQTy zt_vi=8eOW&U>v`Yas^ITlw<4jeO3e_mx9Xp2{0KGxn^99X2nHl)TS4Nl(p~PQY}mv z9cS^=R^yPn|2Bk30jRO+vGiGgMmP4Z>Z+SxEek$Noq%d?Wj0JZN1#$^0|RcVU!HZy zWmVk~OgT-_f-I+D{|W5nJ`t)oOvh-){wpL+(nOq!E}iyS6&K0X!M0b5oO zgocZzD6N%wQr%RYT{__<&wli>&TO=zJ9N3)6S)R1=3bI5?jb; z)fopvmFy`_QqjT|3X7oUpBFs0R&5-)Z(Dz}hq~OakGTbQIx~_R|tP*N^{ zt{cQht}e=VNpc@<)~)SHdZo3=fq2zN3Yg}6y>PlyKV`OAG592{5d>=FX z*~|3TbHyO7WK%cQM#x6j(hKx_Dh?rq#3z%HWIn6O7DxFT@lH4ei!{+`j63Nc zq4`1^nbo|u`X`*jlrlp-{0xhe1^sA*5bJJ4k_?=!#3nQsabtL_t;%k}*AW(|a&h3> zrvr(v05bbuU5-KK1jEJa#fQ9(eJgmN0?r#lCM6Z{HbI+4e;#$QHAyHP(1TJfwbCVZ zQV}*W%3xM6BI<3yL)<+U&9OQZa!(Y3L!XDILOJuvM~~k-a<)3-Drl3wsBso{aL;xa z0r5xM_DJMHO<11;QA@4yPAV>cU?I6y<7d$755~9(N%fLbLiM2TNvSmb!%N&3s~U2R zeKafWdxcDR4*S867gs~5y`n-)2V}T@t?SEh@zz)HTBOfSQFYNt8yia`PK&x!e1V@l zM1apgXzvuX+|l4oc;4OPmgZ<2av{&B>-;G(bDS1m2TcndJHosg+WIyc?A+LBi#vGi zgAl`Q~koLJT5w9{3$Nrn-U z=Vu^wtIs-pTJHU4YqpW7VfR%5wBhwmf7<=w)Pok{0_`gNdoodzx1JpMIIvKu*n!)( zA+RN%-d1QXWE)G5#0tgUq#{6gA?`X`h!ST`@p-8iuD-HCorz^%JSd9cNnh>*6r+SV=MP>Yr4!?8QLFL>o{w9zfg#zC^-hcX|edB;7lG_u$&If;SEproj#LI&? zVE_az?&rH7`aPBOj2&aMQ$;-dp73{cM8?=!bQ5#hb|4R>N-9F)w&anNwEgH_U36is zY=ZKB2WhW`!qA=rWRR5CU7E(*mt+mH*SFh$xsD2CZ!WYdKOt_*tG0!zBNYv)21EZ( zHT5r1Wz;0%byvdEs9FZ!!Co(A(++J8$g@?zy%}e?%zu;0o?qP zyEH+I^R5wpU3ooRPa}XQeI1dxM_XjD0}Y&NaG|Q#_L0F6b%q60Pm_u`KRARJ^=oGF zSupUcKmDY(edZS!J@$f?b=NxuJ^@Ui;%p&BArXYBNudeIf&NdY_m{3w#a$O)k7l=u zWcHL=^b2nb?+7ry>zCN{I~Frfb-e-n(0Xj*x75@Yk+|m8&gbil#B|oxDJENG`Q?CB z3SyI+z$ee9KNqzd5}$H+vbiwTooA zf|~H+0}oosK%5j}*{2 z=hV^xefQexG_NMwVQE2^PrNm!b%;tWb_ExSf^^&)m+v^Js#|SJ8 zi4#OH&SL$DyU!;C>*KuykhZ?L*t}Ts%e@CmN~U-&L59t2t8gPlc1X`eNr%Yi-l(^^ ze}3OaYdX6C1fuB;&m9Ujw6KazpgUkeDSP`Ka=(Jo`zN@$myp8ZG{U2eomsOt@Cpa9 z-Ri9C_5+d*?9=gUD2TF5Ax_+!n`Svzhk+=x#^hXrccY!C&~q8Lz~K(zsHn+X&iBkI zrA!zU!5B%4i<=+KyRV3V>li+|`kTe#h4$U0+2WU*t`KU_b;QcR^L}`nzR&urqtG$jy zh9iWhQB=QyK&ioh34&aHxGKY60$$kF=bT!(WgFUEOE*f3cpNvX$LGb~t`a~cc>*LE z`>FXepx~`w5A!_=J}oL}g%0d|i6wI6akp8<(=fBLV{OXtpk1{Ti)8Kl=D^NsE+z5U4p`BD?>(%Pi(l(=XN{We8+?GY>e0w*C!7%J)>GDKvIu}09*x?6AecpP z64K#>az$`XWK}V9^S9KZ%N!kYkw~G~Qz<2T;<#gJAT2P~p+F5WVJkXk3D{BnzdC~7 z_)%jb(a(E4O%yj%n%gGM(Ihy1S`|4e6GQ+ zD|3yU429!ww;bXpmHw);E=rO z6u~9;p9Ciw_*FN}+15q|sU!?0jRY5nH@9%yr{I;!o~>sk!wyQ+&np&P9!nIlEu_NTNw z9eBP9Q&L=ZTm&;&i2^tN4QtKVmN36w^U8a1ZxUu}oR~K)K@v|hKtCwD>AjUQd)2*S0IdLjh8pi_x9ma<=0xQ zj062Ot2lyomO-&VcPMRl6nc+2r)kfQZ*S30caC^s6Si3lq62mg6x$%6MVd~$$O*O^ zpe>Rrn#)?gk&wPxi)dawC$t^~v{?w;@P`EatC1XWzT2$rm?W3kI*}K-3L``=I_W0v zdL(6mlukhV6-l!!Ttx*sH|Ea%GmAAvk}1jTvSAN}PrAp72!C`p0_{vMG=zOknUVEu z+~QJ`bX2>#MtgX~{YrOb`fL_wAZ`Q2f?A&xAA@OV8{V_zkNL z`&K@q)aHf@#Cx)|3?A&a^hloD^gR|XA5ZwT7NTluQ)-WF zJ{!(AakMN-xG$P99!7Jh?@cbq0S$qS6374L3ggElX7#}F{I_!BH<~hpF_i^}KXgy@ z-y=iBI6`ZP5$CqgwC^`-%4*7T@*ba9kwEuJ;2u6Tt|uE+M4e>Vfuz>xR!0>UTP*a- z+qSIUL8rux+jQ;J2-=>(2eevpA_9Jf$-KYvYH4}X_`wIP4YD*k!G<8BoxZ%i<+MCE z!VHl?<%h_C{(3c7RPfpZ^8F3h4U?nqvTAT$LnUq-fE5W8!oGO*Z059q``SB)XQ@L9 zJZSSpXk}gx`5Ua;33FeqPgHtFU~&)x?jgf+Dw@dZ72&uQRLQSvJ(IcrlHL46f&N(v z!yk|Tt5vfIRVDmxwnf~zUfN>K|L75QSi4DeCj2*K6 z4yrI|_LkykyRap%`#HDU^EiknQl z`{i|nnTHnp$Ah_V*7+;av9MFq#!&X-5y~9^2^^Xm8%z#*`P!N0SW;AKt2sspOd}ab zhOK>mf%(ASp9DhjrbETUW6763SRZHkb&H<7Qcduc>d5nxt_%YzHU#^^8IUXO8F}<6 zlDmp)qeY)aT9`)RSVdHzpgHZupi0PZ8QX?mIF1(4bfs~3=U98X&xBcYvOtkcSZ{ZQ zEb*%yn9U`8SSGu!S6H~60g}s^b6;((43|AmEU6#zcUxrtn9lzDWy_!LsM1JS=<#qU zFT!XHZN*;Ws-)my3(!oOLg{-!+Tm&TCD})@^=PO7!O$aSW@Sk5u_65J2?$V~44h0{ z_TiDHS@&msbp{(-pW<+ygR~XUzH-@kOVdrDSUfEE2`uE*wPzVT=mTWWb#!D6-PToiE3Jn48|!XWLGykf?Ll`^KvX=(;3`*IMQWaX6@2EeiRStE z)XMseDuWw2^80S!KQGPfIT3AzcZ;8lK<+__aXs$*bh3o+39!@Wkhh1pXC1|69YqBg zR}RfSzu*j+(r678-^^#2Vwvk-d1KUtm|Q#p>{%OvpoU3oD9c zFH!tw`l^U;p9@FS!_^^$mLeVYLJaAVVg`j>fqF-fJCOq4b@?BJj5 z80i$y{+SPOGmhy+LA|udrE{lgVV@k7YHO*+Cnm&J1CKz#+_A^~IP}jty#i9=5+^y6 zRRl$KbF7M@WZnlYOOcK;I{?!ENkzXe!rgKGD!_>Ds)D}MfUJ;T_j?%V2cCZMFOQTo zgskCGI*Q?!&!*+c#lKD=`nKIgH^~!nS(WRWb-)cm) z=cJ5zDI6`;Mz^Avoo4u2;>d!!dumS6#QtpxC~y##H=Td# zq30-%cwI)Qo7^%#$SCk{%;O)q?W-+eMd@{WO3ZsljOgTTg z>^?F!srP7W#CX)@BWACr!nYzQNyfFE0KV1j*>| z8+{)E-;~3pwTYt=u9>lS^cQJNHsAfpn6I}jEjm@~O^eXe^ROcn+Tx2@NSZ~(`f5imH?n0V#u-^{9h^xTi zLPyo+QKuB)Rf_O<=uO&?yp{*NUr{)iT+P6z$ zOcAzDQ=&~O8^lq@kOQIoKS{ot{O8F8--Qzh#KMYZIXTZLe&+V5O*W79i$ZCNGvdqP z+kIdX$2!dvH?s!JEqU}5O>LgWr}izPEKpS7iajJAbu)zpC#>p}EPcEWb?{!T7F+$8 zKKKjF<`-B1M3-o;GE9nENv2nd4I^)oj-)t&$pCPu`G4w5)okY(kB}{;WV**6#mW?I z-7tkzS>}?gXYqX`9VKsH<$lzg?fQ6lw41tJ_Xi?q~jA3s~uFbyB z8|`EPTNcoy!~)*A^}L#|0@XFQ*bpW(m2}~+=65n}j(izI0f7+s^z}Z@XtA=_n7=wT zMG0v?WMM#A?#`GZtrL6HRIL+IzOYAZ%5Sghe!Mayc|KQ1$+#t2N2Y_V0E7%l&O(mK ztus?tU8TE5q}=3A83vv z)tEIN>cWs|a!YhjDa$L$otQ}<{bYX-2tmb7tI)+WO0ptM*fNT9$fAOJ_pv)yq!&N# z=xNl;O=9MuiGQd%GAP@sh(?V-Zo4IeeJtJkl}oX9=w zj9tp6w7s4pq2ayl1Jj;xm0~0<^N^DtOr3cjQJ5(C35;%Nm1t8d3wTEC*G6;$HxUjv zr{R7kn=9`*)?vc%{Ze%vMLyZQQck`D&2B8}Aq1}spK~SOg}R(&nJyN>o7Lux7s~hV!Du~nbXZCLS@JU8RXU4ky0?Ud54B(DC?T+z{etBj5 zyuVvo&BFnn1L2LjdO3-&UOgb3?77eoRtrGr8-HU=CP>XBzIsxe#oCZ@W{mu8Mw*h! zlAzTV;vJPHm=0w#7wxs{CgV1SC-vtU%k;Z0=@BF#ABO~KmxJa-hac1JPfHQyi>wQ9 zDP}!OxQ)8Q2(B}*Zmy5&96G8l72H#5$!ZTnycj@O?Lk8=1j6~3K=Ol<^o?jiKe+s7 zX$jvD=Z_!6RQAusDeSKtWAiuuS;6sM_b;$;lRax9Bjh+7dj#O^%z(KSfVY6k%=q*j zX6O>M5+>nWdH0&cHN{xvaV5Ie#h4ca(MZcc=k-_Pb1TGG=2CZ+8c(;zCqA`^W_8eelqd#1XZ5d| zXyiO|mB^Fk?e1qYxj#XYM=`{n=5;%PsjedGKvg>dl4T*~Q(_`DRnL7?V-p?LEE^97d){+BG#-;w1zX|Rk;psSLhtPitQ+3WdgfSN<&I_^Tm_C zYq8o-lYCb;lV4F}3w)@uPu}u&uZ|0?zFb5Dv`z=yR&@pJ(*z_8m|TG0$W=l-eZxl~ zw5!t(DISl1l14{^_&z+oWe@~6t(W*=sAPw9QZXia2n4_Dh~+i^lz#Av$00ofbU0yb ztf9aL@${W;NZXDAi836YKl7k=sGPx?+{x<2Bw^*#^!Dzrwe+%8hqo1^S1O9eDDgg3 zF0Av>hDhUns>M=Q1f-RbkblJH^3y2=^o=bV`ZG3pwm$aMSS(UIDay3Q`xOTv)r=Hg zG~x77mKKy4vSPDJqi;5|68G2=)04H20sTivS=1||m!(^lUjRD zXicP=9W<8P4;hG@{uM#VheN`FrI}CT%CQ{u;CQ1r99qg@xXwttY8ItS)?+X-Q^8Zm ze=#|OVXP+Yv@1``&M&bQ_cDkVj|dp|>7_sLz?N_L=!kYTh{O<_3IPf{2ujL|Sb3PSKVzn<~kWK}U*`B2AaCKa_Feu{3deO+IAW_f?5G)+%fB0F5@P9Fkqi zLKh)X1v&a4jGpyd152*O|PS;$HS<&y$AO5!h55+o_y^CHzfEv+LQWAE2VH|OW@MC7mJ7B1Aue*|C2T1 z2SAixK@k5pYViGu9RD7ajds`e6)G?)r!*^R(#9ZW45T}}pL`A3f#gxaNzW=BtQ+fv zSf3U$p-dPblt&%njG~5S>;eX-?%u+dm)wJISP_nNy^vRBxkZoIKc}q*sSoG+mqDo> z_4(i|44?05p&>3}2`W%w4}u3MtMB_pA>^$6(~1adUE8w}{1UiWhrFVF7!C*rk42ok z<-hG*#CDp+@Wu_fD8|bt^WxK2e7G*FY)YX>-)m9@=P6h>EvNEEH`;%JMGOMM1ZYm4BlBCZ8ZI(>$>SdgqvrEm`56AE8dJ9>3E-5v$ zVsx6G*EvvibIyYeEoiIm)_JX?_^a=lWu7fz zYW@Wv7;{^y@VLE$27gmkfqJ!t%p+Fc6#??SQcpbUo;esHIxlrnFa3T2jrStDOzFDC z;q-f!uYGvA3LVwS8cgiv_(S$U7Wavp6#jmGOB^HFO%LbBtkDVP>z!pmfLF99!8Z&( zMDE(VFlpe&wU{{wIKOL!(Z{?ENUMBBe`?hM>*9gxi-;*2aIo$@U{YAq@Y4JP%U06| zPE8;0QS&Q~W*9nss$`{WiKg08h7K;_Omkn(D(zQ2S!J>zj-}HvXUTeaTOPwf#9uLo zVB>A!`$vzA?}ktsRGirBv<@}C+4NE%ufLB-%Lt}a{Zb_=KzPM0J9E{?Ay%<@7C*)_ z)p5ZupOsiL8MJ(^39P{EDP-v@n2zzs1#2xbfqj1fQxy>(9uHipPD{%iv>C6F%QVN) z28UgB=||hPTkSLbDBB|a z250g+k(TZYun>qhsxbCei$!^?lJ9k3s|+HmyMwquY=${(Ou- zmW;wauAhHT3!VXuXaAa%WzXW$MXj%qsOW5>hK%|9xgxh z*ua2IijROdB@co|@UImtAKN`;Gpw)4rW2fYQNFmx?L(Oz6>w&g3h0r}X~6nFxj}!J z+Jk}Suuvmt<-J1Le)haoh6}1VmeUw>012O!O_92vBC96 zT(=ApKpeD;aZ>{_&a~&GUs499_$M3tmU|hsJ4QYkLz|Ul6yMtmF^c#iB;Ao}o)}6+d^@(iuabbgxoI=}rm$C0=>-wSY0gi<|QHCFF zb4?d*p(f|-DJzILLZ@;#$1>Z#!dZWUasqU+1S+w7{GMl;6Bnfutogd-@fw2e(?e2r z1S)n{1PA08pbH5&IR*wM*x+cV75u%hcPh(eB1P>RB9sF;5s<9)ssh`uPQX>H-v?RR zVfP5n%r_(R;z-#sl^?(h;*P%8Nh?ptHTE@h#o;g!XOrZ8(^MZfl^Q1bMp|e2WfA+p zM2_?9XHaBoWqIDyY#{7aP zBRhj|+b#_j17ZbuamNti)IIP_Rn$`Ykf$WSucOhbxH$Hl+_SFpM!Z8+veQ%9*1Y7T zAm#Dt*{82!E$~SF8yhBXv6oaa2i^yq!Agl*v$I=NS_jk21NzAThAj)uv4&VJ2^ERZ zea?s!A^#-*lhDyRw>rf+LSgX}Fu1E<-OpmW7d+2IFx>$GOP}fj<1nyN732e)XH@C% zC{%B7Gdd2I8}WB8&Fm^c`*PK@RaQF@_#Q2NqMF}|K)NFf7qftVtrx=LwcRLcz9$U| z2USHzJm+9Skw&9NB7t536y|^cotNIMJxCPlh>EG2_u+8=Y zobsrx$bv5}`K83_6s9eeDg^>A46+_IGrtz_sql)QKVisgCL&*e$8yEL!k&|kFaxrwauRKSq zt5*oeJfgssc2&Mm3(I`62w`OPwTOpx*DvL(*`j}XUY4Bed-Y8whCKnC#d7K*K-kXW zEw`CB9bQ$Q?4BH{ytH^`3vHlTeI^J?IPhx_@lVUbKQM+HS6`68j=|*%Bx}~borfcpaRpPaqcVCvo^ZY&bb?EKTXBYT5GM2PZ~B`%bJo}7B(jCS zj)Cog0rTd76;WBE!ewDA2-4Vi@3J(`(ZiM=DIla713cgo7^LfB;L`U29l8q5=o@JG z2S5Cml9hf3%J2KhEH@Lb3XzZ5^fdR(@165RB&H@Fe!C+CVbFoI{^RakRacbsXf*S? zxAgYd!{HUb!1PHAZ>WAR3791t3!58w%4q9s!aQPX=Q^o>ffXJ{A=9_S(pN}+ST+~8 z#x=q7YPmKG$UWgJ#qS2Vwgdo=@V~qJfAI779}F25Vry#9QrBrUM)jCd&UsK)j`1k; zDH;u2@D+%869Z}+0E4MympiN0FVx#9o{?ZKmXK>*aLRntmrgDf=7yOPoZF=WOuAIp zb;bX<46OsT0(#8ar^!;PYDo+3!X4ALJ52;^qE*-m%6)|AY~WXrJ|vO;UMk?#u{Olo zGn$|;P!C2O8O_YYhq2zH(>^Z^2Y7vhj|dS#;|F`-2SF2f0#uFi0wKy~)B1CJLQZAt z4ueK*b^5(AkTUEB&0Xqd1s^>Fwcw+}xe*}tCN(M#CtR%tFE%~`u9DxLM#%(}ckS-4 ztWKC4Y4io_tK)#IVWRW%U;QJgM%wX}Wa!>E}?)1#g?DWpx_x|Xd ztE#%0H#2W!P)0^ZsIX&XmlkQbWbQ0HmIYtMYaD=M0H9~+3_x43jV~aNaQy%V%iMWg2SD6?%{rgWRlto| z-RE5;AczCK4O7I-YwXJ(@waBt9)*c4{Kv19zH+?~$22!+TMt zyexzRPyfz&O+caK_o|4h@_0uH4302cyfCV+n8`FAT8b-ToYo3D_g!{5hrnmNtcNn`=t( zq@`e_h=ImhpVXKRYrx16JfdNTR=A1s6%^1`S%ICSWh^3tus)2<7LKfvC(SU(;j?%S z4#J%94mmg0#uiW~JDSBz+Os2m7GN%@n!VcmdE!IozoOxNhLja%_N<9MVQu3e#J(OO z)AOAuEt<$1(bAv3=HPeK`BzHQx1vFAX@PQA&dZMOd<~;Jd;iHQp8Af zkBJa59QU1{X!G#1n}EQK4_vLg#?l+2oR;oFrSNHVv@NY9MfRDm{3(>QiSj}!0lUDk zrMZwQ__ONb-~bvqO#Dl%zl?=pkcRtQkPeWqDTaM#y0Z7gUqzZtTNt_>sDx+T!q-`v zInv9jt|ug8-Uf} z@k~q#3`Z^^DIs6?Er!G3ALV~sW~AJ z)1u^U(pC{g4k>_b;+U0y0XRCf=*B95>J@?S4*c@F5<5$EYp*)m-I7c!@J8V_LD0Y& z$8e@U@*2>%6UL5fxUroZxuE=1YO&qgHe)dwn|-f^=y&&D_Fag6Mj85TU^`%f`soBV zRL%G#L2)EY%|R*;gJ7T1&IzCLpNs;XKGi1l&Br53Dwu@>ZPC2UlJ0_Mmd@RSpT7R` zKz#g*BmE!IPdfiV2e7})YEJ(&p}-o`ZwzhaOc3irzn6dxDkHx7aZmkepVIMe_oo&D zzF>vwjVC}!H1~=%qJUnCZXBIgZ7DMw_#oS#D77$5$CW5p|7IF3;MT+3=B$y8RdWUU zxfE#j&!$5K9B{rTEao*-uwoust2(mcwXp$d<`wxRxG>c#a6sT+Uw+7SfMwyAz?9m5 z6+eJbFTmMZ%OCB_5TRGhp66hk+0lhgkjEN<|Imd0qyLjqkd&35Juc=ULYl)^( z!pcFBcM>khUu4uZ#7S}bP7tfY0gwVepoT&N)S3Xg5ap3R1CoF^qd$qffXqPy2;cb_ z5FG~XVC^T7BLUE79~k^KtJUWPq-(_INl+4?k&6Pr7T+rR%yZ=P1lYhnaR%Drex~Ap zWSURe9ote#2q|!OZ~LBAm>|S!7tDk!kuO>yfbi33ZWNrH8=fxFO2|ZkM){TBm?#x! zI&vNdGMIbZR6$`?RK4O7cl{)3WV#UckMKoAw1F;~_C2sb>Zj)UlS=v5Wdv49O+p9} zi`MycA8$PoMDwOUe(_Rf7SQ6j$2)Wy<182)(zAc-Sjih<5P1IF>eB#(hmX9XyegHn zB6IA}TJe^_=yMf-%6x%p3~-0bc^)E289as$1U5z%1cZYa$WmzOQ(1E=g+%}XH#usl z!9s#n4@XbGEAUp9J_4R_BAc30lozPGs>)*^n?%&uy-<5nkKFoc>iw08|6#KCw*>K* z8}<2veXRa+PybJnQuhAj=3icU`e*(eZ#wLHx6=*93e}uGi zJcHmD0$3f@jT^CqlQ}?mW1x)S-@2V{D5|zI!-;Gz0_nWpf6b}-EbvRJ*mnn|l%>W@ z%Ht$Lfe#7{mT%bQ@8#X@L4fKuaF6Jxkn#L#{TQMC(T@(7=Sf)+G#VlQ%Ev;b`W|R& zaH7z^`NoTkCal|33YL=0Moc`i6R~x4Ta@Iem#pF$_U671Y}s8Lr3l#bF7rIO_8xTQ&F`=TZ>qrN zOg@l3SnB{vgpJrTR4Nq|CW^O|pROf2n(oX2zke>q_H(<3{TbC}A~IybiP^)rQeT^Wmmk7B z>henPAYpA5z@-oX1W1Fbj6z9SQUr))QUdY_2-O9k%sKD>Y1sS?r1C%ICh{LR`+qHV zU>dOnqa*v(rExaCspy-(Jsc8iaN_=%LN|(oCTP7bsl~VFfZ{+p$?;0ucghXmcL4SC zsH>og;h`Bm;6WNLogR&-azI8#dncb8pb$U!Z7lEojZogitg6EDR6e$I3C~F%N$^BD z`RAY6N6K>7rix&7g=*|a>c-?d=g!58W$}*%0q85?p}q8}q>4o(d9)o>Y*OO|fC5%Io1wSl-&Pn!}6($sGd*TofeGiz>;Xi;yWhrel%;=?*ESib2PE8fG0uGL2 zkc|I~C{KED+eZo;;#5?oZ|@`v=jl^FY}$A~L+!@@W_b5vz}9zcXRJ1 z5blBF$t#z(*0ww>El$a<{4h+!jvpxzn=jm%1*DYF-;US2-@2?pQc_q(|MSxAfh~$l z(>6cW*5*AJpi=%blksLT^PrlvXLrA3ILd)f_dA;xkPD<=va?hRiOni7D+!|k69Dn% zl=%Kcak{@>`25$%^53lC^N$5n_#3sDzkP6kGl&$EIVtSh;Y~)-s58$TD&RluZZVY$ zYAxlm5=Plkmu4>@4#?IoAUJ6&fV1^8K&4=cw$;>xf_74k9C8-Y6LdV_Eb}T5t!^P72TA;eX^7Xo6$fFXpUISheST&xkwrYcXKl>z zE8soch@l94fcHiK%r>WR+nIu<(q)g&k3(sK#`|X^IzX>!M9Xd*yfQLLKEonrorf0r z?EqbwfiY>2W^-Vo{W-;Ao$RS9<{5yGG4;&H_on`x>owUgARe}W7LDKv_lA7F0hM8G zOd{QxJ>EHE{8LlFPc@C2a1M_WBm85<-p*5C;u&YuOSPaE5FHs`AKw!H$OmHwz$-Yz zXwUkF9!81qPLUTTynq}jkp6P)-#XRvdoF%Md~9Z4kTTSRyO99YPd6qO+0Oo{R4m)) z5|7AxLiQJsy-L71n3QZiXXcQ+>dAR!Q8?#)1{CYEGH<{DRoSRoBjjA*;LsM>UsM@$YCDsB6Mc)WO)tK2-qPKFAigi~q(#P2D z1}`AR4uE`uwLoB#QZ~@aP^GBvA0(!NN+J6MB9!pnaRDY;yd{lRB{C(qZ380VoPgT9U_ zx(n}r;I9*W1}+w+?@Mz0g_Qv3Ipk3Jo_%3~DaoCO0UiCF;QdG6_0L}ZuiyBmY;gVc zA+-O^vn*KDFQ@EBX|`Y3IWq`nwRRXlRfK;7R2_^mP1g&XQN~~KCIK8>=KWowp?=DI zrqL?$#b=oNpw6rW@}0&X9&qTuxzcKdY|`z~7AL#9pq|uy7FRC^NPvPnRuy|*DW|Cy zkYP@LbO7?^@B)xe(5p04at&ZlIv_3dxmhFyB9DBk?atg`7$7HCl!3_$NH%Z}qs$aK z3IF0?z^5v=i1$<;xc;<%hED=)pQr$AI_wsmhjRejYJbEAWXMHPRH2KwK`)qgNT>c3|1pE-N~+l(at{}Dj{ z4Zi5}zj=xM-*}(@ryu#FV#fVH4W$1qFX!{Gb=LousrawQ@vn8(KWtq5Z>o0x_1yef zjeo7P{;!&>ssFhHwEt4={yYZ7KeE{Id0CK?1OrC_fdqLC@a`r65%U*cIRSjLdj$gW z4)Ez}>tICZYHVQa=45Q+Xl`rcNav*QXlbW!W9&%lW^H9O#xK)Dj|efrgX~&mBAc${ z`z~Y}UcdkW%I;X)faOLgBir0Ik_c5u|IRe;MYomft2i!$W0q0Ys!|p`#{~gA6Y3!Kv3~dT|$1DJwyf7o?Dh!NrN;xkbgN zp9I?hsfGN^SB#&ga~qYl_gex^Jowxl0ym-r2J|r?W*F?|11Sa;_~hxn44bWgzr#~1 z_}X>Tx_PVA_bf2_lE!RE*_q)`r(l3bJCE*=i4=>XbzQ_HqoQ(+VM41ZefITg$8tiu zv~tFW$yFBsiI*2JkpBp)(0)T34gj3i02lG^!|Jz?WB3U#c}<(1cZiRbGhAwqZ^Mpz zWl+d!Hnu?IJwU<|F$N)F&G1S*ZUqj+yn-r|XRDZM509<)a$zy3>M4=ZzmkJ~b*7qa z7P2h_qm?tlRirtr_d{O!l5@OsZS3Vgi;C66A;*t>=M#6W6wJBXhf0v(N>9&)P0Y4v z@!doVK?s(BBPvtj0XCYrfFT=2xQ1p@RC!yuW*oLH_ZrXCK7QRo{pidgQLZIv?y~K@ zXO480A)oL0&iCvQ3MVh7X(en9iE$LL1Xy$1J*+&hiw=NK+-k!=&&dSct<~tkP z4-C!`4CtRpa2q|cH#pfbs~N<*BR=fvsaZ5fM3G|>4IwYvqpLL~(s|tv3fB=4;TS?~ zy{-PlX+ykiRj7qVgdws4n))4lhcv8TOdv6En?aN;i>+W=BY%p#KZv*@HVb2BHBD;Z zH5Zgb{s;4XPrmQEg#K6<@*R~*hJpfy{nI7)$goIqCpbil_WJ1zTpV4u`G<1E#8OVn zA zOE1upN*~o&r>W+UTddnVVSBhP`8d8`Y5Tpk;~v8eULtiB&6^#L5qpQve0r_>q1~m@ z_FyinZehNgjcTQ1j`<7=MLtHVaRRkd9zms5TaYwNr@nisgU#w&!Wo?Mn6N5k=0J{G z=p@pnAc)y6p*XTAt%;kTh%YQ6Nnjd#D{&X0Ylfp1QIRxg*61;wjVr|Jlr3KztjRcNZ4!nb#`;9BpINPHUFY^0 zEEL!J`$>_BL!#$))jhQrL>HnOG6q0i7X1^k#uMNRGSKfmA%M-=W;&g$^+D0laq>CS%s3piGzCw0q$CsTZ`7;V4Vny;!*I?q;8FX!}sovl#lu z(#NgbSedLcqC3#|RhmPr8=3<~)RdpVVT<#UykwgYgHlt9_7BsDhGaw`kdPAAl zl2Qnmd`oE?`lxWF!<8hOIO|-H*@Uf*D&-E#Ut{X$-*@9x!rJ^el^(+wz-j*k=WU58 zc4TXn3MLc|QRB^Bwq3JQkgYqU;ox7o%Ow0#&Wk^s#?}={HVL}WoM;T$k?SjkE70b z+8mQhG=ki?5lNUgVx7lwZhf!+xHswNDX-=%H`Toj(XIu21bsQzx`})FX==Y62M#cF zhrFdXrWcNcxlgZ0^^!|;2v8(vv+Ycz`0nxo%Ufo_)B8VIrA4TemKLVI^EG09vpr{l z6T*@l!J4vQ*jDec0(-sr4fPet11qsZXfp8u8uIAux~14&dOCDygTJt3F`ZF%XM)bh z;X(d`FHNWUVegjT<@FEA7C#QPnx5}J=J4$6 zurZ}&*`rXjnl@{A|G82bd0_yMVB^CWbXs!@f`y3LH+!m+5Csd;NInI;sCYr){mr^jTYs_H$S3dELGpXAKtHQi#<2;6RpuB zKJ3;i>E(RL(3{nFeAkX2E}JIEx!Z_@F=VR}|EgbTpq{1w^R6FP?S@E;2A=R{xulB_ zQo692d+t`OJIvfMOZxJA=IK%G;twhRO-l;g} zpIEoAngog>-gy?*-Xqk7ldp_J)J6(sXTR@4l8?T>KmHz81Z&%s)F%aznU@(-4N zh*uas{U)isR;dc*T|ad?i{~eAgrfT7bWHF#Ul_W*^ns`28j@Ehei2=SnwCi$DzU!G z`;%@tYUdZ%*J?ZbxvW-mOYI`X+NWpzK9&e(zj-2U-@l;50XAL3Y6&6Pvx z7*0^Kl<>=4V4(_k&eF29Ym^_dw3dII^J=PNIr6-2XRO?D-pBu4)x+miSXLLH1P34; zseYAzzg0FI>2w{8t^Sj;Mhm=UH2%e+haL`c{X_Pto|7)hDZNB+tY-GyB#-YQd9b+^ z=CIZA(s(<|6?yQBpby(}Pl-20=&l(b8x46#gebl#7;oZq|Eo9@Y|&N+VlV_`jQgjG zhl1GzteCH4^2)&mdK4tMNMC7C%9zv0zYX_t%q#SB8`hkdn0AjhJas=>@O3iGx-&+IO|cmfv>%b+-<~F8371TO4YuhDO#OL>VFJI1fo6#tOW5NnPk*G_C zn9v-cJV;48NeLlCgve~Xc{t@dAaRMYjIJYxnVI2n9mkCny+~^GSykQMQrS}}xwn#7 zWuFc%a#S9;zqh*IrcCvp7PoZMK2w<8u<}4Wz8ZJ!e7U)?nS?RqD^CXzUZE{{v}$20 zP#@W~?s$u8yH+GJ17Z_j(ezQ)i>I=wV}Y$!OBtTcfk@K)jpG5k73V}z?Y_=j=tl#U zL9J31>u?tJqGOdEgCCeQSCWr6q{TzG@kP?R_7%d=3`H#~g9W$?CHTf*c5E@r-nPh% z${YtPNV^cz?TRgKRc_*L-?Ant(XC-&mEsM3Yuph3ypm0Is<|m#38Pd3f>_hXGMO(YX(~V+f-%Gb{SDgBQ4S+-w4s*cm-&vP#x< zl%h)lPvugeqGbVZvrHE#jV)du$G(381>*`|KpcE)kKXlITwWAviZAPiCn=gD8t?=$ zF>a)4MfLp3nB9MwYMzI! zno&fVO!JWJX-u{h3#%jt7b;U9EW9LQvr-MQ;$Do#LkK5w$~Q-BeTrHjdY+6}I6CYr z6}n*1CPRY|lE?}>Uv&OcAVJ&&^MzcmF*QcRf?=<-I##yT$itUU9aj)-``;*S}UR|ox2@5JPK$!5MRytDiwH!K^~6|4d$+@Y=< zRuhziM8D=pShuRSe*bLuiif}3x7o~!`nm@$96l;6#}W%M29epX%fBw$8+6ZG(iPt=NH zuTnnEI||}!`_H>Fp^x&n!5OrzBCic{U%`oE5Qzs7BIV;@Im2kau3U|)V*YCVI*u@< zG0wdskm%K@idlQU3V3CD{Gq5M4m9pg&Vy+@yCizJb$m=*j%CZx z&p2^EGh=m@SLyB8KWOYb)rK@qAiq}5fwy&8Z@9(y%M`3x%uaeugV*4#u8NM>^ zt(h)5zO8d&p`phK8l*;8Mg3z}s|q0a;`Pz+A}cz!IvQNUK_gU_mHV%$u?#J8H{S*N4BHkb6MxpXQP=2lLEiM5A0TDw00m1rJj+k26 zIy&k*xYHTg8ai7W+c*JN@XLuy6}hY^0jE^gxQMq_V})ilqKE@dCplwRuW&ZO>)=Hi z92I6=D@QrF(vkEfm1niUu`6FL4cy%^-+Of=sBsVt!6=j~Uw)NCRpO69owYB2KKCkx zDCuS|V)PaF|8@Y+aD91L+3DF*7Lh~`xkp2Arj!#x1*%*G#KEa}n$aKgUcHf*`{SBhLXZDYB{C|aM@Kyh(*d>x!{ zF?XgBRS~Y&k&I|Sg30PeMB|>AhoR8DVR0~Y!2v+MXRS!!i@|Ey)Ll;pt*6aLq9_h2| z7^Bxq>aqlu*FvSrg>#VYPF}MIt;1P-ZmV?$Epno&5tyg-PBKRt?z(CzMf)iFNTLeW zZAF0xa;0pSiZ!9(LA+QZ<)ac$f}VQN$!H&+#Q2w|w&8NvFo znhgDpi^sp}XcfUd`T{!Cn0q+0=h`w)l4kuKl+`>l%{hH7;+2r8fYn>Lx9l@agF@pF zqqx3}4mAPS=Z4tVuq1^Zd8qh+;Czxd!W}|9Uk+Fw>t0t!a$M3t)fa7%XnFCdeD>o( z!wWtx4Z9VX7N zof9mY8Jp``iVZ%tt2(VWbc6X~nmm|WFwDwOb~YarG$P{j>S}3c7L2^FW-d_|#-t{% ziXn)+j9owx&fF=*qe^a{_`)8^!_%9dwVGA$s)byfG+2RC&i2IQDCM;B5m(DIARF>hGKq?Tkn*&a)Cm;Fl*dx(a*^@wI`-Y+(_A23&sH!VPB-RH z)zT?^R@Ol~C;Q8zU8neazcv@_CAgGcylX>7}_akiT8=a^+sd-A2IAl;rg2 z7C;_DGrS$bjHH)z7n%2Fcy0(Ko7;?53Nj9I2teO_o3I(#2v8TlvnQ%i$i)(6={e{W zgxY5-v1G~^Ypf#=Qt9YJ>Wv8pqqK!Q)d_9Lsm8`7qACy*#1&1?IHuVxPn~^jLbF;` zkdlajp>?mQ8%*2f5VwD*LffG6#*Sr?Jw#?Q=9YmdKm;?bc^6-2wLM<*N=+@M5Z`*E zI8WttkJEPkBs%oS>OA4Bj-SLWB>yuQWifnI)mZgQ$llQdjAx@Jvy$Ekr^#$2cT-r` z2Vum5A9suqhY5x-30``+{F^r*dCkpJiyhxko?(d?e_v%PwfY#jK3N?-Gmh^ zqERTlbFUZ(U?@yvxaa9B=pvUz@RIRVpYmh+UVJnr*YU&Da;yl;5hHDrdfG`M`HekpRTC%WrJM{KO7(^6X(Fpg#1hFRSM&Jz___t9mnEM#6Ju5s}B7UJHt$j1cnT z2|csr&QZ;^Uj{^BDmx3}?atdrsyX^QH2o+1#OFKYIGYK|~`;bdf;c2tgYd^)~r ztfJ!45HU8a7BD7!jb(i`H>+exyir9wvH=}H1$rB8ovvobXbuAXDXmX#3ymH^V~mNg zlx1u*#U^UrwXHk=Bd*)|>~17p;LzZ_aNT(}OY~!jiIE!W+^wc$_Y4t5xz@MAZyF3u zGahoIHFUDwTg8&&_7zzWqkCRL1@YmuCb71fE1uAUS=!1B3p{n{vR_qPX;!;frkQ10n3Cr7b|CUSa&&ijlIcW%c%E^ zvg7?VRJxkbcxp+grRD!g}{bB z>C>~(-?lLJ$ghpE>kaM(YBYH^)yaC$VU8jB$k=CdzS9@KS{K zrOqd07fF^8C=#aYTk;YqBDmEr-O*Qq)7<-4OTIUKN>HZnaF-#KT!XLhQv>x+_TwL7 z3*Y&aO_2K;uR3oT7AsaWgcCysxM$}aCYJN;T)Y`RI*uRuHo2fMdr1+DI;1cdsb~Hb zS-xS#@L25|)!mm0oTmnud|%i-mqJ^eZ{7ec>(34PpC;bW?H-Wi-7HRS+A6$$Nha-)(`O_*F3S z^37$N?7P(C-Mv{ZQan&hHB=IEV)T@coM3dlSKT=M<(aPJ?OwtN@9fj6S)IutP4uIKvt&cd5>dYMq6KcHbz8_oBy*2J zbnH4_vq^qy{FvA9A+i&acamQK->c>T_V)mFd5aa^0{{>s0H@x9kb@XHJ384~t65vo znL8O<|J?U@mqQFfp7jn;>97BVO|;8^6JYp%U+w+y?rMz||0OE9{1!CK9~Ul+0O?y- z*h2dUmPHJjqs&+~j%mY_T&>0Cu;VG)Ee+kpr2W;@%El-K^XFs9xEm?iTi4H0Yi|{% zq;j{A>jkO=2*nCXnxYVYSfG76L=Kybt84FDHVuih%HOH>zvomOYl1ZLz5gg4xeRMh z-_0Xm6Diy7)wPT{wRvj8`bDH2CWyb{UAO{bjDucI!elYT430I#RG1e(eag--n+Kh3uiE(BZ7sl9QT6{yJn zsN+ve1}X^?qopD7($08H!b4L#d^B?S2H!>Glv8LE_mhF-D|51T#*>lttNSo)J_hK* z-lKGrb#9#dQSCy5QXyo9yR1lY%{X+@aU#>RSrlg$ZWWRpW9f5m3=wXJXWv7EE!hVX zwy?T;$hcf>@3)v*3M~zvh1@4qddoX45+h>~RPv~Ml7E;pDmc=imueG|a;$bN zVBXh{S3U^Ko~u;MCg@>Mf2qGwEP|VRCNPzH=Oy~Qpz8Gr5SZcja7(3$l`8ZN2*^SZ z2nb*u^vmY2qm#SUFMGW+>7x!y14#X+#Y0?KP7-f)f^*)tRm3+MEktNjS=JZeWCnK0 z^fYGrBeA~3A5b31xGuSMxffSPA2}k`Z~WEHgeua4nN6A>Q*=)IsJ&FF@qQ5M zRB@*T$S19xoSYux9%D!?1`?XoPVCD1>-KUF)<4+{5s9OSvm&6Y3%BkIwj!BViTRJ$ zO1W@GNa&1o8~J16g2>_A7Z3DOzX-VW)rsZi-I>YygygcUBSOtX`4VONbTQyz^;OiB zId+5eb7ATf9qV_Q^()|d()##Oi>>ni> zt9fLGRX-fMGmoiHy;~R@Gj~X@{#=a8)oS62Bw4WyIF=g0DT~*Z%}TTY&u;hGb|bR8{Nx~bof@8b1Git*4HMnc;6=@*!??i-F0{RN*y^t;>hf(pSqn)T+4s58KOdsLCc zW4ed3jd^%f`H_*?okt#(;A~U$jPAa3j|4=<`?<-6kZ%Z;!D69Lmwm7Q*at@k&urpm zoW;-s`zgglNzlklh{)JX2+YJxXjA2$Y_ z=$GT9&vVeRZ+*W9Vmpq_v>H*g$}AY16xR+O&wFH6Ke!Y6X`Y5`BsCDqj~>>CrVZ)@ ziZgifVKbMkYn%slTYe>isY11N3u=#v}Qu z@G3kh54t?DjT@h6+Ey{SS2^CrgGP)F!3(v{h&tMVM)AizpQ6eWk29U>uj_BLMX06B zoIc#VGtu9g9DS#w)y{ca(JK(2Y@ui7D=U-{niOU}2L8N3lldG|@UgJwwTw-$#|H@~ z53!yx0l!aD;>fR%#ATRzh>1A@ELPbbT`1MCCMIYVbylHb90@l*e+_k*;z!hEq+1<$ z6r{(VYSXP{R@(J1@;e9tSK+cwLY+Qs=H;H%ejT|rGSMhpG0uBXT+vX_1D{FCY}Zrx zT0`31P3~E8Vgb^|%@muxWGq9NdRX9)M9CCS*_8^<*(VU)CxDDFdMqT!288O}!fO@2 z8Uhq~Br;6nNS4T|o)pm)U4g_&Za+nbl-p8fxPjgYdP)-JFbPQb+$p{uF$fIrKpA+} zN7i&Ks~_#0Bw=PSa-ZbyF!#S_?orCZi)@=>{pQ*%#LFDrB2GF%R1{RW(}EXw%@TLO zqZ1mM(j?rf4bo-udz(jc%!tZ#4T};T$7cWb-7LpJkX?=TMv;*BaG{a3BK439QW}!6 zJTdrWbN%%zQBBrU?IjZzmfiC+#(PGC_nD#O>&5x9E}H6erE6MA(^u;bx|P!zyWg+5 zZ%Hp_7oItH#9jh&r)wzZu}zj-=UI0|A8<`7-4N02XXSnvhUD7&7#>f;Z+<%NJ4_bp zxu_~6S>By=8NQ}&)^}f?Mn1XJmzBgVgdeVOa~qm+EswBU-sUaE8sG5j*Vd_Hv{|uE zBr2y~(mHDz4#mpeNWbrB(EypuNQT z2iZHW997bqlw5_F^eHa31*G=aINXn8DrZ5y!zvSaiRD@Ck_~<+iB>FnXnLYo8E@v1 z_W1l?J7v8|qL@c2;)B?c`3OVt%Pj$LPPy;kaOg!7cz|n_M2_Gq;DW#{Lj3qfDToh> zQYZ@ym2l$Y0efR(3Ks-r-Y-9404>g5etq>3=GY!^X@jCFXaTN?kH7+8SHN}&K!MG` z089WVMNxkL)?EE#arTESfe%`ME0ND11$h1TfnO_vq9*;P4$)zuz$xqcAt>uyp_Rn! z0T=(j4e;MPrj+~Gte7PL^QQq!ZfEWw=O42^AU&5AUPnAVCss)1>dg5JX^VzGKq~<^tY_;K|vSFlV4F-3>#l_s;(h_Y&p9-R==h5Jh=LZpv~CnKQSlu z>M^U5guT#VV*A2bQf>>^$=zcGd4v<8rN(vz83z)fC8i=`_@DsAogK7K+q}SpyC%ui z_%MY7XQKi~o9Q#ra(uF7fSqsah5hK+gyNw4^nKMXJ8t<29mHo^*D|DtpEZ>C zF(5^BCyzdN94tV48P~m)^Uz>$(?2G!>voAtouMD8QfP%LN%xJ%LQczr#BePp7*e+g za)`wu6)BsE@{M2Q;aGx}Q2+~Tok85DW)W%@het40D<*hF#X_u?kLSkMV6dDYKyC_n zPTGRKxkmyX`cI|o;_&n*3W9O9fCougiee@e1VL3S#JT`Ae$$?_c=>n^bDju=7E`}; zw?#k`n8PSyVwz>Zn^}d?#cGWbB%D%`BISIJ3XPIb6S6DiahW};+;rcm>5wn5mZI6H z4Y|vCeae#7zrKblje0)dce=u6Nxn?6{cMc3g;xCxDNBWhMeLtm=1(OGg)>bSgS0|#Hj85$bz6-%&s$_J4~C7gLrEeEez3< zT2WIJ&|AK7b+ys323k!7V$QygN`^jqxF6mQWv!VEyO$d~u3P&hC8|$-9_1r>Q!bIz z>gsJka!*Qi%*I^JK=a~LU;>e8dO*3hyg-^_K4yykelfYFKn-QO*zm9_DP%}r?S3>) ze(b!e zO1NurkM21T2ft@|lbWKwdZTBrz$CAOj(hyx($=-2q%VT?u%xDTf*e-X5+#{_@teRx zhPhJ9$Hy~9XyWz0HpfOQ^RtifhfJ zD|%J*z|32-7CbH9_w*HIBgWgKR!Xx}MT$j#JD~ z;l$a-(b@Ut+YDl*#b~P3^4Db z{JL%hc*7|E#t$TIdepZ29SX?VL(CI`^r?iig_PlSjg@*$r4ha>xJ7W6C5Y<7+NpeL z>A8U`MP$P%x*IoIX}ii~WR;BH;Kn3->4`?Dz*}&EFM?j&`*$DJJ7^m zv0#!7QrlpD2nThiO>Tl}nC|{TnyHp82fhiT-jb`LovWiYgCTbi zB9EkY{oSqLvQ%qMK=+mWl#s^Lt7-49AD~GL)u*N-nxFJc=Azp&nYcJ&T+?1?3c${^ z=?pwwwL~!lJo+7$FLUr+xrvY+iVGo}zcwe8dT-ehmMm=4CT><&mxiPV|aZ=JZurda2s2(L8+APwe_`b+4atYn7^+A7B zP~t47F`F(Z)>wj~j}R=oM_wrS*tk|5JK=PdP?GKf#y?X1^!3^E$nHv?#?4N!USC3{ z#|_{ARlJa~6m8-6*==_^Z;y&ZiN2RHC*LbEpbcGJ-Sh$D04M6O_`j5 zC_fmh8)b14r5y=atM7$6BA|zd%FUv2^NCfYM zfO~iqgWDA};mq6Dq%Sm4JFKtHJ7xy>A9z{w#R+7g1f3AGZqp8ZuDypbc{Iek$t9d{ z4qa1APjQ)KrwyOs81WE%GepZZxh}KrJ6aQx4Ln4$r;bGaxgQvG4Wn-_4EMZO&wW!U z-Bf)f7mYN*ZUxA0%q(4MT*ISX*?jcDMh9)o;m-Qxm)2fiLvIr!; z$MR9Y6U{}Ts9_)IISVQUI#Oe-)yW?>cjOo)pjbabTJDf4s$w>L4vAOKGG`ZjO;UTu zT}E`y%2-%hHh0}`glQnqr>vNnJrV5T;#|G>540&sV{sGZuW;KJ|t;Q;b zh^Hv1M%EUlcEj9oZlj*)4R2N%0_X)pMwv7ax_s~3XnjhP5q#oJp;8#f=g*Ooox7U{ zk5`><=BcPgi@b?B-ssihcp#i?iG5f=q|CkV$R#qhOyPM!k=Eq0^$5|~0F`MGjm>#4 zpzY9RtuV6jAQU!nvPtYNA@m4}l<5ZxCwm;v-T7m~>ydf1cHvNJ+VseB+Z*S0)N5ox zpmv=~6Yb`sab`~pTcIyq`*}?v*Kh_OG0zW@7eXw2gcQ4n7dnA8$2{B7od zc?AJ-ja=+!35DtvU0sko;sHrHExSXlUgWP{AO^SEdUpg*H~p5#P@QfGu*n(MkeLV` zuVe)=gRl0>Za2?&rtRqlU=u{B6DW8O+t1r>`%itIhHdEz-Otf3x#fJBd8NB+S98b{ z3ZK03Xx+Lm#vKL~ufJ=VKvT^u9TjI*9l6&(q6`@g!Z5t8CSSb|go4`C@gH22+w!Cp z@FwX|?w;P9mz2!70lQ@$ck;+B9Y3&9?=8m3x3U%JnVk1 zjw>}w^hsgo6WAqXaHB5~&N?DUAA!$Th6pMVUG+sAr0?vT@S;L!{gq5;?=-Rz1ACl# zukfvHsxOLy(Abw3MZK(tVH))A3=-KR?R%qzsllidzSoWdjbw`;Yskkv#N6#4B#iq^ zstJpW;FR!WT~^TB6}9f25Uf5YE}w29|kfwln$I z7-?*M8KxK(B*0VlQO*sP1LWr_Y~CxDWgiZ`)S`drIkZT9_RMxItB1SnUi2g&oIE@f z$#hRBiS%J$xe44Zc68}CARA7v(l6i55j%vf5f_Ct!Hp5)f z`qBP23Oc!1=)*TIj$&3BcS0Lsl=GQI2JN7r7p=1^(Xrg)8(v!}YE^83W{KIu??zUj z6l1xEb5V582g;cRQV&<9tU=eSRMpnYNiU}G*h)5~wYqi%yP0}tc=|1o0yq|-%bbfI zI|nP(sqJ#`@Vq72lcFq6SMu=TfUkq1EJk@xNbr~=FSP{GA{LYk4?QLz&iMV=07DxA zD|3G~3E&eI@CnY`kF;kNK@>5GBbr3W?hmIeZwB!&fpk2&{N(r<-}NPKy?=!l?1%j= znJ4eb^W#bXO6J&8(?e||zN-(=&X%uYS7?-gmafd2SR-cq1+0DVSjw>2AUP*3ppc9w zBN~VJRl$hWtk7~Y8snw6&u5J2`|(}>3S2M_gq;oXMDBF1v1wiI1WCLqas_Syy*_36 z9So$*q_H1qrrhux#(LBI&LkLlrA8u%sggHBeH-RN+@6+%;tKQNbtReT8pM9@ir%7` z)O?sDCK^W26>3JE#gPG99I!XktkZk9)h zHR-F&oMTV*f7-e3aJaU2twxO!iOxBQsDnfoi8dj68#P365;hEcDc}E+ z4IOD(Qa@iHzxFN5v}R!8g^Qf^aKnp-?ro+g!ymVfY< zB8X_JaKp|!4*G*j@`Ra^JI#Zgke-tFp^%Be)xfEe&*TJF^$R=&?j=f}h>CB;x!tXZ zyHJuWn5vzOD0I={fmUXRTo_@o;(9Q7wqv;%YRi`QexeRD*L${q*`lFGC+ADy@ zw#9i-ig!~`Cr-d)r*tZov-QQ*NEjp?{RE6wj(v3NQ#J0qi zxQlq@JClX4G8P#6En)U+xQ`*>L-|)1n*#$=qhpybTy)I%yc=25ezBFAc2K6V^_B*q z3OJ$AOpiwlzc_7uBDh5YC!48$hpN@@IxmamMT0y>s6Vg6B?CBu>?#m@z7zRd2YF5mx5D zrn6?oU9`>lite3YxD`$xps_`*amqWkun39QVGD`RPqGrz)U$}N-gEi+0Sx{^jGeOX z=cE3n_Faa@oja){vt9wAxC>tK`&GUQ0gFDCE?<@i3u5na-unI)|FI9C1; zI2xrj8WJ9*#zVUwvMI(EEQa8_GHkHP+C3~zy=LZ+_lJLkg)4!d@v&CnGP_5G ziMW-mm9$4wxtmaWvrA=he}Op6f?$Y#rEHid{ysMw2xJ`J1OkQF-RF+SacC%<8I+6> zXy|-3>xeU!$`K^gSkf+p+uc~mgTtn*Ud-}rT%34w8haMu9A`gNMiDl|n8GZK%fcp{ zR+2Xyc*)0erpE8ZyfLLUZQXDM0!srs2s`l0t#Fs@2M7mu z%(!l@HET0yV*Dz+p7EsIiN|>IQoDJM#*XL;Iki~mPtSQ zc3t|>`9G4R2@ih<5QOB1yS=b&y|L+lg zF(9$+?@DkX#+?9A-=vRz>Rlg@@dh9XYDrZ9q(Kgl-wsUs`#}5lfPXVF>y2B#6d(k$ zL7*!vkeh4dfZF%VwnGT;l|2c|kr!pXK*K$FdpCS+7sLBgk1LV)j-rlMdi7AJ^H7Gd zdvAq~jrL9e1y8C62`mvzALo|B0B$p3YUhA8!JXqRmm?$%33iDGu5^tF(y}Ff$PqK z;O#g2;#xKn<^t^J)Z!(VH?((t7@k!h>oM(5cTPUI0Y03fgn8^Oi-Ju3p{)ezw(- ziAv1=JVEm7FMk|f9qcDH*-R}%NV4e}m>O$}A)vhJ$Y6a!hL2Cf9<1FL1iMz!CdV0A zcO);C(@q<=(wE^E5!+J~ng=R&L?_9WnCZD|D^k`L=|8O2-=Wt54X8L$47znHLl}~od*bGDms9ICi{2AT9$2()Ic0oyOBllz~KSK zZT9bK${A4DvK&zryoq4oF<>8{$TW*+L=6Egj`mkIynj^q$?BvLY3_h4 zP&EZC?sX3SY_;Ta^mOn;_|MY1KP(y@X?{+qOS)$msmTp^4{TrKZWoeJ&G^RK8#9*= zE@RXQYRUJ@eQ>v_61q{THK@CFF~dtGXV6N_z%UPVOThnird`q?VK#4(gMgMN$I=%j z&W&fqD|F5tj=6ImgfRNtbz~|S@*QdrA7P94ld!9kI%Ua1Od+sFWNjoT!Qw*Fd?z=L zw~TUCxgra$OQx4Z&Z5QF7KNzqKb|(NUE|t{R@}9K3Y@ znT&(U{t^S&<^Ol_dun7^$9KMA3~z^obw$vEK}CVin6B>5!`Gp^u0+jR^>L{eW0AHs z#e-tP#$5ph9dCe))N!58SwW?D5CRpF&4DlR1%##N;Xb!pT*YrhY$y?F)11W}^p)Xv z^L(};HrZI$9Y>q+id0Ci{gyH?Of}m}_LBTwlpJtZtIG8AM z-tpIp*Ms=-@R-GzasLS4e4Z=zV4Rv$?PXPcGkYytK3?{4!y}dL&yemF%W~*aLv59# z1D}vGKdccUIHkHeG%@$462=W5x_}3EtmWtR#1&{tHx;obYNJyOQAc!lS|?p_^dhe? zk|(?G{=Fli*UQxDYX)dTbkEYFjN8h5$HOVXIofzRLXnQ|PpCq-d_uTLY}C{xONsT% zlJ1&bR;y&h;PW;w7~JNc^{ws7h|1wH#x~Ab%U{w{AbD%F!}e0FaW72dqEfe`3F%A2 z*^Qd8)7`xma+^d3fWg-X`0otU%%o@vJzqjs;Q(~*Lk`LES8R!curBfYnj#F8QIKK_nm{`uWEMk-v=!nkIo#MvM zHnd($GE+r9&!&sM0V&naEmA9_06Viuc#&0(i{l88@hcdcHirg!)w}k}%|Z;qhn}X} zCu!akpf9(iP5C@J*GIy=ZvG-)^et0A?L3zRo0(>#a@f!KVrRE5s>JNbbPo+k*S0p( zhk5|y_H)j z7c^9-g;RWk^J*9E&&QqJ!c~>Qc|7Lpb@q(vy6kFHP<~^bn)E6`MDa@!scqA!zHF&+ zrH0$tl%-$o1~P;mj@O)>!0hGgMO^W%V-&sybz%zs!WIH=E?nWP>&kn&$w_S@81Z z#jo<|%VW#TQV&So?2z1rr6=zC%WdaJi*|~M6(|hpZ!62zwB5?RQ`pw@ed_~B$_Ia; zcl;7K_{kir%44`)zGSNQB3r%#q=wm*?Ye!Ob&!RpE6}vzP>1q-RcRrLL7vihQax84 zo{6RzYEbJV3AG#GYLEFKc6ndTb+X`?+yyxG)wSgfPyITf$0i;_79Vqz2UU#pV!I;+ zik=&pmV1P%Hp_{8;Fx5Y1lgH=&HZst^X?dAB*@Sz#R*X6%Ql7o-;E`G4({+SsOzI1J< zoQ{;ETk)P~`q^gZdA7!F+&`0f)`>c?URB!rOvf><5*A{1HPZF(Vmz7C!Wdy(jmCc? zMj9f~FGN`yoe4}w3eHpNVw!XK||H-MPEha<9IyLuZdD#{{PO*Ux?*tZ~A-BZ&* zE`)G|i2&FIIVKUKk%3TcUE=y9C^}N5Ok0NVRwAA7HXR?tky!FuE^nZ zRe_{}R!&~Yv{e^oF;@NM6_?X2kOg5!y|U`j4a3Vt>Cl&%Fg0q1v)5J5cOYP6i9{1*h8P4zyEq>qPNIx84-h z`m|kxITD#k;_KrNcZv9wR zW)MMz|?ox_Hrt+3@WIv zt~WJqpc|$wr^;{9(FS$EwP*&3asS}sewCY%B>kP;nmKpxfrZ2!58O79#T}ZkTqzk~ z9XuIW2fwP}l<@OH59a$ks>@y?Nb>lY(Sn54WTVjJiyIbgvM z$XS6_Zpi>E<3OOX5yaKb+|h7<;-{u~k{}&tx8ou}-~tQ zWdF0DNexxpb&rUw&CM)~c`dAs@0nlbv9q~%*d!>0BjV%XhUj*or#?WzsacQUzh=+>?FNA! zV2$GW&W_IW-|Rt;nndwfgN}F(Tjn^)sSSf3RET1V;X$|Pe|hgZ8GeT%#K5TK#X2n* z552Wd#;6@97~w-FKt-&fv!F+6p?Das9Gw};?*tu;?k parser.parseFromString(text, "text/xml"); - -// xmldom serializer อาจคง xml declaration ไว้แล้ว — เติมเฉพาะเมื่อยังไม่มี -// (ถ้า prepend ซ้ำจะได้ declaration สองอัน ไฟล์ XML invalid) -const serialize = (doc) => { - const text = serializer.serializeToString(doc); - return text.startsWith("\r\n' + text; -}; - -// ครอบ w:sdt ที่มี w:tag ทุกตัวด้วย w:permStart/w:permEnd (edGrp="everyone") -// และเติม w:lock="sdtLocked" ให้ CC ที่ยังไม่มี — คืนจำนวน CC ที่ครอบ -function unlockContentControls(zip) { - const select = xpath.useNamespaces({ w: W_NS }); - const asNode = (n) => n; - const parts = Object.keys(zip.files).filter((n) => - /^word\/(document|header\d*|footer\d*)\.xml$/.test(n), - ); - - // id ของ perm ต้องไม่ซ้ำในเอกสาร — เริ่มสูงกว่าค่าสูงสุดที่มีอยู่ (จากทุก part) - let nextId = 999; - for (const part of parts) { - const ids = select( - "//w:permStart/@w:id | //w:permEnd/@w:id", - asNode(parseXml(zip.file(part).asText())), - ); - for (const id of ids) { - const n = Number.parseInt(id.value, 10); - if (Number.isFinite(n) && n >= nextId) nextId = n; - } - } - - let wrapped = 0; - let locked = 0; - for (const part of parts) { - const doc = parseXml(zip.file(part).asText()); - - // เฉพาะ sdt ระดับบนสุด (ไม่อยู่ใน sdt อื่น) มี w:tag = ช่องข้อมูลของ template - // และยังไม่ถูกครอบด้วย permStart อยู่แล้ว (กัน double-wrap ตอนรันซ้ำ) - const sdts = select( - "//w:sdt[./w:sdtPr/w:tag][not(ancestor::w:sdt)][not(preceding-sibling::*[1][self::w:permStart])]", - asNode(doc), - ); - if (sdts.length === 0) continue; - - for (const sdt of sdts) { - // เติม lock ให้ CC ที่ยังไม่มี — กันลบช่องตอนเปิดช่องให้แก้ผ่าน permission - const sdtPr = select("./w:sdtPr", sdt)[0]; - const hasLock = select("./w:lock", sdtPr).length > 0; - if (!hasLock) { - const lock = doc.createElementNS(W_NS, "w:lock"); - lock.setAttribute("w:val", "sdtLocked"); - // w:lock ต้องอยู่หลัง w:id ตามลำดับ schema ของ sdtPr - const idEl = select("./w:id", sdtPr)[0]; - if (idEl && idEl.nextSibling) { - sdtPr.insertBefore(lock, idEl.nextSibling); - } else { - sdtPr.appendChild(lock); - } - locked++; - } - - const start = doc.createElementNS(W_NS, "w:permStart"); - start.setAttribute("w:id", String(nextId)); - start.setAttribute("w:edGrp", "everyone"); - const end = doc.createElementNS(W_NS, "w:permEnd"); - end.setAttribute("w:id", String(nextId)); - nextId++; - - sdt.parentNode.insertBefore(start, sdt); - sdt.parentNode.insertBefore(end, sdt.nextSibling); - wrapped++; - } - - zip.file(part, serialize(doc)); - } - return { wrapped, locked }; -} - -function enableReadOnlyProtection(zip) { - const file = zip.file("word/settings.xml"); - if (!file) throw new Error("word/settings.xml not found"); - - const doc = parseXml(file.asText()); - const prots = doc.getElementsByTagNameNS(W_NS, "documentProtection"); - const prot = prots.length > 0 ? prots[0] : null; - - if (prot) { - prot.setAttribute("w:edit", "readOnly"); - prot.setAttribute("w:enforcement", "1"); - } else { - // T3 มี element นี้อยู่แล้ว แต่รองรับไฟล์ที่ไม่มี: แทรกเป็น child แรกของ w:settings - const el = doc.createElementNS(W_NS, "w:documentProtection"); - el.setAttribute("w:edit", "readOnly"); - el.setAttribute("w:enforcement", "1"); - doc.documentElement.insertBefore(el, doc.documentElement.firstChild); - } - - zip.file("word/settings.xml", serialize(doc)); -} - -function setCustomProperties(zip, props) { - // --- 1. docProps/custom.xml : สร้างใหม่ หรือ merge กับของเดิม --- - const existing = zip.file("docProps/custom.xml"); - let doc; - if (existing) { - doc = parseXml(existing.asText()); - // ลบ property ชื่อซ้ำออกก่อน (เขียนทับค่าใหม่) - const all = Array.from(doc.getElementsByTagNameNS(CUSTOM_NS, "property")); - for (const p of all) { - if (Object.prototype.hasOwnProperty.call(props, p.getAttribute("name"))) { - p.parentNode.removeChild(p); - } - } - } else { - doc = parseXml( - `` + - ``, - ); - } - - // pid ต้อง >= 2 และไม่ซ้ำ — เริ่มจากค่าสูงสุดที่มีอยู่ +1 - const usedPids = Array.from( - doc.getElementsByTagNameNS(CUSTOM_NS, "property"), - ).map((p) => Number.parseInt(p.getAttribute("pid"), 10) || 1); - let nextPid = Math.max(1, ...usedPids) + 1; - - for (const [name, value] of Object.entries(props)) { - const prop = doc.createElementNS(CUSTOM_NS, "property"); - prop.setAttribute("fmtid", FMTID); - prop.setAttribute("pid", String(nextPid++)); - prop.setAttribute("name", name); - const val = doc.createElementNS(VT_NS, "vt:lpwstr"); - val.textContent = value; - prop.appendChild(val); - doc.documentElement.appendChild(prop); - } - zip.file("docProps/custom.xml", serialize(doc)); - - // --- 2. [Content_Types].xml : เพิ่ม Override (ครั้งเดียว) --- - const ct = parseXml(zip.file("[Content_Types].xml").asText()); - const hasCt = Array.from(ct.getElementsByTagName("Override")).some( - (o) => o.getAttribute("PartName") === "/docProps/custom.xml", - ); - if (!hasCt) { - const el = ct.createElement("Override"); - el.setAttribute("PartName", "/docProps/custom.xml"); - el.setAttribute("ContentType", CT_CUSTOM); - ct.documentElement.appendChild(el); - zip.file("[Content_Types].xml", serialize(ct)); - } - - // --- 3. _rels/.rels : เพิ่ม Relationship (ครั้งเดียว) --- - const rels = parseXml(zip.file("_rels/.rels").asText()); - const relNodes = Array.from(rels.getElementsByTagName("Relationship")); - if (!relNodes.some((r) => r.getAttribute("Type") === REL_CUSTOM)) { - const usedIds = new Set(relNodes.map((r) => r.getAttribute("Id"))); - let n = 1; - while (usedIds.has(`rId${n}`)) n++; - const el = rels.createElement("Relationship"); - el.setAttribute("Id", `rId${n}`); - el.setAttribute("Type", REL_CUSTOM); - el.setAttribute("Target", "docProps/custom.xml"); - rels.documentElement.appendChild(el); - zip.file("_rels/.rels", serialize(rels)); - } -} - -// อ่านกลับมาตรวจทุกอย่างหลังเขียนไฟล์ -function verify(path) { - const zip = new PizzaZip(readFileSync(path)); - - const settings = zip.file("word/settings.xml")?.asText() ?? ""; - const prot = settings.match(/]*\/>/)?.[0]; - const enforced = - prot?.includes('w:edit="readOnly"') && prot?.includes('w:enforcement="1"'); - - const customXml = zip.file("docProps/custom.xml")?.asText(); - const propsBack = {}; - if (customXml) { - const doc = parseXml(customXml); - for (const p of Array.from( - doc.getElementsByTagNameNS(CUSTOM_NS, "property"), - )) { - propsBack[p.getAttribute("name")] = p.textContent; - } - } - - const rels = zip.file("_rels/.rels")?.asText() ?? ""; - const ct = zip.file("[Content_Types].xml")?.asText() ?? ""; - - const docXml = zip.file("word/document.xml")?.asText() ?? ""; - const sdtPrs = docXml.match(/[\s\S]*?<\/w:sdtPr>/g) ?? []; - const plain = sdtPrs.filter((p) => / / `${k}="${v.length > 40 ? v.slice(0, 40) + "…" : v}"`) - .join(", ") || "(ว่าง)", - ); - console.log( - `relationship : ${rels.includes(REL_CUSTOM) ? "✓" : "✗ ไม่พบ"} | content-type override: ${ct.includes(CT_CUSTOM) ? "✓" : "✗ ไม่พบ"}`, - ); - console.log(`content controls : ${sdtPrs.length} ตัว (plain=${plain}, rich=${rich}, มี tag=${tagged})`); - console.log( - `permission ranges : permStart=${permStarts} / permEnd=${permEnds} (ต้องเท่ากับมี tag=${tagged}) ` + - (permStarts === tagged && permEnds === tagged ? "✓" : "✗"), - ); - console.log( - `CC ไม่มี lock : ${missingLock} ตัว (ต้องเป็น 0 — กันลบช่อง) ${missingLock === 0 ? "✓" : "✗"}`, - ); - - return ( - enforced && - !!customXml && - permStarts === tagged && - permEnds === tagged && - missingLock === 0 - ); -} - -const zip = new PizzaZip(readFileSync(inPath)); -enableReadOnlyProtection(zip); -const { wrapped, locked } = unlockContentControls(zip); -setCustomProperties(zip, CUSTOM_PROPS); - -if (locked > 0) { - console.log(`เติม w:lock="sdtLocked" ให้ CC อีก ${locked} ตัวที่ยังไม่มี`); -} -console.log(`ครอบ CC ด้วย permission range: ${wrapped} ตัว`); - -const out = zip.generate({ type: "nodebuffer", compression: "DEFLATE" }); -writeFileSync(outPath, out); -console.log(`เขียนไฟล์แล้ว: ${outPath} (${out.length.toLocaleString()} bytes)`); - -const ok = verify(outPath); -process.exit(ok ? 0 : 1); diff --git a/app/api/template/test/note.md b/app/api/template/test/note.md deleted file mode 100644 index 919190b..0000000 --- a/app/api/template/test/note.md +++ /dev/null @@ -1,127 +0,0 @@ -# Docx Template -lib-tmpl.ts เป็นแชร์ไลบารีสำหรับการทำ Template ใช้ Content Control ใน MS Word -## วิธีการใช้ API -```sh -## Swagger UI — เปิดในเบราว์เซอร์ (ลองยิงจริงผ่านปุ่ม Try it out ได้เลย ไม่ต้อง login) -http://localhost:3000/docedit/api/swagger - -## template/test ไว้ทดสอบเท่านั้น -curl.exe http://localhost:3000/docedit/api/template/test - -## extract ค่าออกมาจาก content control -curl.exe -X POST http://localhost:3000/docedit/api/template/extract -F "file=@./output-T3.docx" - -## render เอกสาร (docx) = template(docx)+data(json) -### อาจจะเพิ่มการเอา template จาก url และ file(local) ยังไม่รู้ว่าทางนั้นจะส่งไฟล์แบบไหนมาให้ เอาแค่นี้ไว้ทดสอบก่อน -curl.exe -X POST http://localhost:3000/docedit/api/template/render -F "template=@./T3.docx" -F "data=@./T2.json" -o output.docx -### data เอาจาก string -curl -X POST http://localhost:3000/docedit/api/template/render \ - -F "template=@template.docx" \ - -F 'dataJson={"member_name":"นายสมชาย","member_id":"123456"}' \ - -o generated-document.docx - -``` -data(json) ตอนนี้อยู่ในรูปแบบ -```json -{ - "notname": "คำสั่งเรียก ให้ทำคำให้การ", - "nottype": "(ต. ๑)", - "blackno": "คดีหมายเลขดำที่", - "blackyear": "๑๐/๒๕๖๙", - ... -} -``` -data(json)น่าจะปรับเป็นแบบนี้เพื่อรองรับการทำ Token-Based Seamless Integration -หลักผ่าน API -- ส่วนของ cc จะไปกรอกใน Content Control -- uid,uname,fid,ott,expire สร้างเป็น token ฝังใน custom property ของเอกสาร -```json -{ - "uid":"id ผู้มีสิทธิ์แก้ไขเอกสาร", - "uname": "ชื่อผู้มีสิทธิ์แก้ไขเอกสาร สำหรับแสดงผล", - "fid":"file id จะได้เซฟข้อมูลกลับไปที่ Oracle ถูกตารางและหน่วยงาน", - "ott":"อายุของ one time token แบบระยะสั้น", - "expire": "อายุเอกสารแบบระยะยาวหลังเอา ott ไปแลก (optional)", - cc:{ - "notname": "คำสั่งเรียก ให้ทำคำให้การ", - "nottype": "(ต. ๑)", - "blackno": "คดีหมายเลขดำที่", - "blackyear": "๑๐/๒๕๖๙", - ... - } -} -``` -ที่ต้องทำเพิ่ม -- render ปัจจุบันมีไว้ให้ทางศาลทดสอบ อันที่ใช้จริงหลังกดสร้าง Template เพื่อ Download จะสร้างเอกสารเวอร์ชั่นแรกใน object storage หรือถ้ามีอยู่แล้ว จะเป็นการอัปเดต -- Backend จัดการ version -- อัปเดตข้อมูลและเอกสารกลับระบบเดิม ส่วนนี้ยังไม่ชัดเจนอาจจะทำ mock api เตรียมไว้ก่อน - -# ทดลอง Restrict Edit + Custom Properties (2026-10-07) - -สคริปต์ `make-protected.mjs` — อ่าน docx → เปิด protection + ฝัง custom properties → เขียนไฟล์ → อ่านกลับ verify ทุกครั้ง - -```sh -node app/api/template/test/make-protected.mjs [in.docx] [out.docx] -# ไม่ส่ง argument = อ่านและเขียนทับ output-T3.docx (ค่าตัวอย่างฝังอยู่ในตัวสคริปต์ CUSTOM_PROPS) -``` - -## Restrict Edit (เปิดผ่านโค้ด) - -- อยู่ที่ `word/settings.xml` → `` -- T3.docx มี element นี้อยู่เดิมแต่เป็น `w:enforcement="0"` (ตั้งค่าไว้ใน Word แต่ไม่ได้กดบังคับใช้) — สคริปต์แค่พลิกเป็น 1 ไม่ต้องแทรก element ใหม่ -- **บทเรียนจากทดสอบใน Word จริง (2026-10-07): `readOnly` + enforcement ล็อคทั้งเอกสารรวมถึง Content Control ด้วย** — CC ไม่ได้ถูกยกเว้นให้แก้อัตโนมัติ ต้องคู่กับข้อถัดไป -- **กลไกที่ถูกต้อง: ครอบ `w:sdt` ทุกตัวที่มี `w:tag` ด้วย `` … ``** — คือกลไก "Exceptions (optional): Everyone" ที่แผง Restrict Editing ของ Word ใช้เวลาเราเลือกบริเวณแล้วติ๊ก Everyone (บริเวณใน permission range แก้ได้ นอกนั้นล็อค) — แทรกที่ระดับเดียวกับ sdt (inline ใน `w:p` หรือ block ใน `w:body` ก็ valid ตาม grammar) -- สคริปต์เติม `w:lock="sdtLocked"` ให้ CC ที่ยังไม่มีด้วย (T3 ขาด acc1, nottitle) — พอช่องถูกเปิดให้แก้ผ่าน permission แล้ว ช่องที่ไม่มี lock จะถูก**ลบ**ได้ตามไปด้วย -- ไม่ใส่ password → ผู้ใช้ปลดเองได้จาก Review → Restrict Editing → Stop Protection — ตั้งใจไว้แบบนี้เพราะ concept คือ token ใน custom property เป็นตัวกำกับสิทธิ์จริง ส่วน protection เป็นแค่เกราะกันแก้พลาด (ถ้าจะใส่ password ต้องคำนวณ legacy hash: SHA-1 ของ password+salt วนตาม `w:cryptSpinCount` ตาม ECMA-376 — ยังไม่ทำ) - -## ฝังข้อมูลใน Property เอกสาร (token / primary-key / owner) - -ทางเลือกที่ศึกษา: -1. **`docProps/custom.xml` (custom properties) — ที่เลือกใช้** เห็นได้ใน Word: File → Info → Properties → Advanced Properties → แท็บ Custom; add-in ฝั่ง Word (Office.js) อ่าน/เขียนได้ผ่าน `Document.properties.customProperties` (WordApi 1.3+) - - ไฟล์ที่ไม่มี part นี้อยู่ (กรณีปกติของ docx) ต้องทำครบ 3 อย่างพร้อมกัน ไม่งั้น Word ถือว่าไฟล์เสีย: - 1. สร้าง `docProps/custom.xml` — property แต่ละตัว: `fmtid="{D5CDD505-2E9C-101B-9397-08002B2CF9AE}"` คงที่ + `pid` ≥ 2 ไม่ซ้ำ + `name` + ค่า string ใน `vt:lpwstr` - 2. `[Content_Types].xml` เพิ่ม `` - 3. `_rels/.rels` เพิ่ม Relationship ชนิด `.../relationships/custom-properties` Target `docProps/custom.xml` -2. `docProps/core.xml` (core properties: title/subject/creator/description) — แก้ง่าย เห็นในหน้า Properties ปกติ เหมาะกับข้อมูลที่แสดงต่อผู้ใช้ ไม่ใช่ token -3. `w:docVar` ใน `word/settings.xml` (document variables) — ไม่โผล่ใน UI เลย อ่านได้ผ่าน VBA/VSTO (`Document.Variables`) แต่ Word JS API ไม่มีทางตรง — ไม่เลือก - -gotcha ที่เจอ: `XMLSerializer` ของ @xmldom/xmldom คง `` declaration ไว้ในผลลัพธ์อยู่แล้ว — ห้าม prepend เพิ่มเอง จะได้ declaration ซ้อนเป็น XML invalid (Word/node อ่านไม่ได้) - -## extract บอก alignment รายช่อง — key `@align` (2026-10-07) — **ถอดออกแล้วตามคำขอ (ยังไม่เอา)** - -- เคย implement ใน `lib-tmpl.ts` + spec `lib/openapi.ts` แล้ว**ถอดกลับสถานะเดิม** — extract ปัจจุบันคืนแค่ `{ tag: text }` เหมือนเดิม -- แนวทางที่พิสูจน์แล้วว่าใช้ได้ (เผื่อกลับมาทำภายหลัง): คืน key `@align` (ค่า w:jc เช่น `thaiDistribute`) เฉพาะช่องที่ต่างจาก default ของเอกสาร โดย resolve: `w:pPr/w:jc` ตรงของย่อหน้า → style ที่ใช้ (ไล่ `w:pStyle` → `w:basedOn`) → `docDefaults` ("left"/"start" นับเป็นไม่ตั้ง) — บริบทย่อหน้าของช่อง: CC block = `w:p` แรกใน `w:sdtContent` / CC inline = `w:p` ที่ครอบ sdt -- ผลทดสอบตอน implement: direct pPr ชนะ style ✓ inline/block จับได้ทั้งคู่ ✓ และ T3 จัดช่อง 8 ตัวผ่าน style อยู่แล้ว (nottype/blackyear/redyear/acc1/acc2/groupname/detail1/pname — ชิดขวา/กลาง) — ช่องเหล่านี้จะโผล่ `@align` ตามจริง - -## จัด paragraph แบบ Thai Distributed — ตัดสินใจ: จัดเองใน Word ตอนทำ template (2026-10-07) - -- ค่า OOXML คือ `` ใน `w:pPr` (ใน Word UI: Paragraph → Alignment → Thai Distributed, ทางลัด Ctrl+Shift+J) — ต่างจาก `distribute` ธรรมดาตรงที่จัดกระจายช่องว่างระหว่างคำเป็นหลัก ไม่บีบยืดตัวอักษร/สระ-วรรณยุกต์ -- **ไม่ตั้งผ่านโค้ด** — ผู้ออกแบบ template จัดเองใน Word ตอนทำ template ทางเดียวกับที่ตั้งฟอนต์/ย่อหน้าอื่น ๆ (เคยทดลองตั้ง default ทั้งเอกสารผ่าน `styles.xml` → `w:docDefaults/w:pPrDefault/w:pPr` แล้วถอดออกตามคำขอ) -- วิธีจัดเองใน Word: - - รายย่อหน้า/ราย CC: คลิกในย่อหน้า → `Ctrl+Shift+J` หรือ Home → Paragraph launcher → Alignment เลือก Thai Distributed - - ทั้งเอกสาร: คลิกขวา style Normal → Modify → Format → Paragraph → Thai Distributed - - จัด "เฉพาะช่อง CC" ได้เฉพาะ CC แบบ block (มี `w:p` ใน `w:sdtContent` ของตัวเอง เช่น `detail1`) — CC แบบ inline อยู่ในบรรทัดเดียวกับข้อความอื่น การจัดมีผลทั้งบรรทัด -- จุดสำคัญต่อ render pipeline: `insertBlockContent` ของ lib-tmpl clone `w:pPr` ของย่อหน้าแรกให้ทุกบรรทัดที่แทนค่า — จัดไว้ใน CC ตั้งแต่ต้นค่าจะสืบทอดไปทุกบรรทัดที่ render ใส่ข้อมูลอัตโนมัติ ไม่ต้องทำอะไรฝั่ง render -- (อ้างอิงเชิงเทคนิคกรณีต้องตั้งผ่านโค้ดภายหลัง: `w:jc` ใน `w:pPr` เป็น fixed sequence — ต้องอยู่หลัง `w:autoSpaceDN`/`w:spacing`/`w:ind` และก่อน `w:textDirection`/`w:rPr`/`w:sectPr`) - -## ผล verify (อัตโนมัติทุกครั้งที่รันสคริปต์) - -- `documentProtection` เปิดใช้ (readOnly + enforcement=1) ✓ -- `docProps/custom.xml` มี + relationship + content-type override ครบ ✓ -- Content Control 27 ตัวครบเดิม (plain 26 = มี `w:text`, rich 1 = `detail1`) ✓ -- permission ranges: permStart/permEnd ครบตามจำนวน CC ที่มี tag (27/27) ✓ -- ทุก CC มี `w:lock="sdtLocked"` (กันลบช่อง) ✓ -- `extractContentControls` (lib-tmpl) อ่านคืนได้ทุก tag ตรงกับ output.docx ก่อน protect 100% ✓ - -## ผลทดสอบใน Word จริง (2026-10-07 รอบ 1) - -- ✅ แถบเหลือง protection ขึ้น / แก้นอก CC ไม่ได้ / ลบ CC ไม่ได้ -- ❌ B5: **CC แก้ไม่ได้** เพราะ readOnly ล็อคทั้งเอกสาร → แก้ด้วย permStart/permEnd ตามด้านบนแล้ว (รอบ 2) -- ✅ custom properties ครบ / ปลด protection ได้แบบไม่มี password - -## เหลือตรวจใน Word จริง (รอบ 2) - -- เปิด `output-T3.docx` → พิมพ์/แก้ในช่อง CC ต้องได้แล้ว (B5) -- ลบ CC ต้องไม่ได้ทุกตัว (รวม acc1, nottitle ที่เติม lock ให้) -- ขั้น E เดิม: F12 เซฟสำเนา → เปิดใหม่ → properties ยังอยู่ครบ -- (ผ่านแล้วทั้งหมด → ย้าย logic ไปเป็นฟังก์ชันใน lib-tmpl.ts แล้วต่อเข้า render pipeline) diff --git a/app/api/template/test/output-T3.docx b/app/api/template/test/output-T3.docx deleted file mode 100644 index c7619be14f035d8b5cc6d52b1ac3206ff3b2b584..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 154797 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¹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%0Ap~5VytvB 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 BbV~pL diff --git a/app/api/template/test/test.http b/app/api/template/test/test.http deleted file mode 100644 index d615855..0000000 --- a/app/api/template/test/test.http +++ /dev/null @@ -1,43 +0,0 @@ -@baseUrl = http://localhost:3000/docedit/api - -### 1. Extract Template -POST {{baseUrl}}/template/extract -Content-Type: multipart/form-data; boundary=WebAppBoundary - ---WebAppBoundary -Content-Disposition: form-data; name="file"; filename="output-T3.docx" -Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document - -< ./output-T3.docx ---WebAppBoundary-- - -### 2. Render Template with JSON File -POST {{baseUrl}}/template/render -Content-Type: multipart/form-data; boundary=WebAppBoundary - ---WebAppBoundary -Content-Disposition: form-data; name="template"; filename="T3.docx" -Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document - -< ./T3.docx ---WebAppBoundary -Content-Disposition: form-data; name="data"; filename="T2.json" -Content-Type: application/json - -< ./T2.json ---WebAppBoundary-- - -### 3. Render Template with Raw JSON String -POST {{baseUrl}}/template/render -Content-Type: multipart/form-data; boundary=WebAppBoundary - ---WebAppBoundary -Content-Disposition: form-data; name="template"; filename="template.docx" -Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document - -< ./T3.docx ---WebAppBoundary -Content-Disposition: form-data; name="dataJson" - -{"notname": "คำสั่งเรียก ให้ทำคำให้การ","nottype": "(ต. ๑)","blackno": "คดีหมายเลขดำที่","blackyear": "๑๐/๒๕๖๙","redno": "คดีหมายเลขแดงที่","redyear": "","court": "ศาลปกครองสุพรรณบุรี","curdate": "วันที่ ๗ เดือน สิงหาคม พุทธศักราช ๒๕๖๙","accuser": "นายธัญวิสิษฐ์ ศรแก้ว","acc1": "ผู้ฟ้องคดี","accused": "เจ้าคุณมด ที่ ๑ กับพวกรวม ๓ คน","acc2": "ผู้ถูกฟ้องคดี","nottitle": "คำสั่งถึง","commto": "เจ้าคุณมด","groupname": "ผู้ถูกฟ้องคดีที่ ๑","detail1": "ด้วยผู้ฟ้องคดีได้ยื่นฟ้องผู้ถูกฟ้องคดีต่อศาล มีข้อความตามที่ปรากฏ ในระบบงานคดีปกครองอิเล็กทรอนิกส์\nฉะนั้น ให้ ท่านทำคำให้การแก้คำฟ้อง พร้อมด้วยพยานหลักฐาน ๑ ชุด ต่อศาลภายใน ๓๐ วัน นับแต่วันที่ได้รับคำสั่งนี้","pname": "นางนพเก้า เถื่อนชัย","courtname": "ศาลปกครองสุพรรณบุรี","courtno": "๓๓๓ หมู่ ๖ ตำบลสนามชัย อำเภอเมืองสุพรรณบุรี","courttam": "จังหวัดสุพรรณบุรี ๗๒๐๐๐","courttel": "กลุ่มออกหมาย โทรศัพท์ ๐๓๕ ๔๓๒ ๓๓๓ ต่อ ๑๐๐๑,๒๒๐๓","courttel2": "สอบถามติดตามคดี ขอคำปรึกษาแนะนำ โทรสายด่วน “๑๓๕๕”","sendto": "เจ้าคุณมด","addrno": "เลขที่ - หมู่ที่ - ตรอก/ซอย - ถนน - ตำบล/แขวง - อำเภอ/เขต - จังหวัด - โทรศัพท์ -","remarkhead": "","remark": "","warning": "หากผู้ถูกฟ้องคดีไม่ปฏิบัติตามคำสั่งศาลภายในระยะเวลาที่กำหนด ศาลจะถือว่าผู้ถูกฟ้องคดียอมรับข้อเท็จจริงตามข้อหาของผู้ฟ้องคดี"} ---WebAppBoundary-- diff --git a/app/tmpl/page.tsx b/app/tmpl/page.tsx new file mode 100644 index 0000000..0eaec44 --- /dev/null +++ b/app/tmpl/page.tsx @@ -0,0 +1,100 @@ +// app/page.tsx (ระบบ B) +'use client'; +import type { ChangeEvent } from 'react'; +// import { createDownloadFormAction } from './actions'; +import DependentDropdown, { DropdownData } from '@/components/utils/DependentDropdown'; + + + +export default function HomePage() { + const templateData: DropdownData = { + T_X: ['T_no_warning', 'TF_no_warning_P2', 'TS_P2_IN'], + T12_X: ['T12_b2_IN', 'T12_FD155_3s-long'], + }; + const handleTmplSelect = (template: string, data: string) => { + //console.log('Location Selected:', { template, data }); + }; + const handleInput = (e: ChangeEvent)=>{ + // console.log(e.target) + return true; + } + return ( + + ); +} \ No newline at end of file diff --git a/components/utils/DependentDropdown.tsx b/components/utils/DependentDropdown.tsx new file mode 100644 index 0000000..7331bd9 --- /dev/null +++ b/components/utils/DependentDropdown.tsx @@ -0,0 +1,91 @@ +'use client'; + +import { useState, ChangeEvent, useEffect } from 'react'; + +export interface DropdownData { + [key: string]: string[]; +} + +interface DependentDropdownProps { + data: DropdownData; + name1: string; // เพิ่มมาเพื่อใช้กับ Form Action + name2: string; // เพิ่มมาเพื่อใช้กับ Form Action + label1?: string; + label2?: string; + placeholder1?: string; + placeholder2?: string; + onSelectChange?: (mainValue: string, subValue: string) => void; +} + +export default function DependentDropdown({ + data, + name1, + name2, + label1 = 'เลือกตัวเลือกหลัก:', + label2 = 'เลือกตัวเลือกย่อย:', + placeholder1 = '-- กรุณาเลือก --', + placeholder2 = '-- กรุณาเลือกตัวเลือกหลักก่อน --', + onSelectChange, +}: DependentDropdownProps) { + const [mainSelection, setMainSelection] = useState(''); + const [subSelection, setSubSelection] = useState(''); + + const handleMainChange = (e: ChangeEvent) => { + const value = e.target.value; + setMainSelection(value); + setSubSelection(''); + }; + + const handleSubChange = (e: ChangeEvent) => { + setSubSelection(e.target.value); + }; + + useEffect(() => { + if (onSelectChange) { + onSelectChange(mainSelection, subSelection); + } + }, [mainSelection, subSelection, onSelectChange]); + + const subOptions = mainSelection ? data[mainSelection] : []; + + return ( +
+ {/* Dropdown ที่ 1 */} +
+ + +
+ + {/* Dropdown ที่ 2 */} +
+ + +
+
+ ); +} diff --git a/lib/lib-utils.ts b/lib/lib-utils.ts index f17a247..312d3cf 100644 --- a/lib/lib-utils.ts +++ b/lib/lib-utils.ts @@ -50,7 +50,7 @@ console.log('Is Signature Valid?:', isValid); // true * @param privateKeyHex คีย์ลับในรูปแบบ Hex String (ความยาว 64 ตัวอักษร) * @returns ลายเซ็น (Signature) ในรูปแบบ Hex String (ความยาว 128 ตัวอักษร) */ -export function signDataEd25519(message: string | Buffer, privateKeyHex: string): string { +export function signEd25519(message: string | Buffer, privateKeyHex: string): string { const rawKeyBuffer = Buffer.from(privateKeyHex, 'hex'); // ตัดเอาเฉพาะ 32 bytes (Seed) กรณีที่ Hex ยาว 64 bytes (ที่ต่อกับ Public Key มา) const seed = rawKeyBuffer.length === 64 ? rawKeyBuffer.subarray(0, 32) : rawKeyBuffer; @@ -76,7 +76,7 @@ export function signDataEd25519(message: string | Buffer, privateKeyHex: string) * @param publicKeyHex คีย์สาธารณะในรูปแบบ Hex String (ความยาว 64 ตัวอักษร) * @returns ผลลัพธ์ความถูกต้อง (true = ผ่าน, false = ไม่ผ่าน) */ -export function verifyDataEd25519(message: string | Buffer, signatureHex: string, publicKeyHex: string): boolean { +export function verifyEd25519(message: string | Buffer, signatureHex: string, publicKeyHex: string): boolean { try { const rawPublicKey = Buffer.from(publicKeyHex, 'hex'); diff --git a/lib/openapi.ts b/lib/openapi.ts index a21749e..a407cdf 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -32,13 +32,12 @@ export const openApiSpec = { { name: "template", description: - "Template เอกสาร DOCX ด้วย Content Control — ส่ง template (.docx) + data (JSON) " + - "แล้วได้เอกสารที่กรอกค่าแล้วกลับมา", + "Next Generation of Template : เป็นเทมเพลต MS Word(docx) รองรับรูปแบบข้อมูลที่ซับซ้อนได้ ", }, { name: "documents", description: - "จัด version เอกสาร (docver — docs/PLAN-DOCVER.md): เอกสารเดียวต่อ docId แต่ละ " + + "จัด version เอกสาร (docver): เอกสารเดียวต่อ docId แต่ละ " + "version เก็บ .docx + .pdf ใน MinIO พร้อมผู้อัปโหลด — auth ด้วย access_token " + "ของกลุ่มนี้: ขอที่ POST /api/documents/token (ยืนยัน x-api-key กับ env " + "DOCVER_API_KEY) แล้วแนบ Authorization: Bearer ทุก request — identity/template " + @@ -75,67 +74,15 @@ export const openApiSpec = { }, }, }, - "/api/template/extract": { - post: { - tags: ["template"], - summary: "ดึงค่า Content Control ทั้งหมดออกมาจากไฟล์ .docx", - description: - "อ่าน w:sdt (Content Control) ทุกตัวใน word/document.xml — คืน map " + - "ชื่อแท็ก (w:tag) → ข้อความปัจจุบันในนั้น (ใช้สำรวจว่า template มีแท็กอะไรบ้างก่อนเรียก render)", - requestBody: { - required: true, - content: { - "multipart/form-data": { - schema: { - type: "object", - required: ["file"], - properties: { - file: { - type: "string", - format: "binary", - description: "ไฟล์ .docx ที่มี Content Control", - }, - secret: { - type: "string", - description: - "ค่า secret สำหรับ validate HMAC-SHA256 กับข้อมูลใน custom property" , - example: - "super_secret", - }, - public_key: { - type: "string", - description: - "Public key ในรูปแบบ HEX
openssl genpkey -algorithm ED25519 -out private.pem
openssl pkey -in private.pem -outform DER | tail -c 32 | xxd -p -c 32 > private.hex
openssl pkey -in private.pem -pubout -outform DER | tail -c 32 | xxd -p -c 32 > public.hex", - example: - "66b09b4a1b66271d5728c592c2409657e1c070a7bf371bf9f6ed457855270546", - }, - }, - }, - }, - }, - }, - responses: { - 200: { - description: "รายการแท็กพร้อมข้อความ", - content: { - "application/json": { - schema: { $ref: "#/components/schemas/ExtractResponse" }, - }, - }, - }, - 400: { $ref: "#/components/responses/BadRequest" }, - 500: { $ref: "#/components/responses/ServerError" }, - }, - }, - }, "/api/template/render": { post: { tags: ["template"], - summary: "สร้างเอกสารจาก template (.docx) + data (JSON)", + summary: "สร้างเอกสารจาก template (.docx) + data (JSON) ใช้สำหรับนักพัฒนาเท่านั้น", description: "แทนค่า JSON ลงใน Content Control ของ template แล้วคืนไฟล์ .docx ใหม่ " + '(filename ตอน download = generated-document.docx) — ส่ง data ได้ 2 ทาง: ' + - 'field "data" เป็นไฟล์ JSON หรือ field "dataJson" เป็น string ตรง ๆ (เลือกอย่างใดอย่างหนึ่ง)', + 'field "data" เป็นไฟล์ JSON หรือ field "dataJson" เป็น string ตรง ๆ (เลือกอย่างใดอย่างหนึ่ง)
'+ + 'สำหรับนักพัฒนา Template เท่านั้น ไม่ต้อง ใช้ API Key ต้องตั้งค่า secret และ private key เองไม่ใช้ของระบบของระบบ', requestBody: { required: true, content: { @@ -194,6 +141,410 @@ export const openApiSpec = { }, }, }, + "/api/template/extract": { + post: { + tags: ["template"], + summary: "ดึงค่า Content Control ทั้งหมดออกมาจากไฟล์ .docx ใช้สำหรับนักพัฒนาเท่านั้น", + description: + "อ่าน Content Control, bookmark , property ของ MS Word(docx) ออกมาแสดง" + + "ค่าของ Content Control จะเป็น Plain Text ไม่มี markup ซึ่งเหมาะกับการเอาไปใส่ในฐานข้อมูล"+ + 'สำหรับนักพัฒนา Template เท่านั้น ไม่ต้อง ใช้ API Key ต้องตั้งค่า secret และ Public key เองไม่ใช้ของระบบของระบบ', + requestBody: { + required: true, + content: { + "multipart/form-data": { + schema: { + type: "object", + required: ["file"], + properties: { + file: { + type: "string", + format: "binary", + description: "ไฟล์ .docx ที่มี Content Control", + }, + secret: { + type: "string", + description: + "ค่า secret สำหรับ validate HMAC-SHA256 กับข้อมูลใน custom property", + example: + "super_secret", + }, + public_key: { + type: "string", + description: + "Public key ในรูปแบบ HEX
openssl genpkey -algorithm ED25519 -out private.pem
openssl pkey -in private.pem -outform DER | tail -c 32 | xxd -p -c 32 > private.hex
openssl pkey -in private.pem -pubout -outform DER | tail -c 32 | xxd -p -c 32 > public.hex", + example: + "66b09b4a1b66271d5728c592c2409657e1c070a7bf371bf9f6ed457855270546", + }, + }, + }, + }, + }, + }, + responses: { + 200: { + description: "รายการแท็กพร้อมข้อความ", + content: { + "application/json": { + schema: { $ref: "#/components/schemas/ExtractResponse" }, + }, + }, + }, + 400: { $ref: "#/components/responses/BadRequest" }, + 500: { $ref: "#/components/responses/ServerError" }, + }, + }, + }, + "/api/template/generate": { + post: { + tags: ["template"], + summary: "สร้างเอกสาร DOCX จากข้อมูล JSON และ template ที่กำหนด", + description: + "เทมเพลตมีแท็กแบบพิเศษ(รองรับตัวแปร ลูป เงื่อนไข), Content Control เมื่อรวมกับข้อมูล(JSON) "+ + "จะได้เอกสาร รูปแบบฟอร์มที่กรอกค่าแล้วดึงค่ากลับมาใช้งานต่อได้เช่นอัปเดตฐานข้อมูล
    "+ + "
  • JSON payload ต้องมี info.ticket_data เป็น JSON string รูปแบบถูกต้อวมีค่าครบ(tid, fid และ uid)
  • " + + "
  • ไฟล์เทมเพลต(tid)ในระบบ + ข้อมูล(payload) เพื่อสร้างเอกสาร
  • " + + "
  • ticket_data จะทำการ hash/sign แบบอายุสั้นแล้วนำไปฝังไว้ใน custom property เพื่อนำไปใช้ร่วมกับระบบ Versioning
  • "+ + "
", + security: [{ ApiKeyAuth: [] }], + requestBody: { + required: true, + content: { + "application/json": { + schema: { + type: "object", + required: ["info"], + properties: { + info: { + type: "object", + required: ["ticket_data"], + properties: { + ticket_data: { + type: "string", + description: + "JSON string ที่ต้อง parse ได้ และต้องมี tid, fid และ uid", + example: + '{"tid":"T12","fid":"DOC-2569-0001","uid":"123"}', + }, + }, + additionalProperties: true, + }, + }, + additionalProperties: true, + }, + example: { + "info": { + "ticket_data": "{\"fid\":\"T12_b2_IN\",\"tid\":\"T12_X\",\"uid\":\"oom\",\"uname\":\"สรวิชญ์\"}" + }, + "dc": { + "Interpleader": true, + "docname": "", + "docabbr": "(ต. ๑๒)", + "blackno": "คดีหมายเลขดำที่", + "redno": "คดีหมายเลขแดงที่", + "court": "ศาลปกครองนครราชสีมา", + "panels": [ + { + "pname": "นายวิรุฬห์ จินตนะกุล", + "pmname": "ตุลาการศาลปกครองกลาง", + "pm": "ตุลาการเจ้าของสำนวน" + }, + { + "pname": "นายสัมฤทธิ์ อ่อนคำ", + "pmname": "ตุลาการศาลปกครองสูงสุด" + } + ], + "pnameT": "", + "pmnameT": "", + "pmT": "", + "acc_name": "" + + }, + "cc": { + "blackyear": "๙๐/๒๕๖๙", + "redyear": "", + "curdate": "วันที่ เดือน สิงหาคม พุทธศักราช ๒๕๖๙", + "accuser": "นายธีรพล ร่มเริง", + "acc1": "ผู้ฟ้องคดี", + "accused": "รร", + "acc2": "ผู้ถูกฟ้องคดี", + "interpleader": "สมชาย ใจดี", + "acc3": "ผู้ร้องสอด", + "detail1": "ตุลาการเจ้าของสำนวนได้พิจารณาคำฟ้องและเอกสารประกอบคำฟ้องแล้วเห็นว่าคดีนี้เป็นคดีพิพาทซึ่งไม่อยู่ในอำนาจศาลปกครองสูงสุด ผู้ฟ้องเป็นผู้มีสิทธิฟ้องคดี คดีนี้มีกฎหมายกำหนดให้ต้องดำเนินการแก้ไขความเดือดร้อนหรือเสียหายก่อนฟ้องคดี และผู้ฟ้องคดีได้ดำเนินการครบถ้วน โดยเป็นการยื่นฟ้องคดีภายในระยะเวลาการฟ้องคดีตาม พรบ. จัดตั้งศาลปกครองและวิธีพิจารณาคดีปกครองฯ คดีนี้มีค่าธรรมเนียมที่ต้องชำระ\nโดยผู้ฟ้องคดีได้ชำระค่าธรรมเนียมครบถ้วนแล้ว รวมทั้งคดีนี้ไม่เป็นการฟ้องซ้อน การดำเนินกระบวนพิจารณาซ้ำ และการฟ้องซ้ำ\n\nจึงมีคำสั่งรับคำฟ้องไว้พิจารณาและสั่งให้ส่งสำเนาคำฟ้องพร้อมทั้งสำเนาพยานหลักฐานไปยังผู้ถูกฟ้องคดีเพื่อให้ผู้ถูกฟ้องคดีทำคำให้การยื่นต่อศาลภายใน ๓๐ วัน นับแต่วันที่ได้รับคำสั่ง" + } + }, + }, + }, + }, + responses: { + 200: { + description: "ไฟล์ DOCX ที่สร้างสำเร็จ", + headers: { + "Content-Disposition": { + description: "ชื่อไฟล์สำหรับดาวน์โหลด", + schema: { type: "string" }, + }, + "Cache-Control": { + description: "ป้องกันการ cache response", + schema: { type: "string", example: "no-store, max-age=0" }, + }, + }, + content: { + "application/vnd.openxmlformats-officedocument.wordprocessingml.document": { + schema: { type: "string", format: "binary" }, + }, + }, + }, + 400: { + description: + "JSON ไม่มี info.ticket_data, ticket_data ไม่ใช่ string, JSON string ไม่ถูกต้อง หรือไม่มี tid/fid/uid", + content: { + "application/json": { + schema: { $ref: "#/components/schemas/Error" }, + examples: { + missingTicketData: { + value: { + error: + "template/generate json.info.ticket_data not avaliable", + }, + }, + missingIdentifiers: { + value: { + error: + "template/generate require json.info.ticket_data.(fid|tid|uid)", + }, + }, + }, + }, + }, + }, + 401: { + description: "ไม่ได้ส่ง x-api-key หรือ API key ไม่ถูกต้อง", + content: { + "application/json": { + schema: { $ref: "#/components/schemas/Error" }, + example: { error: "Unauthorized: Invalid API Key" }, + }, + }, + }, + 404: { + description: "ไม่สามารถดึงข้อมูลขึ้นมาได้", + content: { + "application/json": { + schema: { $ref: "#/components/schemas/Error" }, + example: { error: "NotFound: Invalid template ID or incorrect storage path" }, + }, + }, + }, + 500: { + description: + "เกิดข้อผิดพลาดระหว่างอ่าน template หรือสร้างเอกสาร", + content: { + "application/json": { + schema: { $ref: "#/components/schemas/Error" }, + example: { error: "Failed to generate document payload" }, + }, + }, + }, + }, + }, + }, + "/api/template/download-docx": { + get: { + tags: ["template"], + summary: "ดาวน์โหลดเอกสาร DOCX ผ่าน Query Parameters (GET) ใช้เพื่อเป็นตัวอย่างจะถูกเอาออกในภายหลัง", + description: + "รับค่า fid, tid, uid, uname จาก Query Parameter อ่านข้อมูล JSON ประจำไฟล์ fid จาก TMPL_PATH แล้วส่งไปประมวลผลต่อที่ /api/template/generate เพื่อสร้างและคืนค่าเป็นไฟล์ DOCX stream ใช้เพื่อเป็นตัวอย่างสำหรับนักพัฒนา ต้องนำออกก่อน production", + parameters: [ + { + name: "fid", + in: "query", + required: true, + schema: { type: "string" }, + description: "ID เอกสาร (File ID)", + example: "T_no_warning", + }, + { + name: "tid", + in: "query", + required: true, + schema: { type: "string" }, + description: "ID เทมเพลต (Template ID)", + example: "T_X", + }, + { + name: "uid", + in: "query", + required: true, + schema: { type: "string" }, + description: "รหัสผู้ใช้งาน (User ID)", + example: "123", + }, + { + name: "uname", + in: "query", + required: true, + schema: { type: "string" }, + description: "ชื่อผู้ใช้งาน (User Name)", + example: "สมชาย ใจดี", + }, + ], + responses: { + 200: { + description: "ไฟล์ DOCX stream ที่สร้างสำเร็จ", + headers: { + "Content-Disposition": { + description: "ชื่อไฟล์สำหรับการดาวน์โหลด (attachment)", + schema: { type: "string", example: 'attachment; filename="T_no_warning_123_xxx.docx"' }, + }, + "Cache-Control": { + description: "การควบคุม Cache (no-store)", + schema: { type: "string", example: "no-store, max-age=0" }, + }, + }, + content: { + "application/vnd.openxmlformats-officedocument.wordprocessingml.document": { + schema: { type: "string", format: "binary" }, + }, + }, + }, + 400: { + description: "พารามิเตอร์ไม่ครบถ้วน หรือรูปแบบไฟล์ JSON ไม่ถูกต้อง", + content: { + "text/plain": { + schema: { type: "string" }, + examples: { + missingParams: { value: "Missing fid,tid,uid or uname" }, + invalidData: { value: "Invalid data format" }, + }, + }, + }, + }, + 404: { + description: "ไม่พบไฟล์ข้อมูล JSON ใน TMPL_PATH", + content: { + "application/json": { + schema: { $ref: "#/components/schemas/Error" }, + example: { error: "Data not found: /path/to/TMPL_PATH/T_no_warning.json" }, + }, + }, + }, + 502: { + description: "เกิดข้อผิดพลาดในการเรียกไปยังบริการ Docedit /api/template/generate", + content: { + "text/plain": { + schema: { type: "string" }, + example: "Error from docedit generate: Expected docx stream but received text/html (Status 500)", + }, + }, + }, + 500: { + description: "เกิดข้อผิดพลาดภายในเซิร์ฟเวอร์", + content: { + "text/plain": { + schema: { type: "string" }, + example: "Internal Server Error", + }, + }, + }, + }, + }, + post: { + tags: ["template"], + summary: "ดาวน์โหลดเอกสาร DOCX ผ่าน HTML Form / Multipart Form Data (POST) เป็นตัวอย่างเท่านั้น จะถูกเอาออกในภายหลัง", + description: + "รับค่า fid, tid, uid, uname จาก Multipart Form Data หรือ Form-urlencoded อ่านข้อมูล JSON และประมวลผลสร้างไฟล์ DOCX stream คืนกลับไป "+ + "จะเรียก generate ในเบื้องหลัง ใช้เพื่อเป็นตัวอย่างสำหรับนักพัฒนา ต้องนำออกก่อน production", + requestBody: { + required: true, + content: { + "multipart/form-data": { + schema: { + type: "object", + required: ["fid", "tid", "uid", "uname"], + properties: { + fid: { type: "string", description: "ID เอกสาร (File ID)",example:"T12_b2_IN" }, + tid: { type: "string", description: "ID เทมเพลต (Template ID)",example:"T12_X" }, + uid: { type: "string", description: "รหัสผู้ใช้งาน (User ID)", example:"john" }, + uname: { type: "string", description: "ชื่อผู้ใช้งาน (User Name)", example:"John Doe"}, + }, + }, + }, + "application/x-www-form-urlencoded": { + schema: { + type: "object", + required: ["fid", "tid", "uid", "uname"], + properties: { + fid: { type: "string", description: "ID เอกสาร (File ID)" }, + tid: { type: "string", description: "ID เทมเพลต (Template ID)" }, + uid: { type: "string", description: "รหัสผู้ใช้งาน (User ID)" }, + uname: { type: "string", description: "ชื่อผู้ใช้งาน (User Name)" }, + }, + }, + }, + }, + }, + responses: { + 200: { + description: "ไฟล์ DOCX stream ที่สร้างสำเร็จ", + headers: { + "Content-Disposition": { + description: "ชื่อไฟล์สำหรับการดาวน์โหลด (attachment)", + schema: { type: "string", example: 'attachment; filename="T12_b2_IN_john_xxx.docx"' }, + }, + "Cache-Control": { + description: "การควบคุม Cache (no-store)", + schema: { type: "string", example: "no-store, max-age=0" }, + }, + }, + content: { + "application/vnd.openxmlformats-officedocument.wordprocessingml.document": { + schema: { type: "string", format: "binary" }, + }, + }, + }, + 400: { + description: "พารามิเตอร์ไม่ครบถ้วน หรือรูปแบบไฟล์ JSON ไม่ถูกต้อง", + content: { + "text/plain": { + schema: { type: "string" }, + examples: { + missingParams: { value: "Missing fid,tid,uid or uname" }, + invalidData: { value: "Invalid data format" }, + }, + }, + }, + }, + 404: { + description: "ไม่พบไฟล์ข้อมูล JSON ใน TMPL_PATH", + content: { + "application/json": { + schema: { $ref: "#/components/schemas/Error" }, + example: { error: "Data not found: /path/to/TMPL_PATH/T12_b2_IN.json" }, + }, + }, + }, + 502: { + description: "เกิดข้อผิดพลาดในการเรียกไปยังบริการ Docedit /api/template/generate", + content: { + "text/plain": { + schema: { type: "string" }, + example: "Error from docedit generate: Expected docx stream but received text/html (Status 500)", + }, + }, + }, + 500: { + description: "เกิดข้อผิดพลาดภายในเซิร์ฟเวอร์", + content: { + "text/plain": { + schema: { type: "string" }, + example: "Internal Server Error", + }, + }, + }, + }, + }, + }, "/api/note/external/documents": { post: { tags: ["external"], @@ -524,7 +875,6 @@ export const openApiSpec = { Error: { type: "object", properties: { - success: { type: "boolean", example: false }, error: { type: "string" }, }, }, @@ -563,14 +913,44 @@ export const openApiSpec = { ExtractResponse: { type: "object", properties: { - success: { type: "boolean", example: true }, data: { type: "object", description: "ชื่อแท็ก Content Control → ข้อความในนั้น (หลายบรรทัดคั่นด้วย \\n)", additionalProperties: { type: "string" }, example: { - blackno: "คดีหมายเลขดำที่", - court: "ศาลปกครองสุพรรณบุรี", + "cc": { + "blackyear": "๙๐/๒๕๖๙", + "redyear": "xxx", + "curdate": "วันที่ เดือน สิงหาคม พุทธศักราช ๒๕๖๙ ", + "accuser": "นายธีรพล ร่มเริง", + "detail1": "ตุลาการเจ้าของสำนวนได้พิจารณาคำฟ้อง...", + }, + "bookmark": { + "signature": "" + }, + "properties": { + "standard": { + "creator": "mam", + "created": "2026-10-22T02:58:00.000Z", + "modified": "2026-10-22T07:08:00.000Z" + }, + "extended": { + "template": "TS_P2_IN.dotx", + "totalTime": 172, + "pages": 2, + "words": 124, + }, + "custom": { + "ticket_data": "{\"exp\":1793013726,\"fid\":\"T_no_warning\",\"tid\":\"T_X\",\"uid\":\"john\",\"uname\":\"John Doe\"}", + "HmacSha256": "1dc53fbd48410845c895ddb841a8fe4ad93da413126fae9c688202e57f2baaa9", + "Ed25519": "95ea0ca6d81df885c22a2334ed89b2fc3c75a1a309fb5843e0deb0a16c9d05a45b345fb28fd606f3b152285e70560ad43a6389534794aa0d6f06d13f1307b005" + } + }, + "verify": { + "exp": "26/10/2569 18:22:06", + "HmacSha256": true, + "Ed25519": true + } }, }, }, diff --git a/middleware.ts b/middleware.ts index a67185a..712d46e 100644 --- a/middleware.ts +++ b/middleware.ts @@ -37,6 +37,6 @@ export function middleware(req: NextRequest) { // ยังถูกคุมอยู่ดีเพราะ root page ส่งต่อให้ /note ซึ่ง middleware จับตามปกติ export const config = { matcher: [ - "/((?!note/login|api/note|api/template|api/documents|api/swagger|tmpl|wordaddin|note/uploads|note/samples|note/swagger-ui|note/pdf\\.worker\\.min\\.mjs|_next|fonts|favicon\\.ico).+)", + "/((?!note/login|api/note|api/template|tmpl/|api/documents|api/swagger|tmpl|wordaddin|note/uploads|note/samples|note/swagger-ui|note/pdf\\.worker\\.min\\.mjs|_next|fonts|favicon\\.ico).+)", ], }; diff --git a/public/tmpl/T12_FD155_3s-long.json b/public/tmpl/T12_FD155_3s-long.json index 8a27173..294791e 100644 --- a/public/tmpl/T12_FD155_3s-long.json +++ b/public/tmpl/T12_FD155_3s-long.json @@ -1,6 +1,6 @@ { "info": { - "ticket_data": "{\"fid\":\"T12_FD155_3s-long_ZZZ\",\"tid\":\"T12\",\"uid\":\"oom\",\"uname\":\"สรวิชญ์\"}" + "ticket_data": "{\"fid\":\"T12_FD155_3s-long\",\"tid\":\"T12\",\"uid\":\"john\",\"uname\":\"John Doe\"}" }, "dc": { "Interpleader": false, diff --git a/public/tmpl/T12_b2_IN.json b/public/tmpl/T12_b2_IN.json index d38a698..4bd98a5 100644 --- a/public/tmpl/T12_b2_IN.json +++ b/public/tmpl/T12_b2_IN.json @@ -1,6 +1,6 @@ { "info": { - "ticket_data": "{\"fid\":\"T12_b2_IN_XXX\",\"tid\":\"T12\",\"uid\":\"oom\",\"uname\":\"สรวิชญ์\"}" + "ticket_data": "{\"fid\":\"T12_b2_IN\",\"tid\":\"T12\",\"uid\":\"john\",\"uname\":\"John Doe\"}" }, "dc": { "Interpleader": true, diff --git a/public/tmpl/T12_b2_IN.out.json b/public/tmpl/T12_b2_IN.out.json deleted file mode 100644 index e1b24b9..0000000 --- a/public/tmpl/T12_b2_IN.out.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "cc": { - "blackyear": "๙๐/๒๕๖๙", - "redyear": "", - "curdate": "วันที่ เดือน สิงหาคม พุทธศักราช ๒๕๖๙", - "accuser": "นายธีรพล ร่มเริง", - "acc1": "ผู้ฟ้องคดี", - "interpleader": "สมชาย ใจดี", - "acc3": "ผู้ร้องสอด", - "accused": "รร", - "acc2": "ผู้ถูกฟ้องคดี", - "detail1": "ตุลาการเจ้าของสำนวนได้พิจารณาคำฟ้องและเอกสารประกอบคำฟ้องแล้วเห็นว่าคดีนี้เป็นคดีพิพาทซึ่งไม่อยู่ในอำนาจศาลปกครองสูงสุด ผู้ฟ้องเป็นผู้มีสิทธิฟ้องคดี คดีนี้มีกฎหมายกำหนดให้ต้องดำเนินการแก้ไขความเดือดร้อนหรือเสียหายก่อนฟ้องคดี และผู้ฟ้องคดีได้ดำเนินการครบถ้วน โดยเป็นการยื่นฟ้องคดีภายในระยะเวลาการฟ้องคดีตาม พรบ. จัดตั้งศาลปกครองและวิธีพิจารณาคดีปกครองฯ คดีนี้มีค่าธรรมเนียมที่ต้องชำระ\nโดยผู้ฟ้องคดีได้ชำระค่าธรรมเนียมครบถ้วนแล้ว รวมทั้งคดีนี้ไม่เป็นการฟ้องซ้อน การดำเนินกระบวนพิจารณาซ้ำ และการฟ้องซ้ำ\n\nจึงมีคำสั่งรับคำฟ้องไว้พิจารณาและสั่งให้ส่งสำเนาคำฟ้องพร้อมทั้งสำเนาพยานหลักฐานไปยังผู้ถูกฟ้องคดีเพื่อให้ผู้ถูกฟ้องคดีทำคำให้การยื่นต่อศาลภายใน ๓๐ วัน นับแต่วันที่ได้รับคำสั่ง", - "pmname1": "ตุลาการศาลปกครองกลาง", - "pmname2": "ตุลาการศาลปกครองสูงสุด" - }, - "bookmark": { - "signature1": "", - "signature2": "" - }, - "properties": { - "standard": { - "title": " ", - "subject": "", - "creator": "oom", - "keywords": "", - "lastModifiedBy": "Sorawit Bholsithi", - "revision": "61", - "created": "2026-10-15T05:18:00.000Z", - "modified": "2026-10-21T04:59:00.000Z" - }, - "extended": { - "template": "Normal.dotm", - "totalTime": 345, - "pages": 1, - "words": 42, - "characters": 493, - "charactersWithSpaces": 534, - "lines": 4, - "paragraphs": 1, - "application": "Microsoft Office Word", - "appVersion": "16.0000", - "company": "", - "docSecurity": 0 - }, - "custom": { - "ticket_data": "{\"exp\":1792667826,\"fid\":\"T12_b2_IN_XXX\",\"tid\":\"T12\",\"uid\":\"oom\",\"uname\":\"สรวิชญ์\"}", - "HmacSha256": "791987c9274493db02561f4ec16b83d05c89766b54661da615e8e02db293d1d4", - "Ed25519": "7ef8b0f2a03a52c49f8c348e3c392f448b71c8462ee860342b0e06a91b555f860cad5649c648dc466982017683f3718caff139c85e17e952ee1570ecfc021e08" - } - } -} \ No newline at end of file diff --git a/public/tmpl/TF_no_warning_P2.json b/public/tmpl/TF_no_warning_P2.json index f2ef337..152736c 100755 --- a/public/tmpl/TF_no_warning_P2.json +++ b/public/tmpl/TF_no_warning_P2.json @@ -1,6 +1,6 @@ { "info": { - "ticket_data": "{\"fid\":\"TF_no_warning_P2\",\"tid\":\"T_X\",\"uid\":\"oom\",\"uname\":\"สรวิชญ์\"}" + "ticket_data": "{\"fid\":\"TF_no_warning_P2\",\"tid\":\"T_X\",\"uid\":\"john\",\"uname\":\"John Doe\"}" }, "dc": { "Interpleader": false, diff --git a/public/tmpl/TS_P2_IN.json b/public/tmpl/TS_P2_IN.json index a70c9fe..479478a 100755 --- a/public/tmpl/TS_P2_IN.json +++ b/public/tmpl/TS_P2_IN.json @@ -1,6 +1,6 @@ { "info": { - "ticket_data": "{\"fid\":\"TS_P2_IN_YYY\",\"tid\":\"T_X\",\"uid\":\"oom\",\"uname\":\"สรวิชญ์\"}" + "ticket_data": "{\"fid\":\"TS_P2_IN\",\"tid\":\"T_X\",\"uid\":\"john\",\"uname\":\"John Doe\"}" }, "dc": { "Interpleader": true, diff --git a/public/tmpl/T_X.docx b/public/tmpl/T_X.docx index 3ae25d765af1024bee6b12d93b21f20f8c70a67d..06182a4c5acb0448c191b75a8a580c5e3e6ca435 100755 GIT binary patch delta 32533 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пNSWMn?@4c?~mJn=vm z41@mWK>OZKA9F-~^uZ+)jF0}5zY)?OZ1oS)1i(!B4E>+Ls)C4f9$DQAjHRrqz(^|h z{~t@aRQ$^(@UMWcO}70$2Hk_X)IA?Klt1O~aQ+iZu`K-umTDce{1T?>mfjI1d|J|O z+VxgYwo)RT9^=fdq6nOyq30}ntAj%r?9{D5CgQ8zID(ZSn*BDSHXlBAqEOjR32@uK zc>tO}pbIHHbu$~3W(UPC$-i4W{|rA_(rh5sX$>DP(DXTK?tHXUL5>Bn)j&pT zaQ~_hLx}L*ZY9SYOC#oi!H-sBq_$>yDee!($R6eg!K7zfmA-2%8M&o~kmoyhpuxVwRDmWigoY(Hn_xwoTlhh?S{J`v$ z9p>=SL7);tYZF2bazC1wCv4cL8ck9A4mo5&SfBDsMb*4!lnnl|Z2kU(_}!9bi7sxS z1;(Z{efk$%>!FU*PM_lxo31+h!o;wgq-GK`lQfM39mt^|;iuscx9fA`P zKyex-58N27xsnduMKtw5GBo&y<`C+)h4%zNTP3GYmaIgC?<(A7Kf>GGc)vBY1aY^ufOm8*yXVcnFDFPKc(+pW6dF;uF_R@?1@w;Y8-N z5z{9YBaWjFliNtJF2c9J9UGA{yWOb9TDsm-o$Byh9qMnR-2WBAayHMsv!D)e#nk7V ziE5HT+w=6;*)FDUVdWmXdpiQg;f%#$DcjlFGONoy(~%8feflBrM{g>#z*J^0tM3Rn z#{07%Vnu+@j#o6l&P2eSE2sP8;3J(jb0);Wwj2y2(v;XzHm>t>DxVrtKL0(n%M#*Q zD&hD+^l96M?ZKqxG}wtCA&gKb-Pgyzmfkl#sR2sgLI1@Rl1nrI)WH8;kT44T-vtQ@ z!2ey4kPiIc1qt!M|6P!P%J_c_65y{~;_J-8dy_RarDy}l-T`N9dRZ`nFJXN`K+kFQ z8ga_>&D#r5#?evhL3_o<3aX&-lU3%5#*>2qCgekYV&E%RHlaH1c@LlUEe0iI0NL~! zTuGDKZKGj7ItK4l{ilK%76~awD^2i5#_hRl+S)p5{l81gGBh6*#2&*3I#zmPCo z0zV#Aui=!XAOrK(cfzlj8=*7M*aqJVppUb!!R?lE=hmtIG}QUBT8jx6{T&K=g+Jp1 zny;~-_WlJ?eLB6W46uBQX@$|}Y0C_G(M_@h( zlP)KyzEfz_|9D>@DgnTwwf8Acy5rF?zN~aFQt1uD58d1v$9-`4O&fW>=$5ks!Q0`* z&d>jRF!0+|Yi|b2cY1Mrcjgn0)Tac62A!$hvAw$)83O18s>BvfmY4^W9kr|9xJKTm z-Fd+%DhTKYS1>aZI0!b7LBJ1?viPHBi`Zzix1TNQ5;KWDTa827X{+=n_M}gx&iPND-U)={9+_CmPBHI_WLKAWj;5WG=nd(|9ev=+xYg zIk|Ar_zL_m#+>06gm?zbzBK)!ue2G^h1J-B7RCD;cu8eNr1(o2bP((q7nk$vo9J$* z=){K#p$?~u?fAW(fi8oF>Y(^wt_eQzvR51?1CuG6Im{T$vsJ~CM~ngF1pmGL3wT=C zKMygHfBI$&!p}0pN$k{s>5B z5dmQSQ~rOBAp9*3p&^hEhz!QuA?EmxkfufsyYzhA7X&NL`TLD;L K^o_tqApRfM+`!2I delta 32824 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 Rst=;I0I - - - - - Document - - -

Template & Data

- -
    -
  1. T12_X - (T12_b2_IN, T12_FD155_3s-long) -
  2. -
  3. T_X - (TF_no_warning_P2, T_no_warning, TS_P2_IN.json) -
  4. -
- - \ No newline at end of file From 50af6c10fa8e33460c053f60c9941256ee86ccc9 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 27 Oct 2026 09:44:28 +0700 Subject: [PATCH 15/21] Refactor Word Add-in authentication to use e-Court pass-through; remove Keycloak integration - Updated documentation to reflect removal of login and transition to e-Court pass-through authentication. - Modified `authService` to handle authentication via custom properties in documents without user login. - Adjusted `apiClient` to immediately throw `AuthRequiredError` for invalid tokens, as refresh tokens are no longer applicable. - Revised UI flow to accommodate new authentication method, including changes to state management and error handling. - Added new requirements document outlining the add-in workflow and authentication process. --- CLAUDE.md | 16 ++- components/wordaddin/TaskpaneApp.tsx | 136 +++++++++---------------- docs/WORDADDIN-DEV.md | 22 ++-- docs/addin-ui-flow.md | 46 ++++----- docs/requirement/addin-workflow.md | 44 ++++++++ public/wordaddin/manifest.template.xml | 6 +- wordaddin/services/apiClient.ts | 28 ++--- wordaddin/services/authService.ts | 56 ++-------- wordaddin/services/metadataService.ts | 4 +- 9 files changed, 161 insertions(+), 197 deletions(-) create mode 100644 docs/requirement/addin-workflow.md diff --git a/CLAUDE.md b/CLAUDE.md index f5ccb40..7ea752d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,7 +12,7 @@ - Next.js 14 (App Router) + TypeScript **strict** — serve ใต้ basePath `/docedit` + route ทั้งหมดของแอปใต้ `app/note/**` (URL ปลายทางยังเป็น `/docedit/note/…` เหมือนเดิม — basePath `/docedit` เป็นพื้นที่กลางรองรับ sibling ในอนาคต เช่น wordaddin, `next.config.mjs`) - pdfjs-dist **v4** (`^4.10.38`) — built-in TextLayer + worker จาก `public/note/pdf.worker.min.mjs` (postinstall `scripts/patch-pdf-worker.mjs` คัดลอกจาก node_modules + **patch fake-space ไทย** — รายละเอียด invariant #6) - Drizzle ORM + PostgreSQL (Docker container เดียว: `judes-note-db`, image `postgres:17-alpine`, พอร์ต 54322) — **ไม่ใช้ Supabase** + realtime sync ด้วย **SSE (Server-Sent Events)** ผ่าน `app/api/note/annotations/stream` + pub/sub in-process (`lib/annotate-events.ts`) -- **wordaddin** — taskpane ของ Word add-in "e-Court Documents Versioning" พอร์ตมาเป็น route `/docedit/wordaddin` ของแอปนี้ (UI = Genesis, ชั้น services Office.js พอร์ตดิบจาก `D:\word-addin-poc\addin`) — ปัจจุบัน auth + API เป็น **mock** รอของจริง (spec UI: `docs/addin-ui-flow.md`, คู่มือ dev/sideload/สลับกลับ: `docs/WORDADDIN-DEV.md`); Office.js **self-host** ที่ `public/wordaddin/office-js/` ผ่าน postinstall `scripts/fetch-office-js.mjs` — gitignore (ดาวน์โหลดฉบับล่าสุดจาก CDN ของ Microsoft ตอนติดตั้ง ตัวระบบ deploy ใช้ intranet ล้วน; แพ็กเกจ npm `@microsoft/office-js` เลิกอัปเดตที่ 1.1.110 ก่อน WordApi 1.4/1.5 — runtime ตัวนั้นไม่มี `Document.close` ใช้ไม่ได้) +- **wordaddin** — taskpane ของ Word add-in "e-Court Documents Versioning" พอร์ตมาเป็น route `/docedit/wordaddin` ของแอปนี้ (UI = Genesis, ชั้น services Office.js พอร์ตดิบจาก `D:\word-addin-poc\addin`) — **ไม่มี login ยกเลิก Keycloak 2026-10-27**: auth = e-Court pass-through (token/owner ใน custom properties ของไฟล์) ส่วน API ยังเป็น **mock** รอของจริง (spec UI: `docs/addin-ui-flow.md`, คู่มือ dev/sideload/สลับกลับ: `docs/WORDADDIN-DEV.md`); Office.js **self-host** ที่ `public/wordaddin/office-js/` ผ่าน postinstall `scripts/fetch-office-js.mjs` — gitignore (ดาวน์โหลดฉบับล่าสุดจาก CDN ของ Microsoft ตอนติดตั้ง ตัวระบบ deploy ใช้ intranet ล้วน; แพ็กเกจ npm `@microsoft/office-js` เลิกอัปเดตที่ 1.1.110 ก่อน WordApi 1.4/1.5 — runtime ตัวนั้นไม่มี `Document.close` ใช้ไม่ได้) - Tailwind CSS + design system **Genesis** (`docs/DESIGN.md`, 2026-09-23) — palette tokens ใน `tailwind.config.ts` (`primary` เดิม + `primary-hover`/`canvas`/`line`/`ink`/`ink-soft`/`ink-faint`/`success`/`warning`/`error` + ระดับข้อความ `success-ink`/`warning-ink`/`error-ink` ≥5:1 สำหรับข้อความ/ไอคอนสถานะ — token 500 ใช้กับ border/bg tint เท่านั้น) ห้ามใช้ raw palette shades ของ Tailwind (`slate-*`/`amber-*`/`emerald-*`/`red-*` ฯลฯ) อีก; ฟอนต์ display = **General Sans** (self-host จาก Fontshare ผ่าน `next/font/local` — `app/fonts/`, variable weight 200–700, ใช้ intranet ได้), body = **DM Sans** (`next/font/google`), ไทย fallback **Sarabun** ตามลำดับใน stack (Latin ก่อนไทย — `fontFamily`/globals.css); รัศมี: ปุ่ม/input 6px (รวมปุ่ม action เล็กใน popup/composer/dialog — คู่ ยกเลิก/บันทึก ต้องได้รัศมีเดียวกัน), menu/list row ใน dropdown/panel 4px, panel/dropdown 8px, การ์ด 12px; เงาเฉพาะ hover (`shadow-card-hover`/`shadow-btn-glow` — static ไม่มีเงา ยกเว้นชั้น floating: dropdown/popover `shadow-lg` และ pin ลอยบนเอกสาร ดู §App-specific exceptions ของ DESIGN.md); focus indicator = outline น้ำเงินทึบ 2px + offset ผ่าน class `.focus-ring`/`.genesis-input` ใน globals.css (+ สำเนา `FOCUS_RING_CLS` ใน PDFViewer/AnnotationSidebar/AnnotationSelectionLayer/ToolControls — แก้ต้องแก้พร้อมกัน; halo alpha จางเกิน WCAG 1.4.11); น้ำเงินเฉพาะของ interactive (แบรนด์มาร์ก tile JN/แถบหัวหน้าสรุปยกเว้น) - mock login e-Court — หน้า `/note/login` (dropdown `MOCK_USERS` user1–user5 + รหัสผ่านกลาง `Demo1234`) ตั้ง cookie `oacCase` ตาม format จริงของระบบศาล → identity (userId = `u_userid`, userName = `u_userlogin` — **ต่างกันต่อคน** เพื่อสิทธิ์เจ้าของ) อ่านจาก cookie ผ่าน `lib/session.ts` + บังคับทุกหน้าด้วย `middleware.ts` (auth จริงเทียบระบบ e-Court + roles ยัง out of scope) @@ -41,7 +41,7 @@ scripts/copy-swagger-ui.mjs ← postinstall ตัวที่สอง: คั scripts/fetch-office-js.mjs ← postinstall ตัวที่สาม: ดาวน์โหลด Office.js runtime **ฉบับ CDN ล่าสุด** (appsforoffice.microsoft.com — ชุดไฟล์เฉพาะ Word: loader + host files ราย platform + legacy 15.x + o15apptofilemappingtable/microsoftajax + office_strings.js ต่อ locale en-us/th-th บังคับ) ไป public/wordaddin/office-js/ ให้ taskpane โหลด same-origin — npm `@microsoft/office-js` เลิกอัปเดตที่ 1.1.110 (~2018) ไม่มี `Document.close`/enum `CloseBehavior` ที่ runtime ใหม่+`@types/office-js` มี; ไฟล์หลักหาย/ตรวจไม่เจอ `"Document.close"`+`CloseBehavior` ใน word-win32-16.01.js หลังโหลด = exit 1 ดัง ๆ (ไฟล์ปลายทาง gitignore — ดาวน์โหลดใหม่ทุกครั้ง; ติดตั้งต้องออกเน็ตถึง CDN ของ Microsoft ส่วน runtime ตัวระบบ intranet ล้วน) db/schema.ts ← ตาราง + JSONB types + DTO + mapper toDTO db/index.ts ← postgres.js + drizzle (DATABASE_URL → container judes-note-db, prepare:false) -middleware.ts ← บังคับ login ทุกหน้า — ไม่มี cookie oacCase ที่ parse ได้ = redirect /note/login (path ที่ตัด basePath แล้ว: หน้าขึ้นต้น /note/… ส่วน API ขึ้นต้น /api/note/… — ยกเว้น note/login, api/note/*, api/template/* (กลุ่ม docx template เปิดกว้างให้ทางศาล/ผู้ทดสอบภายนอกยิงได้เลย — ไม่แตะ DB), api/documents/* (กลุ่ม docver จัด version เอกสาร — stand-alone ไม่มี session ใช้ auth ของกลุ่มเอง: ขอ access_token ผ่าน x-api-key แล้วแนบ Bearer ทุก endpoint, docs/PLAN-DOCVER.md), api/swagger/* (หน้า Swagger UI), wordaddin/* (taskpane ของ Word add-in — auth mock ของตัวเอง), note/uploads/*, note/samples, note/swagger-ui/*, note/pdf.worker.min.mjs, _next, fonts, favicon และ **root "/" เอง** (matcher ใช้ `.+` — root เป็น redirect สู่ /wordaddin ซึ่ง public ตามคำขอผู้ใช้ 2026-10-14, ลิงก์ e-Court N8 ที่ root ยังถูกคุมผ่าน /note ที่รับส่งต่อ)) +middleware.ts ← บังคับ login ทุกหน้า — ไม่มี cookie oacCase ที่ parse ได้ = redirect /note/login (path ที่ตัด basePath แล้ว: หน้าขึ้นต้น /note/… ส่วน API ขึ้นต้น /api/note/… — ยกเว้น note/login, api/note/*, api/template/* (กลุ่ม docx template เปิดกว้างให้ทางศาล/ผู้ทดสอบภายนอกยิงได้เลย — ไม่แตะ DB), api/documents/* (กลุ่ม docver จัด version เอกสาร — stand-alone ไม่มี session ใช้ auth ของกลุ่มเอง: ขอ access_token ผ่าน x-api-key แล้วแนบ Bearer ทุก endpoint, docs/PLAN-DOCVER.md), api/swagger/* (หน้า Swagger UI), wordaddin/* (taskpane ของ Word add-in — ไม่มี login: auth เป็น e-Court pass-through จาก token/owner ในไฟล์), note/uploads/*, note/samples, note/swagger-ui/*, note/pdf.worker.min.mjs, _next, fonts, favicon และ **root "/" เอง** (matcher ใช้ `.+` — root เป็น redirect สู่ /wordaddin ซึ่ง public ตามคำขอผู้ใช้ 2026-10-14, ลิงก์ e-Court N8 ที่ root ยังถูกคุมผ่าน /note ที่รับส่งต่อ)) lib/annotate-events.ts ← pub/sub in-process (actions publish → SSE route subscribe) lib/server-pdf.ts ← path guard อ่าน PDF จาก public/note/ (export ใช้) lib/session.ts ← getSessionUser — identity จาก cookie oacCase (mock login e-Court) @@ -54,13 +54,9 @@ lib/document-versioning.ts ← docver pure validation (ห้าม import อ lib/docver-token.ts ← docver token + auth ของกลุ่ม /api/documents/* (2026-10-22 — pure module ห้าม import อะไรที่ลาก @/db หรือ minio มาด้วย เหมือน lib/document-versioning.ts; unit test tests/lib/docver-token.test.ts ต้องรันได้โดยไม่ต้องตั้ง env): issueDocverToken/verifyDocverToken (JWT HS256 มือเขียน node:crypto ล้วน — บังคับ alg=HS256 กัน alg-confusion, timingSafeEqual หลัง hash ทั้งสองฝั่ง, exp บังคับ) + requireDocverApiKey (header x-api-key เทียบ env DOCVER_API_KEY — fail closed 401 ไม่ตั้ง/ว่าง, 401 เดียวกันทั้งไม่ใส่/ผิด กัน enumerate, ห้าม log ค่า key/header ดิบ) + requireDocverBearer (Authorization: Bearer → claims {userid, username, fid, template} — secret จาก env DOCVER_TOKEN_SECRET ไม่ตั้ง = 401, ทุกกรณี 401 ข้อความเดียวกัน) + parseTokenRequestBody (allowlist userid/username/fid/template — **fid = fileId ของ e-Court (string, บังคับ 2026-10-26)** เป็น docId ของทุก operation ตรง ๆ · verify ไม่รับ token ที่ไม่มี fid) lib/docver-store.ts ← docver server-side MinIO + DB (env MINIO_* อ่านตอนเรียกไม่ใช่ module load; client + cached ensureBucket เก็บบน globalThis แบบ db/index.ts): object key `docver//document.docx|pdf` (uuid ล้วน ไม่มี input ผู้ใช้), putVersionObjects (docx ก่อน — pdf ล้ม = ลบ docx คืนแล้วโยนต่อ), removeVersionObjects (best-effort), presignVersionUrl (TTL env DOCVER_LINK_TTL_SECONDS), insertVersionRecord (**tx เดียว**: upsert documents ON CONFLICT DO NOTHING → SELECT id FOR UPDATE → max(versionNumber)+1 → insert — จ่ายเลข version ปลอดภัยเรียกคู่ขนาน, UNIQUE(document_id, version_number) เป็น safety net) wordaddin/ ← **client-only zone ของ Word add-in** (ห้าม import จาก lib/ หรือถูก import โดย server component — พอร์ตดิบจาก D:\word-addin-poc\addin\src, spec: docs/addin-ui-flow.md, คู่มือ dev: docs/WORDADDIN-DEV.md) -wordaddin/config.ts ← env NEXT_PUBLIC_ADDIN_API_BASE_URL ("" = ยังไม่ต่อ backend จริง) + NEXT_PUBLIC_ADDIN_MOCK_USERNAME (mock auth) +wordaddin/config.ts ← env NEXT_PUBLIC_ADDIN_API_BASE_URL ("" = ยังไม่ต่อ backend จริง) + NEXT_PUBLIC_ADDIN_MOCK_USERNAME (ชื่อ fallback ของ ?addindemo/ไฟล์ที่มี token ไม่มี owner) wordaddin/models/versionRecord.ts ← types VersionRecord/FieldsSnapshot + readSnapshotField (พอร์ตดิบ) -<<<<<<< HEAD -wordaddin/services/ ← documentService/fileService/metadataService/autoSaveService/versionFlow/apiClient/errors พอร์ตดิบทั้งไฟล์ · **authService = MOCK** (login ผ่านทันที คง surface เดิมครบไว้สลับของจริง — ต้นฉบับ Keycloak PKCE อยู่ที่ D:\word-addin-poc\addin + **`signInFromDocument` e-Court pass-through 2026-10-16: ไฟล์ที่ stamp custom properties `token`/`owner` มา (`readTrackedProperties` อ่านรวม) = `detect()` ของ TaskpaneApp ผ่านเข้าเลยไม่ต้องกด login — username จาก owner → uid ใน JWT → mockUsername, token ไฟล์เก็บ memory ให้ getAccessToken ใช้ต่อ**) · **versioningService = MOCK** (memory + **metadata persist ลง localStorage ต่อ documentId** เพื่อทดสอบวงปิดเอกสาร→เปิดใหม่ได้ — blob ไฟล์จริงเฉพาะ session; คงชื่อ/ลายเซ็น uploadVersion/getVersions/getDownloadLink/newClientVersionId เป๊ะ, download-link คืน about:blank — **backend จริงของ surface นี้มีแล้ว**: กลุ่ม `app/api/documents/**` (docver, docs/PLAN-DOCVER.md — สลับ mock ด้วยการตั้ง `NEXT_PUBLIC_ADDIN_API_BASE_URL`) -======= -wordaddin/services/ ← documentService/fileService/metadataService/autoSaveService/versionFlow/apiClient/errors พอร์ตดิบทั้งไฟล์ + **templateService** (แท็บที่ 2 "เครื่องมือ Template" 2026-10-19 — list/select/applyTestValues + แทรกย่อหน้า-ตัวแบ่งบรรทัด-ตัวแบ่งหน้า ที่ cursor (page break มี fallback OOXML กัน E_FAIL), Office.js ล้วน — ⚠️ `contentControls` ธรรมดาเห็นเฉพาะ rich text CC: feature-detect WordApi 1.5 ใช้ `getContentControls()` เห็นทุกชนิด, ห้าม top-level Office access) · **authService = MOCK** (login ผ่านทันที คง surface เดิมครบไว้สลับของจริง — ต้นฉบับ Keycloak PKCE อยู่ที่ D:\word-addin-poc\addin + **`signInFromDocument` e-Court pass-through 2026-10-16: ไฟล์ที่ stamp custom properties `token`/`owner` มา (`readTrackedProperties` อ่านรวม) = `detect()` ของ TaskpaneApp ผ่านเข้าเลยไม่ต้องกด login — username จาก owner → uid ใน JWT → mockUsername, token ไฟล์เก็บ memory ให้ getAccessToken ใช้ต่อ**) · **versioningService = MOCK** (memory + **metadata persist ลง localStorage ต่อ documentId** เพื่อทดสอบวงปิดเอกสาร→เปิดใหม่ได้ — blob ไฟล์จริงเฉพาะ session; คงชื่อ/ลายเซ็น uploadVersion/getVersions/getDownloadLink/newClientVersionId เป๊ะ, download-link คืน about:blank) ->>>>>>> 6daee1585199721ba931b2bf4fe55277cb7c304a +wordaddin/services/ ← documentService/fileService/metadataService/autoSaveService/versionFlow/apiClient/errors พอร์ตดิบทั้งไฟล์ + **templateService** (แท็บที่ 2 "เครื่องมือ Template" 2026-10-19 — list/select/applyTestValues + แทรกย่อหน้า-ตัวแบ่งบรรทัด-ตัวแบ่งหน้า ที่ cursor (page break มี fallback OOXML กัน E_FAIL), Office.js ล้วน — ⚠️ `contentControls` ธรรมดาเห็นเฉพาะ rich text CC: feature-detect WordApi 1.5 ใช้ `getContentControls()` เห็นทุกชนิด, ห้าม top-level Office access) · **authService = e-Court pass-through ทางเดียว ไม่มี login แล้ว** (ยกเลิก Keycloak 2026-10-27 — surface เหลือ signInForDemo (dev-only) / signInFromDocument / getAccessToken / getUsername; ไฟล์ที่ stamp custom properties `token`/`owner` มา (`readTrackedProperties` อ่านรวม) = `detect()` ของ TaskpaneApp ผ่านเข้าเลย ไม่มี = phase `no-access` — username จาก owner → uid ใน JWT → mockUsername, token ไฟล์เก็บ memory ให้ getAccessToken ใช้ต่อ; apiClient โดน 401 = AuthRequiredError → no-access เลย token ต่ออายุไม่ได้) · **versioningService = MOCK** (memory + **metadata persist ลง localStorage ต่อ documentId** เพื่อทดสอบวงปิดเอกสาร→เปิดใหม่ได้ — blob ไฟล์จริงเฉพาะ session; คงชื่อ/ลายเซ็น uploadVersion/getVersions/getDownloadLink/newClientVersionId เป๊ะ, download-link คืน about:blank — **backend จริงของ surface นี้มีแล้ว**: กลุ่ม `app/api/documents/**` (docver, docs/PLAN-DOCVER.md — สลับ mock ด้วยการตั้ง `NEXT_PUBLIC_ADDIN_API_BASE_URL`) utils/coordinates.ts ← pixel⇄% + getPointerPercent + clamp + mergeRectsByLine utils/geometry.ts ← bbox/translate/scale/hitTestPath ของ annotation (pure, หน่วย %) utils/annotation-display.ts ← label/ข้อความย่อต่อชนิด + TYPE_SHORT/TYPE_ORDER ชื่อย่อ/ลำดับชนิด (บรรทัดสรุปหน้าของหน้าสรุป + แผงตัวกรอง explorer — ย้ายจาก 2 ที่ซ้ำกันมารวมที่นี่ 2026-09-24) + ค่า default TEXTBOX/STAMP + describeAnnotationChange สรุปสิ่งที่เปลี่ยนของ version history F19 (sidebar/สรุป/groups/CSV ใช้ร่วม) @@ -92,8 +88,8 @@ app/note/annotate/[id]/page.tsx ← โหลด file + initial data → PD app/note/annotate/[id]/summary/page.tsx ← หน้าสรุป Annotation+Bookmark ของไฟล์เดียว (server component — query ทั้งหมดฝั่ง server แล้วส่ง `ExplorerItem[]` เรียง หน้า → หน้าเดียวกัน bookmark ก่อน annotation (bookmark ต่อหน้าเรียงตาม yOffset N5 เหมือนเดิม, annotation ตาม createdAt) ให้ `SummaryExplorer` กรองใน memory; design "แฟ้มสำนวน" 2026-09-17 แถบหัวน้ำเงิน + การ์ดรายหน้าขอบ folio + รายการตามหน้าจุดไข่ปลา → **redesign 2026-09-24 ตามคำขอผู้ใช้เป็น explorer 2 คอลัมน์แบบหน้ากลุ่ม N9 โดยมิติแรกของตัวกรองเป็น "หน้า" แทน "ไฟล์"** — แถบหัวน้ำเงิน + action bar (กลับหน้าอ่าน — ปุ่มส่งออก CSV F16/ส่งออก Bookmark JSON N7 ถอด 2026-09-25 แทนด้วย N10 API) + empty state คงเดิม, ไฟล์ว่าง = ไม่ mount explorer เลย; generateMetadata ตั้งชื่อแท็บ browser ตามคดี; ตอนพิมพ์ header เป็นกรอบเส้นน้ำเงิน + แผงตัวกรอง/แถบ action ซ่อนด้วย print:hidden — กฎ @media print อยู่ใน globals.css) — อย่าถอด class `summary-page` ของ
(กฎ coarse 44px ใน globals.css ผูกอยู่กับ selector นี้ ครอบทั้ง `a` และ `button`) app/wordaddin/layout.tsx + page.tsx ← taskpane ของ Word add-in (URL `/docedit/wordaddin` — nested layout แรกของ repo override title แท็บ browser; page เป็น server component บาง ๆ render WordAddinGate ทั้งหมดฝั่ง client) app/wordaddin/manifest.xml/route.ts ← GET serve manifest ของ add-in พร้อม rewrite origin ตามโฮสต์ที่ขอ (template `public/wordaddin/manifest.template.xml` ค่า dev — dev/UAT/production โหลด manifest.xml ขอ environment ตัวเองแล้วใช้ได้เลย ไม่ต้อง rewrite มือ; origin จาก `utils/request-origin.ts` แชร์กับ deep link N10 — x-forwarded-proto/host ก่อน fallback host ตรง + บังคับ https โฮสต์ที่ไม่ใช่ localhost เพราะ Office รับ https เท่านั้น) -components/wordaddin/WordAddinGate.tsx ← client gate: ฉีด script office.js self-host (ผ่าน withBasePath "/wordaddin/office-js/office.js") → Office.onReady → mount TaskpaneApp · `?addindemo=untracked|tracking` (dev-only) ข้าม office.js + mock sign in + ตั้ง phase ตรง เพื่อดู UI ใน browser ธรรมดา — flow ที่แตะ Word จริงจะจบที่ error ตาม expected (FR-17: ใน browser ปกติ banner auto-save error โผล่หลัง ~30 วิ — ถูกต้อง) -components/wordaddin/TaskpaneApp.tsx ← state machine checking/signed-out/untracked/tracking + handler ทุกตัว พอร์ตจาก App.tsx ต้นฉบับ (state/handlers คงเดิม เปลี่ยนแค่ JSX → Genesis) · SaveControls/MessageBar/Spinner/ConfirmVersionDialog(portal แบบ AnnotationSidebar)/VersionHistoryPanel · **2 แท็บ (Versioning / เครื่องมือ Template — toggle `aria-pressed` เฉพาะ phase untracked|tracking, 2026-10-19): แท็บ template = TemplateToolsPanel (สแกน CC/กรอกค่าทดสอบ/แทรกย่อหน้า-ตัวแบ่ง — spec ที่ docs/addin-ui-flow.md §10)** +components/wordaddin/WordAddinGate.tsx ← client gate: ฉีด script office.js self-host (ผ่าน withBasePath "/wordaddin/office-js/office.js") → Office.onReady → mount TaskpaneApp · `?addindemo=untracked|tracking` (dev-only) ข้าม office.js + ตั้ง session mock (`signInForDemo`) + ตั้ง phase ตรง เพื่อดู UI ใน browser ธรรมดา — flow ที่แตะ Word จริงจะจบที่ error ตาม expected (FR-17: ใน browser ปกติ banner auto-save error โผล่หลัง ~30 วิ — ถูกต้อง; เส้นทางจริงไม่มี demo จะจบที่หน้า no-access เพราะอ่าน properties ไม่ได้) +components/wordaddin/TaskpaneApp.tsx ← state machine checking/no-access/untracked/tracking (ไม่มี login — ยกเลิก Keycloak 2026-10-27: detect อ่าน token/owner จากไฟล์ผ่าน signInFromDocument ไม่มี = no-access · AuthRequiredError จาก flow ก็ไป no-access) + handler ทุกตัว พอร์ตจาก App.tsx ต้นฉบับ (เปลี่ยนแค่ JSX → Genesis + ถอดหน้า login/ปุ่มออกจากระบบ — header เหลือ chip username เฉย ๆ) · SaveControls/MessageBar/Spinner/ConfirmVersionDialog(portal แบบ AnnotationSidebar)/VersionHistoryPanel · **2 แท็บ (Versioning / เครื่องมือ Template — toggle `aria-pressed` เฉพาะ phase untracked|tracking, 2026-10-19): แท็บ template = TemplateToolsPanel (สแกน CC/กรอกค่าทดสอบ/แทรกย่อหน้า-ตัวแบ่ง — spec ที่ docs/addin-ui-flow.md §10)** components/summary/SummaryExplorer.tsx ← client root ของหน้าสรุป (redesign 2026-09-24) — state ตัวกรอง `FacetFilterState` จากแผงกลาง explorer + กรองใน memory + facet count/static users derive เอง + มิติ "หน้า" เป็นตัวเลือกแบบ single-select เฉพาะหน้าที่มีรายการ ≥1 เรียงน้อย→มาก (`optionsScroll` ให้ scroll ภายในกลุ่ม max-h-64 กันหน้าเยอะดันกลุ่มอื่นออกจอ) + layout 2 คอลัมน์ (มือถือ < lg ตัวกรองหุบเป็น `
`, desktop aside sticky 15rem — pattern เดียวกับ GroupExplorer; คอลัมน์ content ครอบด้วย `
` ไม่ใช่ `
` เพราะกฎ print break-inside ของ `main.summary-page section` มุ่งการ์ดรายหน้าข้างใน) components/summary/SummaryList.tsx ← คอลัมน์ content ของหน้าสรุป — การ์ดรายหน้า "1 หน้า 1 การ์ด" หัวการ์ด sticky ระดับ viewport (เลขหน้า + บรรทัดสรุปหน้า `pageSummaryLine` **คำนวณจากรายการที่เห็นจริงหลังกรอง** + ปุ่ม "เปิดหน้านี้"; กลับเป็น position static ตอนพิมพ์ผ่านกฎ `.page-head` ใน globals.css) + แถว passive จุดคลิกเดียวคือปุ่ม `` "View" เหมือน GroupList (deep link `?ann=`/`?page=` — bookmark ไปหน้า, annotation ไปรายการ; meta line ไม่มี segment ไฟล์เพราะไฟล์เดียวทั้งหน้า) + header "พบ N รายการ" (aria-live) + ปุ่ม "ล้างตัวกรอง" (เฉพาะมีตัวกรอง active) + empty state กรองจนไม่เหลือ + **ตอนพิมพ์แสดงฉบับเต็มเสมอ** — ตัวกรองที่ตั้งค้างต้องไม่ตัดรายการออกเงียบ ๆ บนกระดาษ: ตอน activeCount > 0 render ชุดบนจอ (print:hidden) + ชุดเต็ม (hidden print:block, ไม่มี id/aria-labelledby ซ้ำ) แยกกัน; anchor `#page-N` ของ section คงจาก design เดิมให้ลิงก์เก่ายังใช้ได้ components/pdf/PDFViewer.tsx ← client root (tool state, zoom, selection, lazy render หน้า) diff --git a/components/wordaddin/TaskpaneApp.tsx b/components/wordaddin/TaskpaneApp.tsx index 5191a79..1b71ed2 100644 --- a/components/wordaddin/TaskpaneApp.tsx +++ b/components/wordaddin/TaskpaneApp.tsx @@ -3,7 +3,7 @@ "use client"; import { useCallback, useEffect, useState } from "react"; -import { FileUp, GitCommitHorizontal, LogOut } from "lucide-react"; +import { FileUp, GitCommitHorizontal } from "lucide-react"; import { autoSaveService } from "@/wordaddin/services/autoSaveService"; import * as authService from "@/wordaddin/services/authService"; @@ -23,22 +23,25 @@ import TemplateToolsPanel from "./TemplateToolsPanel"; import VersionHistoryPanel from "./VersionHistoryPanel"; /** - * Task pane หลัก — state machine ตาม Phase 5: - * checking → (ไม่มี token) signed-out - * → (มี token + ไม่ใช่ tracked document) untracked - * → (มี token + tracked document) tracking - * พอร์ตจาก App.tsx ต้นฉบับ (D:\word-addin-poc\addin) — state/handlers คงเดิมทุกตัว - * เปลี่ยนเฉพาะ: import path, JSX → Genesis/Tailwind, ถอด FluentProvider/makeStyles, + * Task pane หลัก — state machine: + * checking → (ไม่มี token/owner ในไฟล์) no-access + * → (มี token/owner + ไม่ใช่ tracked document) untracked + * → (มี token/owner + tracked document) tracking + * ไม่มี login ตั้งแต่ยกเลิก Keycloak (2026-10-27) — auth เข้าทางเดียวคือ e-Court + * pass-through: `detect()` อ่าน token/owner จาก custom properties แล้วเรียก + * `authService.signInFromDocument` ผ่านเลย + * พอร์ตจาก App.tsx ต้นฉบับ (D:\word-addin-poc\addin) — เปลี่ยน: import path, + * JSX → Genesis/Tailwind, ถอด FluentProvider/makeStyles, ถอดหน้า login, * เพิ่ม prop demo (โหมด ?addindemo ของ WordAddinGate — ตั้ง phase ตรง ไม่ detect จริง) * - * กติกา: ทุก async ต้อง catch — AuthRequiredError → กลับ signed-out อย่างเนียน (AC-9) + * กติกา: ทุก async ต้อง catch — AuthRequiredError → หน้า no-access ให้ขอไฟล์ใหม่จาก e-Court * error อื่นแสดง MessageBar ไม่ปล่อย unhandled rejection * - * FR-17 — auto-save (local เท่านั้น) เริ่มตอน mount ไม่รอ sign-in; FR-3 reminder + * FR-17 — auto-save (local เท่านั้น) เริ่มตอน mount ไม่ผูกกับ phase; FR-3 reminder * กลายเป็น flag `uncommittedSinceVersion` ที่ periodic tick ของ autoSaveService ตั้งค่า */ -type Phase = "checking" | "signed-out" | "untracked" | "tracking"; +type Phase = "checking" | "no-access" | "untracked" | "tracking"; /** แท็บของ taskpane — versioning คือทุกอย่างเดิม, template คือเครื่องมือจัดการ Content Controls * (Office.js ล้วน ไม่ยิง backend จึงใช้ได้ทั้ง untracked และ tracking) */ @@ -108,8 +111,8 @@ const TaskpaneApp: React.FC<{ demo?: "untracked" | "tracking" }> = ({ demo }) => const [historyLoading, setHistoryLoading] = useState(false); const [historyError, setHistoryError] = useState(null); - const [signingIn, setSigningIn] = useState(false); - const [loginError, setLoginError] = useState(null); + /** ข้อความบนหน้า no-access — ต่างกันตามสาเหตุ (ไม่มี token ในไฟล์ / token หมดอายุ) */ + const [noAccessNotice, setNoAccessNotice] = useState(null); const [starting, setStarting] = useState(false); const [saving, setSaving] = useState(false); @@ -135,15 +138,16 @@ const TaskpaneApp: React.FC<{ demo?: "untracked" | "tracking" }> = ({ demo }) => /** FR-3 — periodic tick เห็นการแก้ไขหลัง version ล่าสุด (แสดงเฉพาะตอน tracking) */ const [hasUncommitted, setHasUncommitted] = useState(false); - /** 401 ที่ refresh ไม่ช่วย → กลับหน้า sign-in อย่างเนียน (AC-9) */ + /** AuthRequiredError (ไม่มี token / 401) → หน้า no-access — token จากไฟล์ต่ออายุไม่ได้ + * ผู้ใช้ต้องขอไฟล์ใหม่จาก e-Court จึงรีเซ็ต state ทั้งชุดกันใช้ของเก่าต่อ */ const handleAuthRequired = useCallback((error: unknown): boolean => { if (error instanceof AuthRequiredError) { - setPhase("signed-out"); + setPhase("no-access"); setActiveTab("versioning"); setDocumentId(null); setDocumentTemplateId(null); setVersions([]); - setLoginError("เซสชันหมดอายุ — กรุณา login ใหม่"); + setNoAccessNotice("token ของ e-Court หมดอายุหรือไม่ถูกต้อง — โปรดขอไฟล์ใหม่จาก e-Court แล้วเปิดอีกครั้ง"); return true; } return false; @@ -166,32 +170,31 @@ const TaskpaneApp: React.FC<{ demo?: "untracked" | "tracking" }> = ({ demo }) => [handleAuthRequired], ); - /** ตรวจตอนเปิด task pane (FR-1): token → SavedByAddIn → history + /** ตรวจตอนเปิด task pane (FR-1): token/owner ในไฟล์ → SavedByAddIn → history * FR-16 — อ่าน properties ครั้งเดียวทั้งชุดแล้ว derive tracked เอง เพราะไฟล์ร่าง (untracked) * ต้องได้อ่าน TemplateId ไปใช้ตอน Start Versioning ด้วย (ข้ามทั้งหมดในโหมด demo) - * e-Court pass-through — ยังไม่มี session แต่ไฟล์ stamp token/owner มา = ผ่านเข้าเลยไม่ต้องกด login */ + * e-Court pass-through — auth ทางเข้าเดียว: ไฟล์ที่ stamp token/owner มา = ผ่านเข้าเลย + * ไม่มีขั้นตอน login (นอก Word เช่น browser ธรรมดา → อ่านไม่ได้ = ไป no-access) */ const detect = useCallback(async () => { setPhase("checking"); setBootError(null); try { let properties: metadataService.TrackedDocumentProperties | null = null; - if (!(await authService.getAccessToken())) { - // e-Court pass-through — อ่านไฟล์ก่อนตัดสิน: ไฟล์ที่ stamp token/owner มา = ผ่านเลยไม่ต้องกด login - // (นอก Word เช่น browser ธรรมดา → อ่านไม่ได้ = ไป signed-out ตามเดิม) - try { - properties = await metadataService.readTrackedProperties(); - } catch { - properties = null; - } - if ( - !properties || - !authService.signInFromDocument({ token: properties.ecourtToken, owner: properties.ecourtOwner }) - ) { - setPhase("signed-out"); - return; - } + try { + properties = await metadataService.readTrackedProperties(); + } catch { + properties = null; + } + if ( + !properties || + !authService.signInFromDocument({ token: properties.ecourtToken, owner: properties.ecourtOwner }) + ) { + setNoAccessNotice( + "ไม่พบ token/owner ของ e-Court ในไฟล์ — ขณะนี้ add-in ใช้ได้กับเอกสารที่ส่งมาจาก e-Court เท่านั้น", + ); + setPhase("no-access"); + return; } - authService.startAutoRefresh(); setUsername(authService.getUsername() ?? null); properties ??= await metadataService.readTrackedProperties(); @@ -217,7 +220,7 @@ const TaskpaneApp: React.FC<{ demo?: "untracked" | "tracking" }> = ({ demo }) => void detect(); }, [demo, detect]); - /** FR-17 — เริ่ม auto-save ตั้งแต่ mount ไม่รอ sign-in (ข้ามใน demo กัน error banner จาก Word ที่ไม่มี) + /** FR-17 — เริ่ม auto-save ตั้งแต่ mount ไม่ผูกกับ phase (ข้ามใน demo กัน error banner จาก Word ที่ไม่มี) * ผูก listener ให้ React state: error → banner, dirty→save → flag FR-3 */ useEffect(() => { autoSaveService.onError = (error) => @@ -235,34 +238,6 @@ const TaskpaneApp: React.FC<{ demo?: "untracked" | "tracking" }> = ({ demo }) => }; }, [demo]); - const handleSignIn = async () => { - setSigningIn(true); - setLoginError(null); - try { - const result = await authService.login(); - if (result.ok) { - await detect(); - } else { - setLoginError(result.error); - } - } catch (error) { - setLoginError(getErrorMessage(error, "เข้าสู่ระบบไม่สำเร็จ")); - } finally { - setSigningIn(false); - } - }; - - const handleSignOut = () => { - authService.signOut(); - setUsername(null); - setDocumentId(null); - setDocumentTemplateId(null); - setVersions([]); - setLoginError(null); - setPhase("signed-out"); - setActiveTab("versioning"); - }; - /** FR-18 [บันทึก] — local save เดี่ยวผ่าน lock ของ autoSaveService (ไม่ stamp metadata อะไร) */ const handleSaveOnly = async () => { setSaveBusy(true); @@ -408,18 +383,15 @@ const TaskpaneApp: React.FC<{ demo?: "untracked" | "tracking" }> = ({ demo }) =>
e-Court Documents Versioning - {phase !== "signed-out" && phase !== "checking" && username ? ( - /* จอแคบ: username ย้ายไป title/aria-label — ไม่กินพื้นที่แถบหัวจนตัวชื่อแอป wrap 3-4 บรรทัด */ - + {username} + ) : null}
@@ -430,7 +402,7 @@ const TaskpaneApp: React.FC<{ demo?: "untracked" | "tracking" }> = ({ demo }) => {autoSaveError} ) : null} - {/* toggle แท็บ — แสดงเฉพาะ phase ที่เข้าถึงเอกสารแล้ว (checking/signed-out ยังไม่มีอะไรให้จัดการ) + {/* toggle แท็บ — แสดงเฉพาะ phase ที่เข้าถึงเอกสารแล้ว (checking/no-access ยังไม่มีอะไรให้จัดการ) ปิดทั้งสองปุ่มระหว่าง flow ที่เขียนเอกสาร: saving/starting ของ versioning (กัน progress หาย กลาง flow — handler อยู่ที่ component นี้ flow ยังรันจนจบ แค่หน้าตา), saveBusy ของบันทึก local/ปิดเอกสาร และ templateBusy ที่ panel รายงานผ่าน onBusyChange — กันเครื่องมือ template @@ -481,21 +453,13 @@ const TaskpaneApp: React.FC<{ demo?: "untracked" | "tracking" }> = ({ demo }) =>
) : null} - {phase === "signed-out" ? ( + {phase === "no-access" ? ( + /* ไม่มี login ให้กด — บอกสาเหตุและจบที่นั้น (ขั้นตอนสำหรับเอกสารทั่วไปจะมีในอนาคต) */
-

เข้าสู่ระบบเพื่อเริ่มใช้งาน versioning

+

เอกสารนี้ยังใช้งาน add-in ไม่ได้

- ใช้บัญชีของ realm word-versioning — หน้าต่าง login จะเปิดแยกต่างหาก + {noAccessNotice ?? "ไม่พบ token/owner ของ e-Court ในไฟล์ — ขณะนี้ add-in ใช้ได้กับเอกสารที่ส่งมาจาก e-Court เท่านั้น"}

- - {loginError ? {loginError} : null}
) : null} diff --git a/docs/WORDADDIN-DEV.md b/docs/WORDADDIN-DEV.md index 23af799..55a3826 100644 --- a/docs/WORDADDIN-DEV.md +++ b/docs/WORDADDIN-DEV.md @@ -4,7 +4,7 @@ Taskpane ของ Word add-in "e-Court Documents Versioning" พอร์ตม **ทางเข้า:** เปิด `/docedit` เปล่า ๆ จะ redirect มาที่ taskpane ทันที (ไม่ผ่าน login — คำขอผู้ใช้ 2026-10-14) ยกเว้นลิงก์ e-Court `/docedit/?fileUrl=&fileId=&typeId=` ที่ยังส่งต่อให้ `/note` ตามสัญญา N8 · manifest ชี้ `/docedit/wordaddin` ตรง ๆ (ไม่ว่าผ่าน root เพื่อลด hop ใน webview) -สถานะช่วงนี้: **auth และ API เป็น mock** (login ทันที + version store เก็บ metadata ค้างข้าม session ใน localStorage ของ taskpane เพื่อทดสอบวงปิดเอกสาร→เปิดใหม่ — สลับกลับของจริงด้วยการลบ `wordaddin-mock-versions` หรือเคลียร์ storage ของ origin) รอ backend/auth จริง — ดูหัว "การสลับกลับของจริง" ด้านล่าง +สถานะช่วงนี้: **ไม่มี login แล้ว** (ยกเลิก Keycloak 2026-10-27 — auth เข้าทางเดียวคือ e-Court pass-through จาก token/owner ในไฟล์ ดูหัว "ทดสอบ e-Court pass-through") ส่วน **API ยังเป็น mock** (version store เก็บ metadata ค้างข้าม session ใน localStorage ของ taskpane เพื่อทดสอบวงปิดเอกสาร→เปิดใหม่ — สลับกลับของจริงด้วยการลบ `wordaddin-mock-versions` หรือเคลียร์ storage ของ origin) รอ backend จริง — ดูหัว "การสลับกลับของจริง" ด้านล่าง ## ไฟล์หลัก @@ -12,8 +12,8 @@ Taskpane ของ Word add-in "e-Court Documents Versioning" พอร์ตม |---|---| | `app/wordaddin/page.tsx` + `components/wordaddin/WordAddinGate.tsx` | gate โหลด Office.js (self-host) → `Office.onReady` → mount `TaskpaneApp` | | `app/wordaddin/manifest.xml/route.ts` | serve manifest พร้อม **rewrite origin ตามโฮสต์ที่ขอ** (อ่าน `x-forwarded-proto`/`x-forwarded-host` ก่อนแล้ว fallback host ตรง ผ่าน `utils/request-origin.ts` ที่แชร์กับ origin ของ deep link N10 — โฮสต์ที่ไม่ใช่ localhost บังคับ https เพราะ Office รับ https เท่านั้น) | -| `components/wordaddin/TaskpaneApp.tsx` | state machine `checking/signed-out/untracked/tracking` + ทุก handler | -| `wordaddin/services/*` | ชั้น Office.js พอร์ตดิบจากต้นฉบับ + `authService`/`versioningService` เป็น mock | +| `components/wordaddin/TaskpaneApp.tsx` | state machine `checking/no-access/untracked/tracking` + ทุก handler | +| `wordaddin/services/*` | ชั้น Office.js พอร์ตดิบจากต้นฉบับ + `versioningService` เป็น mock (`authService` = pass-through จริง ไม่มี login) | | `public/wordaddin/manifest.template.xml` | template ของ manifest (ค่า default dev `https://localhost:3000`) — serve ผ่าน route ด้านบนเท่านั้น ไม่ได้ใช้ตรง ๆ | | `public/wordaddin/office-js/` | Office.js runtime — สร้างโดย postinstall `scripts/fetch-office-js.mjs` ดาวน์โหลดฉบับล่าสุดจาก CDN ของ Microsoft (**gitignore** — รัน `npm install` ทุกครั้งหลัง clone เครื่องต้องออกอินเทอร์เน็ตตอนติดตั้ง ส่วนตัวระบบ deploy ใช้ intranet ล้วน) | | `wordaddin/config.ts` | env `NEXT_PUBLIC_ADDIN_*` (ตารางด้านล่าง) | @@ -23,7 +23,7 @@ Taskpane ของ Word add-in "e-Court Documents Versioning" พอร์ตม | ตัวแปร | default | ใช้เมื่อไหร่ | |---|---|---| | `NEXT_PUBLIC_ADDIN_API_BASE_URL` | `""` (ว่าง) | ปลด mock ต่อ backend จริง (ต้องเป็น HTTPS) | -| `NEXT_PUBLIC_ADDIN_MOCK_USERNAME` | `"tester01"` | ชื่อผู้ใช้ของ mock — แสดง header + เป็น `createdByUserId` | +| `NEXT_PUBLIC_ADDIN_MOCK_USERNAME` | `"tester01"` | ชื่อ fallback — ใช้ตอนไฟล์มี token แต่ไม่มี owner/uid และโหมด `?addindemo` (แสดง header + เป็น `createdByUserId` ของ mock) | ## รัน dev ด้วย HTTPS (จำเป็นสำหรับ sideload ใน Word) @@ -52,20 +52,20 @@ Deploy จริง (intranet): **ไม่ต้อง rewrite อะไรแ ## ทดสอบใน browser ธรรมดา (ไม่ต้องเปิด Word) -- **`/docedit/wordaddin`** — เส้นทางจริง: Office.js โหลดได้แม่นนอก host (host เป็น null) → เห็น `signed-out` → กด Sign in (mock ผ่านทันที) → detect พังที่ `Word.run` → `bootError` + ปุ่มลองใหม่ (ตรวจ state machine/handler ได้ครบ) -- **`/docedit/wordaddin?addindemo=untracked`** / **`?addindemo=tracking`** — dev-only: ข้าม Office.js + sign in mock ทันที + ตั้ง phase ตามค่า (tracking มี history ตัวอย่าง 3 รายการ) — ทุกปุ่มกดได้ แต่ flow ที่แตะ Word จริง (Start Versioning / Save & Version / บันทึก) จะจบที่ error ตาม expected +- **`/docedit/wordaddin`** — เส้นทางจริง: Office.js โหลดได้แม้นอก host (host เป็น null) → `detect()` อ่าน custom properties ไม่ได้ (ไม่มี `Word.run`) → เห็น `no-access` — ตรวจ state machine ได้แค่นี้ใน browser ปกติ ส่วน phase `untracked`/`tracking` ต้องใช้ demo ด้านล่าง (หรือ sideload ใน Word จริง) +- **`/docedit/wordaddin?addindemo=untracked`** / **`?addindemo=tracking`** — dev-only: ข้าม Office.js + ตั้ง session mock ทันที + ตั้ง phase ตามค่า (tracking มี history ตัวอย่าง 3 รายการ) — ทุกปุ่มกดได้ แต่ flow ที่แตะ Word จริง (Start Versioning / Save & Version / บันทึก) จะจบที่ error ตาม expected - หมายเหตุ: ใน browser ปกติ (ไม่ใช่ demo) banner "บันทึกอัตโนมัติไม่สำเร็จ" จะโผล่หลัง ~30 วิ เพราะ auto-save (FR-17) พยายาม save ทุก 30 วิแต่ไม่มี Word — ถือว่าถูกต้อง -## ทดสอบ e-Court pass-through (ข้าม login) +## ทดสอบ e-Court pass-through (auth ทางเข้าเดียว) -ไฟล์ที่ e-Court stamp custom properties `token` และ/หรือ `owner` ไว้ จะเข้าใช้ได้เลยโดยไม่ต้องกด Sign in — `detect()` อ่านค่าทั้งสองตอนเปิด task pane แล้วเรียก `authService.signInFromDocument()` (username ได้จาก `owner` → ถอด `uid` ใน JWT payload → `NEXT_PUBLIC_ADDIN_MOCK_USERNAME`, token จากไฟล์เก็บใน memory ให้ `getAccessToken()` ใช้ต่อ) +ไฟล์ที่ e-Court stamp custom properties `token` และ/หรือ `owner` ไว้ จะใช้ได้เลย — ไม่มี login ให้กดตั้งแต่ยกเลิก Keycloak (2026-10-27): `detect()` อ่านค่าทั้งสองตอนเปิด task pane แล้วเรียก `authService.signInFromDocument()` (username ได้จาก `owner` → ถอด `uid` ใน JWT payload → `NEXT_PUBLIC_ADDIN_MOCK_USERNAME`, token จากไฟล์เก็บใน memory ให้ `getAccessToken()` ใช้ต่อ) · ไฟล์ที่ไม่มีทั้งคู่ = phase `no-access` (ขั้นตอนสำหรับเอกสารทั่วไปจะมีในอนาคต) -ทดสอบเองใน Word โดยไม่ต้องรอ e-Court: เปิดไฟล์ → **File → Info → Properties → Advanced Properties → แท็บ Custom** → เพิ่ม property ชื่อ `token` (ค่า JWT ตัวอย่าง `eyJ...` — payload มี `uid` ก็แสดงเป็นชื่อผู้ใช้ได้) และ/หรือ `owner` (เช่น `u1001`) → เปิด task pane ใหม่ → ต้องเข้าเฟส untracked/tracking ทันทีไม่ผ่านหน้า login +ทดสอบเองใน Word โดยไม่ต้องรอ e-Court: เปิดไฟล์ → **File → Info → Properties → Advanced Properties → แท็บ Custom** → เพิ่ม property ชื่อ `token` (ค่า JWT ตัวอย่าง `eyJ...` — payload มี `uid` ก็แสดงเป็นชื่อผู้ใช้ได้) และ/หรือ `owner` (เช่น `u1001`) → เปิด task pane ใหม่ → ต้องเข้าเฟส untracked/tracking ทันทีไม่ผ่านหน้า no-access · ไฟล์ที่ไม่ใส่อะไรเลย = หน้า no-access ## การสลับกลับของจริง (วันหน้า) -- **Auth:** `wordaddin/services/authService.ts` เป็น mock คง surface เดิมครบ (`login/getAccessToken/refreshAccessToken/getUsername/signOut/startAutoRefresh/...`) — เขียน implementation จริงทับ (ต้นฉบับ Keycloak PKCE + Office Dialog API อยู่ที่ `D:\word-addin-poc\addin\src\services\authService.ts`; ถ้าใช้ ต้องเพิม DialogAPI กลับเข้า manifest + หน้า auth-start/auth-callback) · `signInFromDocument` (pass-through จาก token/owner ในไฟล์) เป็น mock-only — วันหน้า token ในไฟล์ควรเป็นแค่ตัวจับคู่เอกสาร ส่วนสิทธิ์ผู้ใช้พิสูจน์ตอน runtime -- **API:** `wordaddin/services/versioningService.ts` เก็บชื่อ/ลายเซ็นเหมือนต้นฉบับเป๊ะ — ก้อบ body เดิม (multipart POST ผ่าน `apiClient` ซึ่งพอร์ตไว้แล้ว) กลับมา + ตั้ง `NEXT_PUBLIC_ADDIN_API_BASE_URL` +- **Auth:** ไม่มี login ให้สลับแล้ว — **ยกเลิก Keycloak ถาวร 2026-10-27** (ต้นฉบับ OIDC/PKCE + Office Dialog API ของ poc เป็น historical record ที่ `D:\word-addin-poc\addin` เท่านั้น) · auth ของ add-in คือ e-Court pass-through ผ่าน `signInFromDocument` (token/owner ในไฟล์) — งานต่อยอดคือรูปแบบ/วิธี verify ของ credential ที่ e-Court stamp (ร่าง: `docs/requirement/addin-workflow.md`) · โดน 401 กลาง flow = `AuthRequiredError` → หน้า `no-access` (token จากไฟล์ต่ออายุไม่ได้ — ต้องได้ไฟล์ใหม่จาก e-Court) +- **API:** `wordaddin/services/versioningService.ts` เก็บชื่อ/ลายเซ็นเหมือนต้นฉบับเป๊ะ — ก้อบ body เดิม (multipart POST ผ่าน `apiClient` ซึ่งพอร์ตไว้แล้ว) กลับมา + ตั้ง `NEXT_PUBLIC_ADDIN_API_BASE_URL` (contract ปัจจุบันของกลุ่ม docver สรุปไว้ใน docblock ของไฟล์นั้น + docs/PLAN-DOCVER.md — identity/docId แกะจาก Bearer token ทั้งหมด) - ชุด unit test ของ services (jest → vitest) ยังไม่พอร์ต — ต้นฉบับอยู่ที่ `D:\word-addin-poc\addin\src\services\*.test.ts` + mock ที่ `src/test/` ## Docker diff --git a/docs/addin-ui-flow.md b/docs/addin-ui-flow.md index 83ef6da..6f5f7bc 100644 --- a/docs/addin-ui-flow.md +++ b/docs/addin-ui-flow.md @@ -10,10 +10,12 @@ Task pane ทั้งหมดเป็น state machine เดียว **ไม่มี routing** — component เดียว (`App`) สลับหน้าตาตาม `phase`: -ตั้งแต่ 2026-10-19 taskpane มี **2 แท็บ** (toggle กลุ่มปุ่ม `aria-pressed` ใต้ header — แสดงเฉพาะ phase `untracked`/`tracking`): **Versioning** คือทุกอย่างในเอกสารนี้ตามเดิม ส่วน **เครื่องมือ Template** (ดูหัวข้อ 10) คือเครื่องมือจัดการ Content Controls ฝั่ง Office.js ล้วน ไม่ยิง backend จึงใช้ได้ทั้งสอง phase ปุ่ม toggle **ทั้งสองปิดระหว่าง flow ที่เขียนเอกสาร** — `saving || starting` (versioning), `saveBusy` (บันทึก local/ปิดเอกสาร), `templateBusy` (operation ของแท็บ template รายงานผ่าน `onBusyChange` — กันสลับกลาง operation แล้ว flow versioning มา stamp/capture ซ้อน หรือกลับกัน; code review 2026-10-20) · สถานะแท็บรีเซ็ตกลับ `versioning` ตอน sign out / AuthRequiredError กัน surprise หลัง login ใหม่ · banner auto-save error (FR-17) ยกไว้เหนือ toggle ให้เห็นทั้งสองแท็บ +ตั้งแต่ 2026-10-19 taskpane มี **2 แท็บ** (toggle กลุ่มปุ่ม `aria-pressed` ใต้ header — แสดงเฉพาะ phase `untracked`/`tracking`): **Versioning** คือทุกอย่างในเอกสารนี้ตามเดิม ส่วน **เครื่องมือ Template** (ดูหัวข้อ 10) คือเครื่องมือจัดการ Content Controls ฝั่ง Office.js ล้วน ไม่ยิง backend จึงใช้ได้ทั้งสอง phase ปุ่ม toggle **ทั้งสองปิดระหว่าง flow ที่เขียนเอกสาร** — `saving || starting` (versioning), `saveBusy` (บันทึก local/ปิดเอกสาร), `templateBusy` (operation ของแท็บ template รายงานผ่าน `onBusyChange` — กันสลับกลาง operation แล้ว flow versioning มา stamp/capture ซ้อน หรือกลับกัน; code review 2026-10-20) · สถานะแท็บรีเซ็ตกลับ `versioning` ตอนเข้า `no-access` กัน surprise · banner auto-save error (FR-17) ยกไว้เหนือ toggle ให้เห็นทั้งสองแท็บ + +**ไม่มี login ตั้งแต่ 2026-10-27** (ยกเลิก Keycloak ตามคำขอผู้ใช้) — auth ทางเข้าเดียวคือ e-Court pass-through: `detect()` อ่าน custom properties `token`/`owner` ที่ e-Court stamp มากับไฟล์ แล้วเรียก `authService.signInFromDocument()` ผ่านเข้าเลย ``` -checking ──(ไม่มี token)──────────────→ signed-out +checking ──(ไม่มี token/owner ในไฟล์)──→ no-access │ ├─(มี token + ไม่ใช่ tracked doc)──→ untracked └─(มี token + tracked doc)────────→ tracking @@ -21,21 +23,20 @@ checking ──(ไม่มี token)────────────── การเปลี่ยน phase อื่นๆ: -- `signed-out` → (sign in สำเร็จ) → `checking` → ตัดสินใหม่ตามไฟล์ปัจจุบัน - `untracked` → (Start Versioning สำเร็จ) → `tracking` -- ทุก phase → (401 ที่ refresh token ไม่ช่วย / กดออกจากระบบ) → `signed-out` -- ปุ่ม "ลองใหม่" มีจริงแค่ 2 จุด: `bootError` ใน `checking` และ history error ใน `VersionHistoryPanel` — `untracked`/`tracking` **ไม่มี**ปุ่มลองใหม่แยกต่างหาก (เริ่ม flow ใหม่ด้วยการกดปุ่ม flow ซ้ำ) · กลไกเดียวที่วิ่งกลับมา `checking` คือ sign-in สำเร็จ +- ทุก phase → (ไม่มี token ตอน detect ใหม่ / 401 = token หมดอายุไม่มีทางต่ออายุ) → `no-access` +- ปุ่ม "ลองใหม่" มีจริงแค่ 2 จุด: `bootError` ใน `checking` และ history error ใน `VersionHistoryPanel` — `untracked`/`tracking` **ไม่มี**ปุ่มลองใหม่แยกต่างหาก (เริ่ม flow ใหม่ด้วยการกดปุ่ม flow ซ้ำ) State ที่ UI ถือนอกเหนือจาก phase: | State | ความหมาย | |---|---| | `activeTab` | แท็บที่เปิดอยู่ `versioning` (default) / `template` — ดูหัวข้อ 10 | -| `username` | ชื่อผู้ใช้จาก token (แสดงใน header) | +| `username` | ชื่อเจ้าของไฟล์จาก pass-through (owner → uid ใน token → fallback) — แสดงเป็น chip เฉย ๆ ใน header | | `documentId` | tracking id ของเอกสาร (มีเฉพาะ `tracking`) | | `documentTemplateId` | template ต้นทางของ**ไฟล์ร่าง** (มีเฉพาะ `untracked` ที่เป็นไฟล์ร่าง — FR-16) | | `versions` + `historyLoading` + `historyError` | version history ของ `documentId` | -| `signingIn`, `loginError` | สถานะหน้า sign-in | +| `noAccessNotice` | ข้อความบนหน้า `no-access` — ต่างกันตามสาเหตุ (ไม่มี token/owner ในไฟล์ หรือ token หมดอายุจาก 401) | | `starting`, `saving`, `progressMessage` | busy + ข้อความ progress ของ flow Start Versioning / Save & Version | | `confirmOpen`, `startConfirmOpen` | เปิด dialog ยืนยันของ flow ไหน | | `flowError`, `flowWarning`, `flowSuccess` | MessageBar 3 แบบของ flow หลัก (แสดงเฉพาะ `tracking`) | @@ -53,21 +54,20 @@ State ที่ UI ถือนอกเหนือจาก phase: ### Header (ทุก phase) - ชื่อแอป: **e-Court Documents Versioning** (semibold) -- ฝั่งขวา (เมื่อ login แล้วและไม่ใช่ `checking`/`signed-out`): ปุ่มเล็ก icon + `ออกจากระบบ` — username อยู่ใน `title`/`aria-label` (จอแคบ ไม่กินพื้นที่แถบหัว) — กดแล้ว sign out + กลับ `signed-out` +- ฝั่งขวา (เมื่อผ่าน pass-through แล้วและไม่ใช่ `no-access`/`checking`): chip เล็กพื้น `canvas` แสดง `username` เฉย ๆ — ไม่ใช่ปุ่ม (ไม่มี login จึงไม่มีออกจากระบบ) · จอแคบตัดด้วย truncate ชื่อเต็มอยู่ใน `title` ### `checking` - `Spinner "กำลังตรวจสอบเอกสาร…"` ตรงกลาง - ถ้า detect พัง (`bootError`): MessageBar **error** + ปุ่ม `ลองใหม่` ข้างใน bar (รัน detect ใหม่) -### `signed-out` +### `no-access` -- ข้อความ: "เข้าสู่ระบบเพื่อเริ่มใช้งาน versioning" -- คำอธิบายจางๆ: "ใช้บัญชีของ realm `word-versioning` — หน้าต่าง login จะเปิดแยกต่างหาก" -- ปุ่ม primary `เข้าสู่ระบบ` → ระหว่างรันเปลี่ยนเป็น `กำลังเข้าสู่ระบบ…` + disable -- ถ้า login พัง: MessageBar **warning** (`loginError`) — เช่น "เซสชันหมดอายุ — กรุณา login ใหม่" ตอนโดน 401 กลาง flow - -> ใน project จริง login เปิดหน้าต่างแยกผ่าน Office Dialog API (ห้าม redirect iframe ไป Keycloak ตรงๆ) — ถ้าโปรเจกต์ใหม่ยังไม่มี auth ให้ mock `login()` คืน `{ ok: true }` ไปก่อน +- ข้อความ: "เอกสารนี้ยังใช้งาน add-in ไม่ได้" +- คำอธิบายจางๆ (`noAccessNotice` ตามสาเหตุ): + - ตอน detect ไม่เจอข้อมูลในไฟล์: "ไม่พบ token/owner ของ e-Court ในไฟล์ — ขณะนี้ add-in ใช้ได้กับเอกสารที่ส่งมาจาก e-Court เท่านั้น" + - ตอนโดน 401 กลาง flow: "token ของ e-Court หมดอายุหรือไม่ถูกต้อง — โปรดขอไฟล์ใหม่จาก e-Court แล้วเปิดอีกครั้ง" +- **ไม่มีปุ่มใด ๆ** — ไม่มี login ให้กด (ยกเลิก Keycloak 2026-10-27; ขั้นตอนสำหรับเอกสารทั่วไปจะมีในอนาคต) ผู้ใช้ได้ไฟล์ใหม่จาก e-Court แล้วเปิดใหม่เท่านั้น ### `untracked` @@ -162,10 +162,10 @@ Dialog เดียวใช้ 2 flow (ปรับข้อความด้ ข้อความ flow (`flowSuccess`/`flowWarning`/`flowError`) ถูกเคลียร์เป็น null ทุกครั้งที่**เริ่ม** flow ใหม่ (Start Versioning / Save & Version / ดาวน์โหลด — ไม่ใช่ตอนจบ ไม่มี timeout) -### Detect (FR-1) — ตอนเปิด task pane / หลัง login / กดลองใหม่ +### Detect (FR-1) — ตอนเปิด task pane / กดลองใหม่ -1. ตรวจ token — ไม่มี → `signed-out` -2. อ่าน custom properties ครั้งเดียวทั้งชุด: `SavedByAddIn`, `DocumentTrackingId`, `LastVersionId`, `LastVersionTimestamp`, `TemplateId` +1. อ่าน custom properties ครั้งเดียวทั้งชุด: `token`, `owner`, `SavedByAddIn`, `DocumentTrackingId`, `LastVersionId`, `LastVersionTimestamp`, `TemplateId` +2. e-Court pass-through: ไม่มี `token`/`owner` เลย (หรืออ่านไฟล์ไม่ได้) → `no-access` · มีอย่างน้อยหนึ่งตัว → `signInFromDocument` (username จาก owner → uid ใน token) แล้วไปต่อ 3. `tracked = (SavedByAddIn === GUID ของ add-in)`: - tracked **และมี** `DocumentTrackingId` → `tracking` + โหลด history - ไม่ track หรือ track แต่ id หาย → `untracked` (เก็บ `TemplateId` ไว้ใช้ตอน Start Versioning — ไฟล์ร่างอยู่กลุ่มนี้) @@ -200,7 +200,7 @@ dialog ยืนยัน → progress ทั้ง 7 ขั้นลำดั ### Auto-save (FR-17) -- เริ่มตั้งแต่ mount **ไม่รอ sign-in** +- เริ่มตั้งแต่ mount **ไม่ผูกกับ phase** (detect ยังไม่จบก็รันได้) - periodic ทุก 30 วิ: ไฟล์ dirty → save + ตั้ง flag `hasUncommitted`; สะอาด → ข้าม - save ล้ม → `autoSaveError` (warning bar); สำเร็จครั้งถัดไป → เคลียร์ bar - ทุก flow versioning ต้อง **suspend** auto-save ตอนเริ่มและ **resume** ตอนจบ (กัน save แทรกกลาง stamp/capture) @@ -251,17 +251,17 @@ interface VersionRecord { response: `{ url: string, expiresAt: string }` — UI เปิด `url` ในเบราว์เซอร์เครื่องผู้ใช้เท่านั้น -### Auth — OIDC PKCE ผ่าน Office Dialog API +### Auth — e-Court pass-through (ไม่มี login) -`displayDialogAsync` เปิดหน้า login แยก → `messageParent` ส่งผลกลับ task pane · token เก็บ **ใน memory เท่านั้น** (ห้าม localStorage) · refresh token auto-refresh เงียบๆ จน 401 → สถานะ `AuthRequiredError` · mock ช่วงรอ: `login()` คืน `{ ok: true }`, `getAccessToken()` คืน token ปลอม, `getUsername()` คืนชื่อ test +ยกเลิก Keycloak OIDC/PKCE แล้ว (2026-10-27) — ไม่มี Office Dialog ไม่มีหน้า sign-in: e-Court stamp custom properties `token`/`owner` ลงไฟล์ตอนสร้าง แล้ว `signInFromDocument()` ใช้ได้ทันที · token เก็บ **ใน memory เท่านั้น** (ห้าม localStorage) และ**ต่ออายุไม่ได้** — โดน 401 = `AuthRequiredError` → หน้า `no-access` ให้ขอไฟล์ใหม่จาก e-Court · token นี้คือสิ่งที่ `apiClient` แนบเป็น `Authorization: Bearer` ทุก request ตอนต่อ backend จริง (กลุ่ม docver บังคับ Bearer — docs/PLAN-DOCVER.md) · **รูปแบบ/วิธี verify ของ credential ที่ e-Court stamp ลงไฟล์ยังเป็นงานออกแบบต่อ** (ร่าง requirement: `docs/requirement/addin-workflow.md`) -> **หมายเหตุสถานะปัจจุบัน (2026-10-14):** UI นี้ถูก implement แล้วใน repo นี้ที่ route `/docedit/wordaddin` (ดู `components/wordaddin/` + `wordaddin/`) — ทั้ง auth (แทนด้วย mock `wordaddin/services/authService.ts`) และ API ทั้งสามข้างบน (แทนด้วย in-memory mock `wordaddin/services/versioningService.ts`) · วิธีสลับกลับของจริง: `docs/WORDADDIN-DEV.md` +> **หมายเหตุสถานะปัจจุบัน:** UI นี้ถูก implement แล้วใน repo นี้ที่ route `/docedit/wordaddin` (ดู `components/wordaddin/` + `wordaddin/`) — auth เป็น e-Court pass-through จริง (`wordaddin/services/authService.ts` — ไม่มี mock ให้ login แล้ว) ส่วน API ทั้งสามข้างบนยังเป็น in-memory mock (`wordaddin/services/versioningService.ts`) · วิธีสลับกลับของจริง: `docs/WORDADDIN-DEV.md` --- ## 8. กติกา error ที่ UI ต้องรองรับ -1. **`AuthRequiredError` (401 ที่ refresh ไม่ช่วย)** — ทุก flow ต้อง catch แล้ว: กลับ `signed-out` + เคลียร์ `documentId`/`documentTemplateId`/`versions` + ตั้ง `loginError` = "เซสชันหมดอายุ — กรุณา login ใหม่" — **ไม่ throw ต่อ ไม่โชว์ error ทับ phase** +1. **`AuthRequiredError` (ไม่มี token / 401)** — ทุก flow ต้อง catch แล้ว: กลับ `no-access` + เคลียร์ `documentId`/`documentTemplateId`/`versions` + ตั้ง `noAccessNotice` = "token ของ e-Court หมดอายุหรือไม่ถูกต้อง — โปรดขอไฟล์ใหม่จาก e-Court แล้วเปิดอีกครั้ง" — **ไม่ throw ต่อ ไม่โชว์ error ทับ phase** 2. error อื่น → MessageBar ณ จุดที่เกิด (login/history/flow/download แยกกัน) — ห้ามปล่อย unhandled rejection 3. แยก error ของ "ตัว version" ออกจาก "ปิดเอกสารไม่สำเร็จ" — อันหลังเป็น warning เพราะข้อมูลปลอดภัยแล้ว 4. PDF เป็น best-effort เสมอ — ล้มแค่เตือน ไม่ fail flow diff --git a/docs/requirement/addin-workflow.md b/docs/requirement/addin-workflow.md new file mode 100644 index 0000000..a4a6bf1 --- /dev/null +++ b/docs/requirement/addin-workflow.md @@ -0,0 +1,44 @@ +# ขั้นตอนการทำงานของ Addin + +## 1. เมื่อเปิดเอกสาร + +- อ่าน custom property ของ word + 1. ticket_data ซึ่งจะมีข้อมูลเบื้องต้น ดังนี้ + ```jsonc + { + "exp": 1793067433, + "fid": "T12_FD155_3s-long_ZZZ", + "tid": "T12", + "uid": "oom", + "uname": "สรวิชญ์", + } + ``` + 2. นำค่า ticket_data ที่เป็น string ก่อนนำไปแปลง json ทำการ sign HMAC-SHA256 โดยใช้ secret "super_secret" ซึ่งต้องรองรับการ config ค่านี้ในอนาคต + 3. นำค่าที่ sign ได้ มาเทียบกับค่า property "HmacSha256" +- ขั้นตอนการทำ authen + ``` + ขั้นตอน + 1. เมื่อเปิดไฟล์ Word ให้อ่าน Custom Property 2 ค่าที่ฝังมาในไฟล์ คือ value จริง และ Hash Value (HMAC) เพื่อตรวจสอบว่าไฟล์สร้างผ่านระบบอย่างถูกต้อง + 2. ถ้า HMAC ไม่ตรงกัน แสดงว่าไฟล์ถูกแก้ไข ให้ Add-in disable การทำงานทั้งหมด + 3. ถ้า HMAC ตรงกัน ให้เก็บค่า uId, uName, fId, tid ไว้ แล้วตรวจสอบว่า userId นี้มีใน localStorage อยู่แล้วหรือไม่ + 4. ถ้ายังไม่มีให้ตรวจค่า Exp ที่ได้จากไฟล์ Word + - หมดอายุ → disable Add-in ทั้งหมด + - ยังไม่หมดอายุ → เก็บใน localStorage + - userName: ค่าจาก uName + - คีย์คือ fid: เอาค่า { + "userid": uid, + "username": uname, + "fid": fid (เป็น  string), + "tid": tid + } + ไปทำการขอ token ที่ /api/documents/token แล้วนำค่าที่ได้มาเก็บไว้ที่ตัวแปรนี้ เพื่อให้อ่านค่าไปใช้ให้ตรงกับค่า fid ที่ได้รับ เนื่องจาก api ในส่วน /documents จะต้องแนบ token ไปไม่ส่งค่าไปตรงๆ เพื่อความถูกต้อง + 5. ถ้ามี Session อยู่แล้ว ให้ตรวจว่า userId ตรงกับ Session หรือไม่ + 6. ถ้า userId ไม่ตรงกัน (user ใหม่) ให้ตรวจค่า Expire + - ยังไม่หมดอายุ → เคลียร์ Session เดิม แล้วสร้าง Session 2 ค่าใหม่ + - หมดอายุ → disable Add-in ทั้งหมด + 7. ถ้า userId ตรงกัน (คนเดิม) ให้ตรวจว่า fid มีใน localStorage แล้วหรือไม่ + - มีแล้ว (กลับมาทำเอกสารเดิม) → ผ่านได้เลย ไม่ต้องตรวจ Expire + - ยังไม่มี (เอกสารใหม่) → ตรวจค่า Expire + - ยังไม่หมดอายุ → ทำการขอ token แล้วเก็บใน localStorage ตามขั้นตอนก่อนหน้า + - หมดอายุ → disable Add-in ทั้งหมด + ``` diff --git a/public/wordaddin/manifest.template.xml b/public/wordaddin/manifest.template.xml index efe2e93..a51ebb1 100644 --- a/public/wordaddin/manifest.template.xml +++ b/public/wordaddin/manifest.template.xml @@ -11,8 +11,10 @@ ผลต่างจากต้นฉบับ: - taskpane ชี้ที่หน้า Next.js (/docedit/wordaddin) แทน taskpane.html ของ webpack - - ตัด DialogAPI (login ช่วงนี้เป็น mock ไม่มี Keycloak dialog) — เหลือ WordApi 1.5 + OpenBrowserWindowApi - - ตัด AppDomain ของ Keycloak (https://localhost:8443) + - ตัด DialogAPI + AppDomain ของ Keycloak (https://localhost:8443) — ยกเลิก auth ด้วย Keycloak + 2026-10-27: ไม่มี login ใน add-in auth เป็น e-Court pass-through (token/owner ใน custom + properties ของไฟล์) requirement set เหลือ WordApi 1.3 + OpenBrowserWindowApi + (ดูรายละเอียดระดับที่ใช้จริงในคอมเมนต์ก่อน ) - Commands.Url ชี้ taskpane URL เดียวกัน (ไม่มี commands.html — ปุ่มเดียวคือ ShowTaskpane) - คง Id GUID เดิม — เครื่องที่เคย sideload ตัว poc จะถือว่าเป็น add-in ตัวเดิม --> diff --git a/wordaddin/services/apiClient.ts b/wordaddin/services/apiClient.ts index 59f9e06..73cf510 100644 --- a/wordaddin/services/apiClient.ts +++ b/wordaddin/services/apiClient.ts @@ -2,17 +2,17 @@ import { config } from "../config"; import * as authService from "./authService"; /** - * fetch wrapper ของ add-in — แนบ Bearer ทุก request - * โดน 401 → silent refresh แล้ว retry ครั้งเดียว; ยัง 401 → AuthRequiredError - * (UI จับ error นี้แล้วพากลับหน้า sign-in อย่างเนียน — AC-9) - * พอร์ตดิบจาก D:\word-addin-poc\addin\src\services\apiClient.ts + * fetch wrapper ของ add-in — แนบ Bearer ทุก request (token จาก custom properties ของไฟล์) + * โดน 401 → AuthRequiredError ทันที: token จากไฟล์ต่ออายุไม่ได้ (ไม่มี refresh token + * ตั้งแต่ยกเลิก Keycloak) — UI จับ error นี้แล้วพาไปหน้า no-access ให้ขอไฟล์ใหม่จาก e-Court + * พอร์ตจาก D:\word-addin-poc\addin\src\services\apiClient.ts * (ช่วง mock ไม่มี caller จริง — เตรียมไว้ให้ versioningService วันปลด mock + AuthRequiredError * ถูก TaskpaneApp ใช้อยู่แล้ว) */ -/** บอกว่าต้องกลับไป login ใหม่ (ไม่มี token / refresh ไม่ได้ / 401 ซ้ำ) */ +/** บอกว่า token จากไฟล์ใช้ไม่ได้ (ไม่มี / หมดอายุ / ไม่ถูกต้อง) — ต้องได้ไฟล์ใหม่จาก e-Court */ export class AuthRequiredError extends Error { - constructor(message = "เซสชันหมดอายุ — ต้อง login ใหม่") { + constructor(message = "token ของ e-Court หมดอายุหรือไม่ถูกต้อง") { super(message); this.name = "AuthRequiredError"; } @@ -54,25 +54,17 @@ async function doFetch( } /** - * request พร้อม Bearer + retry-on-401 ครั้งเดียว - * Response ที่ได้คืนไปให้ caller เช็ค ok เอง (ยกเว้น 401 ที่ refresh ไม่ช่วย → AuthRequiredError) + * request พร้อม Bearer — 401 = token จากไฟล์หมดอายุ/ไม่ถูกต้อง → AuthRequiredError + * Response ที่ได้คืนไปให้ caller เช็ค ok เอง */ export async function apiFetch(path: string, options: ApiRequestOptions = {}): Promise { const accessToken = await authService.getAccessToken(); if (!accessToken) { throw new AuthRequiredError(); } - - let response = await doFetch(path, options, accessToken); + const response = await doFetch(path, options, accessToken); if (response.status === 401) { - const refreshedToken = await authService.refreshAccessToken(); - if (!refreshedToken) { - throw new AuthRequiredError(); - } - response = await doFetch(path, options, refreshedToken); - if (response.status === 401) { - throw new AuthRequiredError(); - } + throw new AuthRequiredError(); } return response; } diff --git a/wordaddin/services/authService.ts b/wordaddin/services/authService.ts index 7cd7509..b625ff3 100644 --- a/wordaddin/services/authService.ts +++ b/wordaddin/services/authService.ts @@ -1,34 +1,21 @@ /** - * MOCK — แทน Keycloak OIDC/PKCE ของต้นฉบับ (ผู้ใช้งานเลือกไม่ใช้ Keycloak ช่วงพอร์ต) - * ต้นฉบับ: D:\word-addin-poc\addin\src\services\authService.ts (login ผ่าน Office Dialog API, - * token เก็บใน memory เท่านั้น, auto-refresh) + * Auth ของ add-in — e-Court pass-through ทางเดียว (ยกเลิก Keycloak 2026-10-27 ตามคำขอผู้ใช้) + * ไม่มีขั้นตอน login ใน add-in อีกต่อไป: ไฟล์ที่ e-Court stamp custom properties + * `token`/`owner` มา (`readTrackedProperties` อ่านรวม) = ใช้งานได้ทันที ไม่ต้องกดปุ่มใด ๆ + * ไฟล์ที่ไม่มีทั้งคู่ = phase no-access ของ TaskpaneApp (ขั้นตอนสำหรับเอกสารทั่วไปจะมีในอนาคต) * - * วันสลับเป็น auth จริง: เขียน implementation ใหม่โดยคง export surface ด้านล่างครบทุกตัว - * (เพิ่ม refreshAccessToken ที่ต้นฉบับมีอยู่แล้วสำหรับ apiClient) — โค้ดอื่นไม่ต้องแตะ + * token เก็บใน memory เท่านั้น — getAccessToken คืน docToken ให้ apiClient แนบ Bearer + * (ช่วงไม่ต่อ backend จริง versioningService เป็น mock ไม่เรียก apiClient อยู่แล้ว) */ import { config } from "../config"; -/** discriminated union เหมือนต้นฉบับ — ok:false ต้องมี error เสมอ */ -export type LoginResult = { ok: true } | { ok: false; error: string }; - -/** state เดียวของ mock — null = signed out */ +/** state เดียว — null = ยังไม่เคย pass-through สำเร็จ (จากไฟล์ หรือ ?addindemo) */ let username: string | null = null; /** token จาก custom properties ของไฟล์ (e-Court pass-through) — เก็บใน memory เท่านั้น */ let docToken: string | null = null; -function delay(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -/** จำลองเปิดหน้าต่าง login — สำเร็จทันที ตั้งชื่อจาก NEXT_PUBLIC_ADDIN_MOCK_USERNAME */ -export async function login(): Promise { - await delay(300); - username = config.mockUsername; - return { ok: true }; -} - -/** mock-only — โหมด ?addindemo ใช้ตั้ง session ทันทีไม่ผ่านปุ่ม Sign in */ +/** dev-only — โหมด ?addindemo ของ WordAddinGate ใช้ตั้ง session ทันทีไม่อ่านไฟล์จริง */ export function signInForDemo(): void { username = config.mockUsername; } @@ -50,8 +37,8 @@ function decodeJwtField(token: string, field: string): string | undefined { } /** - * e-Court pass-through (ระยะแรก) — ไฟล์ที่ e-Court stamp `token`/`owner` ไว้ใน custom properties - * ใช้งานได้เลยโดยไม่ต้องกด login: username จาก owner → uid ใน token → mockUsername + * e-Court pass-through — จุดเข้าเดียวของ auth: ไฟล์ที่ stamp `token`/`owner` ไว้ + * username ได้จาก owner → uid ใน JWT → mockUsername (ตามลำดับ) * คืน true = ผ่านเข้าได้ (มี token หรือ owner อย่างน้อยหนึ่งตัว) */ export function signInFromDocument(input: { token?: string; owner?: string }): boolean { @@ -65,33 +52,12 @@ export function signInFromDocument(input: { token?: string; owner?: string }): b return true; } +/** Bearer ของ apiClient — docToken จากไฟล์ (ส่วน demo/mock คืน token ปลอม) */ export async function getAccessToken(): Promise { if (docToken) return docToken; return username ? `mock-token-${username}` : null; } -/** mock: token ไม่มีวันหมดอายุจริง — คืนตัวเดิมหรือ null ตามสถานะ login */ -export async function refreshAccessToken(): Promise { - return getAccessToken(); -} - export function getUsername(): string | undefined { return username ?? undefined; } - -export function isTokenValid(): boolean { - return username !== null; -} - -export function hasRefreshToken(): boolean { - return username !== null; -} - -export function startAutoRefresh(): void { - // mock: ไม่มีอะไรต้อง refresh -} - -export function signOut(): void { - username = null; - docToken = null; -} diff --git a/wordaddin/services/metadataService.ts b/wordaddin/services/metadataService.ts index 1119a3b..74c33d3 100644 --- a/wordaddin/services/metadataService.ts +++ b/wordaddin/services/metadataService.ts @@ -23,7 +23,7 @@ export const LAST_VERSION_TIMESTAMP_PROPERTY = "LastVersionTimestamp"; /** FR-16 — stamp เฉพาะไฟล์ร่าง (draft) ที่เว็บสร้าง: template ต้นทาง (ค่าเดียวกับ AddInMetadata.TemplateIdPropertyName ฝั่ง backend) */ export const TEMPLATE_ID_PROPERTY = "TemplateId"; -/** e-Court pass-through — ไฟล์ที่ e-Court stamp มาใน custom properties เข้าใช้ได้เลยไม่ต้องกด login */ +/** e-Court pass-through — ไฟล์ที่ e-Court stamp มาใน custom properties เข้าใช้ได้เลย (auth ทางเข้าเดียว) */ export const ECOURT_TOKEN_PROPERTY = "token"; export const ECOURT_OWNER_PROPERTY = "owner"; @@ -35,7 +35,7 @@ export interface TrackedDocumentProperties { lastVersionTimestamp?: string; /** มีเฉพาะไฟล์ร่าง (FR-16) — ส่งต่อให้ backend ตอน Start Versioning เพื่อผูก Document กับ template */ templateId?: string; - /** e-Court pass-through — มีอย่างใดอย่างหนึ่ง = ข้ามหน้า login (username จาก owner หรือ uid ใน token) */ + /** e-Court pass-through — มีอย่างใดอย่างหนึ่ง = ใช้งานได้ทันที (username จาก owner หรือ uid ใน token) */ ecourtToken?: string; ecourtOwner?: string; } From 69d83ea49331873d93c5a2e68946275fe7657ae2 Mon Sep 17 00:00:00 2001 From: oom Date: Tue, 27 Oct 2026 10:15:35 +0700 Subject: [PATCH 16/21] Fix build time fail: export applyTemplate not compatible with Next.js(move to lib-tmpl), add var in .env.example --- .env.example | 16 ++++++++++ app/api/template/generate/route.ts | 6 ++-- app/api/template/render/route.ts | 49 ++-------------------------- lib/lib-tmpl.ts | 51 ++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 49 deletions(-) diff --git a/.env.example b/.env.example index d9988b6..7af635b 100644 --- a/.env.example +++ b/.env.example @@ -6,6 +6,22 @@ DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:54322/ecourt-note # ไม่ตั้ง/ว่าง = ปฏิเสธทุก request (fail closed) — ตั้งค่ายาวและสุ่ม เช่น 32+ ตัวอักษร # EXTERNAL_API_KEY= +# สำหรับตัวอย่างระบบนอกเรียกใช้ API ของ template +DOCEDIT_BASE_URL=http://192.168.2.51:3000/docedit +# สำหรับตัวอย่างระบบนอกเรียกใช้ API ของ template +EXTERNAL_API_KEY='secret-api-key-1234' +# ที่เก็บไฟล์เทมเพลต(docx) +TMPL_PATH='./public/tmpl' + +# Sign/Hash ที่ใช้ฝังในเอกสาร(docx) +SECRET="super_secret" +PRIVATE_KEY_HEX="8f2077b57b39399b6a7828f35da03a63ade8e3a36a6bf437b091a8bdb3d18ee1" +PUBLIC_KEY_HEX="66b09b4a1b66271d5728c592c2409657e1c070a7bf371bf9f6ed457855270546" +# Optional เวลาหมดอายุที่ฝังไว้ในข้อมูล (ticket_data) +TICKET_SEC=300 + + + # MinIO ของ API เอกสาร versioning (docs/PLAN-DOCVER.md §6) — ค่านี้ตรง docker-compose.yml # (MINIO_ENDPOINT/MINIO_ACCESS_KEY/MINIO_SECRET_KEY ไม่ตั้ง = ทุก endpoint ของกลุ่มนี้ตอบ 500) # หมายเหตุ: พอร์ตโฮสต์เป็น 19000 เพราะ 9000/9001 ชนกับโปรเจกต์อื่นในเครื่อง dev diff --git a/app/api/template/generate/route.ts b/app/api/template/generate/route.ts index d5394d2..1a057ee 100644 --- a/app/api/template/generate/route.ts +++ b/app/api/template/generate/route.ts @@ -5,12 +5,12 @@ import { NextRequest, NextResponse } from 'next/server'; import {readFile} from 'fs/promises'; import path from 'path'; -import { applyTemplate } from '../render/route' -import { DocumentTemplate, templateContentControls, setCustomDocProps, templateDynamicContents } from "@/lib/lib-tmpl"; +import {DocumentTemplate,applyTemplate} from "@/lib/lib-tmpl"; const EXTERNAL_API_KEY = process.env.EXTERNAL_API_KEY || 'secret-api-key-1234'; const TMPL_PATH = process.env.TMPL_PATH || '' const PRIVATE_KEY_HEX = process.env.PRIVATE_KEY_HEX ||'' const SECRET = process.env.SECRET || '' +const TICKET_SEC = Number(process.env.TICKET_SEC||'300') if (EXTERNAL_API_KEY || !TMPL_PATH || !PRIVATE_KEY_HEX || !SECRET) console.log("api/template/generate: require EXTERNAL_API_KEY, TMPL_PATH and PRIVATE_KEY_HEX") @@ -61,7 +61,7 @@ export async function POST(request: NextRequest) { const tmplPath = path.join(TMPL_PATH, `${tid}.docx`); let templateBuffer: Buffer = await readFile(tmplPath) - const output = await applyTemplate(templateBuffer, templateData, SECRET, PRIVATE_KEY_HEX); + const output = await applyTemplate(templateBuffer, templateData, SECRET, PRIVATE_KEY_HEX, TICKET_SEC); // สร้าง ReadableStream ของไฟล์ .docx // ในโค้ดจริง ส่วนนี้จะดึง Binary Buffer จาก Library เช่น docx, docxtemplater หรืออ่านจาก File System diff --git a/app/api/template/render/route.ts b/app/api/template/render/route.ts index 9bb8d08..c738afe 100644 --- a/app/api/template/render/route.ts +++ b/app/api/template/render/route.ts @@ -1,8 +1,8 @@ import { NextResponse } from "next/server"; -import { DocumentTemplate, templateContentControls, setCustomDocProps, templateDynamicContents } from "@/lib/lib-tmpl"; -import { generateHmacSha256, signEd25519, sortObjectKeys } from '@/lib/lib-utils'; +import { DocumentTemplate,applyTemplate} from "@/lib/lib-tmpl"; export const runtime = "nodejs"; + const DOCX_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; // cap เดียวกับ N4 (app/actions/files.ts) — route นี้เปิดกว้างให้ภายนอกยิงได้ @@ -102,7 +102,7 @@ export async function POST(request: Request) { let templateBuffer: Buffer = Buffer.from( await template.arrayBuffer(), ); - const output = await applyTemplate(templateBuffer,templateData,TEST_SECRET,TEST_PRIVATE_KEY_HEX); + const output = await applyTemplate(templateBuffer,templateData,TEST_SECRET,TEST_PRIVATE_KEY_HEX,TICKET_SEC); // ----------------------------- // ส่ง DOCX กลับ @@ -133,46 +133,3 @@ export async function POST(request: Request) { } } -export async function applyTemplate(templateBuffer:Buffer,templateData:DocumentTemplate, - secret:string,private_key:string):Promise> -{ - //Calculate hash - const originalTicket_data = templateData?.info?.ticket_data //expect valid json string - // console.log("templateData",templateData?.info?.ticket_data) - if (originalTicket_data && typeof originalTicket_data === 'string') { - try { - const originalTicketObj = JSON.parse(originalTicket_data)//sort and add expire to ticket data - if (originalTicketObj && typeof originalTicketObj === 'object') { - console.log("Found valid ticket data: hash it") - // คำนวณค่า expire (ปัจจุบัน + จำนวนวินาทีที่ต้องการให้หมดอายุ) - // หาร 1000 เพราะต้องการหน่วยวินาที (sec) ไม่ใช่มิลลิวินาที (ms) - const expireTimestamp = Math.floor(Date.now() / 1000) + TICKET_SEC; - const fullObj = { ...originalTicketObj, exp: expireTimestamp }; - const sortedObj = sortObjectKeys(fullObj); - - // แปลงกลับเป็น JSON string เพื่อนำไปใช้ส่งและทำ Hash - const ticket_data = JSON.stringify(sortedObj); - // console.log("check secret:",TEST_SECRET,TEST_PRIVATE_KEY_HEX,ticket_data) - const HmacSha256 = generateHmacSha256(ticket_data, secret) - - const Ed25519 = signEd25519(ticket_data, private_key) - templateBuffer = setCustomDocProps(templateBuffer, { ticket_data, HmacSha256, Ed25519 }) - } - } catch (error) { //just skip - console.error("Set custom property fail",error); - } - } - - // Apply Dynamic Content - if (templateData?.dc && typeof templateData?.dc === "object") { - console.log("Apply Dynamic Content") - templateBuffer = Buffer.from(await templateDynamicContents(templateBuffer, templateData.dc)) - } - // Apply Content Control - if (templateData?.cc && typeof templateData?.cc === "object") { - console.log("Apply Content Control") - templateBuffer = templateContentControls(templateBuffer, templateData.cc); - } - return templateBuffer - -} \ No newline at end of file diff --git a/lib/lib-tmpl.ts b/lib/lib-tmpl.ts index 4bf4b70..bb293d0 100755 --- a/lib/lib-tmpl.ts +++ b/lib/lib-tmpl.ts @@ -1,3 +1,4 @@ +import { generateHmacSha256, signEd25519, sortObjectKeys } from '@/lib/lib-utils'; import createReport from 'docx-templates'; import PizzaZip from "pizzip"; import { @@ -1261,3 +1262,53 @@ export const templateDynamicContents = async (template: Buffer, data: Record> +{ + //Calculate hash + const originalTicket_data = templateData?.info?.ticket_data //expect valid json string + // console.log("templateData",templateData?.info?.ticket_data) + if (originalTicket_data && typeof originalTicket_data === 'string') { + try { + const originalTicketObj = JSON.parse(originalTicket_data)//sort and add expire to ticket data + if (originalTicketObj && typeof originalTicketObj === 'object') { + console.log("Found valid ticket data: hash it") + // คำนวณค่า expire (ปัจจุบัน + จำนวนวินาทีที่ต้องการให้หมดอายุ) + // หาร 1000 เพราะต้องการหน่วยวินาที (sec) ไม่ใช่มิลลิวินาที (ms) + const expireTimestamp = Math.floor(Date.now() / 1000) + ttl; + const fullObj = { ...originalTicketObj, exp: expireTimestamp }; + const sortedObj = sortObjectKeys(fullObj); + + // แปลงกลับเป็น JSON string เพื่อนำไปใช้ส่งและทำ Hash + const ticket_data = JSON.stringify(sortedObj); + // console.log("check secret:",TEST_SECRET,TEST_PRIVATE_KEY_HEX,ticket_data) + const HmacSha256 = generateHmacSha256(ticket_data, secret) + + const Ed25519 = signEd25519(ticket_data, private_key) + templateBuffer = setCustomDocProps(templateBuffer, { ticket_data, HmacSha256, Ed25519 }) + } + } catch (error) { //just skip + console.error("Set custom property fail",error); + } + } + + // Apply Dynamic Content + if (templateData?.dc && typeof templateData?.dc === "object") { + console.log("Apply Dynamic Content") + templateBuffer = Buffer.from(await templateDynamicContents(templateBuffer, templateData.dc)) + } + // Apply Content Control + if (templateData?.cc && typeof templateData?.cc === "object") { + console.log("Apply Content Control") + templateBuffer = templateContentControls(templateBuffer, templateData.cc); + } + return templateBuffer + +} \ No newline at end of file From cffc571da5d8e11ccb9e1424a9918c6c35e14bab Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 27 Oct 2026 11:21:15 +0700 Subject: [PATCH 17/21] feat(addin): implement e-Court ticket auth (ticket_data + HmacSha256) Replace the token/owner pass-through with the ticket flow from docs/requirement/addin-workflow.md (full plan: docs/PLAN-ADDIN-AUTH.md): - utils/docver-ticket.ts: shared pure ticket parse + hex digest helpers, client/server inject their own crypto (Web Crypto / node:crypto) - lib/addin-ticket.ts + POST /api/documents/token second entry: body {ticket_data, HmacSha256} verified server-side (HMAC timing-safe + exp, secret ADDIN_TICKET_SECRET -> SECRET -> "super_secret"); x-api-key path unchanged - wordaddin: ticketService (Web Crypto HMAC, localStorage session wordaddin-auth keyed by fid, same-origin token request), authService rewrite (7-step flow, silent renew), apiClient 401 renew-retry once, metadataService reads ticket_data/HmacSha256, no-access messages per cause - scripts/make-ticket-docx.mjs: dev tool to stamp tickets onto any .docx for testing in real Word - app/wordaddin/error.tsx: error boundary showing the real error + stack in the pane (webviews have no devtools) - tests: docver-ticket + addin-ticket unit suites (21 cases) + temporary crash repro suite for the ongoing client-crash debug session - docs sync: PLAN-DOCVER, WORDADDIN-DEV, addin-ui-flow, openapi (two request bodies for /token), CLAUDE.md, .env.example Co-Authored-By: Claude Code --- .env.example | 11 + CLAUDE.md | 13 +- app/api/documents/token/route.ts | 52 +++- app/wordaddin/error.tsx | 37 +++ components/wordaddin/TaskpaneApp.tsx | 48 ++-- docs/PLAN-ADDIN-AUTH.md | 215 ++++++++++++++ docs/PLAN-DOCVER.md | 8 + docs/WORDADDIN-DEV.md | 30 +- docs/addin-ui-flow.md | 34 +-- lib/addin-ticket.ts | 124 +++++++++ lib/openapi.ts | 61 +++- scripts/make-ticket-docx.mjs | 263 ++++++++++++++++++ tests/lib/addin-ticket.test.ts | 148 ++++++++++ tests/utils/docver-ticket.test.ts | 118 ++++++++ .../wordaddin/ticket-auth-crash-repro.test.ts | 115 ++++++++ utils/docver-ticket.ts | 136 +++++++++ wordaddin/config.ts | 9 +- wordaddin/services/apiClient.ts | 32 ++- wordaddin/services/authService.ts | 192 ++++++++++--- wordaddin/services/metadataService.ts | 27 +- wordaddin/services/ticketService.ts | 176 ++++++++++++ 21 files changed, 1725 insertions(+), 124 deletions(-) create mode 100644 app/wordaddin/error.tsx create mode 100644 docs/PLAN-ADDIN-AUTH.md create mode 100644 lib/addin-ticket.ts create mode 100644 scripts/make-ticket-docx.mjs create mode 100644 tests/lib/addin-ticket.test.ts create mode 100644 tests/utils/docver-ticket.test.ts create mode 100644 tests/wordaddin/ticket-auth-crash-repro.test.ts create mode 100644 utils/docver-ticket.ts create mode 100644 wordaddin/services/ticketService.ts diff --git a/.env.example b/.env.example index d9988b6..39c60cb 100644 --- a/.env.example +++ b/.env.example @@ -27,3 +27,14 @@ DOCVER_API_KEY= # ไม่ตั้ง/ว่าง = ออก token ไม่ได้ (500) และทุก endpoint ตอบ 401 (fail closed) — ห้าม log ค่า secret DOCVER_TOKEN_SECRET= # DOCVER_TOKEN_TTL_SECONDS=3600 + +# add-in ticket auth (2026-10-27, docs/PLAN-ADDIN-AUTH.md): add-in ใน Word ขอ token +# ที่ POST /api/documents/token ด้วย body {ticket_data, HmacSha256} ที่ e-Court stamp +# มากับไฟล์ — secret ต้องตรงกับตัว stamp ของ e-Court ทั้งสองฝั่ง +# ฝั่ง server ตรวจ HMAC ของ ticket: ADDIN_TICKET_SECRET (ไม่ตั้ง = ใช้ env SECRET +# ของกลุ่ม template → ยังไม่ตั้งอีก = "super_secret" ตาม requirement) +# ⚠️ production ต้องตั้งค่าจริง — ค่า default รู้กันหมด = ใครก็ปลอม ticket ได้ +# ADDIN_TICKET_SECRET=super_secret +# ฝั่ง add-in (inline ลง bundle ตอน build — ใช้เป็นแค่ UX gate ตัดเคสไฟล์ถูกแก้ก่อนยิง +# network ส่วนความมั่นคงจริงอยู่ที่ฝั่ง server ข้างบน) +# NEXT_PUBLIC_ADDIN_TICKET_SECRET=super_secret diff --git a/CLAUDE.md b/CLAUDE.md index 7ea752d..f7d1fe1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,7 +12,7 @@ - Next.js 14 (App Router) + TypeScript **strict** — serve ใต้ basePath `/docedit` + route ทั้งหมดของแอปใต้ `app/note/**` (URL ปลายทางยังเป็น `/docedit/note/…` เหมือนเดิม — basePath `/docedit` เป็นพื้นที่กลางรองรับ sibling ในอนาคต เช่น wordaddin, `next.config.mjs`) - pdfjs-dist **v4** (`^4.10.38`) — built-in TextLayer + worker จาก `public/note/pdf.worker.min.mjs` (postinstall `scripts/patch-pdf-worker.mjs` คัดลอกจาก node_modules + **patch fake-space ไทย** — รายละเอียด invariant #6) - Drizzle ORM + PostgreSQL (Docker container เดียว: `judes-note-db`, image `postgres:17-alpine`, พอร์ต 54322) — **ไม่ใช้ Supabase** + realtime sync ด้วย **SSE (Server-Sent Events)** ผ่าน `app/api/note/annotations/stream` + pub/sub in-process (`lib/annotate-events.ts`) -- **wordaddin** — taskpane ของ Word add-in "e-Court Documents Versioning" พอร์ตมาเป็น route `/docedit/wordaddin` ของแอปนี้ (UI = Genesis, ชั้น services Office.js พอร์ตดิบจาก `D:\word-addin-poc\addin`) — **ไม่มี login ยกเลิก Keycloak 2026-10-27**: auth = e-Court pass-through (token/owner ใน custom properties ของไฟล์) ส่วน API ยังเป็น **mock** รอของจริง (spec UI: `docs/addin-ui-flow.md`, คู่มือ dev/sideload/สลับกลับ: `docs/WORDADDIN-DEV.md`); Office.js **self-host** ที่ `public/wordaddin/office-js/` ผ่าน postinstall `scripts/fetch-office-js.mjs` — gitignore (ดาวน์โหลดฉบับล่าสุดจาก CDN ของ Microsoft ตอนติดตั้ง ตัวระบบ deploy ใช้ intranet ล้วน; แพ็กเกจ npm `@microsoft/office-js` เลิกอัปเดตที่ 1.1.110 ก่อน WordApi 1.4/1.5 — runtime ตัวนั้นไม่มี `Document.close` ใช้ไม่ได้) +- **wordaddin** — taskpane ของ Word add-in "e-Court Documents Versioning" พอร์ตมาเป็น route `/docedit/wordaddin` ของแอปนี้ (UI = Genesis, ชั้น services Office.js พอร์ตดิบจาก `D:\word-addin-poc\addin`) — **ไม่มี login ยกเลิก Keycloak 2026-10-27**: auth = **ticket ของ e-Court** (`ticket_data` + `HmacSha256` ใน custom properties — add-in ตรวจ HMAC เองฝั่ง client แล้วขอ token ที่ `/api/documents/token` แบบ body ticket เก็บใน localStorage `wordaddin-auth` ตาม fid, `docs/PLAN-ADDIN-AUTH.md`) ส่วน API ยังเป็น **mock** รอของจริง (spec UI: `docs/addin-ui-flow.md`, คู่มือ dev/sideload/สลับกลับ: `docs/WORDADDIN-DEV.md`); Office.js **self-host** ที่ `public/wordaddin/office-js/` ผ่าน postinstall `scripts/fetch-office-js.mjs` — gitignore (ดาวน์โหลดฉบับล่าสุดจาก CDN ของ Microsoft ตอนติดตั้ง ตัวระบบ deploy ใช้ intranet ล้วน; แพ็กเกจ npm `@microsoft/office-js` เลิกอัปเดตที่ 1.1.110 ก่อน WordApi 1.4/1.5 — runtime ตัวนั้นไม่มี `Document.close` ใช้ไม่ได้) - Tailwind CSS + design system **Genesis** (`docs/DESIGN.md`, 2026-09-23) — palette tokens ใน `tailwind.config.ts` (`primary` เดิม + `primary-hover`/`canvas`/`line`/`ink`/`ink-soft`/`ink-faint`/`success`/`warning`/`error` + ระดับข้อความ `success-ink`/`warning-ink`/`error-ink` ≥5:1 สำหรับข้อความ/ไอคอนสถานะ — token 500 ใช้กับ border/bg tint เท่านั้น) ห้ามใช้ raw palette shades ของ Tailwind (`slate-*`/`amber-*`/`emerald-*`/`red-*` ฯลฯ) อีก; ฟอนต์ display = **General Sans** (self-host จาก Fontshare ผ่าน `next/font/local` — `app/fonts/`, variable weight 200–700, ใช้ intranet ได้), body = **DM Sans** (`next/font/google`), ไทย fallback **Sarabun** ตามลำดับใน stack (Latin ก่อนไทย — `fontFamily`/globals.css); รัศมี: ปุ่ม/input 6px (รวมปุ่ม action เล็กใน popup/composer/dialog — คู่ ยกเลิก/บันทึก ต้องได้รัศมีเดียวกัน), menu/list row ใน dropdown/panel 4px, panel/dropdown 8px, การ์ด 12px; เงาเฉพาะ hover (`shadow-card-hover`/`shadow-btn-glow` — static ไม่มีเงา ยกเว้นชั้น floating: dropdown/popover `shadow-lg` และ pin ลอยบนเอกสาร ดู §App-specific exceptions ของ DESIGN.md); focus indicator = outline น้ำเงินทึบ 2px + offset ผ่าน class `.focus-ring`/`.genesis-input` ใน globals.css (+ สำเนา `FOCUS_RING_CLS` ใน PDFViewer/AnnotationSidebar/AnnotationSelectionLayer/ToolControls — แก้ต้องแก้พร้อมกัน; halo alpha จางเกิน WCAG 1.4.11); น้ำเงินเฉพาะของ interactive (แบรนด์มาร์ก tile JN/แถบหัวหน้าสรุปยกเว้น) - mock login e-Court — หน้า `/note/login` (dropdown `MOCK_USERS` user1–user5 + รหัสผ่านกลาง `Demo1234`) ตั้ง cookie `oacCase` ตาม format จริงของระบบศาล → identity (userId = `u_userid`, userName = `u_userlogin` — **ต่างกันต่อคน** เพื่อสิทธิ์เจ้าของ) อ่านจาก cookie ผ่าน `lib/session.ts` + บังคับทุกหน้าด้วย `middleware.ts` (auth จริงเทียบระบบ e-Court + roles ยัง out of scope) @@ -39,6 +39,7 @@ docs/PLAN-DOCVER.md ← แผนกลุ่ม API docver: จัด version scripts/patch-pdf-worker.mjs ← postinstall: คัดลอก pdf.worker.min.mjs จาก node_modules ไป public/note/ + patch fake-space ไทย (invariant #6 — เจอ pattern ≠ 1 จุดหลังอัปเกรด pdfjs-dist = exit 1 ดัง ๆ) scripts/copy-swagger-ui.mjs ← postinstall ตัวที่สอง: คัดลอก swagger-ui-dist (bundle+css) ไป public/note/swagger-ui/ สำหรับหน้า Swagger UI ที่ GET /api/swagger (intranet ไม่ใช้ CDN — หาไฟล์ไม่เจอหลังอัปเกรด = exit 1 ดัง ๆ เหมือน patch-pdf-worker; Docker build รัน script นี้ด้วย ดู docker/web.Dockerfile) scripts/fetch-office-js.mjs ← postinstall ตัวที่สาม: ดาวน์โหลด Office.js runtime **ฉบับ CDN ล่าสุด** (appsforoffice.microsoft.com — ชุดไฟล์เฉพาะ Word: loader + host files ราย platform + legacy 15.x + o15apptofilemappingtable/microsoftajax + office_strings.js ต่อ locale en-us/th-th บังคับ) ไป public/wordaddin/office-js/ ให้ taskpane โหลด same-origin — npm `@microsoft/office-js` เลิกอัปเดตที่ 1.1.110 (~2018) ไม่มี `Document.close`/enum `CloseBehavior` ที่ runtime ใหม่+`@types/office-js` มี; ไฟล์หลักหาย/ตรวจไม่เจอ `"Document.close"`+`CloseBehavior` ใน word-win32-16.01.js หลังโหลด = exit 1 ดัง ๆ (ไฟล์ปลายทาง gitignore — ดาวน์โหลดใหม่ทุกครั้ง; ติดตั้งต้องออกเน็ตถึง CDN ของ Microsoft ส่วน runtime ตัวระบบ intranet ล้วน) +scripts/make-ticket-docx.mjs ← dev tool รันมือ (ไม่ใช่ postinstall): stamp ticket ขอ e-Court (`ticket_data` + `HmacSha256`) ลงไฟล์ .docx ใด ๆ ผ่าน pizzip เพื่อทดสอบ ticket auth ของ add-in ใน Word จริงโดยไม่ต้องผ่านกลุ่ม template (TMPL_PATH) — คำนวณ HMAC ตรงกับตัว stamp จริง (key เรียงตามตัวอักษรหลังเติม exp + เซ็นบน string ดิบ hex lowercase) · ตัวเลือก --fid/--uid/--uname/--tid/--exp /--secret/--tracked/--in-place/--list (PLAN-ADDIN-AUTH.md §9) db/schema.ts ← ตาราง + JSONB types + DTO + mapper toDTO db/index.ts ← postgres.js + drizzle (DATABASE_URL → container judes-note-db, prepare:false) middleware.ts ← บังคับ login ทุกหน้า — ไม่มี cookie oacCase ที่ parse ได้ = redirect /note/login (path ที่ตัด basePath แล้ว: หน้าขึ้นต้น /note/… ส่วน API ขึ้นต้น /api/note/… — ยกเว้น note/login, api/note/*, api/template/* (กลุ่ม docx template เปิดกว้างให้ทางศาล/ผู้ทดสอบภายนอกยิงได้เลย — ไม่แตะ DB), api/documents/* (กลุ่ม docver จัด version เอกสาร — stand-alone ไม่มี session ใช้ auth ของกลุ่มเอง: ขอ access_token ผ่าน x-api-key แล้วแนบ Bearer ทุก endpoint, docs/PLAN-DOCVER.md), api/swagger/* (หน้า Swagger UI), wordaddin/* (taskpane ของ Word add-in — ไม่มี login: auth เป็น e-Court pass-through จาก token/owner ในไฟล์), note/uploads/*, note/samples, note/swagger-ui/*, note/pdf.worker.min.mjs, _next, fonts, favicon และ **root "/" เอง** (matcher ใช้ `.+` — root เป็น redirect สู่ /wordaddin ซึ่ง public ตามคำขอผู้ใช้ 2026-10-14, ลิงก์ e-Court N8 ที่ root ยังถูกคุมผ่าน /note ที่รับส่งต่อ)) @@ -51,12 +52,13 @@ lib/external-api-auth.ts ← N10 auth ของ /api/note/external/* — require lib/external-api.ts ← N10 ส่วนกลางของ route — parseQueryBody (validate JSON body ของ /api/note/external/documents: รับเฉพาะ fileId/typeId/filter, ต้องมี fileId หรือ typeId อย่างน้อยหนึ่งอย่าง — ส่งทั้งคู่ได้ = AND เหลือเฉพาะเอกสารเดียวที่ตรงทั้งสองเงื่อนไข, คำขอผู้ใช้ 2026-09-25 (รอบเดียวกันเคยเป็น OR แล้วเปลี่ยนหลังทดสอบจริง), filter รับเฉพาะ type ∈ bookmarks|annotations ไม่ส่ง = ทั้งสอง + userId (string|array กรองตามเจ้าของรายการ annotations+bookmarks) + annotationTypes (ค่า enum annotation_type เฉพาะ annotations ค่าอื่น = 400 — เดิมชื่อ types เปลี่ยน 2026-09-25) — คืน discriminated mode "file" (fileId + typeId อาจมาด้วย) / "type" (typeId เดี่ยว)) + loadDocumentBundles(fileRows, parts) (bulk: inArray ทุกไฟล์ในชุด query เดียว = 3 query ต่อ request ไม่ว่ากลุ่มกี่ไฟล์ แทนยิง 3 query ต่อไฟล์พร้อมกันทั้งกลุ่ม, code review 2026-09-25 — กรอง userIds/annotationTypes ใน SQL; คืน bundle ยุบชั้น: field เอกสาร + annotations + bookmarks ระดับเดียวกัน ไม่มี object document ซ้อน; ต่อรายการมี `url` deep link F12 กลับหน้า `/annotate/[id]` — annotation `?ann=` / bookmark `?page=<หน้า 1-based>` — URL เต็ม: origin จาก request ผู้เรียก (route อ่าน x-forwarded-proto/host แล้ว fallback host ตรง ส่งเข้าทาง parameter origin) + basePath ผ่าน withBasePath, คำขอผู้ใช้ 2026-09-25) + type ExternalDocumentDTO (externalFileId เป็น null สำหรับไฟล์ url:-fallback/อัปโหลด N4 ในกลุ่ม) lib/openapi.ts ← OpenAPI 3.0 spec ของ API ทดสอบ (กลุ่ม template + N10 + docver — 2026-10-06) — แหล่งเดียวของ Swagger UI: GET /api/template inline ลง HTML ตรง ๆ ไม่มี endpoint spec แยก; URL ใน paths เป็น path เต็ม /api/… (servers เติมแค่ basePath ตาม invariant #12) — แก้ contract ของ API ต้องมาแก้ไฟล์นี้ให้ตรงด้วย lib/document-versioning.ts ← docver pure validation (ห้าม import อะไรที่ลาก @/db หรือ minio มาด้วย — unit test tests/lib/document-versioning.test.ts ต้องรันได้เหมือน lib/external-api-auth.ts): parseVersionUploadForm (allowlist field — identity ไม่รับจาก form แล้ว แกะจาก token ผ่าน lib/docver-token.ts + fields JSON ≤100 keys + message ≤2048), isDocxBytes/isPdfBytes (magic bytes), parseDownloadFormat/parseVersionIdParam, contentDispositionValue (ASCII fallback + filename*=UTF-8'') + docverError สร้าง Response JSON {"error": ไทย} ใช้ร่วมทุก route ของกลุ่ม (parseDocId ถอดแล้ว 2026-10-26 — docId ไม่มาจาก path param) -lib/docver-token.ts ← docver token + auth ของกลุ่ม /api/documents/* (2026-10-22 — pure module ห้าม import อะไรที่ลาก @/db หรือ minio มาด้วย เหมือน lib/document-versioning.ts; unit test tests/lib/docver-token.test.ts ต้องรันได้โดยไม่ต้องตั้ง env): issueDocverToken/verifyDocverToken (JWT HS256 มือเขียน node:crypto ล้วน — บังคับ alg=HS256 กัน alg-confusion, timingSafeEqual หลัง hash ทั้งสองฝั่ง, exp บังคับ) + requireDocverApiKey (header x-api-key เทียบ env DOCVER_API_KEY — fail closed 401 ไม่ตั้ง/ว่าง, 401 เดียวกันทั้งไม่ใส่/ผิด กัน enumerate, ห้าม log ค่า key/header ดิบ) + requireDocverBearer (Authorization: Bearer → claims {userid, username, fid, template} — secret จาก env DOCVER_TOKEN_SECRET ไม่ตั้ง = 401, ทุกกรณี 401 ข้อความเดียวกัน) + parseTokenRequestBody (allowlist userid/username/fid/template — **fid = fileId ของ e-Court (string, บังคับ 2026-10-26)** เป็น docId ของทุก operation ตรง ๆ · verify ไม่รับ token ที่ไม่มี fid) +lib/docver-token.ts ← docver token + auth ของกลุ่ม /api/documents/* (2026-10-22 — pure module ห้าม import อะไรที่ลาก @/db หรือ minio มาด้วย เหมือน lib/document-versioning.ts; unit test tests/lib/docver-token.test.ts ต้องรันได้โดยไม่ต้องตั้ง env) +lib/addin-ticket.ts ← add-in ticket auth ฝั่ง server ของทางเข้าที่สองของ POST /api/documents/token (2026-10-27, docs/PLAN-ADDIN-AUTH.md — pure เหมือน lib/docver-token.ts; unit test tests/lib/addin-ticket.test.ts): addinTicketSecret (env ADDIN_TICKET_SECRET → SECRET → "super_secret") + verifyAddinTicket (HMAC timing-safe บน ticket_data ดิบ + exp ผ่าน utils/docver-ticket.ts) + ticketToDocverClaims (uid→userid, uname→username, tid→template) + parseTicketTokenBody (allowlist ticket_data/HmacSha256 — missing → 401 / invalid → 400): issueDocverToken/verifyDocverToken (JWT HS256 มือเขียน node:crypto ล้วน — บังคับ alg=HS256 กัน alg-confusion, timingSafeEqual หลัง hash ทั้งสองฝั่ง, exp บังคับ) + requireDocverApiKey (header x-api-key เทียบ env DOCVER_API_KEY — fail closed 401 ไม่ตั้ง/ว่าง, 401 เดียวกันทั้งไม่ใส่/ผิด กัน enumerate, ห้าม log ค่า key/header ดิบ) + requireDocverBearer (Authorization: Bearer → claims {userid, username, fid, template} — secret จาก env DOCVER_TOKEN_SECRET ไม่ตั้ง = 401, ทุกกรณี 401 ข้อความเดียวกัน) + parseTokenRequestBody (allowlist userid/username/fid/template — **fid = fileId ของ e-Court (string, บังคับ 2026-10-26)** เป็น docId ของทุก operation ตรง ๆ · verify ไม่รับ token ที่ไม่มี fid) lib/docver-store.ts ← docver server-side MinIO + DB (env MINIO_* อ่านตอนเรียกไม่ใช่ module load; client + cached ensureBucket เก็บบน globalThis แบบ db/index.ts): object key `docver//document.docx|pdf` (uuid ล้วน ไม่มี input ผู้ใช้), putVersionObjects (docx ก่อน — pdf ล้ม = ลบ docx คืนแล้วโยนต่อ), removeVersionObjects (best-effort), presignVersionUrl (TTL env DOCVER_LINK_TTL_SECONDS), insertVersionRecord (**tx เดียว**: upsert documents ON CONFLICT DO NOTHING → SELECT id FOR UPDATE → max(versionNumber)+1 → insert — จ่ายเลข version ปลอดภัยเรียกคู่ขนาน, UNIQUE(document_id, version_number) เป็น safety net) wordaddin/ ← **client-only zone ของ Word add-in** (ห้าม import จาก lib/ หรือถูก import โดย server component — พอร์ตดิบจาก D:\word-addin-poc\addin\src, spec: docs/addin-ui-flow.md, คู่มือ dev: docs/WORDADDIN-DEV.md) wordaddin/config.ts ← env NEXT_PUBLIC_ADDIN_API_BASE_URL ("" = ยังไม่ต่อ backend จริง) + NEXT_PUBLIC_ADDIN_MOCK_USERNAME (ชื่อ fallback ของ ?addindemo/ไฟล์ที่มี token ไม่มี owner) wordaddin/models/versionRecord.ts ← types VersionRecord/FieldsSnapshot + readSnapshotField (พอร์ตดิบ) -wordaddin/services/ ← documentService/fileService/metadataService/autoSaveService/versionFlow/apiClient/errors พอร์ตดิบทั้งไฟล์ + **templateService** (แท็บที่ 2 "เครื่องมือ Template" 2026-10-19 — list/select/applyTestValues + แทรกย่อหน้า-ตัวแบ่งบรรทัด-ตัวแบ่งหน้า ที่ cursor (page break มี fallback OOXML กัน E_FAIL), Office.js ล้วน — ⚠️ `contentControls` ธรรมดาเห็นเฉพาะ rich text CC: feature-detect WordApi 1.5 ใช้ `getContentControls()` เห็นทุกชนิด, ห้าม top-level Office access) · **authService = e-Court pass-through ทางเดียว ไม่มี login แล้ว** (ยกเลิก Keycloak 2026-10-27 — surface เหลือ signInForDemo (dev-only) / signInFromDocument / getAccessToken / getUsername; ไฟล์ที่ stamp custom properties `token`/`owner` มา (`readTrackedProperties` อ่านรวม) = `detect()` ของ TaskpaneApp ผ่านเข้าเลย ไม่มี = phase `no-access` — username จาก owner → uid ใน JWT → mockUsername, token ไฟล์เก็บ memory ให้ getAccessToken ใช้ต่อ; apiClient โดน 401 = AuthRequiredError → no-access เลย token ต่ออายุไม่ได้) · **versioningService = MOCK** (memory + **metadata persist ลง localStorage ต่อ documentId** เพื่อทดสอบวงปิดเอกสาร→เปิดใหม่ได้ — blob ไฟล์จริงเฉพาะ session; คงชื่อ/ลายเซ็น uploadVersion/getVersions/getDownloadLink/newClientVersionId เป๊ะ, download-link คืน about:blank — **backend จริงของ surface นี้มีแล้ว**: กลุ่ม `app/api/documents/**` (docver, docs/PLAN-DOCVER.md — สลับ mock ด้วยการตั้ง `NEXT_PUBLIC_ADDIN_API_BASE_URL`) +wordaddin/services/ ← documentService/fileService/metadataService/autoSaveService/versionFlow/apiClient/errors พอร์ตดิบทั้งไฟล์ + **templateService** (แท็บที่ 2 "เครื่องมือ Template" 2026-10-19 — list/select/applyTestValues + แทรกย่อหน้า-ตัวแบ่งบรรทัด-ตัวแบ่งหน้า ที่ cursor (page break มี fallback OOXML กัน E_FAIL), Office.js ล้วน — ⚠️ `contentControls` ธรรมดาเห็นเฉพาะ rich text CC: feature-detect WordApi 1.5 ใช้ `getContentControls()` เห็นทุกชนิด, ห้าม top-level Office access) · **authService + ticketService = ticket auth ของ e-Court ทางเดียว ไม่มี login แล้ว** (ยกเลิก Keycloak → pass-through token/owner → ticket 2026-10-27, `docs/PLAN-ADDIN-AUTH.md` ตาม `docs/requirement/addin-workflow.md` — surface เหลือ signInForDemo (dev-only) / signInWithTicket / getAccessToken / renewTokenFromTicket / getUsername; ไฟล์ที่ stamp custom properties `ticket_data`+`HmacSha256` มา = `detect()` ของ TaskpaneApp เรียก signInWithTicket: ตรวจ HMAC ฝั่ง client (Web Crypto + NEXT_PUBLIC_ADDIN_TICKET_SECRET — UX gate) → exp → session localStorage `wordaddin-auth` ตาม uid (user ใหม่ = เคลียร์ทั้งก้อน) → ขอ token ที่ `/api/documents/token` แบบ body ticket ผูก fid; ไม่มี ticket/HMAC ไม่ตรง/หมดอายุ = phase `no-access` แยกข้อความตามสาเหตุ; apiClient โดน 401 = renew จาก ticket 1 ครั้ง ยังไม่ผ่าน = AuthRequiredError → no-access) · **versioningService = MOCK** (memory + **metadata persist ลง localStorage ต่อ documentId** เพื่อทดสอบวงปิดเอกสาร→เปิดใหม่ได้ — blob ไฟล์จริงเฉพาะ session; คงชื่อ/ลายเซ็น uploadVersion/getVersions/getDownloadLink/newClientVersionId เป๊ะ, download-link คืน about:blank — **backend จริงของ surface นี้มีแล้ว**: กลุ่ม `app/api/documents/**` (docver, docs/PLAN-DOCVER.md — สลับ mock ด้วยการตั้ง `NEXT_PUBLIC_ADDIN_API_BASE_URL`) utils/coordinates.ts ← pixel⇄% + getPointerPercent + clamp + mergeRectsByLine utils/geometry.ts ← bbox/translate/scale/hitTestPath ของ annotation (pure, หน่วย %) utils/annotation-display.ts ← label/ข้อความย่อต่อชนิด + TYPE_SHORT/TYPE_ORDER ชื่อย่อ/ลำดับชนิด (บรรทัดสรุปหน้าของหน้าสรุป + แผงตัวกรอง explorer — ย้ายจาก 2 ที่ซ้ำกันมารวมที่นี่ 2026-09-24) + ค่า default TEXTBOX/STAMP + describeAnnotationChange สรุปสิ่งที่เปลี่ยนของ version history F19 (sidebar/สรุป/groups/CSV ใช้ร่วม) @@ -65,6 +67,7 @@ utils/pdf-export.ts ← แปลงพิกัดจอ⇄PDF (/Rotate) + wr utils/user.ts ← parse/build ค่า cookie oacCase → OacUser {userId: u_userid, userName: u_userlogin} (pure module — middleware ใช้ร่วมได้) utils/external-file.ts ← N8 validation ลิงก์ไฟล์รีโมต (allowlist "admincourt.go.th" + env EXTERNAL_FILE_HOSTS, suffix match, https เท่านั้น ห้าม user:pass@) + titleFromExternalUrl/externalIdFor/isHttpUrl + normDocumentGroup (N9 normalize รหัสกลุ่ม — ใช้ร่วม N8/N4, pure) utils/base-path.ts ← withBasePath() เติม basePath /docedit + app path /note ให้ URL ฝั่ง browser ที่ Next ไม่เติมให้เอง (ค่าจาก next.config.mjs ผ่าน env — invariant #12) +utils/docver-ticket.ts ← ticket ของ add-in ส่วน pure แชร์ client/server (2026-10-27, PLAN-ADDIN-AUTH.md §4 — ไม่ import อะไรเลย ให้สองฝั่งฉีด crypto ของตัวเอง: lib/addin-ticket.ts = node:crypto / wordaddin/services/ticketService.ts = Web Crypto): parseDocverTicket (allowlist exp/fid/tid/uid/uname + cap ยาวตาม endpoint) + isTicketExpired + normalizeHexDigest + timingSafeEqualHex + type HmacSha256Hex — unit test tests/utils/docver-ticket.test.ts utils/file-kind.ts ← derive ชนิดไฟล์ pdf/image จาก extension ของ path (N3 — ใช้ร่วม viewer/export/หน้ารายการ/seed) utils/upload-name.ts ← N4 ถอดรหัสชื่อไฟล์จาก FormData ของ Server Action กลับเป็น UTF-8 ก่อนใช้ (undici ถอด header Content-Disposition เป็น latin1 ทำชื่อไทยเพี้ยน "คำ…" — encode กลับ byte แล้วถอด UTF-8; guard: ไม่มีตัว U+0080–U+00FF = ไม่แตะ, ถอดแล้วได้ U+FFFD = เก็บของเดิม, 2026-10-05) utils/tiff-client.ts ← decode TIFF ทุกเฟรม → PNG blob URL ฝั่ง client (N3) @@ -79,7 +82,7 @@ app/api/note/annotations/export/route.ts ← export PDF ฝัง annotation app/api/note/external/documents/route.ts ← N10 API ภายนอกเส้นเดียว (POST + JSON body {fileId และ/หรือ typeId, filter:{type, userId, annotationTypes}} — auth ผ่าน requireExternalApiKey ก่อนแตะ DB, ถาม fileId = ค้น files.external_id = "ecourt:" ไม่เจอ 404 แม้ส่ง typeId มาด้วย / ถาม typeId = ทุกไฟล์ในกลุ่ม กลุ่มว่าง = 200 documents:[] / ส่งทั้งคู่ = AND กรอง externalId+typeId ใน query เดียว เหลือเอกสารเดียว ไม่ตรงคู่ = 404 แยกสาเหตุในข้อความ (2026-09-25 เดิมเคยเป็น OR แล้วเป็น 400); response รูปเดียว {documents:[bundle…]} ผ่าน loadDocumentBundles ทั้งสอง mode; ครอบ try/catch ทุก DB call — ล้ม = log ฝั่ง server + 500 JSON ตาม contract ไม่ปล่อยเป็น 500 ของ framework; read-only ไม่ publish SSE ไม่ revalidatePath — snapshot ณ ตอนเรียก) app/api/swagger/route.ts ← **GET /api/swagger = หน้า Swagger UI** (2026-10-06 — document ของ API ทุกกลุ่ม: spec inline จาก lib/openapi.ts + asset จาก public/note/swagger-ui/ ของ postinstall scripts/copy-swagger-ui.mjs, persistAuthorization เก็บ x-api-key ไว้ใช้ข้าม reload — เปิดกว้างไม่ต้อง login) app/api/template/{test,extract,render}/route.ts ← กลุ่ม API docx template ของ wordaddin (WIP — GET test ตรวจ service / POST extract ดึงค่า Content Control / POST render = template(.docx)+data(JSON ไฟล์หรือ field dataJson) คืน .docx ใหม่; logic รวมอยู่ app/api/template/lib-tmpl.ts เป็นแชร์ไลบรารี pizzip+xpath; ไฟล์ทดสอบ/โน้ตอยู่ app/api/template/test/ — ไม่แตะ DB, URL /docedit/api/template/…) -app/api/documents/token/route.ts + versions/route.ts + versions/[versionId]/download-link/route.ts ← **กลุ่ม API docver จัด version เอกสาร** (docs/PLAN-DOCVER.md — URL /docedit/api/documents/…, auth ของกลุ่มเอง 2026-10-22: POST token ขอ access_token ด้วย header `x-api-key` + env `DOCVER_API_KEY` (lib/docver-token.ts — fail closed 401 ไม่ตั้ง/ว่าง เหมือน N10) แล้ว **endpoint ที่เหลือทุกตัวบังคับ `Authorization: Bearer`** JWT HS256 เซ็นด้วย env `DOCVER_TOKEN_SECRET` (TTL `DOCVER_TOKEN_TTL_SECONDS` default 3600) — identity userid/username + templateId แกะจาก claims ไม่รับจาก request อีกต่อไป; ส่ง userId/userName/templateId ใน form = 400 ไม่รู้จัก field · **2026-10-26 ถอด segment `[docId]` ออกจาก URL ทุก endpoint — docId = claim `fid` (string) จาก token ตรง ๆ (บังคับตั้งแต่ขอ token)**): POST versions = multipart `file`(.docx บังคับ ≤100MB เช็ค size ก่อน buffer → 413)+`pdf`(optional ไฟล์เปล่า = ไม่มี)+fields/message/clientVersionId/fileName → สร้างเอกสารอัตโนมัติถ้า docId ใหม่, MinIO ก่อน DB (DB ล้มหลัง put = เก็บกวาด object แบบ N4) → `{hasPdf, versionId, versionNumber}` (เลขจาก server เป็นค่าจริง) / GET versions = DocumentVersionDTO[] ใหม่→เก่า (ไม่เคยมี docId = 404) / GET download-link?format=docx|pdf = presigned URL อายุสั้น (เช็ค docId+versionId คู่กันกันโยน id ข้ามเอกสาร; ขอ pdf ที่ version ไม่มี = 404) — carve-out invariant #4: ไม่ publish SSE ไม่ revalidatePath +app/api/documents/token/route.ts + versions/route.ts + versions/[versionId]/download-link/route.ts ← **กลุ่ม API docver จัด version เอกสาร** (docs/PLAN-DOCVER.md — URL /docedit/api/documents/…, auth ของกลุ่มเอง 2026-10-22: POST token ขอ access_token ด้วย header `x-api-key` + env `DOCVER_API_KEY` (lib/docver-token.ts — fail closed 401 ไม่ตั้ง/ว่าง เหมือน N10) แล้ว **endpoint ที่เหลือทุกตัวบังคับ `Authorization: Bearer`** JWT HS256 เซ็นด้วย env `DOCVER_TOKEN_SECRET` (TTL `DOCVER_TOKEN_TTL_SECONDS` default 3600) — identity userid/username + templateId แกะจาก claims ไม่รับจาก request อีกต่อไป; ส่ง userId/userName/templateId ใน form = 400 ไม่รู้จัก field · **2026-10-27 POST token มีทางเข้าที่สองสำหรับ add-in ใน Word** (PLAN-ADDIN-AUTH.md): ไม่มี x-api-key + body `{ticket_data, HmacSha256}` — lib/addin-ticket.ts ตรวจ HMAC+exp (secret env ADDIN_TICKET_SECRET → SECRET → default "super_secret") แล้วออก token จาก claims ของ ticket เอง · **2026-10-26 ถอด segment `[docId]` ออกจาก URL ทุก endpoint — docId = claim `fid` (string) จาก token ตรง ๆ (บังคับตั้งแต่ขอ token)**): POST versions = multipart `file`(.docx บังคับ ≤100MB เช็ค size ก่อน buffer → 413)+`pdf`(optional ไฟล์เปล่า = ไม่มี)+fields/message/clientVersionId/fileName → สร้างเอกสารอัตโนมัติถ้า docId ใหม่, MinIO ก่อน DB (DB ล้มหลัง put = เก็บกวาด object แบบ N4) → `{hasPdf, versionId, versionNumber}` (เลขจาก server เป็นค่าจริง) / GET versions = DocumentVersionDTO[] ใหม่→เก่า (ไม่เคยมี docId = 404) / GET download-link?format=docx|pdf = presigned URL อายุสั้น (เช็ค docId+versionId คู่กันกันโยน id ข้ามเอกสาร; ขอ pdf ที่ version ไม่มี = 404) — carve-out invariant #4: ไม่ publish SSE ไม่ revalidatePath app/note/uploads/[name]/route.ts ← เสิร์ฟไฟล์อัปโหลดจาก public/note/uploads/ (N4 — Next production เห็นไฟล์ static เฉพาะที่มีตอน startup ไฟล์ที่เขียน runtime ต้องเสิร์ฟผ่าน route) app/note/page.tsx ← รายการไฟล์จัด section ตามกลุ่ม type_id + "ไม่มีกลุ่ม" ท้ายสุด (N9 — server component) app/note/groups/[typeId]/page.tsx ← N9 หน้าเอกสารในกลุ่ม (server component force-dynamic — files where type_id orderBy createdAt, ไม่มี = 404; ~~หมายเหตุ "แสดงเฉพาะเอกสารที่เคยเปิดในระบบนี้"~~ ถอดออกตามคำขอผู้ใช้ 2026-09-24; **redesign 2026-09-24 ตามคำขอผู้ใช้: ไม่มี navbar** — explorer 2 คอลัมน์ ตัวกรองซ้าย + list เดียวรวม bookmark+annotation ทั้งกลุ่มไม่แยกไฟล์: query ทั้งหมดฝั่ง server แล้วส่ง `GroupFileSummary[]` + `GroupListItem[]` (เรียง ไฟล์ตาม createdAt → หน้า → bookmark ก่อน annotation → createdAt) ให้ `GroupExplorer` กรองใน memory, แถวรายการ deep link `?ann=`/`?page=` เหมือนหน้าสรุป F12 + ไฟล์ที่มาต้น meta line) @@ -89,7 +92,7 @@ app/note/annotate/[id]/summary/page.tsx ← หน้าสรุป Annotation+ app/wordaddin/layout.tsx + page.tsx ← taskpane ของ Word add-in (URL `/docedit/wordaddin` — nested layout แรกของ repo override title แท็บ browser; page เป็น server component บาง ๆ render WordAddinGate ทั้งหมดฝั่ง client) app/wordaddin/manifest.xml/route.ts ← GET serve manifest ของ add-in พร้อม rewrite origin ตามโฮสต์ที่ขอ (template `public/wordaddin/manifest.template.xml` ค่า dev — dev/UAT/production โหลด manifest.xml ขอ environment ตัวเองแล้วใช้ได้เลย ไม่ต้อง rewrite มือ; origin จาก `utils/request-origin.ts` แชร์กับ deep link N10 — x-forwarded-proto/host ก่อน fallback host ตรง + บังคับ https โฮสต์ที่ไม่ใช่ localhost เพราะ Office รับ https เท่านั้น) components/wordaddin/WordAddinGate.tsx ← client gate: ฉีด script office.js self-host (ผ่าน withBasePath "/wordaddin/office-js/office.js") → Office.onReady → mount TaskpaneApp · `?addindemo=untracked|tracking` (dev-only) ข้าม office.js + ตั้ง session mock (`signInForDemo`) + ตั้ง phase ตรง เพื่อดู UI ใน browser ธรรมดา — flow ที่แตะ Word จริงจะจบที่ error ตาม expected (FR-17: ใน browser ปกติ banner auto-save error โผล่หลัง ~30 วิ — ถูกต้อง; เส้นทางจริงไม่มี demo จะจบที่หน้า no-access เพราะอ่าน properties ไม่ได้) -components/wordaddin/TaskpaneApp.tsx ← state machine checking/no-access/untracked/tracking (ไม่มี login — ยกเลิก Keycloak 2026-10-27: detect อ่าน token/owner จากไฟล์ผ่าน signInFromDocument ไม่มี = no-access · AuthRequiredError จาก flow ก็ไป no-access) + handler ทุกตัว พอร์ตจาก App.tsx ต้นฉบับ (เปลี่ยนแค่ JSX → Genesis + ถอดหน้า login/ปุ่มออกจากระบบ — header เหลือ chip username เฉย ๆ) · SaveControls/MessageBar/Spinner/ConfirmVersionDialog(portal แบบ AnnotationSidebar)/VersionHistoryPanel · **2 แท็บ (Versioning / เครื่องมือ Template — toggle `aria-pressed` เฉพาะ phase untracked|tracking, 2026-10-19): แท็บ template = TemplateToolsPanel (สแกน CC/กรอกค่าทดสอบ/แทรกย่อหน้า-ตัวแบ่ง — spec ที่ docs/addin-ui-flow.md §10)** +components/wordaddin/TaskpaneApp.tsx ← state machine checking/no-access/untracked/tracking (ไม่มี login — ยกเลิก Keycloak 2026-10-27: detect อ่าน ticket_data/HmacSha256 จากไฟล์ผ่าน signInWithTicket ไม่ผ่าน = no-access แยกข้อความตามสาเหตุ · AuthRequiredError จาก flow ก็ไป no-access) + handler ทุกตัว พอร์ตจาก App.tsx ต้นฉบับ (เปลี่ยนแค่ JSX → Genesis + ถอดหน้า login/ปุ่มออกจากระบบ — header เหลือ chip username เฉย ๆ) · SaveControls/MessageBar/Spinner/ConfirmVersionDialog(portal แบบ AnnotationSidebar)/VersionHistoryPanel · **2 แท็บ (Versioning / เครื่องมือ Template — toggle `aria-pressed` เฉพาะ phase untracked|tracking, 2026-10-19): แท็บ template = TemplateToolsPanel (สแกน CC/กรอกค่าทดสอบ/แทรกย่อหน้า-ตัวแบ่ง — spec ที่ docs/addin-ui-flow.md §10)** components/summary/SummaryExplorer.tsx ← client root ของหน้าสรุป (redesign 2026-09-24) — state ตัวกรอง `FacetFilterState` จากแผงกลาง explorer + กรองใน memory + facet count/static users derive เอง + มิติ "หน้า" เป็นตัวเลือกแบบ single-select เฉพาะหน้าที่มีรายการ ≥1 เรียงน้อย→มาก (`optionsScroll` ให้ scroll ภายในกลุ่ม max-h-64 กันหน้าเยอะดันกลุ่มอื่นออกจอ) + layout 2 คอลัมน์ (มือถือ < lg ตัวกรองหุบเป็น `
`, desktop aside sticky 15rem — pattern เดียวกับ GroupExplorer; คอลัมน์ content ครอบด้วย `
` ไม่ใช่ `
` เพราะกฎ print break-inside ของ `main.summary-page section` มุ่งการ์ดรายหน้าข้างใน) components/summary/SummaryList.tsx ← คอลัมน์ content ของหน้าสรุป — การ์ดรายหน้า "1 หน้า 1 การ์ด" หัวการ์ด sticky ระดับ viewport (เลขหน้า + บรรทัดสรุปหน้า `pageSummaryLine` **คำนวณจากรายการที่เห็นจริงหลังกรอง** + ปุ่ม "เปิดหน้านี้"; กลับเป็น position static ตอนพิมพ์ผ่านกฎ `.page-head` ใน globals.css) + แถว passive จุดคลิกเดียวคือปุ่ม `` "View" เหมือน GroupList (deep link `?ann=`/`?page=` — bookmark ไปหน้า, annotation ไปรายการ; meta line ไม่มี segment ไฟล์เพราะไฟล์เดียวทั้งหน้า) + header "พบ N รายการ" (aria-live) + ปุ่ม "ล้างตัวกรอง" (เฉพาะมีตัวกรอง active) + empty state กรองจนไม่เหลือ + **ตอนพิมพ์แสดงฉบับเต็มเสมอ** — ตัวกรองที่ตั้งค้างต้องไม่ตัดรายการออกเงียบ ๆ บนกระดาษ: ตอน activeCount > 0 render ชุดบนจอ (print:hidden) + ชุดเต็ม (hidden print:block, ไม่มี id/aria-labelledby ซ้ำ) แยกกัน; anchor `#page-N` ของ section คงจาก design เดิมให้ลิงก์เก่ายังใช้ได้ components/pdf/PDFViewer.tsx ← client root (tool state, zoom, selection, lazy render หน้า) diff --git a/app/api/documents/token/route.ts b/app/api/documents/token/route.ts index 07cd544..2173fe2 100644 --- a/app/api/documents/token/route.ts +++ b/app/api/documents/token/route.ts @@ -1,20 +1,43 @@ // PLAN-DOCVER.md §auth — POST /api/documents/token ออก access_token ของกลุ่ม docver -// ยืนยันด้วย header "x-api-key" เทียบ env DOCVER_API_KEY (fail closed เมื่อ env ไม่ -// ตั้ง/ว่าง) — token เป็น JWT HS256 เซ็นด้วย env DOCVER_TOKEN_SECRET อายุตาม env +// ทางเข้า 2 ทาง (PLAN-ADDIN-AUTH.md §3 — header x-api-key เป็นตัวตั้ง): +// 1) e-Court server-to-server: header "x-api-key" เทียบ env DOCVER_API_KEY +// (fail closed เมื่อ env ไม่ตั้ง/ว่าง) + body claims ตรง ๆ (เดิมทุกอย่าง) +// 2) add-in ใน Word: ไม่มี x-api-key + body {ticket_data, HmacSha256} — server +// ตรวจ HMAC+exp ด้วย env ฝั่ง server (ADDIN_TICKET_SECRET → SECRET → +// "super_secret", lib/addin-ticket.ts) แล้วออก token จาก claims ของ ticket +// (secret ตัวเซ็น JWT ไม่มีทางไปอยู่ใน bundle ของ add-in) +// ทั้งสองทางออก JWT HS256 เซ็นด้วย env DOCVER_TOKEN_SECRET อายุตาม env // DOCVER_TOKEN_TTL_SECONDS (default 3600) แล้วนำไปแนบ Authorization: Bearer กับ // endpoint อื่นของกลุ่ม (lib/docver-token.ts) · segment คงที่ "token" ไม่ชน [docId] // (Next ให้ static ชนะ) และ middleware ยกเว้น api/documents/* อยู่แล้ว import { docverError } from "@/lib/document-versioning"; +import { + addinTicketSecret, + parseTicketTokenBody, + ticketToDocverClaims, + verifyAddinTicket, +} from "@/lib/addin-ticket"; import { issueDocverToken, parseTokenRequestBody, requireDocverApiKey, + type DocverTokenClaims, } from "@/lib/docver-token"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; +/** 401 ของทางเข้า ticket — ข้อความเดียวทุกสาเหตุ (HMAC ผิด/รูปผิด/หมดอายุ/ไม่ส่งมา) กัน oracle */ +const TICKET_DENIED = "ticket ไม่ถูกต้องหรือหมดอายุ"; + export async function POST(request: Request) { + return request.headers.get("x-api-key") !== null + ? postWithApiKey(request) + : postWithTicket(request); +} + +/** ทางเข้า 1 (เดิม) — e-Court server-to-server */ +async function postWithApiKey(request: Request): Promise { const apiKeyFail = requireDocverApiKey(request); if (apiKeyFail !== null) return apiKeyFail; @@ -26,7 +49,30 @@ export async function POST(request: Request) { } const parsed = parseTokenRequestBody(raw); if (!parsed.ok) return docverError(parsed.error, 400); + return issueToken(parsed.claims); +} +/** ทางเข้า 2 — add-in ส่ง ticket ที่ e-Court stamp มากับไฟล์ */ +async function postWithTicket(request: Request): Promise { + let raw: unknown; + try { + raw = await request.json(); + } catch { + return docverError("body ต้องเป็น JSON", 400); + } + const parsed = parseTicketTokenBody(raw); + if (!parsed.ok) { + // missing = ไม่มี ticket มาเล่น ๆ (401 เดียวกับตอน verify ไม่ผ่าน) + // invalid = รูป body ผิดจริง ๆ ให้ 400 ตาม convention กัน typo เงียบ ๆ + return docverError(parsed.error, parsed.kind === "invalid" ? 400 : 401); + } + const verified = verifyAddinTicket(parsed.ticketData, parsed.hmacHex, addinTicketSecret()); + if (!verified.ok) return docverError(TICKET_DENIED, 401); + return issueToken(ticketToDocverClaims(verified.ticket)); +} + +/** ออก JWT ของ claims ที่ผ่านการยืนยันแล้วจากทั้งสองทาง — ส่วนกลาง */ +async function issueToken(claims: DocverTokenClaims): Promise { // ความผิด config ฝั่ง server ไม่ใช่ความผิดของผู้เรียก — ไม่ตั้ง = 500 (ห้าม log ค่า secret) const secret = process.env.DOCVER_TOKEN_SECRET?.trim(); // const secret = @@ -39,7 +85,7 @@ export async function POST(request: Request) { const ttlSeconds = Number.isFinite(ttlRaw) && ttlRaw > 0 ? Math.floor(ttlRaw) : 3600; - const accessToken = issueDocverToken(parsed.claims, secret, ttlSeconds); + const accessToken = issueDocverToken(claims, secret, ttlSeconds); return new Response( JSON.stringify({ access_token: accessToken, diff --git a/app/wordaddin/error.tsx b/app/wordaddin/error.tsx new file mode 100644 index 0000000..ac42382 --- /dev/null +++ b/app/wordaddin/error.tsx @@ -0,0 +1,37 @@ +"use client"; + +// [DBG-r3p1] error boundary ของ taskpane — แสดง error จริงแทนหน้า +// "Application error: a client-side exception has occurred" ของ Next +// (ไว้ใช้ debug ใน webview ของ Word ที่เปิด devtools ไม่ได้ — คงไว้ถาวร +// เพราะดีกว่าข้อความกลาง ๆ ของ Next เสมอ สำหรับ add-in ที่ผู้ใช้เปิด console ไม่ได้) +export default function WordAddinError({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + return ( +
+

task pane ทำงานผิดพลาด

+

+ เกิดข้อผิดพลาดฝั่งหน้าเว็บ — แจ้งข้อความด้านล่าง (ถ่ายภาพหน้าจอนี้มาด้วย) แล้วกดลองใหม่ +

+
+        {error.message}
+        {error.digest ? `\n[digest ${error.digest}]` : ""}
+        {error.stack ? `\n\n${error.stack}` : ""}
+      
+ +
+ ); +} diff --git a/components/wordaddin/TaskpaneApp.tsx b/components/wordaddin/TaskpaneApp.tsx index 1b71ed2..35895b7 100644 --- a/components/wordaddin/TaskpaneApp.tsx +++ b/components/wordaddin/TaskpaneApp.tsx @@ -24,12 +24,13 @@ import VersionHistoryPanel from "./VersionHistoryPanel"; /** * Task pane หลัก — state machine: - * checking → (ไม่มี token/owner ในไฟล์) no-access - * → (มี token/owner + ไม่ใช่ tracked document) untracked - * → (มี token/owner + tracked document) tracking - * ไม่มี login ตั้งแต่ยกเลิก Keycloak (2026-10-27) — auth เข้าทางเดียวคือ e-Court - * pass-through: `detect()` อ่าน token/owner จาก custom properties แล้วเรียก - * `authService.signInFromDocument` ผ่านเลย + * checking → (ไม่มี/ตรวจไม่ผ่าน ticket ในไฟล์) no-access + * → (ticket ผ่าน + ไม่ใช่ tracked document) untracked + * → (ticket ผ่าน + tracked document) tracking + * ไม่มี login ตั้งแต่ยกเลิก Keycloak (2026-10-27) — auth เข้าทางเดียวคือ ticket + * ของ e-Court: `detect()` อ่าน ticket_data + HmacSha256 จาก custom properties + * แล้วเรียก `authService.signInWithTicket` (ตรวจ HMAC → exp → session → ขอ token + * ตาม fid — PLAN-ADDIN-AUTH.md) * พอร์ตจาก App.tsx ต้นฉบับ (D:\word-addin-poc\addin) — เปลี่ยน: import path, * JSX → Genesis/Tailwind, ถอด FluentProvider/makeStyles, ถอดหน้า login, * เพิ่ม prop demo (โหมด ?addindemo ของ WordAddinGate — ตั้ง phase ตรง ไม่ detect จริง) @@ -138,8 +139,9 @@ const TaskpaneApp: React.FC<{ demo?: "untracked" | "tracking" }> = ({ demo }) => /** FR-3 — periodic tick เห็นการแก้ไขหลัง version ล่าสุด (แสดงเฉพาะตอน tracking) */ const [hasUncommitted, setHasUncommitted] = useState(false); - /** AuthRequiredError (ไม่มี token / 401) → หน้า no-access — token จากไฟล์ต่ออายุไม่ได้ - * ผู้ใช้ต้องขอไฟล์ใหม่จาก e-Court จึงรีเซ็ต state ทั้งชุดกันใช้ของเก่าต่อ */ + /** AuthRequiredError (ticket หมดอายุ/token ขอไม่ได้ หลัง renew-retry ของ apiClient) + * → หน้า no-access — ticket ต่ออายุไม่ได้ ผู้ใช้ต้องขอไฟล์ใหม่จาก e-Court + * จึงรีเซ็ต state ทั้งชุดกันใช้ของเก่าต่อ */ const handleAuthRequired = useCallback((error: unknown): boolean => { if (error instanceof AuthRequiredError) { setPhase("no-access"); @@ -147,7 +149,7 @@ const TaskpaneApp: React.FC<{ demo?: "untracked" | "tracking" }> = ({ demo }) => setDocumentId(null); setDocumentTemplateId(null); setVersions([]); - setNoAccessNotice("token ของ e-Court หมดอายุหรือไม่ถูกต้อง — โปรดขอไฟล์ใหม่จาก e-Court แล้วเปิดอีกครั้ง"); + setNoAccessNotice("ticket ของ e-Court หมดอายุหรือไม่ถูกต้อง — โปรดขอไฟล์ใหม่จาก e-Court แล้วเปิดอีกครั้ง"); return true; } return false; @@ -170,11 +172,12 @@ const TaskpaneApp: React.FC<{ demo?: "untracked" | "tracking" }> = ({ demo }) => [handleAuthRequired], ); - /** ตรวจตอนเปิด task pane (FR-1): token/owner ในไฟล์ → SavedByAddIn → history + /** ตรวจตอนเปิด task pane (FR-1): ticket ในไฟล์ → SavedByAddIn → history * FR-16 — อ่าน properties ครั้งเดียวทั้งชุดแล้ว derive tracked เอง เพราะไฟล์ร่าง (untracked) * ต้องได้อ่าน TemplateId ไปใช้ตอน Start Versioning ด้วย (ข้ามทั้งหมดในโหมด demo) - * e-Court pass-through — auth ทางเข้าเดียว: ไฟล์ที่ stamp token/owner มา = ผ่านเข้าเลย - * ไม่มีขั้นตอน login (นอก Word เช่น browser ธรรมดา → อ่านไม่ได้ = ไป no-access) */ + * auth ทางเข้าเดียว: ticket_data + HmacSha256 ที่ e-Court stamp มา — signInWithTicket + * ตรวจ HMAC → exp → session/localStorage แล้วขอ token ตาม fid ให้ครบก่อนไหลต่อ + * (นอก Word เช่น browser ธรรมดา → อ่านไม่ได้ = ไป no-access) */ const detect = useCallback(async () => { setPhase("checking"); setBootError(null); @@ -185,19 +188,22 @@ const TaskpaneApp: React.FC<{ demo?: "untracked" | "tracking" }> = ({ demo }) => } catch { properties = null; } - if ( - !properties || - !authService.signInFromDocument({ token: properties.ecourtToken, owner: properties.ecourtOwner }) - ) { - setNoAccessNotice( - "ไม่พบ token/owner ของ e-Court ในไฟล์ — ขณะนี้ add-in ใช้ได้กับเอกสารที่ส่งมาจาก e-Court เท่านั้น", - ); + if (!properties) { + setNoAccessNotice("อ่านข้อมูลเอกสารไม่สำเร็จ — ลองปิดแล้วเปิด task pane อีกครั้ง"); + setPhase("no-access"); + return; + } + const signIn = await authService.signInWithTicket({ + ticketData: properties.ticketData, + ticketHmac: properties.ticketHmac, + }); + if (!signIn.ok) { + setNoAccessNotice(signIn.message); setPhase("no-access"); return; } setUsername(authService.getUsername() ?? null); - properties ??= await metadataService.readTrackedProperties(); const tracked = properties.savedByAddIn === metadataService.ADDIN_SIGNATURE_GUID; const id = tracked ? properties.documentTrackingId : undefined; if (!id) { @@ -458,7 +464,7 @@ const TaskpaneApp: React.FC<{ demo?: "untracked" | "tracking" }> = ({ demo }) =>

เอกสารนี้ยังใช้งาน add-in ไม่ได้

- {noAccessNotice ?? "ไม่พบ token/owner ของ e-Court ในไฟล์ — ขณะนี้ add-in ใช้ได้กับเอกสารที่ส่งมาจาก e-Court เท่านั้น"} + {noAccessNotice ?? "ไม่พบ ticket ของ e-Court ในไฟล์ — ขณะนี้ add-in ใช้ได้กับเอกสารที่ส่งมาจาก e-Court เท่านั้น"}

) : null} diff --git a/docs/PLAN-ADDIN-AUTH.md b/docs/PLAN-ADDIN-AUTH.md new file mode 100644 index 0000000..12fda16 --- /dev/null +++ b/docs/PLAN-ADDIN-AUTH.md @@ -0,0 +1,215 @@ +# PLAN-ADDIN-AUTH — Add-in Ticket Authen (`ticket_data` + `HmacSha256`) + +> วันที่ตกลงแผน: 2026-10-27 · สถานะ: แผนรออนุมัติ · requirement ต้นทาง: `docs/requirement/addin-workflow.md` + +## 0. บริบท/เป้าหมาย + +เปลี่ยน auth ของ wordaddin จาก e-Court pass-through แบบเดิม (e-Court ขอ token เองแล้ว stamp +`token`/`owner` ลงไฟล์) เป็น **ticket** ตาม requirement: + +- e-Court stamp custom properties 2 ค่าในไฟล์ .docx: `ticket_data` (JSON) + `HmacSha256` +- addin ตรวจ HMAC เองฝั่ง client (gate เชิง UX — ตัดเคสไฟล์ถูกแก้ก่อนยิง network) +- **addin เป็นฝ่ายขอ access_token** ที่ `POST /api/documents/token` เก็บใน localStorage + ตาม fid (session ต่อ userId) — ไม่ใช่ e-Court อีกต่อไป +- ถอดระบบเก่า `token`/`owner` ออกทั้งหมด (implement ไว้ 2026-10-27 ยังไม่มีไฟล์จริงใช้ — + ไม่ต้องรองรับสองแบบพร้อมกัน) + +## 1. ข้อตกลงที่ตัดสินใจแล้ว (2026-10-27) + +| # | เรื่อง | ข้อตกลง | +|---|---|---| +| D1 | addin ยืนยันกับ `/token` อย่างไร | **ขยาย `POST /api/documents/token` รับ ticket เพิ่ม** — body `{ticket_data, HmacSha256}` server ตรวจ HMAC+exp ด้วย secret ใน env ฝั่ง server ก่อนออก token · ทางเข้า `x-api-key` เดิม (e-Court server-to-server) ใช้ต่อคู่กัน | +| D2 | โค้ดซ้ำสองฝั่ง | แชร์ส่วน pure ผ่าน **`utils/docver-ticket.ts`** (โซนกลาง client ใช้ได้ — ไม่แตะกติกา "wordaddin ห้าม import จาก `lib/`") · ฝั่ง crypto ฉีดเข้าทาง parameter: client = Web Crypto, server = `node:crypto` | +| D3 | client-side HMAC | ทำตาม requirement (sign เทียบเองก่อน) โดย secret มาจาก `NEXT_PUBLIC_ADDIN_TICKET_SECRET` — **ยอมรับว่าค่าใน bundle สกัดได้** จึงถือเป็นแค่ UX gate, ความมั่นคงจริงอยู่ที่ server verify | +| D4 | รูปแบบ HMAC | sign บน **string ดิบของ `ticket_data` ตามที่อ่านได้จาก property** (ห้าม trim/normalize) · digest เป็น **hex lowercase 64 ตัว** — การเทียบ normalize เป็น lowercase ก่อน (ฝั่ง e-Court stamp ตัวพิมพ์ใหญ่ก็ผ่าน) | +| D5 | `tid` | = `template` ของ claims (`tid` optional → `template: null`) · map `uid→userid`, `uname→username`, `fid` ตรง ๆ | +| D6 | token หมดอายุ (TTL ~1 ชม.) แต่ ticket ยังไม่หมด | เก็บ `exp` ของ token (decode จาก JWT) — ก่อนใช้ถ้าหมดอายุและ ticket ยังไม่หมด → **renew อัตโนมัติ** · โดน 401 กลาง session → renew จาก ticket 1 ครั้งแล้ว retry ก่อนไป no-access | +| D7 | localStorage | key เดียว `wordaddin-auth` เก็บ JSON `{v:1, userId, userName, tokens: {[fid]: {token, exp}}}` · เจอ user ใหม่ = **เคลียร์ทั้งก้อน** (รวม token ทุก fid ของ user เก่า) · webview block localStorage = fallback memory (pattern `versioningService`) | +| D8 | "disable Add-in ทั้งหมด" | = phase `no-access` ที่มีอยู่ โดยแยกข้อความตามสาเหตุ: ไม่พบ ticket / ไฟล์ถูกแก้ไข (HMAC) / ticket หมดอายุ | +| D9 | ช่วง mock (`NEXT_PUBLIC_ADDIN_API_BASE_URL` ว่าง) | addin ยิงขอ token จริงที่ same-origin `withBasePath("/api/documents/token")` (ไม่ผูกกับ apiBaseUrl ของ versioning) — versioning ยัง mock ต่อ | +| D10 | เครื่องมือทดสอบ | มีสคริปต์ stamp ticket ลงไฟล์ .docx ตัวอย่าง (`scripts/make-ticket-docx.mjs`) — ไม่งั้นทดสอบ flow นี้ใน Word จริงไม่ได้เลย | + +## 2. Contract ของ ticket (ต้องส่งให้ทีม e-Court ฝั่ง stamp) + +Custom properties ของไฟล์ .docx (ชื่อ key ต้องตรงเป๊ะ — case-sensitive): + +``` +ticket_data = {"exp":1793067433,"fid":"T12_FD155_3s-long_ZZZ","tid":"T12","uid":"oom","uname":"สรวิชญ์"} +HmacSha256 = = HMAC-SHA256(secret, ticket_data ดิบทั้ง string ไม่ trim) +``` + +| field | type | เงื่อนไข | +|---|---|---| +| `exp` | int (unix วินาที) | บังคับ · `exp <= now` = หมดอายุ | +| `fid` | string | บังคับ · trim 1–200 · เป็น `docId` ของทุก operation ของกลุ่ม docver | +| `uid` | string | บังคับ · trim 1–100 | +| `uname` | string | บังคับ · trim 1–200 | +| `tid` | string | optional · ≤100 · template ต้นทาง (ไม่มี = `template: null`) | + +- field อื่นปนมา = reject (allowlist ตาม convention ของ repo) +- secret ปัจจุบัน = `"super_secret"` — **e-Court + `ADDIN_TICKET_SECRET` (server) + + `NEXT_PUBLIC_ADDIN_TICKET_SECRET` (client) ต้องตรงกันทั้งสาม** +- ⚠️ **custom property ของ Word จำกัด value ~255 ตัวอักษร** — JSON ของ `ticket_data` + ต้องออกแบบสั้น (fid/tid/uname ยาวมากจะทะลุ; ฝั่ง addin เจอค่าเกิน = ถือว่าไฟล์เสียหาย → no-access) + +## 3. ฝั่ง server — `POST /api/documents/token` รับ ticket เพิ่ม + +Branch ตาม header (ตัวตั้ง = `x-api-key`): + +| เคส | พฤติกรรม | +|---|---| +| มี header `x-api-key` | **ทางเดิมทุกอย่าง** — `requireDocverApiKey` + `parseTokenRequestBody` (body allowlist `userid`/`username`/`fid`/`template`, ส่ง field ticket ปนมา = 400 ตาม allowlist เดิม) | +| ไม่มี `x-api-key` + body `{ticket_data: string, HmacSha256: string}` | **ทางใหม่** — `verifyAddinTicket` → map claims → `issueDocverToken` → response รูปเดียวกัน `{access_token, token_type, expires_in}` | +| ไม่มี `x-api-key` + body ไม่ใช่ object / มี field อื่นปน / field ไม่ใช่ string | 400 (รูปผิดก่อนถึงการ verify) | +| HMAC ผิด / parse ticket ไม่ผ่าน / `exp` หมดอายุ | 401 **ข้อความเดียวกันทุกสาเหตุ** (กัน oracle — addin รู้สาเหตุจริงจากการตรวจเองฝั่ง client อยู่แล้ว) | +| ไม่มี `x-api-key` และไม่มี ticket fields เลย | 401 | +| `DOCVER_TOKEN_SECRET` ไม่ตั้ง | 500 (เดิม) | + +- การเทียบ digest ฝั่ง server: hash ทั้งสองฝั่งด้วย SHA-256 แล้ว `timingSafeEqual` — pattern + `verifyDocverToken` ของ `lib/docver-token.ts` เป๊ะ +- middleware ยกเว้น `api/documents/*` อยู่แล้ว — ไม่ต้องแต้ + +## 4. โค้ดแชร์ — `utils/docver-ticket.ts` (pure, ไม่มี dependency เลย) + +```ts +export interface DocverTicket { exp: number; fid: string; tid: string | null; uid: string; uname: string } +export function parseDocverTicket(raw: string | unknown): { ok: true; ticket: DocverTicket } | { ok: false; error: string } +export function isTicketExpired(exp: number, nowSec?: number): boolean // exp <= now +export function normalizeHexDigest(value: string): string | null // trim+lowercase+ตรวจ ^[0-9a-f]{64}$ +export function timingSafeEqualHex(a: string, b: string): boolean // constant-time บน hex ที่ normalize แล้ว +export type HmacSha256Hex = (message: string, secret: string) => Promise // adapter ที่สองฝั่งฉีด +``` + +- `lib/` (server) กับ `wordaddin/` (client) ใช้ `parseDocverTicket`/`isTicketExpired` ตัวเดียวกัน — + schema drift กันได้จากฝั่งเดียว + +## 5. ฝั่ง addin — flow ตอนเปิดเอกสาร (map ขั้นตอน 1–7 ของ requirement) + +`TaskpaneApp.detect()` → `authService.signInWithTicket({ticketData, ticketHmac})`: + +``` +1. อ่าน properties ticket_data + HmacSha256 (readTrackedProperties) +2. ขาดข้อใดข้อหนึ่ง / ค่ายาวเกิน 255 → no-access "ไม่พบ ticket ของ e-Court ในไฟล์" + parseDocverTicket ไม่ผ่าน → no-access "ไฟล์ถูกแก้ไข" + HMAC (Web Crypto + NEXT_PUBLIC_ADDIN_TICKET_SECRET) ไม่ตรง → no-access "ไฟล์ถูกแก้ไข" +3. session = localStorage["wordaddin-auth"] (fallback memory) +4. ไม่มี session หรือ uid ≠ session.userId (user ใหม่): + · ticket หมดอายุ → no-access "ticket หมดอายุ" + · เคลียร์ session ทั้งก้อน → ขอ token (ทาง ticket ของ /token) → fail → no-access "ขอ token ไม่สำเร็จ" + · เก็บ session ใหม่ {userId: uid, userName: uname, tokens: {[fid]: {token, exp}}} +5. uid ตรงกัน (คนเดิม): + · tokens[fid] มี (กลับมาทำเอกสารเดิม) → ผ่านเลย ไม่ตรวจ ticket exp (ตามเอกสาร) + — token หมดอายุ + ticket ยังไม่หมด → renew เงียบ ๆ ก่อน (D6) + — token หมดอายุ + ticket หมดด้วย → เข้า UI ได้แต่ API จะ 401 → renew fail → no-access ตอนใช้งาน + · tokens[fid] ไม่มี (เอกสารใหม่) → ตรวจ exp แล้วขอ token เก็บเหมือนข้อ 4 +6. ตั้ง state ภายใน (fid/token/username/ticket ดิบไว้ renew) → ไหลต่อ phase + untracked/tracking เดิม (SavedByAddIn → history) ไม่เปลี่ยน +``` + +- `authService` surface ใหม่: `signInWithTicket` / `getAccessToken` (token ของ fid ปัจจุบัน) / + `renewTokenFromTicket` / `getUsername` — ถอด `signInFromDocument` + `decodeJwtField` สำหรับ uid + (ticket มี uid/uname ตรง ๆ แล้ว เหลือ decode เฉพาะ `exp` ของ JWT ตอนเก็บ token) +- `apiClient` ตอนโดน 401: `renewTokenFromTicket()` → สำเร็จ = retry request 1 ครั้ง → ยัง 401/ไม่มี + ticket = `AuthRequiredError` (UI ไป no-access — เดิมมีอยู่แล้ว) +- `?addindemo` ยังใช้ได้ (`signInForDemo` คงเดิม) + +## 6. ไฟล์ที่สร้าง/แต้/ถอด + +**สร้าง** +- `utils/docver-ticket.ts` — §4 +- `lib/addin-ticket.ts` — server-only: `addinTicketSecret()` (env `ADDIN_TICKET_SECRET` trim หรือ default + `"super_secret"`) + `verifyAddinTicket(ticketData, hmacHex, secret, nowSec?)` (HMAC `node:crypto` + + timing-safe + parse + exp) + `ticketToDocverClaims(ticket)` — ห้าม import `@/db`/minio (unit test รันได้เปล่า ๆ) +- `wordaddin/services/ticketService.ts` — client: `verifyTicketHmacWeb` (crypto.subtle), session store + (`loadSession`/`saveSession`/`clearSession` — try/catch + memory fallback), `requestDocverToken` + (POST `withBasePath("/api/documents/token")` body ticket) +- `scripts/make-ticket-docx.mjs` — §9 +- `tests/utils/docver-ticket.test.ts`, `tests/lib/addin-ticket.test.ts` (vitest — pattern `tests/lib/docver-token.test.ts`) + +**แต้** +- `app/api/documents/token/route.ts` — branch ตาม §3 +- `wordaddin/config.ts` — `ticketSecret: process.env.NEXT_PUBLIC_ADDIN_TICKET_SECRET ?? "super_secret"` +- `wordaddin/services/metadataService.ts` — ถอด `ECOURT_TOKEN_PROPERTY`/`ECOURT_OWNER_PROPERTY` → + `TICKET_DATA_PROPERTY = "ticket_data"` + `TICKET_HMAC_PROPERTY = "HmacSha256"` (`readTrackedProperties` + อ่านคู่นี้แทน `ecourtToken`/`ecourtOwner`) +- `wordaddin/services/authService.ts` — rewrite ตาม §5 +- `wordaddin/services/apiClient.ts` — renew-retry ตอน 401 +- `components/wordaddin/TaskpaneApp.tsx` — `detect()` เรียก `signInWithTicket` + ข้อความ no-access 3 แบบ +- `lib/openapi.ts` — POST /token มี 2 รูป body (oneOf: identity+x-api-key / ticket) +- `.env.example`, `docs/WORDADDIN-DEV.md`, `docs/addin-ui-flow.md`, `docs/PLAN-DOCVER.md` (§token ชี้มาที่นี่), `CLAUDE.md` + +**ถอด** +- ระบบ `token`/`owner` ทั้งเส้น (properties + `signInFromDocument` + `decodeJwtField` ส่วน uid) — + แทนด้วย ticket เต็มตัว + +## 7. Env + +| env | default | หมายเหตุ | +|---|---|---| +| `ADDIN_TICKET_SECRET` | `SECRET` ของกลุ่ม template → `"super_secret"` | server ตรวจ HMAC ทาง ticket ของ /token — อ่านตามลำดับ `ADDIN_TICKET_SECRET` → `SECRET` (deployment เดียวกับตัว stamp ของกลุ่ม template มักตั้งไว้แล้ว) → `"super_secret"` · **production ต้องตั้งค่าใหม่** (ค่า default รู้กันหมด = ปลอม ticket ได้) และต้องตรงกับฝั่ง e-Court · ห้าม log ค่าดิบ | +| `NEXT_PUBLIC_ADDIN_TICKET_SECRET` | `"super_secret"` | ฝั่ง addin (UX gate) — inline ลง bundle · ต้องตรงกับฝั่ง e-Court เช่นกัน | + +> หมายเหตุจากตอน implement: ตัว stamp ticket จริงคือกลุ่ม template +> (`app/api/template/render/route.ts` → `applyTemplate` — sort key ตามตัวอักษรหลังเติม +> `exp` แล้ว HMAC hex lowercase ผ่าน `lib/lib-utils.ts` + stamp ลงไฟล์ด้วย +> `setCustomDocProps` ของ `lib/lib-tmpl.ts` อ่าน env `SECRET` กับ `TICKET_SEC` default +> 300 วิ — ไฟล์ยังมี property `Ed25519` เพิ่มด้วยแต่ out of scope ของแผนนี้) + +(เลือก default แทน fail-closed ตาม requirement ที่ระบุค่าปัจจุบันไว้ — dev ใช้ได้ทันที +แต่ comment เตือน production ชัด ๆ ทั้ง `.env.example` และแผนนี้) + +## 8. Edge cases + +- **property ยาวเกิน 255** → ไฟล์เสียหาย → no-access (Word ตัดค่าทิ้งจะ parse/HMAC ไม่ผ่านอยู่แล้ว — + เช็คความยาวเพื่อข้อความที่ชัดกว่า) +- **localStorage ถูก block** (webview บางตัว/private mode) → session อยู่ใน memory ต่อ session เดียว + (เปิดใหม่เริ่มใหม่ ขอ token ใหม่ — ยอมรับได้) +- **uname ไทย** — เป็น string ตรง ๆ ไม่ผ่าน base64 ไม่มีปัญหา mojibake (ต่างจาก decode JWT payload) +- **exp == now** → หมดอายุ +- **401 จาก endpoint อื่นระหว่างใช้งาน** → renew 1 ครั้ง → retry → ยังไม่ผ่าน → no-access +- **Word เปิดหลายเอกสารพร้อมกัน (user เดียวกัน fid ต่างกัน)** — tokens map รองรับ; current fid เป็น + state ต่อ taskpane instance (memory) — แยกกันอยู่แล้ว +- **ไฟล์ทั่วไปไม่มี ticket** → no-access เหมือนเดิม (ข้อความเดิมปรับคำ) + +## 9. สคริปต์ทดสอบ — `scripts/make-ticket-docx.mjs` + +Dev tool รันมือ `node scripts/make-ticket-docx.mjs [options]`: + +- option: `--fid --uid --uname --tid --exp --secret ` และ + `--tracked` (stamp `SavedByAddIn`+`DocumentTrackingId` เพื่อทดสอบ phase tracking ด้วย) +- ทำงาน: สร้าง JSON ticket → HMAC-SHA256 hex → เขียน custom properties ลง `docProps/custom.xml` + ผ่าน **pizzip** (dependency อยู่แล้วจากกลุ่ม template) — สร้าง custom.xml เองถ้าไฟล์ยังไม่มี + (พร้อม override ใน `[Content_Types].xml`) · เขียนทับไฟล์เดิม**เฉพาะเมื่อส่ง `--in-place`** + ไม่งั้นเขียนไฟล์ใหม่ `.ticket.docx` (กฎ non-destructive ผูกกับ `public/` เท่านั้นแต่ถือหลักเดียวกัน) +- default secret/exp สอดคล้อง .env ตัวอย่าง + +## 10. Verify criteria + +1. `npx vitest run` ผ่านทั้งชุด (ไฟล์ใหม่ + เดิม — โดยเฉพาะ `tests/lib/docver-token.test.ts` ไม่พัง) +2. `npx tsc --noEmit` ผ่าน +3. curl regression ที่ /token: (ก) `x-api-key` + identity body ออก token ได้เหมือนเดิม + (ข) ticket body ออก token ได้ (ค) HMAC ผิดกับ exp หมด = 401 ข้อความเดียวกัน (ง) body ปนกัน = 400 +4. ทดสอบใน Word จริง (sideload ตาม `docs/WORDADDIN-DEV.md`) ด้วยไฟล์จาก make-ticket-docx: + ไฟล์ถูกต้อง → ใช้ได้ ชื่อผู้ใช้ = uname · แก้ `ticket_data` 1 ตัวอักษร → no-access "ไฟล์ถูกแก้ไข" · + exp อดีต → no-access "ticket หมดอายุ" · เปิดเอกสารเดิมซ้ำ → ไม่ยิง /token ซ้ำ (ใช้ token เดิม) · + ตั้ง `DOCVER_TOKEN_TTL_SECONDS` สั้น ๆ แล้วเปิดซ้ำหลังหมดอายุ → renew อัตโนมัติใช้ต่อได้ · + เปิดไฟล์ uid คนละคน → session เดิมถูกเคลียร์ +5. รัน `plan-reviewer` + `code-reviewer` ตามกติกา `.claude/` + +## 11. ลำดับงาน + +1. `utils/docver-ticket.ts` + `tests/utils/docver-ticket.test.ts` +2. `lib/addin-ticket.ts` + `tests/lib/addin-ticket.test.ts` +3. `app/api/documents/token/route.ts` + `lib/openapi.ts` → verify 10.3 ด้วย curl +4. `wordaddin/config.ts` + `metadataService.ts` (properties) +5. `ticketService.ts` + `authService.ts` (rewrite) +6. `TaskpaneApp.tsx` (detect + ข้อความ) + `apiClient.ts` (renew-retry) +7. `scripts/make-ticket-docx.mjs` +8. env/docs/CLAUDE.md sync → verify 10.1–10.5 + +## 12. Out of scope + +- ปลด mock `versioningService` (ยัง mock ต่อ — token/flow ที่ทำรอบนี้พร้อมใช้วันปลด) +- ความไม่ตรงกันของ `documentId` (GUID ที่ stamp) กับ `docId` (= fid) ของกลุ่ม docver — + เป็นเรื่องของวันปลด mock (token ผูก fid แต่ mock ยังเรียกด้วย GUID — ไม่กระทบ auth) +- UI จัดการ session/ออกจากระบบ, refresh token แบบมี revoke, ฝั่ง e-Court stamp (เขาทำ — เราส่ง §2 ให้) diff --git a/docs/PLAN-DOCVER.md b/docs/PLAN-DOCVER.md index fff1526..652097f 100644 --- a/docs/PLAN-DOCVER.md +++ b/docs/PLAN-DOCVER.md @@ -41,6 +41,14 @@ middleware เหมือน `api/template`) auth: header `x-api-key` เทียบ env `DOCVER_API_KEY` (ไม่ตั้ง/ว่าง = 401 fail closed · ไม่ใส่/ผิด = 401 ข้อความเดียวกันเป๊ะ กัน enumerate) — ตรวจก่อนอ่าน body เสมอ +**ทางเข้าที่สอง (2026-10-27, add-in ใน Word — PLAN-ADDIN-AUTH.md):** ไม่มี header +`x-api-key` + body `{ticket_data, HmacSha256}` (ชื่อ field เหมือน custom properties +ในไฟล์) — server ตรวจ HMAC-SHA256(secret, ticket_data ดิบ) + exp ผ่าน +`lib/addin-ticket.ts` (env `ADDIN_TICKET_SECRET` → `SECRET` → default `"super_secret"`) +แล้วออก token จาก claims ใน ticket (`uid→userid`, `uname→username`, `fid`, `tid→template`) +· response/error รูปเดียวกับทางแรกทุกอย่าง (HMAC ผิด/หมดอายุ/ไม่ส่งมา = 401 ข้อความ +เดียวกัน · body ผิดรูป = 400) + JSON body (allowlist — key อื่น = 400 กัน typo): | field | type | หมายเหตุ | diff --git a/docs/WORDADDIN-DEV.md b/docs/WORDADDIN-DEV.md index 55a3826..bb97631 100644 --- a/docs/WORDADDIN-DEV.md +++ b/docs/WORDADDIN-DEV.md @@ -4,7 +4,7 @@ Taskpane ของ Word add-in "e-Court Documents Versioning" พอร์ตม **ทางเข้า:** เปิด `/docedit` เปล่า ๆ จะ redirect มาที่ taskpane ทันที (ไม่ผ่าน login — คำขอผู้ใช้ 2026-10-14) ยกเว้นลิงก์ e-Court `/docedit/?fileUrl=&fileId=&typeId=` ที่ยังส่งต่อให้ `/note` ตามสัญญา N8 · manifest ชี้ `/docedit/wordaddin` ตรง ๆ (ไม่ว่าผ่าน root เพื่อลด hop ใน webview) -สถานะช่วงนี้: **ไม่มี login แล้ว** (ยกเลิก Keycloak 2026-10-27 — auth เข้าทางเดียวคือ e-Court pass-through จาก token/owner ในไฟล์ ดูหัว "ทดสอบ e-Court pass-through") ส่วน **API ยังเป็น mock** (version store เก็บ metadata ค้างข้าม session ใน localStorage ของ taskpane เพื่อทดสอบวงปิดเอกสาร→เปิดใหม่ — สลับกลับของจริงด้วยการลบ `wordaddin-mock-versions` หรือเคลียร์ storage ของ origin) รอ backend จริง — ดูหัว "การสลับกลับของจริง" ด้านล่าง +สถานะช่วงนี้: **ไม่มี login แล้ว** (ยกเลิก Keycloak 2026-10-27 — auth เข้าทางเดียวคือ **ticket ของ e-Court** (`ticket_data` + `HmacSha256` ในไฟล์): add-in ตรวจ HMAC เอง → ขอ token ที่ `/api/documents/token` → เก็บใน localStorage ตาม fid ดูหัว "ทดสอบ e-Court ticket" และแผน `docs/PLAN-ADDIN-AUTH.md`) ส่วน **API ยังเป็น mock** (version store เก็บ metadata ค้างข้าม session ใน localStorage ของ taskpane เพื่อทดสอบวงปิดเอกสาร→เปิดใหม่ — สลับกลับของจริงด้วยการลบ `wordaddin-mock-versions` หรือเคลียร์ storage ของ origin) รอ backend จริง — ดูหัว "การสลับกลับของจริง" ด้านล่าง ## ไฟล์หลัก @@ -13,7 +13,7 @@ Taskpane ของ Word add-in "e-Court Documents Versioning" พอร์ตม | `app/wordaddin/page.tsx` + `components/wordaddin/WordAddinGate.tsx` | gate โหลด Office.js (self-host) → `Office.onReady` → mount `TaskpaneApp` | | `app/wordaddin/manifest.xml/route.ts` | serve manifest พร้อม **rewrite origin ตามโฮสต์ที่ขอ** (อ่าน `x-forwarded-proto`/`x-forwarded-host` ก่อนแล้ว fallback host ตรง ผ่าน `utils/request-origin.ts` ที่แชร์กับ origin ของ deep link N10 — โฮสต์ที่ไม่ใช่ localhost บังคับ https เพราะ Office รับ https เท่านั้น) | | `components/wordaddin/TaskpaneApp.tsx` | state machine `checking/no-access/untracked/tracking` + ทุก handler | -| `wordaddin/services/*` | ชั้น Office.js พอร์ตดิบจากต้นฉบับ + `versioningService` เป็น mock (`authService` = pass-through จริง ไม่มี login) | +| `wordaddin/services/*` | ชั้น Office.js พอร์ตดิบจากต้นฉบับ + `versioningService` เป็น mock (`authService` + `ticketService` = ticket auth จริง ไม่มี login — PLAN-ADDIN-AUTH.md) | | `public/wordaddin/manifest.template.xml` | template ของ manifest (ค่า default dev `https://localhost:3000`) — serve ผ่าน route ด้านบนเท่านั้น ไม่ได้ใช้ตรง ๆ | | `public/wordaddin/office-js/` | Office.js runtime — สร้างโดย postinstall `scripts/fetch-office-js.mjs` ดาวน์โหลดฉบับล่าสุดจาก CDN ของ Microsoft (**gitignore** — รัน `npm install` ทุกครั้งหลัง clone เครื่องต้องออกอินเทอร์เน็ตตอนติดตั้ง ส่วนตัวระบบ deploy ใช้ intranet ล้วน) | | `wordaddin/config.ts` | env `NEXT_PUBLIC_ADDIN_*` (ตารางด้านล่าง) | @@ -22,8 +22,9 @@ Taskpane ของ Word add-in "e-Court Documents Versioning" พอร์ตม | ตัวแปร | default | ใช้เมื่อไหร่ | |---|---|---| -| `NEXT_PUBLIC_ADDIN_API_BASE_URL` | `""` (ว่าง) | ปลด mock ต่อ backend จริง (ต้องเป็น HTTPS) | -| `NEXT_PUBLIC_ADDIN_MOCK_USERNAME` | `"tester01"` | ชื่อ fallback — ใช้ตอนไฟล์มี token แต่ไม่มี owner/uid และโหมด `?addindemo` (แสดง header + เป็น `createdByUserId` ของ mock) | +| `NEXT_PUBLIC_ADDIN_API_BASE_URL` | `""` (ว่าง) | ปลด mock ต่อ backend จริง (ต้องเป็น HTTPS) — ไม่เกี่ยวกับการขอ token (ยิง same-origin `/docedit/api/documents/token` เสมอ) | +| `NEXT_PUBLIC_ADDIN_MOCK_USERNAME` | `"tester01"` | ชื่อ fallback ของโหมด `?addindemo` (แสดง header + เป็น `createdByUserId` ของ mock) | +| `NEXT_PUBLIC_ADDIN_TICKET_SECRET` | `"super_secret"` | secret ตรวจ HMAC ของ ticket ฝั่ง client (UX gate) — ต้องตรงกับตัว stamp ของ e-Court และ `ADDIN_TICKET_SECRET`/`SECRET` ฝั่ง server (PLAN-ADDIN-AUTH.md §7) · **production ต้องตั้งค่าจริงทั้งสองฝั่ง** | ## รัน dev ด้วย HTTPS (จำเป็นสำหรับ sideload ใน Word) @@ -56,15 +57,28 @@ Deploy จริง (intranet): **ไม่ต้อง rewrite อะไรแ - **`/docedit/wordaddin?addindemo=untracked`** / **`?addindemo=tracking`** — dev-only: ข้าม Office.js + ตั้ง session mock ทันที + ตั้ง phase ตามค่า (tracking มี history ตัวอย่าง 3 รายการ) — ทุกปุ่มกดได้ แต่ flow ที่แตะ Word จริง (Start Versioning / Save & Version / บันทึก) จะจบที่ error ตาม expected - หมายเหตุ: ใน browser ปกติ (ไม่ใช่ demo) banner "บันทึกอัตโนมัติไม่สำเร็จ" จะโผล่หลัง ~30 วิ เพราะ auto-save (FR-17) พยายาม save ทุก 30 วิแต่ไม่มี Word — ถือว่าถูกต้อง -## ทดสอบ e-Court pass-through (auth ทางเข้าเดียว) +## ทดสอบ e-Court ticket (auth ทางเข้าเดียว) -ไฟล์ที่ e-Court stamp custom properties `token` และ/หรือ `owner` ไว้ จะใช้ได้เลย — ไม่มี login ให้กดตั้งแต่ยกเลิก Keycloak (2026-10-27): `detect()` อ่านค่าทั้งสองตอนเปิด task pane แล้วเรียก `authService.signInFromDocument()` (username ได้จาก `owner` → ถอด `uid` ใน JWT payload → `NEXT_PUBLIC_ADDIN_MOCK_USERNAME`, token จากไฟล์เก็บใน memory ให้ `getAccessToken()` ใช้ต่อ) · ไฟล์ที่ไม่มีทั้งคู่ = phase `no-access` (ขั้นตอนสำหรับเอกสารทั่วไปจะมีในอนาคต) +ไฟล์ที่ e-Court stamp custom properties **`ticket_data`** (JSON `exp`/`fid`/`tid`/`uid`/`uname`) + **`HmacSha256`** (HMAC-SHA256 ของ string ดิบ ด้วย secret ปัจจุบัน `super_secret`) ไว้ จะใช้ได้เลย — ไม่มี login ให้กด: `detect()` อ่านคู่นี้ตอนเปิด task pane แล้วเรียก `authService.signInWithTicket()` ตาม flow ของ `docs/requirement/addin-workflow.md` (ตรวจ HMAC ฝั่ง client → ตรวจ exp → ดู session ใน localStorage `wordaddin-auth` ตาม uid/fid → ขอ access_token ที่ `/docedit/api/documents/token` แบบ body ticket → เก็บ token ตาม fid) · ไฟล์ที่ไม่มีคู่นี้ / HMAC ไม่ตรง / ticket หมดอายุ = phase `no-access` แยกข้อความตามสาเหตุ -ทดสอบเองใน Word โดยไม่ต้องรอ e-Court: เปิดไฟล์ → **File → Info → Properties → Advanced Properties → แท็บ Custom** → เพิ่ม property ชื่อ `token` (ค่า JWT ตัวอย่าง `eyJ...` — payload มี `uid` ก็แสดงเป็นชื่อผู้ใช้ได้) และ/หรือ `owner` (เช่น `u1001`) → เปิด task pane ใหม่ → ต้องเข้าเฟส untracked/tracking ทันทีไม่ผ่านหน้า no-access · ไฟล์ที่ไม่ใส่อะไรเลย = หน้า no-access +ทดสอบเองใน Word โดยไม่ต้องรอ e-Court — สร้างไฟล์ทดสอบด้วยสคริปต์ (คำนวณ HMAC ตรงกับตัว stamp จริงของกลุ่ม template): + +```bash +# สร้าง .ticket.docx (ไม่แตะไฟล์เดิม) exp +5 นาที เท่า TICKET_SEC ของตัว stamp +node scripts/make-ticket-docx.mjs path/to/file.docx --fid DEMO_FID_001 --uid oom --uname "สรวิชญ์" --tid T12 +# ดู custom properties ที่มีในไฟล์ +node scripts/make-ticket-docx.mjs path/to/file.ticket.docx --list +# ทดสอบ phase tracking ด้วย (stamp SavedByAddIn + DocumentTrackingId เพิ่ม) +node scripts/make-ticket-docx.mjs path/to/file.docx --tracked --in-place +``` + +แล้วเปิดไฟล์ใน Word ที่ sideload แล้ว → ต้องเข้าเฟส untracked (หรือ tracking เมื่อ `--tracked`) ทันที ชื่อผู้ใช้ = `uname` · ทดสอบ fail-case: แก้ `ticket_data` ผ่าน **File → Info → Properties → Advanced Properties → Custom** สัก 1 ตัวอักษร → no-access "ไฟล์ถูกแก้ไข" · สร้างด้วย `--exp +1` รอ 1 นาทีแล้วเปิด → no-access "ticket หมดอายุ" · เปิดเอกสารเดิมซ้ำ (จน ticket หมดอายุแต่ token ยังอยู่ใน localStorage) → ยังใช้ได้ตาม requirement ขั้น 7 + +หมายเหตุ: การขอ token ยิง same-origin `/docedit/api/documents/token` จริง (ไม่ผูก `NEXT_PUBLIC_ADDIN_API_BASE_URL`) — dev server ต้องรันอยู่และ secret ฝั่ง server (`ADDIN_TICKET_SECRET` → `SECRET` → default `super_secret`) ตรงกับ `--secret` ของสคริปต์ ## การสลับกลับของจริง (วันหน้า) -- **Auth:** ไม่มี login ให้สลับแล้ว — **ยกเลิก Keycloak ถาวร 2026-10-27** (ต้นฉบับ OIDC/PKCE + Office Dialog API ของ poc เป็น historical record ที่ `D:\word-addin-poc\addin` เท่านั้น) · auth ของ add-in คือ e-Court pass-through ผ่าน `signInFromDocument` (token/owner ในไฟล์) — งานต่อยอดคือรูปแบบ/วิธี verify ของ credential ที่ e-Court stamp (ร่าง: `docs/requirement/addin-workflow.md`) · โดน 401 กลาง flow = `AuthRequiredError` → หน้า `no-access` (token จากไฟล์ต่ออายุไม่ได้ — ต้องได้ไฟล์ใหม่จาก e-Court) +- **Auth:** ไม่มี login ให้สลับแล้ว — **ยกเลิก Keycloak ถาวร 2026-10-27** · auth ของ add-in คือ ticket ของ e-Court ผ่าน `signInWithTicket` (ticket_data + HmacSha256 ในไฟล์ — แผนเต็ม `docs/PLAN-ADDIN-AUTH.md`): add-in ตรวจ HMAC ฝั่ง client (UX gate) แล้วขอ access_token ที่ `/api/documents/token` ซึ่ง server ตรวจ HMAC+exp อีกชั้นด้วย secret ฝั่ง server · โดน 401 กลาง flow = ลอง renew จาก ticket 1 ครั้ง ยังไม่ผ่าน = `AuthRequiredError` → หน้า `no-access` (ticket ต่ออายุไม่ได้ — ต้องได้ไฟล์ใหม่จาก e-Court) - **API:** `wordaddin/services/versioningService.ts` เก็บชื่อ/ลายเซ็นเหมือนต้นฉบับเป๊ะ — ก้อบ body เดิม (multipart POST ผ่าน `apiClient` ซึ่งพอร์ตไว้แล้ว) กลับมา + ตั้ง `NEXT_PUBLIC_ADDIN_API_BASE_URL` (contract ปัจจุบันของกลุ่ม docver สรุปไว้ใน docblock ของไฟล์นั้น + docs/PLAN-DOCVER.md — identity/docId แกะจาก Bearer token ทั้งหมด) - ชุด unit test ของ services (jest → vitest) ยังไม่พอร์ต — ต้นฉบับอยู่ที่ `D:\word-addin-poc\addin\src\services\*.test.ts` + mock ที่ `src/test/` diff --git a/docs/addin-ui-flow.md b/docs/addin-ui-flow.md index 6f5f7bc..d47ba2a 100644 --- a/docs/addin-ui-flow.md +++ b/docs/addin-ui-flow.md @@ -12,19 +12,19 @@ Task pane ทั้งหมดเป็น state machine เดียว **ไ ตั้งแต่ 2026-10-19 taskpane มี **2 แท็บ** (toggle กลุ่มปุ่ม `aria-pressed` ใต้ header — แสดงเฉพาะ phase `untracked`/`tracking`): **Versioning** คือทุกอย่างในเอกสารนี้ตามเดิม ส่วน **เครื่องมือ Template** (ดูหัวข้อ 10) คือเครื่องมือจัดการ Content Controls ฝั่ง Office.js ล้วน ไม่ยิง backend จึงใช้ได้ทั้งสอง phase ปุ่ม toggle **ทั้งสองปิดระหว่าง flow ที่เขียนเอกสาร** — `saving || starting` (versioning), `saveBusy` (บันทึก local/ปิดเอกสาร), `templateBusy` (operation ของแท็บ template รายงานผ่าน `onBusyChange` — กันสลับกลาง operation แล้ว flow versioning มา stamp/capture ซ้อน หรือกลับกัน; code review 2026-10-20) · สถานะแท็บรีเซ็ตกลับ `versioning` ตอนเข้า `no-access` กัน surprise · banner auto-save error (FR-17) ยกไว้เหนือ toggle ให้เห็นทั้งสองแท็บ -**ไม่มี login ตั้งแต่ 2026-10-27** (ยกเลิก Keycloak ตามคำขอผู้ใช้) — auth ทางเข้าเดียวคือ e-Court pass-through: `detect()` อ่าน custom properties `token`/`owner` ที่ e-Court stamp มากับไฟล์ แล้วเรียก `authService.signInFromDocument()` ผ่านเข้าเลย +**ไม่มี login ตั้งแต่ 2026-10-27** (ยกเลิก Keycloak ตามคำขอผู้ใช้) — auth ทางเข้าเดียวคือ **ticket ของ e-Court**: `detect()` อ่าน custom properties `ticket_data` + `HmacSha256` ที่ e-Court stamp มากับไฟล์ แล้วเรียก `authService.signInWithTicket()` — ตรวจ HMAC ฝั่ง client → ตรวจ exp → ดู session ใน localStorage ตาม uid/fid → ขอ access_token ที่ `/api/documents/token` เก็บตาม fid (flow เต็ม: `docs/requirement/addin-workflow.md` + แผน `docs/PLAN-ADDIN-AUTH.md`) ``` -checking ──(ไม่มี token/owner ในไฟล์)──→ no-access +checking ──(ไม่มี ticket / HMAC ไม่ตรง / ticket หมดอายุ / ขอ token ไม่สำเร็จ)──→ no-access │ - ├─(มี token + ไม่ใช่ tracked doc)──→ untracked - └─(มี token + tracked doc)────────→ tracking + ├─(ticket ผ่าน + ไม่ใช่ tracked doc)──→ untracked + └─(ticket ผ่าน + tracked doc)────────→ tracking ``` การเปลี่ยน phase อื่นๆ: - `untracked` → (Start Versioning สำเร็จ) → `tracking` -- ทุก phase → (ไม่มี token ตอน detect ใหม่ / 401 = token หมดอายุไม่มีทางต่ออายุ) → `no-access` +- ทุก phase → (ไม่มี ticket ตอน detect ใหม่ / 401 หลังพยายาม renew จาก ticket แล้ว = ticket หมดอายุไม่มีทางต่ออายุ) → `no-access` - ปุ่ม "ลองใหม่" มีจริงแค่ 2 จุด: `bootError` ใน `checking` และ history error ใน `VersionHistoryPanel` — `untracked`/`tracking` **ไม่มี**ปุ่มลองใหม่แยกต่างหาก (เริ่ม flow ใหม่ด้วยการกดปุ่ม flow ซ้ำ) State ที่ UI ถือนอกเหนือจาก phase: @@ -32,11 +32,11 @@ State ที่ UI ถือนอกเหนือจาก phase: | State | ความหมาย | |---|---| | `activeTab` | แท็บที่เปิดอยู่ `versioning` (default) / `template` — ดูหัวข้อ 10 | -| `username` | ชื่อเจ้าของไฟล์จาก pass-through (owner → uid ใน token → fallback) — แสดงเป็น chip เฉย ๆ ใน header | +| `username` | ชื่อจาก `uname` ของ ticket — แสดงเป็น chip เฉย ๆ ใน header | | `documentId` | tracking id ของเอกสาร (มีเฉพาะ `tracking`) | | `documentTemplateId` | template ต้นทางของ**ไฟล์ร่าง** (มีเฉพาะ `untracked` ที่เป็นไฟล์ร่าง — FR-16) | | `versions` + `historyLoading` + `historyError` | version history ของ `documentId` | -| `noAccessNotice` | ข้อความบนหน้า `no-access` — ต่างกันตามสาเหตุ (ไม่มี token/owner ในไฟล์ หรือ token หมดอายุจาก 401) | +| `noAccessNotice` | ข้อความบนหน้า `no-access` — ต่างกันตามสาเหตุ (ไม่มี ticket ในไฟล์ / HMAC ไม่ตรง = ไฟล์ถูกแก้ / ticket หมดอายุ / ขอ token ไม่สำเร็จ) | | `starting`, `saving`, `progressMessage` | busy + ข้อความ progress ของ flow Start Versioning / Save & Version | | `confirmOpen`, `startConfirmOpen` | เปิด dialog ยืนยันของ flow ไหน | | `flowError`, `flowWarning`, `flowSuccess` | MessageBar 3 แบบของ flow หลัก (แสดงเฉพาะ `tracking`) | @@ -54,7 +54,7 @@ State ที่ UI ถือนอกเหนือจาก phase: ### Header (ทุก phase) - ชื่อแอป: **e-Court Documents Versioning** (semibold) -- ฝั่งขวา (เมื่อผ่าน pass-through แล้วและไม่ใช่ `no-access`/`checking`): chip เล็กพื้น `canvas` แสดง `username` เฉย ๆ — ไม่ใช่ปุ่ม (ไม่มี login จึงไม่มีออกจากระบบ) · จอแคบตัดด้วย truncate ชื่อเต็มอยู่ใน `title` +- ฝั่งขวา (เมื่อผ่าน ticket auth แล้วและไม่ใช่ `no-access`/`checking`): chip เล็กพื้น `canvas` แสดง `username` เฉย ๆ — ไม่ใช่ปุ่ม (ไม่มี login จึงไม่มีออกจากระบบ) · จอแคบตัดด้วย truncate ชื่อเต็มอยู่ใน `title` ### `checking` @@ -65,8 +65,10 @@ State ที่ UI ถือนอกเหนือจาก phase: - ข้อความ: "เอกสารนี้ยังใช้งาน add-in ไม่ได้" - คำอธิบายจางๆ (`noAccessNotice` ตามสาเหตุ): - - ตอน detect ไม่เจอข้อมูลในไฟล์: "ไม่พบ token/owner ของ e-Court ในไฟล์ — ขณะนี้ add-in ใช้ได้กับเอกสารที่ส่งมาจาก e-Court เท่านั้น" - - ตอนโดน 401 กลาง flow: "token ของ e-Court หมดอายุหรือไม่ถูกต้อง — โปรดขอไฟล์ใหม่จาก e-Court แล้วเปิดอีกครั้ง" + - ไม่มี ticket ในไฟล์: "ไม่พบ ticket ของ e-Court ในไฟล์ — add-in ใช้ได้กับเอกสารที่ส่งมาจาก e-Court เท่านั้น" + - ticket รูปผิด / HMAC ไม่ตรง: "ตรวจสอบ ticket ไม่ผ่าน — ไฟล์ถูกแก้ไขหรือเสียหาย" + - ticket หมดอายุ (ตอนเปิด หรือโดน 401 กลาง flow หลังพยายาม renew): "ticket ของ e-Court หมดอายุ — โปรดขอไฟล์ใหม่จาก e-Court แล้วเปิดอีกครั้ง" + - ขอ token ไม่สำเร็จ (server ลง/network): "ขอ token จากระบบไม่สำเร็จ — ตรวจการเชื่อมต่อแล้วเปิดเอกสารอีกครั้ง" - **ไม่มีปุ่มใด ๆ** — ไม่มี login ให้กด (ยกเลิก Keycloak 2026-10-27; ขั้นตอนสำหรับเอกสารทั่วไปจะมีในอนาคต) ผู้ใช้ได้ไฟล์ใหม่จาก e-Court แล้วเปิดใหม่เท่านั้น ### `untracked` @@ -164,8 +166,8 @@ Dialog เดียวใช้ 2 flow (ปรับข้อความด้ ### Detect (FR-1) — ตอนเปิด task pane / กดลองใหม่ -1. อ่าน custom properties ครั้งเดียวทั้งชุด: `token`, `owner`, `SavedByAddIn`, `DocumentTrackingId`, `LastVersionId`, `LastVersionTimestamp`, `TemplateId` -2. e-Court pass-through: ไม่มี `token`/`owner` เลย (หรืออ่านไฟล์ไม่ได้) → `no-access` · มีอย่างน้อยหนึ่งตัว → `signInFromDocument` (username จาก owner → uid ใน token) แล้วไปต่อ +1. อ่าน custom properties ครั้งเดียวทั้งชุด: `ticket_data`, `HmacSha256`, `SavedByAddIn`, `DocumentTrackingId`, `LastVersionId`, `LastVersionTimestamp`, `TemplateId` +2. ticket auth (`signInWithTicket` — PLAN-ADDIN-AUTH.md §5): อ่านไฟล์ไม่ได้ / ไม่มี ticket / HMAC ไม่ตรง / ticket หมดอายุ (ตามเงื่อนไข uid/fid ของ session) / ขอ token ไม่สำเร็จ → `no-access` แยกข้อความตามสาเหตุ · ผ่าน = มี access_token ของ fid นี้เก็บใน localStorage `wordaddin-auth` แล้วไปต่อ 3. `tracked = (SavedByAddIn === GUID ของ add-in)`: - tracked **และมี** `DocumentTrackingId` → `tracking` + โหลด history - ไม่ track หรือ track แต่ id หาย → `untracked` (เก็บ `TemplateId` ไว้ใช้ตอน Start Versioning — ไฟล์ร่างอยู่กลุ่มนี้) @@ -251,17 +253,17 @@ interface VersionRecord { response: `{ url: string, expiresAt: string }` — UI เปิด `url` ในเบราว์เซอร์เครื่องผู้ใช้เท่านั้น -### Auth — e-Court pass-through (ไม่มี login) +### Auth — e-Court ticket (ไม่มี login) -ยกเลิก Keycloak OIDC/PKCE แล้ว (2026-10-27) — ไม่มี Office Dialog ไม่มีหน้า sign-in: e-Court stamp custom properties `token`/`owner` ลงไฟล์ตอนสร้าง แล้ว `signInFromDocument()` ใช้ได้ทันที · token เก็บ **ใน memory เท่านั้น** (ห้าม localStorage) และ**ต่ออายุไม่ได้** — โดน 401 = `AuthRequiredError` → หน้า `no-access` ให้ขอไฟล์ใหม่จาก e-Court · token นี้คือสิ่งที่ `apiClient` แนบเป็น `Authorization: Bearer` ทุก request ตอนต่อ backend จริง (กลุ่ม docver บังคับ Bearer — docs/PLAN-DOCVER.md) · **รูปแบบ/วิธี verify ของ credential ที่ e-Court stamp ลงไฟล์ยังเป็นงานออกแบบต่อ** (ร่าง requirement: `docs/requirement/addin-workflow.md`) +ยกเลิก Keycloak OIDC/PKCE แล้ว (2026-10-27) — ไม่มี Office Dialog ไม่มีหน้า sign-in: e-Court stamp custom properties **`ticket_data`** (JSON `exp`/`fid`/`tid`/`uid`/`uname`) + **`HmacSha256`** (HMAC-SHA256 ของ string ดิบ ด้วย secret ที่ config ได้ — ปัจจุบัน `super_secret`) ลงไฟล์ตอนสร้าง แล้ว `signInWithTicket()` ใช้ได้ทันที · flow ตาม `docs/requirement/addin-workflow.md` (แผนเต็ม `docs/PLAN-ADDIN-AUTH.md`): ตรวจ HMAC ฝั่ง client (UX gate) → ตรวจ exp → session ใน localStorage `wordaddin-auth` ตาม uid (user ใหม่ = เคลียร์ session เก่าทั้งก้อน) → **ขอ access_token ที่ `POST /api/documents/token`** แบบ body `{ticket_data, HmacSha256}` (server ตรวจ HMAC+exp อีกชั้นด้วย secret ฝั่ง server) → เก็บ token ตาม fid ("คีย์คือ fid") — กลับมาเปิดเอกสารเดิม = ใช้ token เดิมไม่ต้องขอใหม่ · token หมดอายุกลางใช้งาน (โดน 401) = ลองขอใหม่จาก ticket 1 ครั้ง (ticket ต้องยังไม่หมดอายุ) ยังไม่ผ่าน = `AuthRequiredError` → หน้า `no-access` ให้ขอไฟล์ใหม่จาก e-Court · token นี้คือสิ่งที่ `apiClient` แนบเป็น `Authorization: Bearer` ทุก request ตอนต่อ backend จริง (กลุ่ม docver บังคับ Bearer — docs/PLAN-DOCVER.md) -> **หมายเหตุสถานะปัจจุบัน:** UI นี้ถูก implement แล้วใน repo นี้ที่ route `/docedit/wordaddin` (ดู `components/wordaddin/` + `wordaddin/`) — auth เป็น e-Court pass-through จริง (`wordaddin/services/authService.ts` — ไม่มี mock ให้ login แล้ว) ส่วน API ทั้งสามข้างบนยังเป็น in-memory mock (`wordaddin/services/versioningService.ts`) · วิธีสลับกลับของจริง: `docs/WORDADDIN-DEV.md` +> **หมายเหตุสถานะปัจจุบัน:** UI นี้ถูก implement แล้วใน repo นี้ที่ route `/docedit/wordaddin` (ดู `components/wordaddin/` + `wordaddin/`) — auth เป็น ticket auth จริงครบ flow (`wordaddin/services/authService.ts` + `ticketService.ts` + ทางเข้า ticket ของ `/api/documents/token`) ส่วน API ทั้งสามข้างบนยังเป็น in-memory mock (`wordaddin/services/versioningService.ts`) · วิธีสลับกลับของจริง: `docs/WORDADDIN-DEV.md` · สร้างไฟล์ทดสอบพร้อม ticket: `node scripts/make-ticket-docx.mjs` --- ## 8. กติกา error ที่ UI ต้องรองรับ -1. **`AuthRequiredError` (ไม่มี token / 401)** — ทุก flow ต้อง catch แล้ว: กลับ `no-access` + เคลียร์ `documentId`/`documentTemplateId`/`versions` + ตั้ง `noAccessNotice` = "token ของ e-Court หมดอายุหรือไม่ถูกต้อง — โปรดขอไฟล์ใหม่จาก e-Court แล้วเปิดอีกครั้ง" — **ไม่ throw ต่อ ไม่โชว์ error ทับ phase** +1. **`AuthRequiredError` (ไม่มี token / 401 หลัง renew-retry)** — ทุก flow ต้อง catch แล้ว: กลับ `no-access` + เคลียร์ `documentId`/`documentTemplateId`/`versions` + ตั้ง `noAccessNotice` = "ticket ของ e-Court หมดอายุหรือไม่ถูกต้อง — โปรดขอไฟล์ใหม่จาก e-Court แล้วเปิดอีกครั้ง" — **ไม่ throw ต่อ ไม่โชว์ error ทับ phase** 2. error อื่น → MessageBar ณ จุดที่เกิด (login/history/flow/download แยกกัน) — ห้ามปล่อย unhandled rejection 3. แยก error ของ "ตัว version" ออกจาก "ปิดเอกสารไม่สำเร็จ" — อันหลังเป็น warning เพราะข้อมูลปลอดภัยแล้ว 4. PDF เป็น best-effort เสมอ — ล้มแค่เตือน ไม่ fail flow diff --git a/lib/addin-ticket.ts b/lib/addin-ticket.ts new file mode 100644 index 0000000..766db45 --- /dev/null +++ b/lib/addin-ticket.ts @@ -0,0 +1,124 @@ +// PLAN-ADDIN-AUTH.md §3 — ตรวจ ticket ของ add-in ฝั่ง server (ทางเข้าที่สองของ +// POST /api/documents/token) — secret อยู่ฝั่ง server อย่างเดียว เพราะทางเข้านี้ +// ผู้เรียกคือ add-in ใน webview ของ Word ซึ่งแนบ x-api-key (server-to-server +// ของ e-Court) มาไม่ได้ — ตัวพิสูจน์ตัวตนคือ ticket_data + HmacSha256 ที่ e-Court +// stamp มากับไฟล์ (คำนวณ HMAC ตรงกับ lib/lib-utils.ts generateHmacSha256 +// ของกลุ่ม template: hex lowercase บน string ดิบ) +// +// ห้าม import อะไรที่ลาก @/db หรือ minio มาด้วย — unit test (tests/lib/) ต้องรัน +// ได้โดยไม่ต้องตั้ง env อะไรเลย (pattern เดียวกับ lib/docver-token.ts) +import { createHash, createHmac, timingSafeEqual } from "node:crypto"; + +import { + isTicketExpired, + normalizeHexDigest, + parseDocverTicket, + type DocverTicket, +} from "@/utils/docver-ticket"; +import type { DocverTokenClaims } from "@/lib/docver-token"; + +/** + * secret ของ ticket — อ่านตอนเรียก ไม่ใช่ตอน module load + * ลำดับ: ADDIN_TICKET_SECRET (ของกลุ่มนี้) → SECRET (env เดียวกับตัว stamp + * ของกลุ่ม template — deployment เดียวกันมักตั้งค่านี้อยู่แล้ว) → "super_secret" + * (ค่าตาม requirement — dev ใช้ได้ทันที แต่ production ต้องตั้งค่าจริงเพราะ + * ค่าเดิมรู้กันหมด = ใครก็ปลอม ticket ได้) · ห้าม log ค่าดิบ + */ +export function addinTicketSecret(): string { + return ( + process.env.ADDIN_TICKET_SECRET?.trim() || + process.env.SECRET?.trim() || + "super_secret" + ); +} + +/** + * ตรวจ ticket เต็มชุด (HMAC → parse → exp) — ไม่แยกสาเหตุออกมา + * (route ตอบ 401 ข้อความเดียวกันทุกกรณี กันใช้ endpoint เป็น oracle — + * add-in รู้สาเหตุจริงจากการตรวจเองฝั่ง client อยู่แล้ว) + */ +export function verifyAddinTicket( + ticketData: string, + hmacHex: string, + secret: string, + nowSec: number = Math.floor(Date.now() / 1000), +): { ok: true; ticket: DocverTicket } | { ok: false } { + const digest = normalizeHexDigest(hmacHex); + if (digest === null) return { ok: false }; + + // เทียบแบบ timing-safe ตาม pattern verifyDocverToken — hash ทั้งสองฝั่ง + // ด้วย SHA-256 ก่อนเพื่อให้ digest ยาวเท่ากันเสมอ (timingSafeEqual ไม่ throw) + const expected = createHmac("sha256", secret).update(ticketData, "utf8").digest(); + const expectedHash = createHash("sha256").update(expected).digest(); + const providedHash = createHash("sha256").update(Buffer.from(digest, "hex")).digest(); + if (!timingSafeEqual(providedHash, expectedHash)) return { ok: false }; + + const parsed = parseDocverTicket(ticketData); + if (!parsed.ok) return { ok: false }; + if (isTicketExpired(parsed.ticket.exp, nowSec)) return { ok: false }; + return { ok: true, ticket: parsed.ticket }; +} + +/** แปลง ticket → claims ของ access_token (uid→userid, uname→username, tid→template) */ +export function ticketToDocverClaims(ticket: DocverTicket): DocverTokenClaims { + return { userid: ticket.uid, username: ticket.uname, fid: ticket.fid, template: ticket.tid }; +} + +/** body ของทางเข้า ticket — allowlist 2 field เป๊ะ (ชื่อเดียวกับ custom properties) */ +const TICKET_BODY_FIELDS = ["ticket_data", "HmacSha256"] as const; + +export type TicketTokenBodyResult = + | { ok: true; ticketData: string; hmacHex: string } + | { ok: false; kind: "missing" | "invalid"; error: string }; + +/** + * parse body ของ POST /api/documents/token ทางเข้า ticket + * - `missing` = ไม่มี ticket มาเลย (route ตอบ 401 — คำเดียวกับตอน verify ไม่ผ่าน) + * - `invalid` = รูปผิด (route ตอบ 400 ตาม convention กัน typo เงียบ ๆ) + */ +export function parseTicketTokenBody(raw: unknown): TicketTokenBodyResult { + if (raw === null || typeof raw !== "object" || Array.isArray(raw)) { + return { ok: false, kind: "invalid", error: "body ต้องเป็น JSON object" }; + } + const record = raw as Record; + for (const key of Object.keys(record)) { + if (!(TICKET_BODY_FIELDS as readonly string[]).includes(key)) { + return { + ok: false, + kind: "invalid", + error: `ไม่รู้จัก field '${key}' (รับเฉพาะ ${TICKET_BODY_FIELDS.join(", ")})`, + }; + } + } + + const read = (name: (typeof TICKET_BODY_FIELDS)[number]): string | null => { + const value = record[name]; + if (value === undefined || value === null || value === "") return null; // ไม่ส่ง = missing + if (typeof value !== "string") return ""; // ส่งผิดชนิด = invalid + return value; + }; + + const ticketData = read("ticket_data"); + if (ticketData === null) { + return { ok: false, kind: "missing", error: "ต้องระบุ ticket_data (string)" }; + } + if (ticketData === "") { + return { ok: false, kind: "invalid", error: "ticket_data ต้องเป็น string" }; + } + if (ticketData.length > 2048) { + return { ok: false, kind: "invalid", error: "ticket_data ยาวเกินไป" }; + } + + const hmacHex = read("HmacSha256"); + if (hmacHex === null) { + return { ok: false, kind: "missing", error: "ต้องระบุ HmacSha256 (string)" }; + } + if (hmacHex === "") { + return { ok: false, kind: "invalid", error: "HmacSha256 ต้องเป็น string" }; + } + if (hmacHex.length > 128) { + return { ok: false, kind: "invalid", error: "HmacSha256 ยาวเกินไป" }; + } + + return { ok: true, ticketData, hmacHex }; +} diff --git a/lib/openapi.ts b/lib/openapi.ts index a407cdf..4dc3b66 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -39,8 +39,9 @@ export const openApiSpec = { description: "จัด version เอกสาร (docver): เอกสารเดียวต่อ docId แต่ละ " + "version เก็บ .docx + .pdf ใน MinIO พร้อมผู้อัปโหลด — auth ด้วย access_token " + - "ของกลุ่มนี้: ขอที่ POST /api/documents/token (ยืนยัน x-api-key กับ env " + - "DOCVER_API_KEY) แล้วแนบ Authorization: Bearer ทุก request — identity/template " + + "ของกลุ่มนี้: ขอที่ POST /api/documents/token ได้ 2 ทาง (x-api-key + body claims " + + "สำหรับระบบภายนอก หรือ body ticket_data+HmacSha256 สำหรับ add-in — PLAN-ADDIN-AUTH.md) " + + "แล้วแนบ Authorization: Bearer ทุก request — identity/template " + "และ docId (= fid — fileId ของ e-Court, string) แกะจาก claims ของ token ทั้งหมด " + "ไม่รับจาก URL/multipart form อีก", }, @@ -613,18 +614,28 @@ export const openApiSpec = { summary: "ขอ access_token ของกลุ่ม documents (docver)", description: "ออก access_token ใช้กับ endpoint อื่นของกลุ่ม documents เท่านั้น — token เป็น " + - "JWT HS256 ฝัง claims {userid, username, fid, template} ตามที่ส่งมา อายุตาม env " + + "JWT HS256 ฝัง claims {userid, username, fid, template} อายุตาม env " + "DOCVER_TOKEN_TTL_SECONDS (default 3600 วินาที) · identity (userId/userName), " + "templateId และ docId (= fid) ตอนทำงานแกะจาก claims ของ token นี้ — " + - "fid จึงบังคับ", + "fid จึงบังคับ · ทางเข้า 2 ทาง (header x-api-key เป็นตัวตั้ง): " + + "(1) ระบบภายนอก server-to-server — header x-api-key (env DOCVER_API_KEY) + body " + + "claims ตรง ๆ (2) add-in ใน Word — ไม่มี x-api-key + body {ticket_data, " + + "HmacSha256} ตามที่ e-Court stamp มากับไฟล์ — server ตรวจ HMAC-SHA256 ด้วย env " + + "ADDIN_TICKET_SECRET (ไม่ตั้ง = env SECRET ไม่ตั้ง = \"super_secret\") และ exp " + + "ของ ticket ก่อนออก token จาก claims ในตัว ticket เอง", security: [{ ApiKeyAuth: [] }], requestBody: { required: true, content: { "application/json": { - schema: { $ref: "#/components/schemas/TokenRequest" }, + schema: { + oneOf: [ + { $ref: "#/components/schemas/TokenRequest" }, + { $ref: "#/components/schemas/TicketTokenRequest" }, + ], + }, examples: { - "ครบทุก claim": { + "server-to-server ด้วย x-api-key (ครบทุก claim)": { value: { userid: "123", username: "Sorawit Bholsithi", @@ -632,9 +643,17 @@ export const openApiSpec = { template: "T12", }, }, - "ขั้นต่ำ (template เป็นทางเลือก)": { + "server-to-server ขั้นต่ำ (template เป็นทางเลือก)": { value: { userid: "123", username: "Sorawit Bholsithi", fid: "3" }, }, + "add-in ด้วย ticket จากไฟล์ (ไม่ต้องมี x-api-key)": { + value: { + ticket_data: + '{"exp":1793067433,"fid":"T12_FD155_3s-long_ZZZ","tid":"T12","uid":"oom","uname":"สรวิชญ์"}', + HmacSha256: + "1dc53fbd48410845c895ddb841a8fe4ad93da413126fae9c688202e57f2baaa9", + }, + }, }, }, }, @@ -650,7 +669,9 @@ export const openApiSpec = { }, 400: { $ref: "#/components/responses/BadRequest" }, 401: { - description: "ไม่ได้ส่ง header x-api-key หรือค่าไม่ถูกต้อง (env DOCVER_API_KEY)", + description: + "ทางเข้า x-api-key: ไม่ได้ส่ง header หรือค่าไม่ถูกต้อง (env DOCVER_API_KEY) · " + + "ทางเข้า ticket: ticket ไม่ถูกต้อง/หมดอายุ หรือไม่ได้ส่งมา (ข้อความเดียวกันทุกสาเหตุ)", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" }, @@ -880,7 +901,8 @@ export const openApiSpec = { }, TokenRequest: { type: "object", - description: "claims ที่จะฝังใน access_token ของกลุ่ม documents — key อื่น = 400", + description: + "claims ที่จะฝังใน access_token (ทางเข้า x-api-key) — key อื่น = 400", required: ["userid", "username", "fid"], properties: { userid: { type: "string", description: "รหัสผู้ใช้ (บังคับ, ≤ 100 ตัวอักษร)" }, @@ -898,6 +920,27 @@ export const openApiSpec = { }, }, }, + TicketTokenRequest: { + type: "object", + description: + "ทางเข้าของ add-in (ไม่ต้องมี x-api-key) — ส่ง ticket ที่ e-Court stamp มากับ " + + "ไฟล์ .docx ใน custom properties ตามเป๊ะ · key อื่น = 400 · server ตรวจ " + + "HMAC-SHA256(ลับ, ticket_data ดิบ) เทียบ HmacSha256 + exp ของ ticket — " + + "ไม่ผ่าน = 401 ข้อความเดียวกันทุกสาเหตุ", + required: ["ticket_data", "HmacSha256"], + properties: { + ticket_data: { + type: "string", + description: + 'JSON string ของ ticket เช่น {"exp":1793067433,"fid":"…","tid":"…","uid":"…","uname":"…"} ' + + "(exp = unix วินาที · tid optional · ส่งตรง ๆ ไม่แก้ string — HMAC ผูกกับค่าดิบ)", + }, + HmacSha256: { + type: "string", + description: "hex 64 ตัว ของ HMAC-SHA256(secret, ticket_data ดิบ)", + }, + }, + }, TokenResponse: { type: "object", description: "access_token ของกลุ่ม documents (JWT HS256 อายุสั้น)", diff --git a/scripts/make-ticket-docx.mjs b/scripts/make-ticket-docx.mjs new file mode 100644 index 0000000..e22f6d0 --- /dev/null +++ b/scripts/make-ticket-docx.mjs @@ -0,0 +1,263 @@ +#!/usr/bin/env node +// PLAN-ADDIN-AUTH.md §9 — dev tool stamp ticket ของ e-Court (custom properties +// `ticket_data` + `HmacSha256`) ลงไฟล์ .docx เพื่อทดสอบ auth ของ add-in ใน Word จริง +// โดยไม่ต้องผ่านกลุ่ม template (TMPL_PATH) — คำนวณ HMAC ตรงกับตัว stamp จริง +// (app/api/template/render/route.ts: key เรียงตามตัวอักษรหลังเติม exp → +// HMAC-SHA256(secret, ticket_data ดิบ) เป็น hex lowercase) +// +// ใช้: node scripts/make-ticket-docx.mjs [options] +// --fid default DEMO_FID_001 +// --uid default oom +// --uname default "สรวิชญ์" +// --tid template id (optional — ไม่ส่ง = ไม่มี tid ใน ticket) +// --exp default +300 (5 นาที เท่า TICKET_SEC ของตัว stamp) +// --secret default "super_secret" (ต้องตรง env ฝั่ง server/add-in) +// --tracked [id] stamp SavedByAddIn + DocumentTrackingId ด้วย (ทดสอบ phase tracking) +// --in-place เขียนทับไฟล์เดิม (ปกติเขียน <ชื่อเดิม>.ticket.docx) +// --list แค่อ่าน custom properties ที่มีในไฟล์แล้วออกมาดู +// +// รันใน repo เท่านั้น (import pizzip จาก node_modules) — ไม่ใช่ postinstall + +import { createHmac, randomUUID } from "node:crypto"; +import { readFileSync, writeFileSync } from "node:fs"; +import path from "node:path"; +import PizZip from "pizzip"; + +const CUSTOM_PATH = "docProps/custom.xml"; +const CT_PATH = "[Content_Types].xml"; +const RELS_PATH = "_rels/.rels"; +const CUSTOM_FMTID = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}"; +/** ต้องตรง metadataService.ADDIN_SIGNATURE_GUID ฝั่ง add-in */ +const ADDIN_SIGNATURE_GUID = "9B7A3C51-4E6F-4D2A-8B5C-1E0F2A3B4C5D"; + +function usage(code = 1) { + console.log(`ใช้: node scripts/make-ticket-docx.mjs [options] + --fid --uid --uname --tid + --exp --secret + --tracked [trackingId] --in-place --list`); + process.exit(code); +} + +function parseArgs(argv) { + const options = { + file: null, + fid: "DEMO_FID_001", + uid: "oom", + uname: "สรวิชญ์", + tid: null, + exp: "+300", + secret: "super_secret", + tracked: false, + trackingId: null, + inPlace: false, + list: false, + }; + for (let i = 0; i < argv.length; i++) { + const arg = argv[i]; + const next = () => { + const value = argv[++i]; + if (value === undefined) usage(); + return value; + }; + switch (arg) { + case "--fid": options.fid = next(); break; + case "--uid": options.uid = next(); break; + case "--uname": options.uname = next(); break; + case "--tid": options.tid = next(); break; + case "--exp": options.exp = next(); break; + case "--secret": options.secret = next(); break; + case "--tracked": + options.tracked = true; + if (argv[i + 1] && !argv[i + 1].startsWith("--")) options.trackingId = next(); + break; + case "--in-place": options.inPlace = true; break; + case "--list": options.list = true; break; + case "--help": case "-h": usage(0); break; + default: + if (arg.startsWith("--")) usage(); + if (options.file !== null) usage(); + options.file = arg; + } + } + if (options.file === null) usage(); + return options; +} + +function resolveExp(raw) { + if (raw.startsWith("+")) { + const seconds = Number(raw.slice(1)); + if (!Number.isFinite(seconds) || seconds <= 0) throw new Error("--exp +<วินาที> ไม่ถูกต้อง"); + return Math.floor(Date.now() / 1000) + Math.floor(seconds); + } + const value = Number(raw); + if (!Number.isInteger(value) || value <= 0) throw new Error("--exp ต้องเป็น unix วินาที หรือ +<วินาที>"); + return value; +} + +function escapeXml(value) { + return String(value) + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} + +/** ticket_data ตามรูปตัว stamp จริง — key เรียงตามตัวอักษรหลังเติม exp */ +function buildTicketData(options) { + const ticket = { exp: resolveExp(options.exp), fid: options.fid, uid: options.uid, uname: options.uname }; + if (options.tid !== null) ticket.tid = options.tid; + const sorted = Object.keys(ticket) + .sort() + .reduce((result, key) => { + result[key] = ticket[key]; + return result; + }, {}); + return JSON.stringify(sorted); +} + +function unescapeXml(value) { + return String(value) + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/&/g, "&"); +} + +/** ถอด ทั้งชุดจาก custom.xml — ไว้ --list และหา pid ที่ใช้แล้ว */ +function readProperties(xml) { + const properties = []; + const regex = /]*name="([^"]*)"[^>]*>([\s\S]*?)<\/Property>|]*name="([^"]*)"[^>]*\/>/g; + let match; + while ((match = regex.exec(xml)) !== null) { + const name = match[1] ?? match[3]; + const inner = match[2] ?? ""; + const value = inner.replace(/<[^>]+>/g, ""); + properties.push({ name: unescapeXml(name), value: unescapeXml(value.trim()) }); + } + return properties; +} + +function usedPids(xml) { + const pids = new Set([1]); + const regex = /pid="(\d+)"/g; + let match; + while ((match = regex.exec(xml)) !== null) pids.add(Number(match[1])); + return pids; +} + +function propertyXml(name, pid, value) { + return ` ${escapeXml(value)}\n`; +} + +/** upsert property ลง custom.xml (สร้างไฟล์ใหม่ถ้ายังไม่มี) — คืน XML ที่พร้อมเขียนกลับ zip */ +function upsertProperties(existingXml, entries) { + // เอา Property เก่าของชื่อที่จะเขียนทิ้งก่อน (replace แบบตัวเอง) + let xml = existingXml; + for (const entry of entries) { + const removal = new RegExp( + `\\s*]*name="${escapeXml(entry.name).replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}"[^>]*>([\\s\\S]*?)`, + "g", + ); + xml = xml.replace(removal, ""); + } + const pids = usedPids(xml); + let nextPid = 2; + const takePid = () => { + while (pids.has(nextPid)) nextPid++; + pids.add(nextPid); + return nextPid++; + }; + const block = entries.map((entry) => propertyXml(entry.name, takePid(), entry.value)).join(""); + const hasRoot = xml.includes(""); + if (hasRoot) { + xml = xml.replace(/<\/Properties>/, `${block}`); + } else { + xml = + `\n` + + `\n${block}`; + } + return xml; +} + +/** ผูก docProps/custom.xml เข้ากับแพ็กเกจ — Word ไม่เห็น part ที่ไม่มี relationship/content-type */ +function ensurePartRegistered(zip) { + const ct = zip.file(CT_PATH); + if (ct && !ct.asText().includes('PartName="/docProps/custom.xml"')) { + const updated = ct + .asText() + .replace( + /<\/Types>/, + ` \n`, + ); + zip.file(CT_PATH, updated); + } + const rels = zip.file(RELS_PATH); + if (rels && !rels.asText().includes('Target="docProps/custom.xml"')) { + const match = rels.asText().match(/Id="rId(\d+)"/g); + const maxId = match ? Math.max(...match.map((item) => Number(item.match(/\d+/)[0]))) : 0; + const updated = rels + .asText() + .replace( + /<\/Relationships>/, + ` \n`, + ); + zip.file(RELS_PATH, updated); + } +} + +// --------------------------------------------------------------------------- + +const options = parseArgs(process.argv.slice(2)); +const source = readFileSync(options.file); + +if (source.subarray(0, 2).toString("latin1") !== "PK") { + console.error(`✗ ${options.file} ไม่ใช่ไฟล์ .docx (zip)`); + process.exit(1); +} + +const zip = new PizZip(source); + +if (options.list) { + const custom = zip.file(CUSTOM_PATH); + if (!custom) { + console.log(`(ไม่มี ${CUSTOM_PATH} ในไฟล์นี้)`); + } else { + for (const property of readProperties(custom.asText())) { + console.log(`${property.name} = ${property.value}`); + } + } + process.exit(0); +} + +const ticketData = buildTicketData(options); +const hmac = createHmac("sha256", options.secret).update(ticketData, "utf8").digest("hex"); +if (ticketData.length > 255) { + console.warn(`⚠ ticket_data ยาว ${ticketData.length} ตัวอักษร — เกินขีดจำกัด ~255 ของ Word custom property อาจถูกตัด`); +} + +const entries = [ + { name: "ticket_data", value: ticketData }, + { name: "HmacSha256", value: hmac }, +]; +if (options.tracked) { + entries.push({ name: "SavedByAddIn", value: ADDIN_SIGNATURE_GUID }); + entries.push({ name: "DocumentTrackingId", value: options.trackingId ?? randomUUID() }); +} + +const existing = zip.file(CUSTOM_PATH); +zip.file(CUSTOM_PATH, upsertProperties(existing ? existing.asText() : "", entries)); +ensurePartRegistered(zip); + +const output = zip.generate({ type: "nodebuffer", compression: "DEFLATE" }); +const target = options.inPlace + ? options.file + : `${options.file.replace(/\.docx$/i, "")}.ticket.docx`; +writeFileSync(target, output); + +console.log(`✓ stamp ticket ลง ${path.relative(process.cwd(), target)}${options.inPlace ? " (ทับไฟล์เดิม)" : ""}`); +console.log(` ticket_data : ${ticketData}`); +console.log(` HmacSha256 : ${hmac}`); +console.log(` secret : ${options.secret}`); +console.log(` exp : ${new Date(resolveExp(options.exp) * 1000).toLocaleString("th-TH")}`); diff --git a/tests/lib/addin-ticket.test.ts b/tests/lib/addin-ticket.test.ts new file mode 100644 index 0000000..5c75931 --- /dev/null +++ b/tests/lib/addin-ticket.test.ts @@ -0,0 +1,148 @@ +import { createHmac } from "node:crypto"; +import { describe, expect, it } from "vitest"; + +import { + addinTicketSecret, + parseTicketTokenBody, + ticketToDocverClaims, + verifyAddinTicket, +} from "@/lib/addin-ticket"; + +// PLAN-ADDIN-AUTH.md §3 — pure ล้วน (verifyAddinTicket รับ secret ทาง parameter) +// ไม่แตะ @/db/minio — รันได้โดยไม่ต้องตั้ง env อะไร (เหมือน docver-token.test.ts) + +const SECRET = "super_secret"; + +/** ตัว ticket ตัวอย่าง — key เรียงตามตัวอักษรเหมือนตัว stamp จริง (sortObjectKeys ของ lib-utils) */ +const TICKET_DATA = JSON.stringify({ + exp: Math.floor(Date.now() / 1000) + 300, + fid: "T12_FD155_3s-long_ZZZ", + tid: "T12", + uid: "oom", + uname: "สรวิชญ์", +}); + +function sign(message: string, secret: string = SECRET): string { + return createHmac("sha256", secret).update(message, "utf8").digest("hex"); +} + +function ok(result: { ok: boolean } & T): asserts result is { ok: true } & T { + expect(result.ok).toBe(true); +} + +describe("verifyAddinTicket", () => { + it("ticket ถูกต้อง + HMAC ตรง = ผ่าน ได้ ticket แกะแล้ว", () => { + const result = verifyAddinTicket(TICKET_DATA, sign(TICKET_DATA), SECRET); + ok(result); + expect(result.ticket.fid).toBe("T12_FD155_3s-long_ZZZ"); + expect(result.ticket.uid).toBe("oom"); + expect(result.ticket.tid).toBe("T12"); + }); + + it("HMAC ผิด (แก้ ticket 1 ตัวอักษร / secret ผิด / digest เพี้ยน) = ไม่ผ่านทุกกรณี", () => { + const digest = sign(TICKET_DATA); + const tampered = `${TICKET_DATA.slice(0, -2)}xx}`; + expect(verifyAddinTicket(tampered, digest, SECRET).ok).toBe(false); + expect(verifyAddinTicket(TICKET_DATA, sign(TICKET_DATA, "wrong-secret"), SECRET).ok).toBe(false); + const flipped = `${digest.slice(0, -1)}${digest.endsWith("0") ? "1" : "0"}`; + expect(verifyAddinTicket(TICKET_DATA, flipped, SECRET).ok).toBe(false); + }); + + it("digest รูปไม่ใช่ (ไม่ใช่ hex / สั้น / ว่าง) = ไม่ผ่าน · uppercase hex normalize แล้วผ่านได้", () => { + expect(verifyAddinTicket(TICKET_DATA, "zz".repeat(32), SECRET).ok).toBe(false); + expect(verifyAddinTicket(TICKET_DATA, "abcd", SECRET).ok).toBe(false); + expect(verifyAddinTicket(TICKET_DATA, "", SECRET).ok).toBe(false); + expect(verifyAddinTicket(TICKET_DATA, sign(TICKET_DATA).toUpperCase(), SECRET).ok).toBe(true); + }); + + it("ticket หมดอายุ (exp อดีต / เท่ากับ now) = ไม่ผ่าน แม้ HMAC ถูก", () => { + const now = Math.floor(Date.now() / 1000); + for (const exp of [now - 1, now]) { + const expired = JSON.stringify({ exp, fid: "f", uid: "u", uname: "n" }); + expect(verifyAddinTicket(expired, sign(expired), SECRET, now).ok, `exp=${exp}`).toBe(false); + } + const alive = JSON.stringify({ exp: now + 1, fid: "f", uid: "u", uname: "n" }); + expect(verifyAddinTicket(alive, sign(alive), SECRET, now).ok).toBe(true); + }); + + it("HMAC ถูกแต่ ticket ผิดกติกา (field หลุด allowlist / exp ไม่ใช่ integer) = ไม่ผ่าน", () => { + const extra = JSON.stringify({ exp: Math.floor(Date.now() / 1000) + 60, fid: "f", uid: "u", uname: "n", oops: 1 }); + expect(verifyAddinTicket(extra, sign(extra), SECRET).ok).toBe(false); + const badExp = JSON.stringify({ exp: "123", fid: "f", uid: "u", uname: "n" }); + expect(verifyAddinTicket(badExp, sign(badExp), SECRET).ok).toBe(false); + }); +}); + +describe("ticketToDocverClaims", () => { + it("แปลง uid→userid, uname→username, tid→template · tid ไม่มี = null", () => { + expect( + ticketToDocverClaims({ exp: 1, fid: "F1", tid: "T12", uid: "oom", uname: "สรวิชญ์" }), + ).toEqual({ userid: "oom", username: "สรวิชญ์", fid: "F1", template: "T12" }); + expect( + ticketToDocverClaims({ exp: 1, fid: "F1", tid: null, uid: "oom", uname: "สรวิชญ์" }), + ).toEqual({ userid: "oom", username: "สรวิชญ์", fid: "F1", template: null }); + }); +}); + +describe("parseTicketTokenBody", () => { + it("ครบทั้งคู่ = ได้ค่าตรง ๆ (ไม่แตะค่า — HMAC ต้องคำนวณบน string ดิบ)", () => { + const digest = sign(TICKET_DATA); + const result = parseTicketTokenBody({ ticket_data: TICKET_DATA, HmacSha256: digest }); + ok(result); + expect(result.ticketData).toBe(TICKET_DATA); + expect(result.hmacHex).toBe(digest); + }); + + it("ไม่ใช่ object / มี field อื่นปน = invalid (→ 400)", () => { + for (const raw of [[], "text", null, { ticket_data: "x", HmacSha256: "y", extra: 1 }]) { + const result = parseTicketTokenBody(raw); + expect(result.ok, JSON.stringify(raw)).toBe(false); + if (!result.ok) expect(result.kind).toBe("invalid"); + } + }); + + it("ขาด field (ไม่ส่ง/ว่าง/null) = missing (→ 401 กัน oracle)", () => { + for (const raw of [{}, { ticket_data: "" }, { HmacSha256: "y" }, { ticket_data: null, HmacSha256: "y" }]) { + const result = parseTicketTokenBody(raw); + expect(result.ok, JSON.stringify(raw)).toBe(false); + if (!result.ok) expect(result.kind).toBe("missing"); + } + }); + + it("field ไม่ใช่ string / ยาวเกิน cap = invalid", () => { + for (const raw of [ + { ticket_data: 123, HmacSha256: "y" }, + { ticket_data: "x", HmacSha256: "y".repeat(129) }, + { ticket_data: "x".repeat(2049), HmacSha256: "y" }, + ]) { + const result = parseTicketTokenBody(raw); + expect(result.ok, JSON.stringify(raw)).toBe(false); + if (!result.ok) expect(result.kind).toBe("invalid"); + } + }); +}); + +describe("addinTicketSecret", () => { + // env save/restore — addinTicketSecret อ่านตอนเรียก + const ORIG_ADDIN = process.env.ADDIN_TICKET_SECRET; + const ORIG_SECRET = process.env.SECRET; + + it("ลำดับ ADDIN_TICKET_SECRET → SECRET → default \"super_secret\" (trim ทุกตัว)", () => { + delete process.env.ADDIN_TICKET_SECRET; + delete process.env.SECRET; + expect(addinTicketSecret()).toBe("super_secret"); + + process.env.SECRET = " tmpl-secret "; + expect(addinTicketSecret()).toBe("tmpl-secret"); + + process.env.ADDIN_TICKET_SECRET = " addin-secret "; + expect(addinTicketSecret()).toBe("addin-secret"); + + delete process.env.ADDIN_TICKET_SECRET; + process.env.SECRET = " "; // ว่างล้วน = ข้ามไปตัวถัดไป + expect(addinTicketSecret()).toBe("super_secret"); + + process.env.ADDIN_TICKET_SECRET = ORIG_ADDIN; + process.env.SECRET = ORIG_SECRET; + }); +}); diff --git a/tests/utils/docver-ticket.test.ts b/tests/utils/docver-ticket.test.ts new file mode 100644 index 0000000..bdde305 --- /dev/null +++ b/tests/utils/docver-ticket.test.ts @@ -0,0 +1,118 @@ +import { describe, expect, it } from "vitest"; + +import { + isTicketExpired, + normalizeHexDigest, + parseDocverTicket, + timingSafeEqualHex, +} from "@/utils/docver-ticket"; + +// PLAN-ADDIN-AUTH.md §4 — pure ล้วนไม่แตะ env/DB อะไรเลย +// รูป ticket_data อ้างอิงจากตัว stamp จริงของกลุ่ม template (app/api/template/ +// render/route.ts applyTemplate — key เรียงตามตัวอักษรหลังเติม exp) + +const VALID = JSON.stringify({ + exp: 1793067433, + fid: "T12_FD155_3s-long_ZZZ", + tid: "T12", + uid: "oom", + uname: "สรวิชญ์", +}); + +function ok(result: { ok: boolean } & T): asserts result is { ok: true } & T { + expect(result.ok).toBe(true); +} + +describe("parseDocverTicket", () => { + it("ticket ครบทุก field = แกะได้ (trim ค่า string)", () => { + const result = parseDocverTicket(VALID); + ok(result); + expect(result.ticket).toEqual({ + exp: 1793067433, + fid: "T12_FD155_3s-long_ZZZ", + tid: "T12", + uid: "oom", + uname: "สรวิชญ์", + }); + }); + + it("tid optional — ไม่ส่ง/ส่งค่าว่าง = null", () => { + const noTid = parseDocverTicket(JSON.stringify({ exp: 1, fid: "f", uid: "u", uname: "n" })); + ok(noTid); + expect(noTid.ticket.tid).toBeNull(); + const blankTid = parseDocverTicket( + JSON.stringify({ exp: 1, fid: "f", tid: " ", uid: "u", uname: "n" }), + ); + ok(blankTid); + expect(blankTid.ticket.tid).toBeNull(); + }); + + it("JSON ไม่ผ่าน / ไม่ใช่ object (array/string/null) = ไม่ผ่าน", () => { + for (const raw of ["not json", "[1,2]", '"text"', "null", "{}"]) { + expect(parseDocverTicket(raw).ok, raw).toBe(false); + } + }); + + it("field อื่นนอก allowlist = ไม่ผ่าน (กัน typo/field หลุดจาก spec)", () => { + const result = parseDocverTicket(JSON.stringify({ exp: 1, fid: "f", uid: "u", uname: "n", oops: 1 })); + expect(result.ok).toBe(false); + if (!result.ok) expect(result.error).toContain("oops"); + }); + + it("exp ไม่ใช่จำนวนเต็ม / หาย / uid-uname-fid หายหรือไม่ใช่ string = ไม่ผ่าน", () => { + for (const ticket of [ + { fid: "f", uid: "u", uname: "n" }, // ไม่มี exp + { exp: "1793067433", fid: "f", uid: "u", uname: "n" }, // exp เป็น string + { exp: 1.5, fid: "f", uid: "u", uname: "n" }, // exp ไม่ใช่ integer + { exp: 1, uid: "u", uname: "n" }, // ไม่มี fid + { exp: 1, fid: "f", uname: "n" }, // ไม่มี uid + { exp: 1, fid: "f", uid: "u" }, // ไม่มี uname + { exp: 1, fid: "", uid: "u", uname: "n" }, // fid ว่าง + { exp: 1, fid: 3, uid: "u", uname: "n" }, // fid เป็น number + { exp: 1, fid: "f", uid: "u", uname: "n", tid: 12 }, // tid ไม่ใช่ string + ]) { + expect(parseDocverTicket(JSON.stringify(ticket)).ok, JSON.stringify(ticket)).toBe(false); + } + }); + + it("ความยาวเกิน cap: uid > 100 / uname > 200 / fid > 200 / tid > 100 = ไม่ผ่าน", () => { + expect(parseDocverTicket(JSON.stringify({ exp: 1, fid: "f", uid: "u".repeat(101), uname: "n" })).ok).toBe(false); + expect(parseDocverTicket(JSON.stringify({ exp: 1, fid: "f", uid: "u", uname: "n".repeat(201) })).ok).toBe(false); + expect(parseDocverTicket(JSON.stringify({ exp: 1, fid: "f".repeat(201), uid: "u", uname: "n" })).ok).toBe(false); + expect(parseDocverTicket(JSON.stringify({ exp: 1, fid: "f", uid: "u", uname: "n", tid: "t".repeat(101) })).ok).toBe(false); + }); +}); + +describe("isTicketExpired", () => { + it("exp อนาคต = ยังไม่หมด · exp ตรง now พอดี/อดีต = หมดแล้ว", () => { + const now = 1_793_000_000; + expect(isTicketExpired(now + 1, now)).toBe(false); + expect(isTicketExpired(now, now)).toBe(true); + expect(isTicketExpired(now - 1, now)).toBe(true); + }); +}); + +describe("normalizeHexDigest", () => { + const HEX64 = "1dc53fbd48410845c895ddb841a8fe4ad93da413126fae9c688202e57f2baaa9"; + + it("hex lowercase ครบ 64 = ผ่าน · trim และ uppercase ก็ normalize ให้", () => { + expect(normalizeHexDigest(HEX64)).toBe(HEX64); + expect(normalizeHexDigest(` ${HEX64} `)).toBe(HEX64); + expect(normalizeHexDigest(HEX64.toUpperCase())).toBe(HEX64); + }); + + it("รูปไม่ใช่ (สั้น/ยาว/ไม่ใช่ hex/ว่าง) = null", () => { + for (const bad of ["", "abc", `${HEX64}00`, `g${HEX64.slice(1)}`, " "]) { + expect(normalizeHexDigest(bad), bad).toBeNull(); + } + }); +}); + +describe("timingSafeEqualHex", () => { + it("ค่าเท่ากัน = true ต่างกัน = false · ความยาวไม่เท่า = false ทันที", () => { + const a = "ab".repeat(32); + expect(timingSafeEqualHex(a, a)).toBe(true); + expect(timingSafeEqualHex(a, "cd".repeat(32))).toBe(false); + expect(timingSafeEqualHex(a, "ab")).toBe(false); + }); +}); diff --git a/tests/wordaddin/ticket-auth-crash-repro.test.ts b/tests/wordaddin/ticket-auth-crash-repro.test.ts new file mode 100644 index 0000000..abecb22 --- /dev/null +++ b/tests/wordaddin/ticket-auth-crash-repro.test.ts @@ -0,0 +1,115 @@ +// @vitest-environment jsdom +// [DBG-r3p1] repro ชั่วคราว — หา client-side crash ของ taskpane หลัง implement ticket auth +// (ไม่ใช่ regression test ถาวร — ถอดทิ้งเมื่อหาสาเหตุเจอแล้ว; import .tsx ไม่ได้ใน vitest ปัจจุบัน +// จึงโฟกัส service layer ที่เป็นโค้ดใหม่ทั้งหมด — component repro ใช้ Chrome headless แทน) +import { createHmac, webcrypto } from "node:crypto"; +import { afterEach, describe, expect, it, vi } from "vitest"; + +import * as authService from "@/wordaddin/services/authService"; +import * as ticketService from "@/wordaddin/services/ticketService"; + +// จับ error ทุกทางที่จะกลายเป็น "Application error: a client-side exception" บน Next +const errors: unknown[] = []; +function trapErrors() { + window.addEventListener("error", (event) => errors.push(event.error ?? event.message)); + window.addEventListener("unhandledrejection", (event) => errors.push(event.reason)); +} + +function makeTicket(): { ticketData: string; ticketHmac: string } { + const ticket = { + exp: Math.floor(Date.now() / 1000) + 300, + fid: "DBG_FID_001", + tid: "T12", + uid: "oom", + uname: "สรวิชญ์", + }; + const ticketData = JSON.stringify(ticket); + const ticketHmac = createHmac("sha256", "super_secret").update(ticketData, "utf8").digest("hex"); + return { ticketData, ticketHmac }; +} + +afterEach(() => { + window.localStorage.clear(); + // memory fallback ของ session เป็น module state — เคลียร์ด้วยไม่งั้นค้างข้ามเคส + ticketService.clearSession(); + authService.signInForDemo(); // รีเซ็ต state ของ authService (username/fid/ticket) + vi.restoreAllMocks(); + vi.unstubAllGlobals(); + errors.length = 0; +}); + +describe("[DBG-r3p1] ticket auth crash repro (service layer)", () => { + it("B: signInWithTicket happy-path (มี crypto.subtle + fetch ตอบ 200) ไม่ต้อง throw", async () => { + trapErrors(); + // jsdom ไม่มี crypto.subtle — ใส่ webcrypto ของ node แทน (secure context ของ webview จริงมีอยู่แล้ว) + vi.stubGlobal("crypto", webcrypto); + + const { ticketData, ticketHmac } = makeTicket(); + const fetchMock = vi.fn( + async () => + new Response( + JSON.stringify({ + access_token: [ + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9", + Buffer.from( + JSON.stringify({ userid: "oom", fid: "DBG_FID_001", exp: Math.floor(Date.now() / 1000) + 3600 }), + ).toString("base64url"), + "sig", + ].join("."), + token_type: "Bearer", + expires_in: 3600, + }), + { status: 200, headers: { "content-type": "application/json" } }, + ), + ); + vi.stubGlobal("fetch", fetchMock); + + const first = await authService.signInWithTicket({ ticketData, ticketHmac }); + expect(first.ok, JSON.stringify(first)).toBe(true); + + // เปิดเอกสารเดิมซ้ำ — ต้องไม่ยิง /token ซ้ำ (ใช้ token เดิมจาก localStorage) + await authService.signInWithTicket({ ticketData, ticketHmac }); + expect(fetchMock).toHaveBeenCalledTimes(1); + + expect(errors).toEqual([]); + expect(window.localStorage.getItem("wordaddin-auth")).toBeTruthy(); + }); + + it("C: signInWithTicket แบบไม่มี crypto.subtle (webview เก่า) ต้องจบเป็น no-access ไม่ใช่ throw", async () => { + trapErrors(); + const noSubtle = { getRandomValues: () => new Uint8Array(0) }; + vi.stubGlobal("crypto", noSubtle); // ไม่มี .subtle เลย + const { ticketData, ticketHmac } = makeTicket(); + const result = await authService.signInWithTicket({ ticketData, ticketHmac }); + expect(result.ok).toBe(false); + expect(errors).toEqual([]); + }); + + it("D: ticket ผิดทุกแบบ (ขาด/hex เพี้ยน/parse ไม่ผ่าน/หมดอายุ/fetch 401) ต้องคืน result ไม่ใช่ throw", async () => { + trapErrors(); + vi.stubGlobal("crypto", webcrypto); + vi.stubGlobal( + "fetch", + vi.fn(async () => new Response(JSON.stringify({ error: "no" }), { status: 401 })), + ); + const { ticketData, ticketHmac } = makeTicket(); + const bad = [ + { ticketData: undefined, ticketHmac: undefined }, + { ticketData: "not-json", ticketHmac }, + { ticketData, ticketHmac: "zz".repeat(32) }, + { + ticketData: JSON.stringify({ exp: 1, fid: "f", uid: "u", uname: "n" }), // หมดอายุ + HMAC ถูก + ticketHmac: createHmac("sha256", "super_secret") + .update(JSON.stringify({ exp: 1, fid: "f", uid: "u", uname: "n" }), "utf8") + .digest("hex"), + }, + { ticketData, ticketHmac }, // ถูก แต่ server 401 → token-request-failed + ] satisfies { ticketData?: string; ticketHmac?: string }[]; + for (const input of bad) { + const result = await authService.signInWithTicket(input); + expect(result.ok, JSON.stringify(input)).toBe(false); + if (!result.ok) expect(typeof result.message).toBe("string"); + } + expect(errors).toEqual([]); + }); +}); diff --git a/utils/docver-ticket.ts b/utils/docver-ticket.ts new file mode 100644 index 0000000..14130c6 --- /dev/null +++ b/utils/docver-ticket.ts @@ -0,0 +1,136 @@ +// PLAN-ADDIN-AUTH.md §4 — ticket ของ add-in (custom properties `ticket_data` + +// `HmacSha256` ที่ e-Court ผ่านกลุ่ม template stamp ลงไฟล์) — ส่วน pure ที่ +// ใช้ร่วมกันทั้งฝั่ง server (lib/addin-ticket.ts — route /api/documents/token) +// และฝั่ง client ของ add-in (wordaddin/services/ticketService.ts) +// +// กติกา (docs/requirement/addin-workflow.md + lib/lib-utils.ts generateHmacSha256 +// ของตัว stamp ฝั่ง template): +// - ticket_data เป็น JSON string ตามที่ stamp ไว้เป๊ะ (key เรียงตามตัวอักษร +// หลังเติม exp) — **HMAC ต้องคำนวณบน string ดิบนี้ ห้าม trim/normalize** +// - HmacSha256 เป็น hex lowercase 64 ตัว (การเทียบ normalize เป็น lowercase +// ให้ก่อน — ฝั่ง stamp ตัวพิมพ์ใหญ่ก็ผ่านได้) +// - ห้าม import อะไรที่เป็น platform-specific (node:crypto/crypto.subtle) — +// module นี้ต้องรันได้ทั้งใน route handler และ webview ของ Word โดยส่ง +// ตัวคำนวณ HMAC เข้ามาทาง parameter แทน + +/** ticket ที่แกะจาก ticket_data — ชื่อ field ตามที่ e-Court กำหนด (requirement) */ +export interface DocverTicket { + /** หมดอายุเมื่อไร — unix epoch วินาที */ + exp: number; + /** fileId ของ e-Court — เป็น docId ของทุก operation ของกลุ่ม docver ตรง ๆ */ + fid: string; + /** template ต้นทาง — optional (ไม่มี = null) */ + tid: string | null; + /** รหัสผู้ใช้ → claim `userid` */ + uid: string; + /** ชื่อผู้ใช้ → claim `username` */ + uname: string; +} + +/** ความยาว cap ต่อ field — ตาม parseTokenRequestBody ของ /api/documents/token */ +const TICKET_LIMITS = { + uid: 100, + uname: 200, + fid: 200, + tid: 100, +} as const; + +/** allowlist ของ ticket_data — field อื่นปนมา = reject (กัน typo เงียบ ๆ ตาม convention ของ repo) */ +const TICKET_FIELDS = ["exp", "fid", "tid", "uid", "uname"] as const; + +export type DocverTicketParseResult = + | { ok: true; ticket: DocverTicket } + | { ok: false; error: string }; + +/** + * แกะ + ตรวจ ticket จาก string ดิบของ property `ticket_data` + * คืนค่าที่ trim แล้ว (uid/uname/fid/tid) — exp เป็น number ตรง ๆ + */ +export function parseDocverTicket(raw: string): DocverTicketParseResult { + let parsed: unknown; + try { + parsed = JSON.parse(raw); + } catch { + return { ok: false, error: "ticket_data ไม่ใช่ JSON ที่ถูกต้อง" }; + } + if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) { + return { ok: false, error: "ticket_data ต้องเป็น JSON object" }; + } + const record = parsed as Record; + for (const key of Object.keys(record)) { + if (!(TICKET_FIELDS as readonly string[]).includes(key)) { + return { + ok: false, + error: `ticket_data มี field ที่ไม่รู้จัก '${key}' (รับเฉพาะ ${TICKET_FIELDS.join(", ")})`, + }; + } + } + + const exp = record.exp; + if (typeof exp !== "number" || !Number.isSafeInteger(exp)) { + return { ok: false, error: "ticket_data ต้องมี exp เป็นจำนวนเต็ม (unix วินาที)" }; + } + + const field = (name: "uid" | "uname" | "fid"): string | null => { + const value = record[name]; + if (typeof value !== "string" || value.trim() === "") { + return null; + } + const trimmed = value.trim(); + return trimmed.length <= TICKET_LIMITS[name] ? trimmed : null; + }; + + const uid = field("uid"); + if (uid === null) return { ok: false, error: "ticket_data ต้องมี uid (string 1–100)" }; + const uname = field("uname"); + if (uname === null) return { ok: false, error: "ticket_data ต้องมี uname (string 1–200)" }; + const fid = field("fid"); + if (fid === null) return { ok: false, error: "ticket_data ต้องมี fid (string 1–200)" }; + + let tid: string | null = null; + if (record.tid !== undefined) { + if (typeof record.tid !== "string") { + return { ok: false, error: "ticket_data ของ tid ต้องเป็น string" }; + } + const trimmed = record.tid.trim(); + if (trimmed !== "") { + if (trimmed.length > TICKET_LIMITS.tid) { + return { ok: false, error: "ticket_data ของ tid ยาวเกิน 100 ตัวอักษร" }; + } + tid = trimmed; + } + } + + return { ok: true, ticket: { exp, fid, tid, uid, uname } }; +} + +/** exp หมดอายุไหม — exp เท่ากับ now พอดีก็ถือว่าหมดแล้ว */ +export function isTicketExpired(exp: number, nowSec: number = Math.floor(Date.now() / 1000)): boolean { + return exp <= nowSec; +} + +/** + * normalize ค่า digest ที่ได้รับ (property `HmacSha256` / body field) — + * trim + lowercase แล้วตรวจว่าเป็น hex ครบ 64 ตัว คืน null เมื่อรูปไม่ใช่ + */ +export function normalizeHexDigest(value: string): string | null { + const normalized = value.trim().toLowerCase(); + return /^[0-9a-f]{64}$/.test(normalized) ? normalized : null; +} + +/** + * เทียบ hex digest สองค่าแบบ constant-time (best-effort ของ pure JS — + * ฝั่ง server จริงใช้ node:crypto timingSafeEqual ผ่าน lib/addin-ticket.ts + * ตัวนี้ใช้กับ UX gate ฝั่ง client เป็นหลัก) — ค่าที่ส่งเข้าควร normalize แล้ว + */ +export function timingSafeEqualHex(a: string, b: string): boolean { + if (a.length !== b.length) return false; + let diff = 0; + for (let i = 0; i < a.length; i++) { + diff |= a.charCodeAt(i) ^ b.charCodeAt(i); + } + return diff === 0; +} + +/** ตัวคำนวณ HMAC-SHA256 คืนค่าเป็น hex lowercase — สองฝั่งฉีดของตัวเอง (Web Crypto / node:crypto) */ +export type HmacSha256Hex = (message: string, secret: string) => Promise; diff --git a/wordaddin/config.ts b/wordaddin/config.ts index e142b70..646079e 100644 --- a/wordaddin/config.ts +++ b/wordaddin/config.ts @@ -2,12 +2,19 @@ * Config ของ wordaddin — ค่าจาก NEXT_PUBLIC_* env (Next inline ตอน build) * (ต้นฉบับ D:\word-addin-poc\addin inject ผ่าน webpack DefinePlugin — พอร์ตมาเป็น env ของ Next) * - * ช่วง mock: authService/versioningService เป็น mock ไม่ใช้ apiBaseUrl — + * ช่วง mock: versioningService เป็น mock ไม่ใช้ apiBaseUrl — * ตั้งค่านี้เมื่อปลด mock ต่อ backend จริง (ต้องเป็น HTTPS เสมอ — webview ของ Word บล็อก http) + * ส่วนการขอ token (ticket auth) ยิง same-origin /api/documents/token เสมอ ไม่ผูกกับ apiBaseUrl */ export const config = { /** base URL ของ backend API (ไม่มี slash ท้าย) — ว่าง = ยังไม่ต่อ backend จริง */ apiBaseUrl: process.env.NEXT_PUBLIC_ADDIN_API_BASE_URL ?? "", /** ชื่อผู้ใช้ของ mock auth — แสดงหน้า header + เป็น createdByUserId ของ version ที่ mock สร้าง */ mockUsername: process.env.NEXT_PUBLIC_ADDIN_MOCK_USERNAME ?? "tester01", + /** + * secret ตรวจ HMAC ของ ticket ฝั่ง client (UX gate — ตัดเคสไฟล์ถูกแก้ก่อนยิง network; + * ความมั่นคงจริงอยู่ที่ server ตรวจอีกชั้น เพราะค่าใน bundle สกัดได้) — ต้องตรงกับ + * ตัว stamp ของ e-Court และ env ADDIN_TICKET_SECRET/SECRET ฝั่ง server (PLAN-ADDIN-AUTH.md §7) + */ + ticketSecret: process.env.NEXT_PUBLIC_ADDIN_TICKET_SECRET ?? "super_secret", }; diff --git a/wordaddin/services/apiClient.ts b/wordaddin/services/apiClient.ts index 73cf510..5db4122 100644 --- a/wordaddin/services/apiClient.ts +++ b/wordaddin/services/apiClient.ts @@ -2,17 +2,18 @@ import { config } from "../config"; import * as authService from "./authService"; /** - * fetch wrapper ของ add-in — แนบ Bearer ทุก request (token จาก custom properties ของไฟล์) - * โดน 401 → AuthRequiredError ทันที: token จากไฟล์ต่ออายุไม่ได้ (ไม่มี refresh token - * ตั้งแต่ยกเลิก Keycloak) — UI จับ error นี้แล้วพาไปหน้า no-access ให้ขอไฟล์ใหม่จาก e-Court + * fetch wrapper ของ add-in — แนบ Bearer ทุก request (access_token ที่ authService + * ขอจาก /api/documents/token ด้วย ticket ของไฟล์ — PLAN-ADDIN-AUTH.md) + * โดน 401 → ลองขอ token ใหม่จาก ticket แล้ว retry 1 ครั้ง ยังไม่ผ่าน → AuthRequiredError: + * UI จับ error นี้แล้วพาไปหน้า no-access ให้ขอไฟล์ใหม่จาก e-Court * พอร์ตจาก D:\word-addin-poc\addin\src\services\apiClient.ts * (ช่วง mock ไม่มี caller จริง — เตรียมไว้ให้ versioningService วันปลด mock + AuthRequiredError * ถูก TaskpaneApp ใช้อยู่แล้ว) */ -/** บอกว่า token จากไฟล์ใช้ไม่ได้ (ไม่มี / หมดอายุ / ไม่ถูกต้อง) — ต้องได้ไฟล์ใหม่จาก e-Court */ +/** บอกว่า ticket ใช้ไม่ได้แล้ว (หมดอายุ / token ขอไม่ได้) — ต้องได้ไฟล์ใหม่จาก e-Court */ export class AuthRequiredError extends Error { - constructor(message = "token ของ e-Court หมดอายุหรือไม่ถูกต้อง") { + constructor(message = "ticket ของ e-Court หมดอายุหรือไม่ถูกต้อง") { super(message); this.name = "AuthRequiredError"; } @@ -54,8 +55,10 @@ async function doFetch( } /** - * request พร้อม Bearer — 401 = token จากไฟล์หมดอายุ/ไม่ถูกต้อง → AuthRequiredError - * Response ที่ได้คืนไปให้ caller เช็ค ok เอง + * request พร้อม Bearer — โดน 401 → ขอ token ใหม่จาก ticket ของเอกสารปัจจุบัน + * (renewTokenFromTicket — ticket ยังไม่หมดอายุเท่านั้น) แล้ว retry 1 ครั้ง + * ยัง 401 อีก / renew ไม่ได้ = AuthRequiredError (UI พาไป no-access ให้ขอไฟล์ + * ใหม่จาก e-Court) · Response ที่ได้คืนไปให้ caller เช็ค ok เอง */ export async function apiFetch(path: string, options: ApiRequestOptions = {}): Promise { const accessToken = await authService.getAccessToken(); @@ -63,10 +66,19 @@ export async function apiFetch(path: string, options: ApiRequestOptions = {}): P throw new AuthRequiredError(); } const response = await doFetch(path, options, accessToken); - if (response.status === 401) { - throw new AuthRequiredError(); + if (response.status !== 401) { + return response; } - return response; + if (await authService.renewTokenFromTicket()) { + const retryToken = await authService.getAccessToken(); + if (retryToken) { + const retry = await doFetch(path, options, retryToken); + if (retry.status !== 401) { + return retry; + } + } + } + throw new AuthRequiredError(); } /** อ่าน error message จาก response body — backend error ส่วนใหญ่เป็น plain text, บางทีเป็น JSON problem details */ diff --git a/wordaddin/services/authService.ts b/wordaddin/services/authService.ts index b625ff3..fe92661 100644 --- a/wordaddin/services/authService.ts +++ b/wordaddin/services/authService.ts @@ -1,60 +1,176 @@ /** - * Auth ของ add-in — e-Court pass-through ทางเดียว (ยกเลิก Keycloak 2026-10-27 ตามคำขอผู้ใช้) - * ไม่มีขั้นตอน login ใน add-in อีกต่อไป: ไฟล์ที่ e-Court stamp custom properties - * `token`/`owner` มา (`readTrackedProperties` อ่านรวม) = ใช้งานได้ทันที ไม่ต้องกดปุ่มใด ๆ - * ไฟล์ที่ไม่มีทั้งคู่ = phase no-access ของ TaskpaneApp (ขั้นตอนสำหรับเอกสารทั่วไปจะมีในอนาคต) + * Auth ของ add-in — e-Court ticket ทางเข้าเดียว (PLAN-ADDIN-AUTH.md §5 ตาม + * docs/requirement/addin-workflow.md) * - * token เก็บใน memory เท่านั้น — getAccessToken คืน docToken ให้ apiClient แนบ Bearer - * (ช่วงไม่ต่อ backend จริง versioningService เป็น mock ไม่เรียก apiClient อยู่แล้ว) + * ไม่มี login ใน add-in: ไฟล์ที่ e-Court stamp custom properties `ticket_data` + * + `HmacSha256` มา (ผ่านกลุ่ม template) = ผ่านเข้าได้ — signInWithTicket ตรวจ + * HMAC → exp → session ใน localStorage ตาม uid/fid แล้วขอ access_token จาก + * /api/documents/token เก็บตาม fid ("คีย์คือ fid") + * + * token เก็บใน localStorage (ข้าม session ได้ — กลับมาเปิดเอกสารเดิมไม่ต้องขอใหม่ + * ตาม requirement ขั้น 7) ส่วน ticket ดิบของเอกสารปัจจุบันเก็บใน memory ไว้ renew + * ตอน token หมดอายุกลาง session */ import { config } from "../config"; +import { isTicketExpired, parseDocverTicket } from "@/utils/docver-ticket"; +import * as ticketService from "./ticketService"; -/** state เดียว — null = ยังไม่เคย pass-through สำเร็จ (จากไฟล์ หรือ ?addindemo) */ +/** สาเหตุที่เข้าไม่ได้ — UI แยกข้อความตาม reason (TaskpaneApp) */ +export type SignInFailureReason = "missing" | "tampered" | "expired" | "token-request-failed"; + +export type SignInResult = { ok: true } | { ok: false; reason: SignInFailureReason; message: string }; + +/** state ของ taskpane instance นี้ (ต่อเอกสารที่เปิด) — null = ยังไม่ได้ sign in */ let username: string | null = null; -/** token จาก custom properties ของไฟล์ (e-Court pass-through) — เก็บใน memory เท่านั้น */ -let docToken: string | null = null; +/** fid ของเอกสารปัจจุบัน — token ที่ใช้ผูกกับ fid นี้เสมอ */ +let currentFid: string | null = null; +/** ticket ดิบของเอกสารปัจจุบัน — ไว้ขอ token ใหม่ตอน 401/token หมดอายุ */ +let currentTicket: { data: string; hmac: string } | null = null; /** dev-only — โหมด ?addindemo ของ WordAddinGate ใช้ตั้ง session ทันทีไม่อ่านไฟล์จริง */ export function signInForDemo(): void { username = config.mockUsername; -} - -/** ถอด field จาก JWT payload (base64url) โดยไม่ตรวจ signature — ใช้ดู uid ประกอบชื่อผู้ใช้เท่านั้น */ -function decodeJwtField(token: string, field: string): string | undefined { - const payload = token.split(".")[1]; - if (!payload) return undefined; - try { - // atob คืน binary string (1 char = 1 byte) — ต้องถอด UTF-8 ด้วย TextDecoder ก่อน JSON.parse - // ไม่งั้น uid ที่ไม่ใช่ ASCII (เช่น ชื่อไทย) จะกลายเป็น mojibake - const bytes = Uint8Array.from(atob(payload.replace(/-/g, "+").replace(/_/g, "/")), (char) => char.charCodeAt(0)); - const json: unknown = JSON.parse(new TextDecoder().decode(bytes)); - const value = typeof json === "object" && json !== null ? (json as Record)[field] : undefined; - return typeof value === "string" && value !== "" ? value : undefined; - } catch { - return undefined; - } + currentFid = null; + currentTicket = null; } /** - * e-Court pass-through — จุดเข้าเดียวของ auth: ไฟล์ที่ stamp `token`/`owner` ไว้ - * username ได้จาก owner → uid ใน JWT → mockUsername (ตามลำดับ) - * คืน true = ผ่านเข้าได้ (มี token หรือ owner อย่างน้อยหนึ่งตัว) + * e-Court ticket — จุดเข้าเดียวของ auth ตอนเปิดเอกสาร (requirement ขั้น 1–7): + * 1. ตรวจมี ticket_data + HmacSha256 ครบ + * 2. parse + ตรวจ HMAC ฝั่ง client (UX gate) + * 3. session ใน localStorage: ยังไม่มี/user ใหม่ → ตรวจ exp → เคลียร์ session เก่า + * แล้วขอ token ใหม่ · user เดิม + fid เดิม → ผ่านเลย (renew เงียบ ๆ ถ้า token + * หมดอายุแต่ ticket ยังไม่หมด) · user เดิม + fid ใหม่ → ตรวจ exp → ขอ token เก็บเพิ่ม */ -export function signInFromDocument(input: { token?: string; owner?: string }): boolean { - const token = input.token?.trim() || undefined; - const owner = input.owner?.trim() || (token ? decodeJwtField(token, "uid") : undefined); - if (!token && !owner) { - return false; +export async function signInWithTicket(input: { + ticketData?: string; + ticketHmac?: string; +}): Promise { + const ticketData = input.ticketData ?? ""; + const ticketHmac = input.ticketHmac ?? ""; + // Word custom property จำกัด value ~255 ตัวอักษร — เกิน = ไฟล์เสียหาย (ถูกตัด) + if (ticketData === "" || ticketHmac === "" || ticketData.length > 255 || ticketHmac.length > 255) { + return { + ok: false, + reason: "missing", + message: "ไม่พบ ticket ของ e-Court ในไฟล์ — add-in ใช้ได้กับเอกสารที่ส่งมาจาก e-Court เท่านั้น", + }; } - username = owner ?? config.mockUsername; - docToken = token ?? null; - return true; + + const parsed = parseDocverTicket(ticketData); + if (!parsed.ok) { + return { + ok: false, + reason: "tampered", + message: "ตรวจสอบ ticket ไม่ผ่าน (รูปแบบไม่ถูกต้อง) — ไฟล์ถูกแก้ไขหรือเสียหาย", + }; + } + if (!(await ticketService.verifyTicketHmacWeb(ticketData, ticketHmac, config.ticketSecret))) { + return { + ok: false, + reason: "tampered", + message: "ตรวจสอบลายเซ็น (HMAC) ของ ticket ไม่ผ่าน — ไฟล์ถูกแก้ไข", + }; + } + const ticket = parsed.ticket; + const ticketAlive = !isTicketExpired(ticket.exp); + + const session = ticketService.loadSession(); + if (session === null || session.userId !== ticket.uid) { + // ไม่มี session / user ใหม่ — ตรวจ exp ก่อนแล้วเริ่ม session ใหม่ทั้งก้อน + if (!ticketAlive) { + return { + ok: false, + reason: "expired", + message: "ticket ของ e-Court หมดอายุ — โปรดขอไฟล์ใหม่จาก e-Court แล้วเปิดอีกครั้ง", + }; + } + ticketService.clearSession(); + let entry: ticketService.SessionTokenEntry; + try { + entry = await ticketService.requestDocverToken(ticketData, ticketHmac); + } catch { + return { + ok: false, + reason: "token-request-failed", + message: "ขอ token จากระบบไม่สำเร็จ — ตรวจการเชื่อมต่อแล้วเปิดเอกสารอีกครั้ง", + }; + } + ticketService.saveSession({ userId: ticket.uid, userName: ticket.uname, tokens: { [ticket.fid]: entry } }); + } else { + // คนเดิม — userName อัปเดตตามไฟล์ล่าสุด (ชื่อเปลี่ยนได้) + session.userName = ticket.uname; + let entry = session.tokens[ticket.fid] ?? null; + if (entry !== null && ticketService.isTokenExpired(entry)) { + // token หมดอายุ — renew ได้เฉพาะตอน ticket ยังไม่หมด; ticket หมดด้วยก็เก็บของ + // เก่าไว้ (requirement ขั้น 7: ผ่านเลยไม่ตรวจ exp) ให้ server ตัดสินตอนใช้งาน + if (ticketAlive) { + try { + entry = await ticketService.requestDocverToken(ticketData, ticketHmac); + session.tokens[ticket.fid] = entry; + } catch { + // ใช้ token เก่าต่อ — ถ้าเน่าจริง apiClient จะ 401 → renew-retry → no-access + } + } + } + if (entry === null) { + // เอกสารใหม่ของคนเดิม — ตรวจ exp แล้วขอ token เก็บเพิ่ม + if (!ticketAlive) { + return { + ok: false, + reason: "expired", + message: "ticket ของ e-Court หมดอายุ — โปรดขอไฟล์ใหม่จาก e-Court แล้วเปิดอีกครั้ง", + }; + } + try { + session.tokens[ticket.fid] = await ticketService.requestDocverToken(ticketData, ticketHmac); + } catch { + return { + ok: false, + reason: "token-request-failed", + message: "ขอ token จากระบบไม่สำเร็จ — ตรวจการเชื่อมต่อแล้วเปิดเอกสารอีกครั้ง", + }; + } + } + ticketService.saveSession(session); + } + + currentFid = ticket.fid; + currentTicket = { data: ticketData, hmac: ticketHmac }; + username = ticket.uname; + return { ok: true }; } -/** Bearer ของ apiClient — docToken จากไฟล์ (ส่วน demo/mock คืน token ปลอม) */ +/** + * ขอ token ใหม่จาก ticket ของเอกสารปัจจุบัน (ticket ต้องยังไม่หมดอายุ) — + * เรียกจาก apiClient ตอนโดน 401 ก่อน throw AuthRequiredError 1 รอบ + * คืน true = ได้ token ใหม่เรียบร้อย (getAccessToken จะเห็นค่าใหม่) + */ +export async function renewTokenFromTicket(): Promise { + if (currentTicket === null || currentFid === null) return false; + const parsed = parseDocverTicket(currentTicket.data); + if (!parsed.ok || isTicketExpired(parsed.ticket.exp)) return false; + try { + const entry = await ticketService.requestDocverToken(currentTicket.data, currentTicket.hmac); + const session = + ticketService.loadSession() ?? + { userId: parsed.ticket.uid, userName: parsed.ticket.uname, tokens: {} }; + session.userName = parsed.ticket.uname; + session.tokens[parsed.ticket.fid] = entry; + ticketService.saveSession(session); + return true; + } catch { + return false; + } +} + +/** Bearer ของ apiClient — token ของ fid ปัจจุบัน (stale ได้ — apiClient เป็นคนตัดสิน 401) */ export async function getAccessToken(): Promise { - if (docToken) return docToken; + if (currentFid !== null) { + return ticketService.loadSession()?.tokens[currentFid]?.token ?? null; + } + // demo mode (?addindemo) — ไม่มี ticket ใช้ token ปลอมเหมือนเดิม return username ? `mock-token-${username}` : null; } diff --git a/wordaddin/services/metadataService.ts b/wordaddin/services/metadataService.ts index 74c33d3..68a8693 100644 --- a/wordaddin/services/metadataService.ts +++ b/wordaddin/services/metadataService.ts @@ -23,9 +23,10 @@ export const LAST_VERSION_TIMESTAMP_PROPERTY = "LastVersionTimestamp"; /** FR-16 — stamp เฉพาะไฟล์ร่าง (draft) ที่เว็บสร้าง: template ต้นทาง (ค่าเดียวกับ AddInMetadata.TemplateIdPropertyName ฝั่ง backend) */ export const TEMPLATE_ID_PROPERTY = "TemplateId"; -/** e-Court pass-through — ไฟล์ที่ e-Court stamp มาใน custom properties เข้าใช้ได้เลย (auth ทางเข้าเดียว) */ -export const ECOURT_TOKEN_PROPERTY = "token"; -export const ECOURT_OWNER_PROPERTY = "owner"; +/** e-Court ticket — ไฟล์ที่ e-Court stamp คู่นี้มาใน custom properties คือ auth ทางเข้าเดียวของ add-in + * (PLAN-ADDIN-AUTH.md §2: HMAC-SHA256(secret, ticket_data ดิบ) เป็น hex lowercase 64 ตัว) */ +export const TICKET_DATA_PROPERTY = "ticket_data"; +export const TICKET_HMAC_PROPERTY = "HmacSha256"; /** ค่า property ที่หาไม่เจอเป็น `undefined` (ไม่ใช่ throw) */ export interface TrackedDocumentProperties { @@ -35,9 +36,9 @@ export interface TrackedDocumentProperties { lastVersionTimestamp?: string; /** มีเฉพาะไฟล์ร่าง (FR-16) — ส่งต่อให้ backend ตอน Start Versioning เพื่อผูก Document กับ template */ templateId?: string; - /** e-Court pass-through — มีอย่างใดอย่างหนึ่ง = ใช้งานได้ทันที (username จาก owner หรือ uid ใน token) */ - ecourtToken?: string; - ecourtOwner?: string; + /** e-Court ticket — มีครบทั้งคู่ = ตรวจ HMAC แล้วเข้าใช้งานได้ (auth ทางเข้าเดียว) */ + ticketData?: string; + ticketHmac?: string; } /** item ใน Custom XML Part `` */ @@ -49,7 +50,7 @@ export interface XmlVersionEntry { } /** อ่าน custom properties ทั้ง 7 ตัวที่ระบบสนใจ (4 ตัวของ tracked + TemplateId ของไฟล์ร่าง FR-16 - * + token/owner ที่ e-Court stamp มากับไฟล์) */ + * + ticket_data/HmacSha256 ที่ e-Court stamp มากับไฟล์) */ export async function readTrackedProperties(): Promise { return Word.run(async (context) => { const properties = context.document.properties.customProperties; @@ -58,16 +59,16 @@ export async function readTrackedProperties(): Promise @@ -81,8 +82,8 @@ export async function readTrackedProperties(): Promise; +} + +/** memory fallback — ใช้เมื่อ localStorage โดน block (webview บางตัว/private mode) */ +let memorySession: AddinSession | null = null; + +function parseSession(raw: string | null): AddinSession | null { + if (!raw) return null; + try { + const parsed = JSON.parse(raw) as AddinSession; + if ( + typeof parsed.userId !== "string" || + typeof parsed.userName !== "string" || + parsed.userId === "" || + typeof parsed.tokens !== "object" || + parsed.tokens === null || + Array.isArray(parsed.tokens) + ) { + return null; + } + for (const entry of Object.values(parsed.tokens)) { + if (typeof entry.token !== "string" || typeof entry.exp !== "number") return null; + } + return parsed; + } catch { + return null; + } +} + +export function loadSession(): AddinSession | null { + if (typeof window === "undefined") return memorySession; // SSR ของ client component + try { + return parseSession(window.localStorage.getItem(SESSION_STORAGE_KEY)) ?? memorySession; + } catch { + return memorySession; + } +} + +/** เขียน session — localStorage พัง = เก็บ memory ต่อ (ไม่ throw) */ +export function saveSession(session: AddinSession): void { + memorySession = session; + if (typeof window === "undefined") return; + try { + window.localStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(session)); + } catch { + // ignore — quota/private mode: มี memory ของ session นี้อยู่ + } +} + +/** ล้างทั้งก้อน — ใช้ตอนเจอ user ใหม่ (requirement ขั้น 6: เคลียร์ Session เดิม) */ +export function clearSession(): void { + memorySession = null; + if (typeof window === "undefined") return; + try { + window.localStorage.removeItem(SESSION_STORAGE_KEY); + } catch { + // ignore + } +} + +/** token หมดอายุหรือยัง (มี margin 30 วิ กันขอบเขตตกหล่น) */ +export function isTokenExpired(entry: SessionTokenEntry): boolean { + return entry.exp * 1000 <= Date.now() - 30_000; +} + +// --------------------------------------------------------------------------- +// HMAC ฝั่ง client — Web Crypto (crypto.subtle ให้ promise เสมอ) +// --------------------------------------------------------------------------- + +/** คำนวณ HMAC-SHA256 คืน hex lowercase — รูปแบบเดียวกับ generateHmacSha256 ฝั่ง server */ +export async function hmacSha256HexWeb(message: string, secret: string): Promise { + const encoder = new TextEncoder(); + const key = await crypto.subtle.importKey( + "raw", + encoder.encode(secret), + { name: "HMAC", hash: "SHA-256" }, + false, + ["sign"], + ); + const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(message)); + return Array.from(new Uint8Array(signature), (byte) => byte.toString(16).padStart(2, "0")).join(""); +} + +/** + * ตรวจ HMAC ของ ticket ฝั่ง client — message คือ string ดิบของ ticket_data ตามที่อ่านจาก + * property (ห้าม trim — ตัว stamp เซ็นบนค่าดิบ) · เทียบหลัง normalize lowercase + */ +export async function verifyTicketHmacWeb(ticketData: string, expectedHex: string, secret: string): Promise { + const expected = normalizeHexDigest(expectedHex); + if (expected === null) return false; + try { + return (await hmacSha256HexWeb(ticketData, secret)) === expected; + } catch { + return false; + } +} + +// --------------------------------------------------------------------------- +// ขอ token จาก /api/documents/token (ทางเข้า ticket — PLAN-ADDIN-AUTH.md §3) +// --------------------------------------------------------------------------- + +interface TokenResponse { + access_token: string; + token_type: string; + expires_in: number; +} + +/** ถอด exp จาก JWT payload โดยไม่ตรวจ signature (แม่นกว่า expires_in — ใช้นาฬิกา server) */ +function jwtExp(token: string): number | undefined { + const payload = token.split(".")[1]; + if (!payload) return undefined; + try { + const bytes = Uint8Array.from(atob(payload.replace(/-/g, "+").replace(/_/g, "/")), (char) => char.charCodeAt(0)); + const json: unknown = JSON.parse(new TextDecoder().decode(bytes)); + const exp = typeof json === "object" && json !== null ? (json as Record).exp : undefined; + return typeof exp === "number" && Number.isFinite(exp) ? exp : undefined; + } catch { + return undefined; + } +} + +/** + * ขอ access_token ด้วย ticket — โยน Error เมื่อ status ไม่สำเร็จ + * (401 = ticket ไม่ผ่านการตรวจฝั่ง server — ให้ caller ไป no-access ตาม flow) + */ +export async function requestDocverToken(ticketData: string, ticketHmac: string): Promise { + const response = await fetch(withBasePath("/api/documents/token"), { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ ticket_data: ticketData, HmacSha256: ticketHmac }), + }); + if (!response.ok) { + throw new Error(`ขอ token ไม่สำเร็จ (HTTP ${response.status})`); + } + let data: TokenResponse; + try { + data = (await response.json()) as TokenResponse; + } catch { + throw new Error("ขอ token ไม่สำเร็จ (response ไม่ใช่ JSON)"); + } + if (typeof data.access_token !== "string" || data.access_token === "") { + throw new Error("ขอ token ไม่สำเร็จ (ไม่มี access_token)"); + } + const exp = + jwtExp(data.access_token) ?? + (typeof data.expires_in === "number" ? Math.floor(Date.now() / 1000) + data.expires_in : 0); + return { token: data.access_token, exp }; +} From 42001762396b7fdb1b8eb5c338ccea189e906f88 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 27 Oct 2026 11:54:05 +0700 Subject: [PATCH 18/21] test(addin): convert crash-repro suite to permanent ticket-auth flow tests Rename tests/wordaddin/ticket-auth-crash-repro.test.ts to ticket-auth-flow.test.ts and strip the temporary [DBG-r3p1] markers. The debug session closed with an environment cause (stale Next server holding the port / corrupted .next chunks), not a code defect, so the suite stays as regression coverage for the signInWithTicket service layer: single token fetch per fid, token reuse on reopen without re-hitting /token, memory-fallback session teardown, and no uncaught throw/rejection on every failure path (missing/invalid ticket, expired ticket, server 401, webview without crypto.subtle). Co-Authored-By: Claude Code --- ...h-crash-repro.test.ts => ticket-auth-flow.test.ts} | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) rename tests/wordaddin/{ticket-auth-crash-repro.test.ts => ticket-auth-flow.test.ts} (86%) diff --git a/tests/wordaddin/ticket-auth-crash-repro.test.ts b/tests/wordaddin/ticket-auth-flow.test.ts similarity index 86% rename from tests/wordaddin/ticket-auth-crash-repro.test.ts rename to tests/wordaddin/ticket-auth-flow.test.ts index abecb22..250a5fa 100644 --- a/tests/wordaddin/ticket-auth-crash-repro.test.ts +++ b/tests/wordaddin/ticket-auth-flow.test.ts @@ -1,14 +1,15 @@ // @vitest-environment jsdom -// [DBG-r3p1] repro ชั่วคราว — หา client-side crash ของ taskpane หลัง implement ticket auth -// (ไม่ใช่ regression test ถาวร — ถอดทิ้งเมื่อหาสาเหตุเจอแล้ว; import .tsx ไม่ได้ใน vitest ปัจจุบัน -// จึงโฟกัส service layer ที่เป็นโค้ดใหม่ทั้งหมด — component repro ใช้ Chrome headless แทน) +// ticket auth ของ add-in (PLAN-ADDIN-AUTH.md §5) — คุมพฤติกรรม service layer: +// happy-path ขอ token ครั้งเดียวต่อ fid, เปิดเอกสารเดิมซ้ำใช้ token เดิมไม่ยิง /token ซ้ำ, +// memory fallback ของ session, และทุก fail path ต้องคืน result ไม่ใช่ throw +// (jsdom ไม่มี crypto.subtle — ฉีด webcrypto ของ node แทน secure context ของ webview จริง) import { createHmac, webcrypto } from "node:crypto"; import { afterEach, describe, expect, it, vi } from "vitest"; import * as authService from "@/wordaddin/services/authService"; import * as ticketService from "@/wordaddin/services/ticketService"; -// จับ error ทุกทางที่จะกลายเป็น "Application error: a client-side exception" บน Next +// จับ error ทุกทาง (window error / unhandled rejection) — flow ต้องไม่ปล่อยอะไรหลุดเลย const errors: unknown[] = []; function trapErrors() { window.addEventListener("error", (event) => errors.push(event.error ?? event.message)); @@ -38,7 +39,7 @@ afterEach(() => { errors.length = 0; }); -describe("[DBG-r3p1] ticket auth crash repro (service layer)", () => { +describe("signInWithTicket (service layer)", () => { it("B: signInWithTicket happy-path (มี crypto.subtle + fetch ตอบ 200) ไม่ต้อง throw", async () => { trapErrors(); // jsdom ไม่มี crypto.subtle — ใส่ webcrypto ของ node แทน (secure context ของ webview จริงมีอยู่แล้ว) From 37b18c58c24a5230c09ad73690157901a95a2ea2 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 27 Oct 2026 12:52:29 +0700 Subject: [PATCH 19/21] =?UTF-8?q?feat(addin):=20unmock=20versioning=20?= =?UTF-8?q?=E2=80=94=20real=20upload/download=20against=20docver=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the in-memory versioningService with real calls to the docver group (/api/documents/*) via apiClient with the ticket-issued Bearer; keep the mock only for ?addindemo (authService.isDemoMode). - uploadVersion: multipart POST /api/documents/versions (file, optional pdf when non-empty, clientVersionId, fields JSON, trimmed message, fileName) — identity fields are never sent (userId/userName/templateId in the form = 400 per the 2026-10-22 contract; docId comes from the fid claim) - getVersions: GET → DocumentVersionDTO mapped to VersionRecord (adds optional createdByUserName; history panel shows it with userId fallback) - getDownloadLink: GET presigned URL (+ ?format=pdf) - apiClient.toUrl: empty NEXT_PUBLIC_ADDIN_API_BASE_URL = same-origin through withBasePath (/docedit/api/...); the env is now only for a split deployment (must include basePath, HTTPS) - documentId/templateId params stay in signatures but are not sent - tests: versioningService.test.ts covers both modes (real sign-in via signInWithTicket, FormData contract assertions, DTO mapping, format query, 401 -> AuthRequiredError, demo mock without network) - docs sync: WORDADDIN-DEV (new "การรันกับ backend จริง" section), addin-ui-flow status note, PLAN-DOCVER §8, PLAN-ADDIN-AUTH §12, CLAUDE.md Verified: tsc clean, 286/286 tests, full E2E against MinIO (ticket -> token -> upload docx / docx+pdf -> history -> presigned download of both formats). Co-Authored-By: Claude Code --- CLAUDE.md | 4 +- components/wordaddin/VersionHistoryPanel.tsx | 2 +- docs/PLAN-ADDIN-AUTH.md | 5 +- docs/PLAN-DOCVER.md | 5 +- docs/WORDADDIN-DEV.md | 13 +- docs/addin-ui-flow.md | 2 +- tests/wordaddin/versioningService.test.ts | 197 +++++++++++++++++++ wordaddin/models/versionRecord.ts | 2 + wordaddin/services/apiClient.ts | 10 +- wordaddin/services/authService.ts | 9 + wordaddin/services/versioningService.ts | 192 +++++++++++++----- 11 files changed, 373 insertions(+), 68 deletions(-) create mode 100644 tests/wordaddin/versioningService.test.ts diff --git a/CLAUDE.md b/CLAUDE.md index f7d1fe1..4e15d3e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,7 +12,7 @@ - Next.js 14 (App Router) + TypeScript **strict** — serve ใต้ basePath `/docedit` + route ทั้งหมดของแอปใต้ `app/note/**` (URL ปลายทางยังเป็น `/docedit/note/…` เหมือนเดิม — basePath `/docedit` เป็นพื้นที่กลางรองรับ sibling ในอนาคต เช่น wordaddin, `next.config.mjs`) - pdfjs-dist **v4** (`^4.10.38`) — built-in TextLayer + worker จาก `public/note/pdf.worker.min.mjs` (postinstall `scripts/patch-pdf-worker.mjs` คัดลอกจาก node_modules + **patch fake-space ไทย** — รายละเอียด invariant #6) - Drizzle ORM + PostgreSQL (Docker container เดียว: `judes-note-db`, image `postgres:17-alpine`, พอร์ต 54322) — **ไม่ใช้ Supabase** + realtime sync ด้วย **SSE (Server-Sent Events)** ผ่าน `app/api/note/annotations/stream` + pub/sub in-process (`lib/annotate-events.ts`) -- **wordaddin** — taskpane ของ Word add-in "e-Court Documents Versioning" พอร์ตมาเป็น route `/docedit/wordaddin` ของแอปนี้ (UI = Genesis, ชั้น services Office.js พอร์ตดิบจาก `D:\word-addin-poc\addin`) — **ไม่มี login ยกเลิก Keycloak 2026-10-27**: auth = **ticket ของ e-Court** (`ticket_data` + `HmacSha256` ใน custom properties — add-in ตรวจ HMAC เองฝั่ง client แล้วขอ token ที่ `/api/documents/token` แบบ body ticket เก็บใน localStorage `wordaddin-auth` ตาม fid, `docs/PLAN-ADDIN-AUTH.md`) ส่วน API ยังเป็น **mock** รอของจริง (spec UI: `docs/addin-ui-flow.md`, คู่มือ dev/sideload/สลับกลับ: `docs/WORDADDIN-DEV.md`); Office.js **self-host** ที่ `public/wordaddin/office-js/` ผ่าน postinstall `scripts/fetch-office-js.mjs` — gitignore (ดาวน์โหลดฉบับล่าสุดจาก CDN ของ Microsoft ตอนติดตั้ง ตัวระบบ deploy ใช้ intranet ล้วน; แพ็กเกจ npm `@microsoft/office-js` เลิกอัปเดตที่ 1.1.110 ก่อน WordApi 1.4/1.5 — runtime ตัวนั้นไม่มี `Document.close` ใช้ไม่ได้) +- **wordaddin** — taskpane ของ Word add-in "e-Court Documents Versioning" พอร์ตมาเป็น route `/docedit/wordaddin` ของแอปนี้ (UI = Genesis, ชั้น services Office.js พอร์ตดิบจาก `D:\word-addin-poc\addin`) — **ไม่มี login ยกเลิก Keycloak 2026-10-27**: auth = **ticket ของ e-Court** (`ticket_data` + `HmacSha256` ใน custom properties — add-in ตรวจ HMAC เองฝั่ง client แล้วขอ token ที่ `/api/documents/token` แบบ body ticket เก็บใน localStorage `wordaddin-auth` ตาม fid, `docs/PLAN-ADDIN-AUTH.md`) และ **upload/download เป็นของจริงแล้ว** (ปลด mock 2026-10-27 — `versioningService` ยิง `/api/documents/*` ผ่าน Bearer same-origin; mock เหลือเฉพาะ `?addindemo`) (spec UI: `docs/addin-ui-flow.md`, คู่มือ dev/sideload: `docs/WORDADDIN-DEV.md`); Office.js **self-host** ที่ `public/wordaddin/office-js/` ผ่าน postinstall `scripts/fetch-office-js.mjs` — gitignore (ดาวน์โหลดฉบับล่าสุดจาก CDN ของ Microsoft ตอนติดตั้ง ตัวระบบ deploy ใช้ intranet ล้วน; แพ็กเกจ npm `@microsoft/office-js` เลิกอัปเดตที่ 1.1.110 ก่อน WordApi 1.4/1.5 — runtime ตัวนั้นไม่มี `Document.close` ใช้ไม่ได้) - Tailwind CSS + design system **Genesis** (`docs/DESIGN.md`, 2026-09-23) — palette tokens ใน `tailwind.config.ts` (`primary` เดิม + `primary-hover`/`canvas`/`line`/`ink`/`ink-soft`/`ink-faint`/`success`/`warning`/`error` + ระดับข้อความ `success-ink`/`warning-ink`/`error-ink` ≥5:1 สำหรับข้อความ/ไอคอนสถานะ — token 500 ใช้กับ border/bg tint เท่านั้น) ห้ามใช้ raw palette shades ของ Tailwind (`slate-*`/`amber-*`/`emerald-*`/`red-*` ฯลฯ) อีก; ฟอนต์ display = **General Sans** (self-host จาก Fontshare ผ่าน `next/font/local` — `app/fonts/`, variable weight 200–700, ใช้ intranet ได้), body = **DM Sans** (`next/font/google`), ไทย fallback **Sarabun** ตามลำดับใน stack (Latin ก่อนไทย — `fontFamily`/globals.css); รัศมี: ปุ่ม/input 6px (รวมปุ่ม action เล็กใน popup/composer/dialog — คู่ ยกเลิก/บันทึก ต้องได้รัศมีเดียวกัน), menu/list row ใน dropdown/panel 4px, panel/dropdown 8px, การ์ด 12px; เงาเฉพาะ hover (`shadow-card-hover`/`shadow-btn-glow` — static ไม่มีเงา ยกเว้นชั้น floating: dropdown/popover `shadow-lg` และ pin ลอยบนเอกสาร ดู §App-specific exceptions ของ DESIGN.md); focus indicator = outline น้ำเงินทึบ 2px + offset ผ่าน class `.focus-ring`/`.genesis-input` ใน globals.css (+ สำเนา `FOCUS_RING_CLS` ใน PDFViewer/AnnotationSidebar/AnnotationSelectionLayer/ToolControls — แก้ต้องแก้พร้อมกัน; halo alpha จางเกิน WCAG 1.4.11); น้ำเงินเฉพาะของ interactive (แบรนด์มาร์ก tile JN/แถบหัวหน้าสรุปยกเว้น) - mock login e-Court — หน้า `/note/login` (dropdown `MOCK_USERS` user1–user5 + รหัสผ่านกลาง `Demo1234`) ตั้ง cookie `oacCase` ตาม format จริงของระบบศาล → identity (userId = `u_userid`, userName = `u_userlogin` — **ต่างกันต่อคน** เพื่อสิทธิ์เจ้าของ) อ่านจาก cookie ผ่าน `lib/session.ts` + บังคับทุกหน้าด้วย `middleware.ts` (auth จริงเทียบระบบ e-Court + roles ยัง out of scope) @@ -58,7 +58,7 @@ lib/docver-store.ts ← docver server-side MinIO + DB (env MINIO_* อ่า wordaddin/ ← **client-only zone ของ Word add-in** (ห้าม import จาก lib/ หรือถูก import โดย server component — พอร์ตดิบจาก D:\word-addin-poc\addin\src, spec: docs/addin-ui-flow.md, คู่มือ dev: docs/WORDADDIN-DEV.md) wordaddin/config.ts ← env NEXT_PUBLIC_ADDIN_API_BASE_URL ("" = ยังไม่ต่อ backend จริง) + NEXT_PUBLIC_ADDIN_MOCK_USERNAME (ชื่อ fallback ของ ?addindemo/ไฟล์ที่มี token ไม่มี owner) wordaddin/models/versionRecord.ts ← types VersionRecord/FieldsSnapshot + readSnapshotField (พอร์ตดิบ) -wordaddin/services/ ← documentService/fileService/metadataService/autoSaveService/versionFlow/apiClient/errors พอร์ตดิบทั้งไฟล์ + **templateService** (แท็บที่ 2 "เครื่องมือ Template" 2026-10-19 — list/select/applyTestValues + แทรกย่อหน้า-ตัวแบ่งบรรทัด-ตัวแบ่งหน้า ที่ cursor (page break มี fallback OOXML กัน E_FAIL), Office.js ล้วน — ⚠️ `contentControls` ธรรมดาเห็นเฉพาะ rich text CC: feature-detect WordApi 1.5 ใช้ `getContentControls()` เห็นทุกชนิด, ห้าม top-level Office access) · **authService + ticketService = ticket auth ของ e-Court ทางเดียว ไม่มี login แล้ว** (ยกเลิก Keycloak → pass-through token/owner → ticket 2026-10-27, `docs/PLAN-ADDIN-AUTH.md` ตาม `docs/requirement/addin-workflow.md` — surface เหลือ signInForDemo (dev-only) / signInWithTicket / getAccessToken / renewTokenFromTicket / getUsername; ไฟล์ที่ stamp custom properties `ticket_data`+`HmacSha256` มา = `detect()` ของ TaskpaneApp เรียก signInWithTicket: ตรวจ HMAC ฝั่ง client (Web Crypto + NEXT_PUBLIC_ADDIN_TICKET_SECRET — UX gate) → exp → session localStorage `wordaddin-auth` ตาม uid (user ใหม่ = เคลียร์ทั้งก้อน) → ขอ token ที่ `/api/documents/token` แบบ body ticket ผูก fid; ไม่มี ticket/HMAC ไม่ตรง/หมดอายุ = phase `no-access` แยกข้อความตามสาเหตุ; apiClient โดน 401 = renew จาก ticket 1 ครั้ง ยังไม่ผ่าน = AuthRequiredError → no-access) · **versioningService = MOCK** (memory + **metadata persist ลง localStorage ต่อ documentId** เพื่อทดสอบวงปิดเอกสาร→เปิดใหม่ได้ — blob ไฟล์จริงเฉพาะ session; คงชื่อ/ลายเซ็น uploadVersion/getVersions/getDownloadLink/newClientVersionId เป๊ะ, download-link คืน about:blank — **backend จริงของ surface นี้มีแล้ว**: กลุ่ม `app/api/documents/**` (docver, docs/PLAN-DOCVER.md — สลับ mock ด้วยการตั้ง `NEXT_PUBLIC_ADDIN_API_BASE_URL`) +wordaddin/services/ ← documentService/fileService/metadataService/autoSaveService/versionFlow/apiClient/errors พอร์ตดิบทั้งไฟล์ + **templateService** (แท็บที่ 2 "เครื่องมือ Template" 2026-10-19 — list/select/applyTestValues + แทรกย่อหน้า-ตัวแบ่งบรรทัด-ตัวแบ่งหน้า ที่ cursor (page break มี fallback OOXML กัน E_FAIL), Office.js ล้วน — ⚠️ `contentControls` ธรรมดาเห็นเฉพาะ rich text CC: feature-detect WordApi 1.5 ใช้ `getContentControls()` เห็นทุกชนิด, ห้าม top-level Office access) · **authService + ticketService = ticket auth ของ e-Court ทางเดียว ไม่มี login แล้ว** (ยกเลิก Keycloak → pass-through token/owner → ticket 2026-10-27, `docs/PLAN-ADDIN-AUTH.md` ตาม `docs/requirement/addin-workflow.md` — surface เหลือ signInForDemo (dev-only) / signInWithTicket / getAccessToken / renewTokenFromTicket / getUsername; ไฟล์ที่ stamp custom properties `ticket_data`+`HmacSha256` มา = `detect()` ของ TaskpaneApp เรียก signInWithTicket: ตรวจ HMAC ฝั่ง client (Web Crypto + NEXT_PUBLIC_ADDIN_TICKET_SECRET — UX gate) → exp → session localStorage `wordaddin-auth` ตาม uid (user ใหม่ = เคลียร์ทั้งก้อน) → ขอ token ที่ `/api/documents/token` แบบ body ticket ผูก fid; ไม่มี ticket/HMAC ไม่ตรง/หมดอายุ = phase `no-access` แยกข้อความตามสาเหตุ; apiClient โดน 401 = renew จาก ticket 1 ครั้ง ยังไม่ผ่าน = AuthRequiredError → no-access) · **versioningService = ของจริง** (ปลด mock 2026-10-27 — uploadVersion/getVersions/getDownloadLink ยิงกลุ่ม `/api/documents/*` ผ่าน apiClient (Bearer same-origin ผ่าน withBasePath — `NEXT_PUBLIC_ADDIN_API_BASE_URL` ตั้งเฉพาะวัน deploy คนละ origin ต้องรวม basePath); เอกสารระบุด้วย fid ใน token — พารามิเตอร์ documentId/templateId เหลือตาม signature เดิมไม่ส่งถึง server และ **ห้ามส่ง userId/userName/templateId ใน form (contract 2026-10-22 ส่งมา = 400)**; mock เหลือเฉพาะโหมด `?addindemo` ผ่าน `authService.isDemoMode()` — memory + metadata persist ลง localStorage ต่อ documentId, download-link คืน about:blank; unit test tests/wordaddin/versioningService.test.ts คุมทั้งสองโหมด) utils/coordinates.ts ← pixel⇄% + getPointerPercent + clamp + mergeRectsByLine utils/geometry.ts ← bbox/translate/scale/hitTestPath ของ annotation (pure, หน่วย %) utils/annotation-display.ts ← label/ข้อความย่อต่อชนิด + TYPE_SHORT/TYPE_ORDER ชื่อย่อ/ลำดับชนิด (บรรทัดสรุปหน้าของหน้าสรุป + แผงตัวกรอง explorer — ย้ายจาก 2 ที่ซ้ำกันมารวมที่นี่ 2026-09-24) + ค่า default TEXTBOX/STAMP + describeAnnotationChange สรุปสิ่งที่เปลี่ยนของ version history F19 (sidebar/สรุป/groups/CSV ใช้ร่วม) diff --git a/components/wordaddin/VersionHistoryPanel.tsx b/components/wordaddin/VersionHistoryPanel.tsx index 7755035..930a4d1 100644 --- a/components/wordaddin/VersionHistoryPanel.tsx +++ b/components/wordaddin/VersionHistoryPanel.tsx @@ -135,7 +135,7 @@ export function VersionHistoryPanel({

) : null}

- {formatTimestamp(version.createdAt)} · โดย {version.createdByUserId} + {formatTimestamp(version.createdAt)} · โดย {version.createdByUserName ?? version.createdByUserId}

); diff --git a/docs/PLAN-ADDIN-AUTH.md b/docs/PLAN-ADDIN-AUTH.md index 12fda16..a6b8f62 100644 --- a/docs/PLAN-ADDIN-AUTH.md +++ b/docs/PLAN-ADDIN-AUTH.md @@ -209,7 +209,6 @@ Dev tool รันมือ `node scripts/make-ticket-docx.mjs [opti ## 12. Out of scope -- ปลด mock `versioningService` (ยัง mock ต่อ — token/flow ที่ทำรอบนี้พร้อมใช้วันปลด) -- ความไม่ตรงกันของ `documentId` (GUID ที่ stamp) กับ `docId` (= fid) ของกลุ่ม docver — - เป็นเรื่องของวันปลด mock (token ผูก fid แต่ mock ยังเรียกด้วย GUID — ไม่กระทบ auth) +- ~~ปลด mock `versioningService`~~ (ทำแล้ว 2026-10-27 — `versioningService` ยิงกลุ่ม docver จริงผ่าน Bearer same-origin; mock เหลือเฉพาะ `?addindemo` ดู `docs/WORDADDIN-DEV.md`) +- ~~ความไม่ตรงกันของ `documentId` (GUID ที่ stamp) กับ `docId` (= fid)~~ (ยุติแล้ว 2026-10-27 — เอกสารระบุด้วย fid ใน token ฝั่งเดียว; พารามิเตอร์ documentId ของ versioningService เหลือตาม signature เดิมไม่ส่งถึง server) - UI จัดการ session/ออกจากระบบ, refresh token แบบมี revoke, ฝั่ง e-Court stamp (เขาทำ — เราส่ง §2 ให้) diff --git a/docs/PLAN-DOCVER.md b/docs/PLAN-DOCVER.md index 652097f..3f9769f 100644 --- a/docs/PLAN-DOCVER.md +++ b/docs/PLAN-DOCVER.md @@ -1,7 +1,8 @@ # PLAN-DOCVER — Document Versioning API (stand-alone) > วันที่ตกลงแผน: 2026-10-20 · เพิ่ม auth ด้วย access_token: 2026-10-22 · สถานะ: implement -> รอบแรก (API ก่อน — ยังไม่ต่อ wordaddin) +> รอบแรก (API ก่อน) · **ปลด mock wordaddin แล้ว 2026-10-27** — `versioningService` +> ยิงกลุ่มนี้จริงผ่าน Bearer (mock เหลือเฉพาะ `?addindemo`) ## 0. บริบท/เป้าหมาย @@ -225,6 +226,6 @@ best-effort + ASCII fallback `document.docx`/`document.pdf`) — presigned GET ## 8. Out of scope รอบนี้ -- ไม่แต้ `wordaddin/` หรือ client ใด ๆ (สลับ mock ทีหลังผ่าน `NEXT_PUBLIC_ADDIN_API_BASE_URL`) +- ~~ไม่แต้ `wordaddin/` หรือ client ใด ๆ~~ (ปลด mock แล้ว 2026-10-27 — `wordaddin/services/versioningService.ts` ยิงกลุ่มนี้จริง same-origin ผ่าน Bearer; ดู `docs/WORDADDIN-DEV.md` หัว "การรันกับ backend จริง") - ไม่มีหน้า UI ดูรายการเอกสาร/version ของกลุ่มนี้ · ไม่มี GC object กำพร้า · ไม่มี idempotency บน `clientVersionId` · ไม่มีการแปลง docx→pdf ฝั่ง server (Office.js ของ add-in ทำ) diff --git a/docs/WORDADDIN-DEV.md b/docs/WORDADDIN-DEV.md index bb97631..301bf33 100644 --- a/docs/WORDADDIN-DEV.md +++ b/docs/WORDADDIN-DEV.md @@ -4,7 +4,7 @@ Taskpane ของ Word add-in "e-Court Documents Versioning" พอร์ตม **ทางเข้า:** เปิด `/docedit` เปล่า ๆ จะ redirect มาที่ taskpane ทันที (ไม่ผ่าน login — คำขอผู้ใช้ 2026-10-14) ยกเว้นลิงก์ e-Court `/docedit/?fileUrl=&fileId=&typeId=` ที่ยังส่งต่อให้ `/note` ตามสัญญา N8 · manifest ชี้ `/docedit/wordaddin` ตรง ๆ (ไม่ว่าผ่าน root เพื่อลด hop ใน webview) -สถานะช่วงนี้: **ไม่มี login แล้ว** (ยกเลิก Keycloak 2026-10-27 — auth เข้าทางเดียวคือ **ticket ของ e-Court** (`ticket_data` + `HmacSha256` ในไฟล์): add-in ตรวจ HMAC เอง → ขอ token ที่ `/api/documents/token` → เก็บใน localStorage ตาม fid ดูหัว "ทดสอบ e-Court ticket" และแผน `docs/PLAN-ADDIN-AUTH.md`) ส่วน **API ยังเป็น mock** (version store เก็บ metadata ค้างข้าม session ใน localStorage ของ taskpane เพื่อทดสอบวงปิดเอกสาร→เปิดใหม่ — สลับกลับของจริงด้วยการลบ `wordaddin-mock-versions` หรือเคลียร์ storage ของ origin) รอ backend จริง — ดูหัว "การสลับกลับของจริง" ด้านล่าง +สถานะช่วงนี้: **ไม่มี login แล้ว** (ยกเลิก Keycloak 2026-10-27 — auth เข้าทางเดียวคือ **ticket ของ e-Court** (`ticket_data` + `HmacSha256` ในไฟล์): add-in ตรวจ HMAC เอง → ขอ token ที่ `/api/documents/token` → เก็บใน localStorage ตาม fid ดูหัว "ทดสอบ e-Court ticket" และแผน `docs/PLAN-ADDIN-AUTH.md`) และ **upload/download เป็นของจริงแล้ว** (ปลด mock 2026-10-27 — `versioningService` ยิงกลุ่ม `/api/documents/*` ผ่าน Bearer จาก ticket: Save & Version ขึ้น MinIO จริง ดาวน์โหลดเปิด presigned URL ในเบราว์เซอร์; เหลือ mock เฉพาะโหมด `?addindemo` เพื่อดู UI ใน browser โดยไม่ต้องมี backend — ดูหัว "การรันกับ backend จริง") ## ไฟล์หลัก @@ -76,11 +76,14 @@ node scripts/make-ticket-docx.mjs path/to/file.docx --tracked --in-place หมายเหตุ: การขอ token ยิง same-origin `/docedit/api/documents/token` จริง (ไม่ผูก `NEXT_PUBLIC_ADDIN_API_BASE_URL`) — dev server ต้องรันอยู่และ secret ฝั่ง server (`ADDIN_TICKET_SECRET` → `SECRET` → default `super_secret`) ตรงกับ `--secret` ของสคริปต์ -## การสลับกลับของจริง (วันหน้า) +## การรันกับ backend จริง (ปลด mock แล้ว 2026-10-27) -- **Auth:** ไม่มี login ให้สลับแล้ว — **ยกเลิก Keycloak ถาวร 2026-10-27** · auth ของ add-in คือ ticket ของ e-Court ผ่าน `signInWithTicket` (ticket_data + HmacSha256 ในไฟล์ — แผนเต็ม `docs/PLAN-ADDIN-AUTH.md`): add-in ตรวจ HMAC ฝั่ง client (UX gate) แล้วขอ access_token ที่ `/api/documents/token` ซึ่ง server ตรวจ HMAC+exp อีกชั้นด้วย secret ฝั่ง server · โดน 401 กลาง flow = ลอง renew จาก ticket 1 ครั้ง ยังไม่ผ่าน = `AuthRequiredError` → หน้า `no-access` (ticket ต่ออายุไม่ได้ — ต้องได้ไฟล์ใหม่จาก e-Court) -- **API:** `wordaddin/services/versioningService.ts` เก็บชื่อ/ลายเซ็นเหมือนต้นฉบับเป๊ะ — ก้อบ body เดิม (multipart POST ผ่าน `apiClient` ซึ่งพอร์ตไว้แล้ว) กลับมา + ตั้ง `NEXT_PUBLIC_ADDIN_API_BASE_URL` (contract ปัจจุบันของกลุ่ม docver สรุปไว้ใน docblock ของไฟล์นั้น + docs/PLAN-DOCVER.md — identity/docId แกะจาก Bearer token ทั้งหมด) -- ชุด unit test ของ services (jest → vitest) ยังไม่พอร์ต — ต้นฉบับอยู่ที่ `D:\word-addin-poc\addin\src\services\*.test.ts` + mock ที่ `src/test/` +- **Auth:** ไม่มี login — **ยกเลิก Keycloak ถาวร 2026-10-27** · auth ของ add-in คือ ticket ของ e-Court ผ่าน `signInWithTicket` (ticket_data + HmacSha256 ในไฟล์ — แผนเต็ม `docs/PLAN-ADDIN-AUTH.md`): add-in ตรวจ HMAC ฝั่ง client (UX gate) แล้วขอ access_token ที่ `/api/documents/token` ซึ่ง server ตรวจ HMAC+exp อีกชั้นด้วย secret ฝั่ง server · โดน 401 กลาง flow = ลอง renew จาก ticket 1 ครั้ง ยังไม่ผ่าน = `AuthRequiredError` → หน้า `no-access` (ticket ต่ออายุไม่ได้ — ต้องได้ไฟล์ใหม่จาก e-Court) +- **Upload/download ของจริงเป็น default แล้ว:** `wordaddin/services/versioningService.ts` ยิงกลุ่ม `/api/documents/*` ผ่าน `apiClient` (Bearer จาก ticket) — **ไม่ต้องตั้ง env อะไรเพิ่ม** เพราะ `apiClient.toUrl` ใช้ same-origin `withBasePath("/api/documents/…")` เมื่อ `NEXT_PUBLIC_ADDIN_API_BASE_URL` ว่าง (API ของกลุ่ม docver อยู่แอปนี้เอง) · ตั้งค่า env นั้นเฉพาะวันที่ deploy add-in กับ API คนละ origin (ต้องรวม basePath เช่น `https://host/docedit` และเป็น HTTPS) + - **เอกสารถูกระบุด้วย fid ใน token** — พารามิเตอร์ `documentId` (GUID ที่ stamp ในไฟล์) ของ uploadVersion/getVersions/getDownloadLink เหลือไว้ตาม signature เดิม ไม่ถูกส่งถึง server · templateId ก็เช่นกัน (มาจาก claims ของ token — ส่งใน form = 400) +- **สิ่งที่ต้องรันฝั่ง server ก่อนใช้จริง:** MinIO (`docker compose up -d minio` — bucket `docver` app สร้างเอง) + env `DOCVER_API_KEY`/`DOCVER_TOKEN_SECRET`/`MINIO_*` ตาม `.env.example` ของกลุ่ม docver (docs/PLAN-DOCVER.md §6) · ทดสอบวงเต็ม: สร้างไฟล์ด้วย `make-ticket-docx.mjs` → เปิดใน Word → Save & Version → version ขึ้น MinIO จริง → กดดาวน์โหลด/PDF เปิด presigned URL ในเบราว์เซอร์ (โฮสต์ MinIO ต้องเข้าถึงได้จากเครื่องผู้ใช้) +- **mock เหลือเฉพาะโหมด `?addindemo`** (dev-only — ดู UI ใน browser ไม่ต้องมี backend: version เก็บในเครื่อง metadata ใน localStorage `wordaddin-mock-versions`, download-link คืน about:blank — เคลียร์ด้วยการลบ key นี้หรือ clear storage ของ origin) +- ชุด unit test ของ services: ต้นฉบับ jest อยู่ที่ `D:\word-addin-poc\addin\src\services\*.test.ts` — ฝั่งนี้เขียนใหม่เป็น vitest เฉพาะส่วนที่เป็นโค้ดใหม่ (ticket auth + versioningService สองโหมด ที่ `tests/wordaddin/`) ## Docker diff --git a/docs/addin-ui-flow.md b/docs/addin-ui-flow.md index d47ba2a..7739cb5 100644 --- a/docs/addin-ui-flow.md +++ b/docs/addin-ui-flow.md @@ -257,7 +257,7 @@ response: `{ url: string, expiresAt: string }` — UI เปิด `url` ใน ยกเลิก Keycloak OIDC/PKCE แล้ว (2026-10-27) — ไม่มี Office Dialog ไม่มีหน้า sign-in: e-Court stamp custom properties **`ticket_data`** (JSON `exp`/`fid`/`tid`/`uid`/`uname`) + **`HmacSha256`** (HMAC-SHA256 ของ string ดิบ ด้วย secret ที่ config ได้ — ปัจจุบัน `super_secret`) ลงไฟล์ตอนสร้าง แล้ว `signInWithTicket()` ใช้ได้ทันที · flow ตาม `docs/requirement/addin-workflow.md` (แผนเต็ม `docs/PLAN-ADDIN-AUTH.md`): ตรวจ HMAC ฝั่ง client (UX gate) → ตรวจ exp → session ใน localStorage `wordaddin-auth` ตาม uid (user ใหม่ = เคลียร์ session เก่าทั้งก้อน) → **ขอ access_token ที่ `POST /api/documents/token`** แบบ body `{ticket_data, HmacSha256}` (server ตรวจ HMAC+exp อีกชั้นด้วย secret ฝั่ง server) → เก็บ token ตาม fid ("คีย์คือ fid") — กลับมาเปิดเอกสารเดิม = ใช้ token เดิมไม่ต้องขอใหม่ · token หมดอายุกลางใช้งาน (โดน 401) = ลองขอใหม่จาก ticket 1 ครั้ง (ticket ต้องยังไม่หมดอายุ) ยังไม่ผ่าน = `AuthRequiredError` → หน้า `no-access` ให้ขอไฟล์ใหม่จาก e-Court · token นี้คือสิ่งที่ `apiClient` แนบเป็น `Authorization: Bearer` ทุก request ตอนต่อ backend จริง (กลุ่ม docver บังคับ Bearer — docs/PLAN-DOCVER.md) -> **หมายเหตุสถานะปัจจุบัน:** UI นี้ถูก implement แล้วใน repo นี้ที่ route `/docedit/wordaddin` (ดู `components/wordaddin/` + `wordaddin/`) — auth เป็น ticket auth จริงครบ flow (`wordaddin/services/authService.ts` + `ticketService.ts` + ทางเข้า ticket ของ `/api/documents/token`) ส่วน API ทั้งสามข้างบนยังเป็น in-memory mock (`wordaddin/services/versioningService.ts`) · วิธีสลับกลับของจริง: `docs/WORDADDIN-DEV.md` · สร้างไฟล์ทดสอบพร้อม ticket: `node scripts/make-ticket-docx.mjs` +> **หมายเหตุสถานะปัจจุบัน:** UI นี้ถูก implement แล้วใน repo นี้ที่ route `/docedit/wordaddin` (ดู `components/wordaddin/` + `wordaddin/`) — auth เป็น ticket auth จริงครบ flow (`wordaddin/services/authService.ts` + `ticketService.ts` + ทางเข้า ticket ของ `/api/documents/token`) และ **API ทั้งสามข้างบนเป็นของจริงแล้ว** (`wordaddin/services/versioningService.ts` ยิงกลุ่ม `/api/documents/*` ผ่าน Bearer — ปลด mock 2026-10-27; เหลือ mock เฉพาะโหมด `?addindemo`) · รายละเอียดการรันกับ backend: `docs/WORDADDIN-DEV.md` · สร้างไฟล์ทดสอบพร้อม ticket: `node scripts/make-ticket-docx.mjs` --- diff --git a/tests/wordaddin/versioningService.test.ts b/tests/wordaddin/versioningService.test.ts new file mode 100644 index 0000000..b89ae20 --- /dev/null +++ b/tests/wordaddin/versioningService.test.ts @@ -0,0 +1,197 @@ +// @vitest-environment jsdom +// versioningService หลังปลด mock (2026-10-27) — คุมทั้งสองโหมด: +// - ของจริง: sign-in ผ่าน signInWithTicket ให้ได้ Bearer ก่อน แล้วตรวจ FormData ของ +// uploadVersion ตรง contract POST /api/documents/versions (ห้ามส่ง userId/userName/ +// templateId — identity มาจาก Bearer หมด), getVersions แปลง DTO, getDownloadLink ใส่ ?format= +// - โหมด ?addindemo: ใช้ mock ในเครื่อง (ไม่ยิง network เลย) +import { createHmac, webcrypto } from "node:crypto"; +import { afterEach, describe, expect, it, vi } from "vitest"; + +import * as authService from "@/wordaddin/services/authService"; +import * as ticketService from "@/wordaddin/services/ticketService"; +import { + getDownloadLink, + getVersions, + uploadVersion, +} from "@/wordaddin/services/versioningService"; + +const TOKEN = "header.payload.sig"; // ค่าหยง ๆ ก็ได้ — apiClient แนบตรง ๆ ไม่ decode + +/** sign-in จริงผ่าน signInWithTicket (fetch ตอบ token 1 ครั้ง) — real mode + มี Bearer ใน session */ +async function signInReal(): Promise { + vi.stubGlobal("crypto", webcrypto); // jsdom ไม่มี crypto.subtle + const ticket = { + exp: Math.floor(Date.now() / 1000) + 300, + fid: "REAL_FID_001", + uid: "oom", + uname: "สรวิชญ์", + }; + const ticketData = JSON.stringify(ticket); + const ticketHmac = createHmac("sha256", "super_secret").update(ticketData, "utf8").digest("hex"); + vi.stubGlobal( + "fetch", + vi.fn( + async () => + new Response( + JSON.stringify({ access_token: TOKEN, token_type: "Bearer", expires_in: 3600 }), + { status: 200, headers: { "content-type": "application/json" } }, + ), + ), + ); + const result = await authService.signInWithTicket({ ticketData, ticketHmac }); + expect(result.ok, JSON.stringify(result)).toBe(true); + vi.unstubAllGlobals(); +} + +describe("versioningService (real mode)", () => { + it("uploadVersion สร้าง multipart ตรง contract + แนบ Bearer ผ่าน apiClient + parse ผล", async () => { + await signInReal(); + const fetchMock = vi.fn( + async (_url: string, _init?: RequestInit) => + new Response( + JSON.stringify({ hasPdf: true, versionId: "v-1", versionNumber: 3 }), + { status: 200, headers: { "content-type": "application/json" } }, + ), + ); + vi.stubGlobal("fetch", fetchMock); + + const docx = new Blob(["PK\x03\x04 docx"], { + type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + }); + const pdf = new Blob(["%PDF-"], { type: "application/pdf" }); + const result = await uploadVersion( + "doc-guid-ไม่ส่งถึง-server", + docx, + "client-vid-1", + { blackno: "ด1" }, + "คำฟ้อง.docx", + " แก้ไขย่อหน้า 3 ", + pdf, + "T12-ไม่ส่งถึง-server", + ); + + expect(result).toEqual({ hasPdf: true, versionId: "v-1", versionNumber: 3 }); + expect(fetchMock).toHaveBeenCalledTimes(1); + const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit]; + // same-origin ผ่าน withBasePath (ใน vitest env NEXT_PUBLIC_BASE_PATH ว่าง — Next inline ตอน build + // เท่านั้น จึง assert เฉพาะ path ที่อยู่หลัง basePath) + expect(url.endsWith("/api/documents/versions")).toBe(true); + expect(init.method).toBe("POST"); + expect((init.headers as Headers).get("authorization")).toBe(`Bearer ${TOKEN}`); + const form = init.body as FormData; + expect(form.get("file")).toBeInstanceOf(File); + expect((form.get("file") as File).name).toBe("คำฟ้อง.docx"); + expect((form.get("pdf") as File).name).toBe("document.pdf"); + expect(form.get("clientVersionId")).toBe("client-vid-1"); + expect(JSON.parse(form.get("fields") as string)).toEqual({ blackno: "ด1" }); + expect(form.get("message")).toBe("แก้ไขย่อหน้า 3"); // trim แล้ว + // contract 2026-10-22: ห้ามมี field identity/template เหลืออยู่เลย + expect(form.has("userId")).toBe(false); + expect(form.has("userName")).toBe(false); + expect(form.has("templateId")).toBe(false); + }); + + it("pdf ว่าง/ไม่แนบ = ไม่ append field pdf (ไฟล์เปล่า = version ไม่มี PDF)", async () => { + await signInReal(); + const fetchMock = vi.fn( + async (_url: string, _init?: RequestInit) => + new Response(JSON.stringify({ hasPdf: false, versionId: "v-2", versionNumber: 1 }), { status: 200 }), + ); + vi.stubGlobal("fetch", fetchMock); + await uploadVersion("d", new Blob(["PK"]), "cv", undefined, undefined, undefined, new Blob([])); + const form = (fetchMock.mock.calls[0] as unknown[])[1] as RequestInit; + expect((form.body as FormData).has("pdf")).toBe(false); + }); + + it("getVersions แปลง DocumentVersionDTO → VersionRecord ครบทุก field", async () => { + await signInReal(); + const fetchMock = vi.fn( + async (_url: string, _init?: RequestInit) => + new Response( + JSON.stringify([ + { + versionId: "v-9", + versionNumber: 9, + createdAt: "2026-10-27T05:00:00Z", + createdByUserId: "oom", + createdByUserName: "สรวิชญ์", + contentHash: "ab", + fields: { blackno: "ด1" }, + message: null, + hasPdf: true, + }, + ]), + { status: 200 }, + ), + ); + vi.stubGlobal("fetch", fetchMock); + const rows = await getVersions("anything"); + expect(rows).toEqual([ + { + versionId: "v-9", + versionNumber: 9, + createdAt: "2026-10-27T05:00:00Z", + createdByUserId: "oom", + createdByUserName: "สรวิชญ์", + contentHash: "ab", + fields: { blackno: "ด1" }, + message: null, + hasPdf: true, + }, + ]); + }); + + it("getDownloadLink ใส่ ?format=pdf · format docx/ไม่ส่ง = ไม่มี query", async () => { + await signInReal(); + const fetchMock = vi.fn( + async (_url: string, _init?: RequestInit) => + new Response(JSON.stringify({ url: "http://minio/x", expiresAt: "2026-10-27T06:00:00Z" }), { status: 200 }), + ); + vi.stubGlobal("fetch", fetchMock); + await getDownloadLink("d", "v-9", "pdf"); + expect(String(fetchMock.mock.calls[0][0]).endsWith("/api/documents/versions/v-9/download-link?format=pdf")).toBe(true); + await getDownloadLink("d", "v-9"); + expect(String(fetchMock.mock.calls[1][0]).endsWith("/api/documents/versions/v-9/download-link")).toBe(true); + await getDownloadLink("d", "v-9", "docx"); + expect(String(fetchMock.mock.calls[2][0]).endsWith("/api/documents/versions/v-9/download-link")).toBe(true); + }); + + it("โดน 401 แล้ว renew ไม่สำเร็จ = AuthRequiredError (ไม่ใช่ error อื่น)", async () => { + await signInReal(); + // fetch ตอบ 401 ทุกอัน (ทั้ง request หลักและการ renew token จาก ticket) + vi.stubGlobal("fetch", vi.fn(async () => new Response("{}", { status: 401 }))); + await expect(getVersions("d")).rejects.toMatchObject({ name: "AuthRequiredError" }); + }); +}); + +// โหมด demo ไว้ท้ายสุด — signInForDemo เปิด flag ค้างใน module จนกว่าจะ sign-in จริง +describe("versioningService (demo mode = mock)", () => { + it("signInForDemo แล้วใช้ mock ในเครื่อง — ไม่ยิง network และ persist ตาม documentId", async () => { + authService.signInForDemo(); + const fetchMock = vi.fn(); + vi.stubGlobal("fetch", fetchMock); + + const first = await uploadVersion("demo-doc", new Blob(["PK"]), "cv1", undefined, undefined, "vแรก"); + const second = await uploadVersion("demo-doc", new Blob(["PK2"]), "cv2"); + expect(first.versionNumber).toBe(1); + expect(second.versionNumber).toBe(2); + + const rows = await getVersions("demo-doc"); + expect(rows.map((row) => row.versionNumber)).toEqual([2, 1]); // ใหม่ → เก่า + expect(rows[0].message).toBeNull(); + expect(rows[1].message).toBe("vแรก"); + + const link = await getDownloadLink("demo-doc", rows[0].versionId, "pdf"); + expect(link.url).toBe("about:blank"); + expect(fetchMock).not.toHaveBeenCalled(); + expect(window.localStorage.getItem("wordaddin-mock-versions")).toBeTruthy(); + }); +}); + +afterEach(() => { + window.localStorage.clear(); + ticketService.clearSession(); + authService.signInForDemo(); // รีเซ็ต authService ให้มี username เสมอ (เคสถัดไป sign-in ใหม่เอง) + vi.restoreAllMocks(); + vi.unstubAllGlobals(); +}); diff --git a/wordaddin/models/versionRecord.ts b/wordaddin/models/versionRecord.ts index 7af9352..3a1087d 100644 --- a/wordaddin/models/versionRecord.ts +++ b/wordaddin/models/versionRecord.ts @@ -32,6 +32,8 @@ export interface VersionRecord { /** ISO-8601 */ createdAt: string; createdByUserId: string; + /** ชื่อผู้สร้าง — ส่วนเสริมของ DTO กลุ่ม docver (mock/demo อาจไม่มี → UI fallback เป็น userId) */ + createdByUserName?: string; /** SHA256 hex */ contentHash: string; fields: FieldsSnapshot | null; diff --git a/wordaddin/services/apiClient.ts b/wordaddin/services/apiClient.ts index 5db4122..0b4476e 100644 --- a/wordaddin/services/apiClient.ts +++ b/wordaddin/services/apiClient.ts @@ -1,4 +1,5 @@ import { config } from "../config"; +import { withBasePath } from "@/utils/base-path"; import * as authService from "./authService"; /** @@ -36,8 +37,15 @@ export interface ApiRequestOptions { body?: BodyInit | null; } +/** + * path เต็มเสมอ — ไม่ตั้ง `NEXT_PUBLIC_ADDIN_API_BASE_URL` = same-origin ผ่าน withBasePath + * (ได้ /docedit/api/… ถูกต้องตาม invariant #12 — API ของกลุ่ม docver อยู่แอปนี้เอง) + * · ตั้งค่า = ชี้ backend คนละ origin (ต้องรวม basePath แล้ว เช่น https://host/docedit — ใช้ตอน + * deploy add-in กับ API คนละตัว) และต้องเป็น HTTPS เสมอ (webview ของ Word บล็อก http) + */ function toUrl(path: string): string { - return path.startsWith("http") ? path : `${config.apiBaseUrl}${path}`; + if (path.startsWith("http")) return path; + return config.apiBaseUrl ? `${config.apiBaseUrl}${path}` : withBasePath(path); } async function doFetch( diff --git a/wordaddin/services/authService.ts b/wordaddin/services/authService.ts index fe92661..6904876 100644 --- a/wordaddin/services/authService.ts +++ b/wordaddin/services/authService.ts @@ -27,12 +27,20 @@ let username: string | null = null; let currentFid: string | null = null; /** ticket ดิบของเอกสารปัจจุบัน — ไว้ขอ token ใหม่ตอน 401/token หมดอายุ */ let currentTicket: { data: string; hmac: string } | null = null; +/** โหมด ?addindemo (dev-only) — เปิดโดย signInForDemo เท่านั้น */ +let demoMode = false; /** dev-only — โหมด ?addindemo ของ WordAddinGate ใช้ตั้ง session ทันทีไม่อ่านไฟล์จริง */ export function signInForDemo(): void { username = config.mockUsername; currentFid = null; currentTicket = null; + demoMode = true; +} + +/** โหมด ?addindemo หรือเปล่า — versioningService ใช้ mock แทน backend จริงเฉพาะโหมดนี้ */ +export function isDemoMode(): boolean { + return demoMode; } /** @@ -139,6 +147,7 @@ export async function signInWithTicket(input: { currentFid = ticket.fid; currentTicket = { data: ticketData, hmac: ticketHmac }; username = ticket.uname; + demoMode = false; // sign-in จริงเคลียร์โหมด demo (ถ้าเคยเปิดค้างใน page เดียวกัน) return { ok: true }; } diff --git a/wordaddin/services/versioningService.ts b/wordaddin/services/versioningService.ts index 506130a..36a97dc 100644 --- a/wordaddin/services/versioningService.ts +++ b/wordaddin/services/versioningService.ts @@ -1,26 +1,146 @@ /** - * MOCK — version API ฝั่ง client (รอ backend จริงตาม contract ใน docs/addin-ui-flow.md §7) - * ต้นฉบับ: D:\word-addin-poc\addin\src\services\versioningService.ts (multipart POST ผ่าน apiClient) + * Version API ฝั่ง client — **ของจริง** (กลุ่ม docver /api/documents/* ผ่าน apiClient + * ซึ่งแนบ Bearer แล้ว — contract ตาม docs/PLAN-DOCVER.md) · เหลือ mock เฉพาะโหมด + * `?addindemo` (dev-only ดู UI ใน browser ไม่ต้องมี backend/MinIO — authService.isDemoMode) * - * - คงชื่อ/ลายเซ็น uploadVersion/getVersions/getDownloadLink/newClientVersionId ไว้เป๊ะ — - * วันปลด mock: ก้อบ body ของต้นฉบับกลับมาได้เลย - * - ต้นฉบับมีอีก 2 ตัวที่ไม่มี caller ใน UI จึงไม่พอร์ต: buildDownloadUrl, downloadVersion - * - **วันปลด mock ห้ามส่ง userId/userName/templateId ใน FormData** — contract ของ - * POST /api/documents/versions ถอด 3 field นี้ออกแล้ว (ส่งมา = 400) และไม่มี - * segment {docId} ใน URL อีกต่อไป: server แกะ identity/template และ docId - * (= fid — fileId ของ e-Court, string) จาก Bearer token (ออกที่ POST - * /api/documents/token ด้วย x-api-key, fid บังคับ) ที่ apiClient.ts แนบให้อยู่แล้ว - * - **metadata ของ version ถูก persist ลง localStorage** (record เล็ก ๆ ต่อ documentId) — - * ไม่งั้นปิดเอกสาร (taskpane ปิดตาม) แล้วเปิดใหม่ history ว่างเปล่า ทั้งที่ metadata - * การ track อยู่ในไฟล์จริง ทำให้ทดสอบวง FR-18 ปิด→เปิดใหม่ไม่ได้; blob ของไฟล์จริง - * ยังเก็บเฉพาะ session (mock download คืน about:blank อยู่แล้ว) — localStorage เข้า - * ไม่ได้ (webview บางตัว block) = ใช้ memory อย่างเดียวแบบเดิม ไม่ throw + * - คงชื่อ/ลายเซ็น uploadVersion/getVersions/getDownloadLink/newClientVersionId ตามต้นฉบับ + * D:\word-addin-poc\addin\src\services\versioningService.ts — versionFlow เรียกต่อได้เดิม + * - **เอกสารถูกระบุด้วย fid ใน Bearer token** (ไม่ใช่ documentId GUID ที่ stamp ในไฟล์ — + * 2026-10-26 contract ถอด {docId} ออกจาก URL ทุก endpoint) พารามิเตอร์ documentId จึงเหลือ + * ไว้เพื่อ signature เดิมเท่านั้น ไม่ถูกส่งถึง server · templateId ก็เช่นกัน (มาจาก claims + * ของ token — ส่งมาใน form = 400) + * - **ห้ามส่ง userId/userName/templateId ใน FormData** (contract 2026-10-22 — ส่งมา = 400) + * - mock ยัง persist metadata ลง localStorage ต่อ documentId เพื่อทดสอบวงปิดเอกสาร→เปิดใหม่ + * ในโหมด demo — blob ไฟล์จริงเฉพาะ session; localStorage เข้าไม่ได้ (webview บางตัว block) + * = ใช้ memory อย่างเดียว ไม่ throw */ import * as authService from "./authService"; import { generateGuid } from "./metadataService"; +import { apiGetJson, apiSendExpectJson } from "./apiClient"; import { UploadVersionResult, VersionRecord } from "../models/versionRecord"; +// --------------------------------------------------------------------------- +// ของจริง — กลุ่ม docver (Bearer จาก ticket auth แนบอัตโนมัติโดย apiClient) +// --------------------------------------------------------------------------- + +/** DTO ของ GET /api/documents/versions (ใหม่ → เก่า) — PLAN-DOCVER.md §1 */ +interface DocumentVersionDTO { + versionId: string; + versionNumber: number; + createdAt: string; + createdByUserId: string; + createdByUserName: string; + contentHash: string; + fields: Record | null; + message: string | null; + hasPdf: boolean; +} + +/** FR-5 — อัปโหลดไฟล์ปัจจุบันเป็น version ใหม่ (multipart POST) + * + FR-13: message คือ commit message ทางเลือก + * + FR-16: templateId เก็บไว้ตาม signature เดิม — ไม่ส่งถึง server (มาจาก claims ของ token) */ +export async function uploadVersion( + documentId: string, + file: Blob, + clientVersionId: string, + fields?: Record, + fileName?: string, + message?: string, + pdf?: Blob, + templateId?: string +): Promise { + if (!authService.isDemoMode()) { + void templateId; // contract 2026-10-22 — ห้ามส่ง (identity ทั้งหมดจาก Bearer token) + const form = new FormData(); + form.append("file", file, fileName ?? "document.docx"); + // pdf optional: ไม่แนบ/ไฟล์เปล่า = version นี้ไม่มีสำเนา PDF + if (pdf && pdf.size > 0) { + form.append("pdf", pdf, "document.pdf"); + } + if (clientVersionId) { + form.append("clientVersionId", clientVersionId); + } + if (fields && Object.keys(fields).length > 0) { + form.append("fields", JSON.stringify(fields)); + } + const trimmedMessage = message?.trim(); + if (trimmedMessage) { + form.append("message", trimmedMessage); + } + if (fileName) { + form.append("fileName", fileName); + } + return apiSendExpectJson( + "/api/documents/versions", + { method: "POST", body: form }, + "อัปโหลด version ไม่สำเร็จ", + ); + } + return uploadVersionMock(documentId, file, clientVersionId, fields, message, pdf); +} + +/** FR-6 — version history เรียงใหม่ → เก่า (คืนสำเนากัน caller mutate store ตรง ๆ) + * docId มาจาก fid ใน token — documentId พารามิเตอร์ไม่ถูกใช้กับ server ของจริง */ +export async function getVersions(documentId: string): Promise { + if (!authService.isDemoMode()) { + const rows = await apiGetJson( + "/api/documents/versions", + "โหลด version history ไม่สำเร็จ", + ); + // server เรียงใหม่ → เก่าอยู่แล้ว — แปลงเป็น VersionRecord ตามที่ UI ใช้ + return rows.map((row) => ({ + versionId: row.versionId, + versionNumber: row.versionNumber, + createdAt: row.createdAt, + createdByUserId: row.createdByUserId, + createdByUserName: row.createdByUserName, + contentHash: row.contentHash, + fields: row.fields && Object.keys(row.fields).length > 0 ? row.fields : null, + message: row.message, + hasPdf: row.hasPdf, + })); + } + return getVersionsMock(documentId); +} + +export interface VersionDownloadLink { + url: string; + /** ISO-8601 เวลาที่ URL หมดอายุ */ + expiresAt: string; +} + +/** + * FR-7 — ขอ presigned URL อายุสั้นของ version หนึ่ง เพื่อเปิดในเบราว์เซอร์ของผู้ใช้ + * (webview ของ Word ทิ้ง blob download เงียบๆ — ให้เบราว์เซอร์จริงจัดการแทน) + * URL เป็น MinIO presigned GET (อายุ env DOCVER_LINK_TTL_SECONDS default 900 วิ) — + * โฮสต์ MinIO ต้องเข้าถึงได้จากเครื่องผู้ใช้ + */ +export async function getDownloadLink( + documentId: string, + versionId: string, + format?: "docx" | "pdf" +): Promise { + if (!authService.isDemoMode()) { + void documentId; // เอกสารมาจาก fid ใน token — versionId เช็คคู่กับ docId ฝั่ง server + const query = format && format !== "docx" ? `?format=${encodeURIComponent(format)}` : ""; + return apiGetJson( + `/api/documents/versions/${encodeURIComponent(versionId)}/download-link${query}`, + "ขอลิงก์ดาวน์โหลดไม่สำเร็จ", + ); + } + return getDownloadLinkMock(); +} + +/** สร้าง clientVersionId ใหม่สำหรับ upload ครั้งถัดไป */ +export function newClientVersionId(): string { + return generateGuid(); +} + +// --------------------------------------------------------------------------- +// Mock — เฉพาะโหมด ?addindemo (dev-only) +// --------------------------------------------------------------------------- + interface StoredVersion { record: VersionRecord; docxBlob: Blob | null; @@ -78,22 +198,15 @@ async function mockContentHash(file: Blob): Promise { } } -/** FR-5 — อัปโหลดไฟล์ปัจจุบันเป็น version ใหม่ (mock: เก็บลง memory) - * + FR-13: message คือ commit message ทางเลือก - * + FR-16: templateId เฉพาะไฟล์ร่าง (mock ไม่ใช้ — คงไว้ตาม signature ต้นฉบับ) */ -export async function uploadVersion( +async function uploadVersionMock( documentId: string, file: Blob, clientVersionId: string, fields?: Record, - fileName?: string, message?: string, - pdf?: Blob, - templateId?: string + pdf?: Blob ): Promise { void clientVersionId; // mock ไม่ใช้ — id ของ mock สร้างเอง - void fileName; - void templateId; await delay(500); // ให้ข้อความ progress ของ flow ขึ้นจริง const versions = store.get(documentId) ?? []; @@ -117,8 +230,7 @@ export async function uploadVersion( return { hasPdf, versionId: record.versionId, versionNumber }; } -/** FR-6 — version history เรียงใหม่ → เก่า (คืนสำเนากัน caller mutate store ตรง ๆ) */ -export async function getVersions(documentId: string): Promise { +async function getVersionsMock(documentId: string): Promise { await delay(200); const versions = store.get(documentId) ?? []; return versions @@ -127,33 +239,7 @@ export async function getVersions(documentId: string): Promise .map((item) => ({ ...item.record })); } -export interface VersionDownloadLink { - url: string; - /** ISO-8601 เวลาที่ URL หมดอายุ */ - expiresAt: string; -} - -/** - * FR-7 — ขอ presigned URL อายุสั้นของ version หนึ่ง เพื่อเปิดในเบราว์เซอร์ของผู้ใช้ - * (webview ของ Word ทิ้ง blob download เงียบๆ — ให้เบราว์เซอร์จริงจัดการแทน) - * mock: ไม่มี URL จริงให้คืน — blob:/data: ใช้ผ่าน openBrowserWindow ไม่ได้ (blob ผูกกับ - * context ของ webview, data: โดนเบราว์เซอร์ block) ส่วน about:blank ที่คืนไปนี้ host จะ - * ปฏิเสธเงียบๆ เพราะ openBrowserWindow เปิดเฉพาะ http/https และเป็น void API ไม่รายงานกลับ - * — กดปุ่มดาวน์โหลด/PDF ตอน mock จึงไม่มีหน้าต่างเบราว์เซอร์เปิด (UI ยังเดินครบ flow) - */ -export async function getDownloadLink( - documentId: string, - versionId: string, - format?: "docx" | "pdf" -): Promise { - void documentId; - void versionId; - void format; +async function getDownloadLinkMock(): Promise { await delay(150); return { url: "about:blank", expiresAt: new Date(Date.now() + 10 * 60_000).toISOString() }; } - -/** สร้าง clientVersionId ใหม่สำหรับ upload ครั้งถัดไป */ -export function newClientVersionId(): string { - return generateGuid(); -} From b8ff622999407ed2be5550645b49747319015d68 Mon Sep 17 00:00:00 2001 From: Suphonchai Phoonsawat Date: Tue, 27 Oct 2026 13:14:04 +0700 Subject: [PATCH 20/21] fix(docver): drop fields value limit + treat history 404 as empty in add-in MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two fixes from first real-Word testing of the unmocked versioning flow: - fields value length: remove the limit entirely (was 10,000, briefly 100,000) — real court documents run 100-200 pages with content controls several hundred thousand chars long; callers are Bearer-only and the column is Postgres jsonb (up to ~255MB), so no storage bottleneck. Structural guards stay: ≤1,000 keys (was 100), key ≤200 chars, value must be string. - getVersions in versioningService now maps the history 404 to an empty list: files stamped SavedByAddIn back in the mock era enter the tracking phase before any real upload exists, so the pane shows "ยังไม่มี version ในระบบ" and Save & Version creates v1 (implicit document creation) instead of surfacing a 404 error banner. Other errors still propagate. Tests: 288/288 (new cases — 1M-char value passes, key cap 1,000/1,001, history 404 -> []). E2E against MinIO: 50k and 1M-char Thai field values upload 200 and roundtrip byte-exact through jsonb. Docs synced: PLAN-DOCVER §1, CLAUDE.md. Co-Authored-By: Claude Code --- CLAUDE.md | 2 +- docs/PLAN-DOCVER.md | 2 +- lib/document-versioning.ts | 10 +++++----- tests/lib/document-versioning.test.ts | 19 +++++++++++++++++++ tests/wordaddin/versioningService.test.ts | 12 ++++++++++++ wordaddin/services/versioningService.ts | 20 ++++++++++++++------ 6 files changed, 52 insertions(+), 13 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 4e15d3e..170bbc0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -51,7 +51,7 @@ lib/remote-file.ts ← N8 ดึงไฟล์รีโมตจาก allow lib/external-api-auth.ts ← N10 auth ของ /api/note/external/* — requireExternalApiKey เทียบ header `x-api-key` กับ env `EXTERNAL_API_KEY` ด้วย SHA-256 + crypto.timingSafeEqual (env ไม่ตั้ง/ว่าง = 401 fail closed ทุก request; ข้อความ 401 อันเดียวกันทั้งไม่ใส่/key ผิด กัน enumerate; ห้าม log ค่า key/header ดิบ) + externalApiError สร้าง Response ผิดพลาด JSON {"error": ไทย} ใช้ร่วมทุก route ของ N10 — **module นี้ห้าม import อะไรที่ลาก @/db มาด้วย** (db/index.ts throw ตอน import เมื่อไม่มี DATABASE_URL — unit test tests/lib/external-api-auth.test.ts ต้องรันได้โดยไม่แตะ DB) lib/external-api.ts ← N10 ส่วนกลางของ route — parseQueryBody (validate JSON body ของ /api/note/external/documents: รับเฉพาะ fileId/typeId/filter, ต้องมี fileId หรือ typeId อย่างน้อยหนึ่งอย่าง — ส่งทั้งคู่ได้ = AND เหลือเฉพาะเอกสารเดียวที่ตรงทั้งสองเงื่อนไข, คำขอผู้ใช้ 2026-09-25 (รอบเดียวกันเคยเป็น OR แล้วเปลี่ยนหลังทดสอบจริง), filter รับเฉพาะ type ∈ bookmarks|annotations ไม่ส่ง = ทั้งสอง + userId (string|array กรองตามเจ้าของรายการ annotations+bookmarks) + annotationTypes (ค่า enum annotation_type เฉพาะ annotations ค่าอื่น = 400 — เดิมชื่อ types เปลี่ยน 2026-09-25) — คืน discriminated mode "file" (fileId + typeId อาจมาด้วย) / "type" (typeId เดี่ยว)) + loadDocumentBundles(fileRows, parts) (bulk: inArray ทุกไฟล์ในชุด query เดียว = 3 query ต่อ request ไม่ว่ากลุ่มกี่ไฟล์ แทนยิง 3 query ต่อไฟล์พร้อมกันทั้งกลุ่ม, code review 2026-09-25 — กรอง userIds/annotationTypes ใน SQL; คืน bundle ยุบชั้น: field เอกสาร + annotations + bookmarks ระดับเดียวกัน ไม่มี object document ซ้อน; ต่อรายการมี `url` deep link F12 กลับหน้า `/annotate/[id]` — annotation `?ann=` / bookmark `?page=<หน้า 1-based>` — URL เต็ม: origin จาก request ผู้เรียก (route อ่าน x-forwarded-proto/host แล้ว fallback host ตรง ส่งเข้าทาง parameter origin) + basePath ผ่าน withBasePath, คำขอผู้ใช้ 2026-09-25) + type ExternalDocumentDTO (externalFileId เป็น null สำหรับไฟล์ url:-fallback/อัปโหลด N4 ในกลุ่ม) lib/openapi.ts ← OpenAPI 3.0 spec ของ API ทดสอบ (กลุ่ม template + N10 + docver — 2026-10-06) — แหล่งเดียวของ Swagger UI: GET /api/template inline ลง HTML ตรง ๆ ไม่มี endpoint spec แยก; URL ใน paths เป็น path เต็ม /api/… (servers เติมแค่ basePath ตาม invariant #12) — แก้ contract ของ API ต้องมาแก้ไฟล์นี้ให้ตรงด้วย -lib/document-versioning.ts ← docver pure validation (ห้าม import อะไรที่ลาก @/db หรือ minio มาด้วย — unit test tests/lib/document-versioning.test.ts ต้องรันได้เหมือน lib/external-api-auth.ts): parseVersionUploadForm (allowlist field — identity ไม่รับจาก form แล้ว แกะจาก token ผ่าน lib/docver-token.ts + fields JSON ≤100 keys + message ≤2048), isDocxBytes/isPdfBytes (magic bytes), parseDownloadFormat/parseVersionIdParam, contentDispositionValue (ASCII fallback + filename*=UTF-8'') + docverError สร้าง Response JSON {"error": ไทย} ใช้ร่วมทุก route ของกลุ่ม (parseDocId ถอดแล้ว 2026-10-26 — docId ไม่มาจาก path param) +lib/document-versioning.ts ← docver pure validation (ห้าม import อะไรที่ลาก @/db หรือ minio มาด้วย — unit test tests/lib/document-versioning.test.ts ต้องรันได้เหมือน lib/external-api-auth.ts): parseVersionUploadForm (allowlist field — identity ไม่รับจาก form แล้ว แกะจาก token ผ่าน lib/docver-token.ts + fields JSON ≤1,000 keys **ความยาวค่าไม่จำกัด** (2026-10-27 เอกสารจริง 100–200 หน้า) + message ≤2048), isDocxBytes/isPdfBytes (magic bytes), parseDownloadFormat/parseVersionIdParam, contentDispositionValue (ASCII fallback + filename*=UTF-8'') + docverError สร้าง Response JSON {"error": ไทย} ใช้ร่วมทุก route ของกลุ่ม (parseDocId ถอดแล้ว 2026-10-26 — docId ไม่มาจาก path param) lib/docver-token.ts ← docver token + auth ของกลุ่ม /api/documents/* (2026-10-22 — pure module ห้าม import อะไรที่ลาก @/db หรือ minio มาด้วย เหมือน lib/document-versioning.ts; unit test tests/lib/docver-token.test.ts ต้องรันได้โดยไม่ต้องตั้ง env) lib/addin-ticket.ts ← add-in ticket auth ฝั่ง server ของทางเข้าที่สองของ POST /api/documents/token (2026-10-27, docs/PLAN-ADDIN-AUTH.md — pure เหมือน lib/docver-token.ts; unit test tests/lib/addin-ticket.test.ts): addinTicketSecret (env ADDIN_TICKET_SECRET → SECRET → "super_secret") + verifyAddinTicket (HMAC timing-safe บน ticket_data ดิบ + exp ผ่าน utils/docver-ticket.ts) + ticketToDocverClaims (uid→userid, uname→username, tid→template) + parseTicketTokenBody (allowlist ticket_data/HmacSha256 — missing → 401 / invalid → 400): issueDocverToken/verifyDocverToken (JWT HS256 มือเขียน node:crypto ล้วน — บังคับ alg=HS256 กัน alg-confusion, timingSafeEqual หลัง hash ทั้งสองฝั่ง, exp บังคับ) + requireDocverApiKey (header x-api-key เทียบ env DOCVER_API_KEY — fail closed 401 ไม่ตั้ง/ว่าง, 401 เดียวกันทั้งไม่ใส่/ผิด กัน enumerate, ห้าม log ค่า key/header ดิบ) + requireDocverBearer (Authorization: Bearer → claims {userid, username, fid, template} — secret จาก env DOCVER_TOKEN_SECRET ไม่ตั้ง = 401, ทุกกรณี 401 ข้อความเดียวกัน) + parseTokenRequestBody (allowlist userid/username/fid/template — **fid = fileId ของ e-Court (string, บังคับ 2026-10-26)** เป็น docId ของทุก operation ตรง ๆ · verify ไม่รับ token ที่ไม่มี fid) lib/docver-store.ts ← docver server-side MinIO + DB (env MINIO_* อ่านตอนเรียกไม่ใช่ module load; client + cached ensureBucket เก็บบน globalThis แบบ db/index.ts): object key `docver//document.docx|pdf` (uuid ล้วน ไม่มี input ผู้ใช้), putVersionObjects (docx ก่อน — pdf ล้ม = ลบ docx คืนแล้วโยนต่อ), removeVersionObjects (best-effort), presignVersionUrl (TTL env DOCVER_LINK_TTL_SECONDS), insertVersionRecord (**tx เดียว**: upsert documents ON CONFLICT DO NOTHING → SELECT id FOR UPDATE → max(versionNumber)+1 → insert — จ่ายเลข version ปลอดภัยเรียกคู่ขนาน, UNIQUE(document_id, version_number) เป็น safety net) diff --git a/docs/PLAN-DOCVER.md b/docs/PLAN-DOCVER.md index 3f9769f..64de641 100644 --- a/docs/PLAN-DOCVER.md +++ b/docs/PLAN-DOCVER.md @@ -80,7 +80,7 @@ multipart/form-data: | `file` | File (.docx) | บังคับ — ≤ 100MB, magic bytes `PK\x03\x04` | | `pdf` | File (.pdf) | optional — `size === 0` = ถือว่าไม่มี (add-in แนบ blob เปล่าได้) | | `clientVersionId` | string | optional ≤100 — GUID ที่ client generate เอง | -| `fields` | string (JSON) | optional — map `{ [sdtTag]: text }` (≤100 keys, value ≤10,000) | +| `fields` | string (JSON) | optional — map `{ [sdtTag]: text }` (≤1,000 keys · **ความยาวค่าไม่จำกัด** 2026-10-27 ตามคำขอผู้ใช้ — เอกสาร 100–200 หน้า; jsonb รับได้ถึง 255MB) | | `message` | string | optional — commit message ≤2048 (trim ว่าง = NULL) | | `fileName` | string | optional — ชื่อไฟล์ดั้งเดิม (ผ่าน `decodeUploadFilename` แล้ว) | diff --git a/lib/document-versioning.ts b/lib/document-versioning.ts index bbbb054..7d1dd63 100644 --- a/lib/document-versioning.ts +++ b/lib/document-versioning.ts @@ -124,8 +124,11 @@ export function parseFieldsJson(raw: string): } const record = parsed as Record; const keys = Object.keys(record); - if (keys.length > 100) { - return { ok: false, error: 'field "fields" มี key เกิน 100 รายการ' }; + // เหลือ guard เชิงโครงสร้างเท่านั้น — ความยาวค่าไม่จำกัดแล้ว (2026-10-27 ตามคำขอผู้ใช้: + // เอกสารจริง 100–200 หน้า มี CC ยาวหลายแสนตัวอักษร; ผู้เรียกผ่าน Bearer เท่านั้น + jsonb + // ของ Postgres รับได้ถึงระดับ 255MB จึงไม่มีคอขวดฝั่งเก็บ) + if (keys.length > 1_000) { + return { ok: false, error: 'field "fields" มี key เกิน 1,000 รายการ' }; } for (const key of keys) { if (key.length > 200) { @@ -135,9 +138,6 @@ export function parseFieldsJson(raw: string): if (typeof value !== "string") { return { ok: false, error: 'ค่าของแต่ละรายการใน "fields" ต้องเป็น string' }; } - if (value.length > 10_000) { - return { ok: false, error: 'ค่าของแต่ละรายการใน "fields" ยาวเกิน 10,000 ตัวอักษร' }; - } } return { ok: true, fields: keys.length === 0 ? null : (parsed as Record) }; } diff --git a/tests/lib/document-versioning.test.ts b/tests/lib/document-versioning.test.ts index da18b93..a8d13a4 100644 --- a/tests/lib/document-versioning.test.ts +++ b/tests/lib/document-versioning.test.ts @@ -153,6 +153,25 @@ describe("parseVersionUploadForm", () => { form.append("fields", '{"a":1}'); expect(parseVersionUploadForm(form).ok).toBe(false); }); + + it("ค่าของ fields ไม่จำกัดความยาว (เอกสาร 100–200 หน้า — 2026-10-27) · key รับได้ถึง 1,000", () => { + const big = baseForm(); + big.append("fields", JSON.stringify({ detail1: "พ".repeat(1_000_000) })); + expect(parseVersionUploadForm(big).ok).toBe(true); + + const manyKeys = baseForm(); + manyKeys.append( + "fields", + JSON.stringify(Object.fromEntries(Array.from({ length: 1_000 }, (_, i) => [`tag${i}`, "x"]))), + ); + expect(parseVersionUploadForm(manyKeys).ok).toBe(true); + const over = baseForm(); + over.append( + "fields", + JSON.stringify(Object.fromEntries(Array.from({ length: 1_001 }, (_, i) => [`tag${i}`, "x"]))), + ); + expect(parseVersionUploadForm(over).ok).toBe(false); + }); }); describe("sanitizeDisplayFileName / sanitizeFilePartName", () => { diff --git a/tests/wordaddin/versioningService.test.ts b/tests/wordaddin/versioningService.test.ts index b89ae20..4de9046 100644 --- a/tests/wordaddin/versioningService.test.ts +++ b/tests/wordaddin/versioningService.test.ts @@ -162,6 +162,18 @@ describe("versioningService (real mode)", () => { vi.stubGlobal("fetch", vi.fn(async () => new Response("{}", { status: 401 }))); await expect(getVersions("d")).rejects.toMatchObject({ name: "AuthRequiredError" }); }); + + it("404 จาก history (เอกสารยังไม่เคยมี version เช่น stamp ค้างสมัย mock) = list ว่าง ไม่ใช่ error", async () => { + await signInReal(); + vi.stubGlobal( + "fetch", + vi.fn( + async (_url: string, _init?: RequestInit) => + new Response(JSON.stringify({ error: "ไม่พบเอกสารนี้ในระบบ (ยังไม่เคยอัปโหลด version)" }), { status: 404 }), + ), + ); + await expect(getVersions("d")).resolves.toEqual([]); + }); }); // โหมด demo ไว้ท้ายสุด — signInForDemo เปิด flag ค้างใน module จนกว่าจะ sign-in จริง diff --git a/wordaddin/services/versioningService.ts b/wordaddin/services/versioningService.ts index 36a97dc..f8fb51a 100644 --- a/wordaddin/services/versioningService.ts +++ b/wordaddin/services/versioningService.ts @@ -17,7 +17,7 @@ import * as authService from "./authService"; import { generateGuid } from "./metadataService"; -import { apiGetJson, apiSendExpectJson } from "./apiClient"; +import { ApiError, apiGetJson, apiSendExpectJson } from "./apiClient"; import { UploadVersionResult, VersionRecord } from "../models/versionRecord"; // --------------------------------------------------------------------------- @@ -81,13 +81,21 @@ export async function uploadVersion( } /** FR-6 — version history เรียงใหม่ → เก่า (คืนสำเนากัน caller mutate store ตรง ๆ) - * docId มาจาก fid ใน token — documentId พารามิเตอร์ไม่ถูกใช้กับ server ของจริง */ + * docId มาจาก fid ใน token — documentId พารามิเตอร์ไม่ถูกใช้กับ server ของจริง + * 404 = เอกสารนี้ยังไม่เคยมี version (เช่น ไฟล์ที่ stamp SavedByAddIn ไว้สมัย mock แล้วเพิ่ง + * มาใช้ backend จริง) — สำหรับ add-in ความหมายเดียวกับ list ว่าง: UI แสดง "ยังไม่มี version + * ในระบบ" และกด Save & Version สร้าง v1 ได้เลย (upload สร้างเอกสารให้อัตโนมัติ) */ export async function getVersions(documentId: string): Promise { if (!authService.isDemoMode()) { - const rows = await apiGetJson( - "/api/documents/versions", - "โหลด version history ไม่สำเร็จ", - ); + let rows: DocumentVersionDTO[]; + try { + rows = await apiGetJson("/api/documents/versions", "โหลด version history ไม่สำเร็จ"); + } catch (error) { + if (error instanceof ApiError && error.status === 404) { + return []; + } + throw error; + } // server เรียงใหม่ → เก่าอยู่แล้ว — แปลงเป็น VersionRecord ตามที่ UI ใช้ return rows.map((row) => ({ versionId: row.versionId, From ce646ffc20b4e949866a6462c3dfa8636e816a35 Mon Sep 17 00:00:00 2001 From: oom Date: Tue, 27 Oct 2026 13:24:15 +0700 Subject: [PATCH 21/21] Fix Link bug: Replace with and add more demo page(dl.html) --- app/api/template/download-docx/route.ts | 1 + app/api/template/generate/route.ts | 7 ++++-- app/tmpl/page.tsx | 31 +++++++++++++------------ public/tmpl/dl.html | 28 ++++++++++++++++++++++ 4 files changed, 50 insertions(+), 17 deletions(-) create mode 100644 public/tmpl/dl.html diff --git a/app/api/template/download-docx/route.ts b/app/api/template/download-docx/route.ts index 13f8725..f7e8017 100644 --- a/app/api/template/download-docx/route.ts +++ b/app/api/template/download-docx/route.ts @@ -12,6 +12,7 @@ if (!DOCEDIT_BASE_URL||!EXTERNAL_API_KEY||!TMPL_PATH) { console.error('Missing variable DOCEDIT_BASE_URL, EXTERNAL_API_KEY or TMPL_PATH') } +// console.log("download-docx env",DOCEDIT_BASE_URL,EXTERNAL_API_KEY,TMPL_PATH) async function processRequest(fid: string,tid:string,uid:string,uname:string):Promise> { diff --git a/app/api/template/generate/route.ts b/app/api/template/generate/route.ts index 1a057ee..7b0f506 100644 --- a/app/api/template/generate/route.ts +++ b/app/api/template/generate/route.ts @@ -12,9 +12,12 @@ const PRIVATE_KEY_HEX = process.env.PRIVATE_KEY_HEX ||'' const SECRET = process.env.SECRET || '' const TICKET_SEC = Number(process.env.TICKET_SEC||'300') -if (EXTERNAL_API_KEY || !TMPL_PATH || !PRIVATE_KEY_HEX || !SECRET) +if (!EXTERNAL_API_KEY || !TMPL_PATH || !PRIVATE_KEY_HEX || !SECRET) console.log("api/template/generate: require EXTERNAL_API_KEY, TMPL_PATH and PRIVATE_KEY_HEX") +// console.log("generate env:",EXTERNAL_API_KEY,TMPL_PATH,PRIVATE_KEY_HEX,SECRET) + + export async function POST(request: NextRequest) { // 1. ตรวจสอบ API Key จาก HTTP Header (x-api-key) const apiKey = request.headers.get('x-api-key'); @@ -29,7 +32,7 @@ export async function POST(request: NextRequest) { try { // รับข้อมูล JSON Payload const templateData:DocumentTemplate = (await request.json()); - console.log(templateData) + // console.log(templateData) // ต้องมี templateData.info.ticket_data เป็น json string เสมอ if (!templateData || !templateData.info || !templateData.info.ticket_data || diff --git a/app/tmpl/page.tsx b/app/tmpl/page.tsx index 0eaec44..fa31778 100644 --- a/app/tmpl/page.tsx +++ b/app/tmpl/page.tsx @@ -1,5 +1,6 @@ // app/page.tsx (ระบบ B) 'use client'; +import Link from "next/link"; import type { ChangeEvent } from 'react'; // import { createDownloadFormAction } from './actions'; import DependentDropdown, { DropdownData } from '@/components/utils/DependentDropdown'; @@ -25,10 +26,10 @@ export default function HomePage() {
ตัวอย่าง Frontend ส่งข้อมูลผ่าน <form>(POST) หรือ <a>(GET) เพื่อรองรับ Vanilla JavaScript และ Classic Framework ใช้การเปลี่ยนหน้าเพื่อดาว์นโหลดไฟล์ - ต้องทำให้ ASP.NET มีฟังก์ชั่นเหมือน /docedit/api/template/download-docx + ตัวอย่างหน้าแบบ Plain HTML
-
+

Form POST

@@ -55,23 +56,23 @@ export default function HomePage() {
-

A Link Get

-
T_no_warning_john_xxx.docx
- TF_no_warning_P2_john_xxx.docx
- TS_P2_IN_john_xxx.docx
+

A Link Get(new)

+ T_no_warning_john_xxx.docx
+ TF_no_warning_P2_john_xxx.docx
+ TS_P2_IN_john_xxx.docx
- T12_b2_IN_john_xxx.docx
- T12_FD155_3s-long_john_xxx.docx
+ T12_b2_IN_john_xxx.docx
+ T12_FD155_3s-long_john_xxx.docx

Template & Data

- Template: T_X.docx,   - T12_X.docx
- Data: TF_no_warning_P2.json,   - T_no_warning.json,   - TS_P2_IN.json
- T12_b2_IN.json,   - T12_FD155_3s-long.json + Template: T_X.docx,   + T12_X.docx
+ Data: TF_no_warning_P2.json,   + T_no_warning.json,   + TS_P2_IN.json
+ T12_b2_IN.json,   + T12_FD155_3s-long.json

Flow

diff --git a/public/tmpl/dl.html b/public/tmpl/dl.html new file mode 100644 index 0000000..6114ce9 --- /dev/null +++ b/public/tmpl/dl.html @@ -0,0 +1,28 @@ + + + + + + Test Download DOCX a + + +

A a Get

+ T_no_warning_john_xxx.docx
+ TF_no_warning_P2_john_xxx.docx
+ TS_P2_IN_john_xxx.docx
+ + T12_b2_IN_john_xxx.docx
+ T12_FD155_3s-long_john_xxx.docx
+ +

Template & Data

+ Template: T_X.docx,   + T12_X.docx
+ Data: TF_no_warning_P2.json,   + T_no_warning.json,   + TS_P2_IN.json
+ T12_b2_IN.json,   + T12_FD155_3s-long.json + + + + \ No newline at end of file
+
+

ตัวอย่างการใช้ สร้างเอกสารจาก Template

+
+ ตัวอย่าง Frontend ส่งข้อมูลผ่าน <form>(POST) หรือ <a>(GET) + เพื่อรองรับ Vanilla JavaScript และ Classic Framework ใช้การเปลี่ยนหน้าเพื่อดาว์นโหลดไฟล์ + ต้องทำให้ ASP.NET มีฟังก์ชั่นเหมือน /docedit/api/template/download-docx +
+
+
+

Form POST

+ +
+
+ uid: +
+
+ uname: +
+ + +
+
+
+

Template & Data

+ Template: T_X.docx,   + T12_X.docx
+ Data: TF_no_warning_P2.json,   + T_no_warning.json,   + TS_P2_IN.json
+ T12_b2_IN.json,   + T12_FD155_3s-long.json +
+
+

Flow

+
+ หน้านี้(เลือกไฟล์)
+ ↓
+ download-docx(ตรวจสิทธิ์,ส่งเทมเพลต ID,ส่งข้อมูล)
+ ↓
+ generate(เปิดเทมเพลต,สร้าง docx)
+ ↓
+ ดาว์นโหลด docx เปิด MS Word อัตโนมัติ
+ ↓
+ Add-ins ตรวจสอบไฟล์, ผู้ใช้แก้ไข docx
+ ↓
+ Versioning(คุมเวอร์ชั่น)
+ ↓
+ ecourt(อัปเดตฐานข้อมูลและไฟล์)
+ +
+ +
+ +
+