# Word Versioning Add-in ระบบจัดการ version history ของเอกสาร Word — สร้างเอกสารจาก template ผ่านเว็บ ติดตามทุกการแก้ไขผ่าน add-in ใน Word และ sync ข้อมูลสำคัญกลับฐานข้อมูลอัตโนมัติ ## คุณสมบัติหลัก - 📝 **สร้างเอกสารจาก template** — กรอก form บนเว็บ → ระบบ generate .docx จาก template (แทนที่ค่าใน content controls) พร้อม stamp metadata และขึ้นเป็น **version 1 อัตโนมัติ** - 🗂️ **Template management** — upload ไฟล์ .docx ที่มี content controls แล้วระบบ **detect fields อัตโนมัติ** ตั้งชื่อ/ชนิด/required ต่อ field ผ่านหน้าเว็บ - 📌 **ตรวจจับ tracked document** — เปิดไฟล์ใน Word (Desktop Win/Mac หรือ Online) แล้ว add-in รู้ทันทีว่าไฟล์นี้อยู่ในระบบ พร้อมแสดง version history - 💾 **Save & Version** — ปุ่มใน task pane สร้าง version ใหม่ (stamp metadata + อัปโหลด snapshot ไป backend) - 🔄 **Field sync** — ทุกครั้งที่สร้าง version ระบบ extract ค่าจาก content controls ของเอกสารไป update fields ใน DB ให้ sync กับเนื้อหาจริงเสมอ - ⬇️ **ดาวน์โหลด version ย้อนหลัง** — ทุก version เก็บครบพร้อม SHA256 hash ตรวจสอบความถูกต้องได้ ## สถาปัตยกรรม | ส่วน | Stack | หน้าที่ | |---|---|---| | **Add-in** (`/addin`) | Office.js, TypeScript, React, Fluent UI | Task pane ใน Word — ตรวจจับ document, Save & Version, version history, field extraction | | **Backend** (`/backend`) | ASP.NET Core 8, EF Core, PostgreSQL, MinIO, OpenXML | Version API, template system, docx generation, JWT validation | | **Web Portal** (`/web`) | Nuxt.js, TailwindCSS | จัดการ template + สร้างเอกสารจาก form (light/dark theme) | | **Auth** | Keycloak (OIDC + PKCE) | Public clients ทั้ง add-in และเว็บ, backend validate ผ่าน JWKS | | **Storage** | MinIO (S3-compatible) | bucket `docx-versions` (snapshot ต่อ version) + `docx-templates` (ไฟล์ template) — private ทั้งคู่ | **Flow หลัก:** 1. Admin upload template (.docx ที่มี content controls) → ระบบ detect fields อัตโนมัติ 2. ผู้ใช้กรอก form บนเว็บ → ได้ไฟล์ .docx ที่เป็น version 1 แล้ว → เปิดใน Word 3. Add-in รู้จักเอกสารทันที → แก้ไขเนื้อหา → กด **Save & Version** → เกิด version ใหม่ + fields ใน DB sync ตามเนื้อหาล่าสุด ## สถานะโปรเจกต์ | Phase | เนื้อหา | สถานะ | |---|---|---| | 0 | Scaffold 3 โปรเจกต์ (addin / backend / web) | ✅ เสร็จ | | 1 | Local dev infra — docker-compose + Keycloak (realm, 3 clients + PKCE) + MinIO (buckets) | ✅ เสร็จ | | 2 | Backend core — version API (FR-5–7) + JWT auth (FR-8) | ✅ เสร็จ | | 3 | Template system + docx generation + field sync (FR-9–11) | ✅ เสร็จ | | 4–5 | Add-in services + task pane UI (FR-1–4) | ⏳ รอ implement | | 6 | Web Portal UI (FR-9–10) | ⏳ รอ implement | | 7–8 | Tests + เอกสารฉบับเต็ม | ⏳ รอ implement | **ใช้ได้แล้วตอนนี้:** infra ทั้งชุด + backend API ครบทั้ง version API (upload / history / download) และ template system (upload template พร้อม auto-detect fields, generate เอกสารเป็น v1, field sync ตอนสร้าง version) — ลองเล่นได้ผ่าน Swagger หรือ curl (ตัวอย่างด้านล่าง) ## เอกสาร - [specs/spec-officejs.md](specs/spec-officejs.md) — spec เต็มรูปแบบ: functional requirements (FR-1–11), data models, acceptance criteria (1–14) - [docs/plans/officejs-implementation-plan.md](docs/plans/officejs-implementation-plan.md) — implementation plan แบ่ง 9 phases พร้อม definition of done สำหรับผู้เริ่มพัฒนา: อ่าน spec ก่อน แล้วตามด้วย plan (เริ่มจาก Phase 0) ## การพัฒนา (local) ### Prerequisites - Docker Desktop - .NET 8 SDK + `dotnet-ef` tool (สำหรับ migration): ```bash dotnet tool install --global dotnet-ef --version 8.0.11 ``` - Node.js LTS (add-in และ web — ยังเป็น scaffold) ### 1) รัน infrastructure (PostgreSQL + MinIO + Keycloak) ```bash cp .env.example .env # ครั้งแรกครั้งเดียว — แก้ password ได้ตามต้องการ docker compose up -d ``` เมื่อขึ้นครบแล้ว: | บริการ | URL | เข้าด้วย | |---|---|---| | Keycloak admin console | http://localhost:8080/admin/ | `KC_BOOTSTRAP_ADMIN_USERNAME` / `KC_BOOTSTRAP_ADMIN_PASSWORD` จาก `.env` | | MinIO console | http://localhost:9001 | `MINIO_ROOT_USER` / `MINIO_ROOT_PASSWORD` จาก `.env` | | PostgreSQL | `localhost:5432` | `POSTGRES_USER` / `POSTGRES_PASSWORD` จาก `.env`, ฐานข้อมูล `wordversioning` | Keycloak จะ import realm `word-versioning` อัตโนมัติ (clients 3 ตัว + test user) จาก [keycloak/realm-export.json](keycloak/realm-export.json) — ไฟล์นี้คือ source of truth ของ realm ฝั่ง dev: แก้ realm ให้แก้ที่ไฟล์นี้แล้วรัน `docker compose rm -sf keycloak && docker compose up -d` เพื่อ import ใหม่ (การแก้ผ่าน admin console จะหายเมื่อ recreate container) **Test user สำหรับล็อกอิน:** `testuser` / `Test1234!` (เปลี่ยนได้ใน realm-export.json) MinIO จะมี bucket `docx-versions` และ `docx-templates` (private ทั้งคู่) สร้างให้อัตโนมัติ ### 2) Backend API ```bash cd backend/WordVersioning.Api dotnet ef database update # ครั้งแรก / เมื่อมี migration ใหม่ dotnet run # Swagger ที่ http://localhost:5075/swagger ``` ค่าเชื่อมต่อ (DB/MinIO/Keycloak) อยู่ใน `appsettings.json` + `appsettings.Development.json` — override ผ่าน env ได้ทั้งหมด เช่น `ConnectionStrings__Default`, `MinIO__Endpoint`, `Authentication__Authority` **API ที่มีตอนนี้** (ทุก endpoint ต้องแนบ `Authorization: Bearer `): ``` # Documents / versions (FR-5–7, 11) POST /api/documents/{documentId}/versions # อัปโหลด version ใหม่ + field sync GET /api/documents/{documentId}/versions # version history ใหม่ → เก่า (พร้อม snapshot fields) GET /api/documents/{documentId}/versions/{versionId}/download # ดาวน์โหลด version (proxy stream จาก MinIO) # Generate จาก template (FR-10) POST /api/documents/generate # { templateId, values } → เอกสาร v1 พร้อม metadata # Templates (FR-9) GET /api/templates # รายการ (?includeInactive=true สำหรับ admin) GET /api/templates/{id} # รายละเอียด + field definitions POST /api/templates # upload .docx → detect fields อัตโนมัติ PUT /api/templates/{id} # แก้ metadata + field definitions (label/type/required/ลำดับ) PUT /api/templates/{id}/file # แทนที่ไฟล์ → re-detect (บอก tag ที่เพิ่ม/หาย) DELETE /api/templates/{id} # deactivate (soft-delete) ``` - Upload เป็น multipart/form-data: `file` (.docx เท่านั้น, จำกัด 100MB) + `clientVersionId` (GUID) + `fields` (optional, JSON map tag→text สำหรับ field sync) - `documentId` ยังไม่มีในระบบ = สร้างใหม่อัตโนมัติ · `versionNumber` เพิ่มอัตโนมัติต่อเอกสาร · เก็บ SHA256 ของทุก version - Field sync policy (FR-11): field ที่ส่งมาขาด (ผู้ใช้ลบ content control ใน Word) → เก็บค่าเดิม + mark missing ใน snapshot **ไม่ fail request** · ส่งเกินนิยาม → ignore - Generate (FR-10): backend เปิด template ด้วย OpenXML → แทนที่ค่าใน SDT ที่ **run level** (รักษา formatting) → stamp custom properties (`SavedByAddIn`, `DocumentTrackingId`) → upload เป็น v1 → response `{ documentId, versionId, versionNumber, downloadUrl }` — เปิดไฟล์ใน Word แล้ว add-in (Phase 4–5) จะรู้จักทันที ### ขอ token ทดสอบ (dev) ทุก client บังคับ PKCE (ปิด direct grants ตาม spec) — วิธีเร็วสุดตอนทดสอบคือขอผ่าน client `admin-cli` ของ realm: ```bash curl -s -X POST http://localhost:8080/realms/word-versioning/protocol/openid-connect/token \ -d "client_id=admin-cli&grant_type=password&username=testuser&password=Test1234!" \ | jq -r .access_token ``` แล้วลอง upload: ```bash DOC_ID=$(uuidgen | tr 'A-Z' 'a-z') curl -X POST http://localhost:5075/api/documents/$DOC_ID/versions \ -H "Authorization: Bearer $TOKEN" \ -F "file=@เอกสาร.docx;type=application/vnd.openxmlformats-officedocument.wordprocessingml.document" \ -F "clientVersionId=$(uuidgen)" # → { "versionId": "...", "versionNumber": 1 } ``` ### ลองเล่น template → generate → field sync (Phase 3) **0) เตรียมไฟล์ template** — เขียน .docx ใน Word แล้วแทรก content controls ในจุดที่จะเป็น field (Developer tab → Rich Text Content Control → Properties ตั้ง **Tag** ให้ unique ต่อไฟล์ เช่น `employee_name`, `salary`) **1) Upload template → ระบบ detect fields อัตโนมัติ:** ```bash curl -X POST http://localhost:5075/api/templates \ -H "Authorization: Bearer $TOKEN" \ -F "name=สัญญาจ้างงาน" \ -F "file=@template.docx;type=application/vnd.openxmlformats-officedocument.wordprocessingml.document" # → { "id": "...", "fields": [ { "key": "employee_name", ... }, ... ] } ``` **2) ตั้งค่า field definitions** (label ที่แสดงในฟอร์ม / ชนิด `text|number|date` / required / ลำดับ): ```bash curl -X PUT http://localhost:5075/api/templates/$TPL_ID \ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ -d '{"fields": [ { "key": "employee_name", "label": "ชื่อ-นามสกุล", "type": "text", "isRequired": true, "sortOrder": 0 }, { "key": "salary", "label": "เงินเดือน", "type": "number", "isRequired": true, "sortOrder": 1 } ]}' ``` **3) Generate เอกสาร — ได้ .docx ที่เป็น version 1 พร้อม metadata:** ```bash curl -X POST http://localhost:5075/api/documents/generate \ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ -d "{\"templateId\":\"$TPL_ID\",\"values\":{\"employee_name\":\"สมชาย ใจดี\",\"salary\":\"50000\"}}" # → { "documentId": "...", "versionId": "...", "versionNumber": 1, "downloadUrl": "/api/documents/.../download" } curl -H "Authorization: Bearer $TOKEN" -o ไฟล์ที่สร้าง.docx \ "http://localhost:5075$DOWNLOAD_URL" ``` เปิดไฟล์ที่ได้ตรวจได้ว่า: ค่าถูกทุก field (formatting ของ template คงเดิม) และมี custom properties `SavedByAddIn` / `DocumentTrackingId` (unzip ดู `docProps/custom.xml` หรือ Word → File → Info → Properties → Advanced Properties) **4) Field sync ตอนสร้าง version ถัดไป** — upload v2 พร้อมแนบ `fields` (map tag→text ที่ extract จาก content controls — ในอนาคตฝั่ง add-in ทำให้อัตโนมัติ): ```bash curl -X POST http://localhost:5075/api/documents/$DOC_ID/versions \ -H "Authorization: Bearer $TOKEN" \ -F "file=@ไฟล์แก้แล้ว.docx;type=application/vnd.openxmlformats-officedocument.wordprocessingml.document" \ -F "clientVersionId=$(uuidgen)" \ -F 'fields={"employee_name":"สมชาย ใจดี มากกว่าเดิม","salary":"60000"}' ``` แล้วดู `GET /api/documents/$DOC_ID/versions` — แต่ละ version มี snapshot fields ของตัวเอง หากลบ content control ออกจาก Word ก่อน upload field นั้นจะเห็น `"missing": true` โดยค่าเดิมยังคงอยู่ (ไม่ error) ### 3) Add-in และ Web Portal (ยังเป็น scaffold) ```bash cd addin && npm start # https://localhost:3000 — ครั้งแรก: npx office-addin-dev-certs install cd web && npm run dev # http://localhost:5173 ``` สองส่วนนี้ยังเป็นโครงจาก Phase 0 — logic จริง (login, Save & Version, template UI) มาตาม Phase 4–6 > คำแนะนำฉบับเต็ม (sideload บน Word Desktop/Online, สร้าง template แรก, สร้างเอกสารจากเว็บ) จะเติมเมื่อ implement ครบตาม Phase 8 ของแผน ## เทคโนโลยี - **Add-in**: Office.js (WordApi 1.5+), TypeScript, React, Fluent UI, webpack — scaffold ด้วย `yo office` - **Backend**: .NET 8, EF Core + Npgsql (PostgreSQL), AWSSDK.S3 → MinIO (`ForcePathStyle = true`), DocumentFormat.OpenXml, JwtBearer - **Web**: Nuxt.js (Vue 3), TailwindCSS (`darkMode: 'class'`) - **Auth**: Keycloak — realm `word-versioning`, clients: `word-addin-officejs`, `word-versioning-web`, `word-addin-vsto` (public + PKCE S256 ทุกตัว)