diff --git a/CLAUDE.md b/CLAUDE.md index 432922e..382196a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,7 +14,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co Infra ทั้งหมดรันผ่าน `docker-compose.yml`: PostgreSQL + MinIO + Keycloak (OIDC, 3 public clients พร้อม PKCE: `word-addin-officejs`, `word-versioning-web`, `word-addin-vsto`) -**สถานะปัจจุบัน: Phase 0–5 เสร็จ** (backend ครบ + add-in ครบทั้ง services และ task pane UI — sideload ทดสอบจริงบน Word for Mac แล้ว แก้ปัญหาตกมุม 4 รอบ) — งานต่อคือ phase 6 (web portal), 7 (tests ครบชุด), 8 (ไล่ acceptance criteria) ตามแผน +**สถานะปัจจุบัน: Phase 0–6 เสร็จ** (backend + add-in + web portal ครบ — ตรวจอัตโนมัติผ่านทั้งหมด เหลือยืนยันด้วยมือ: click-through บน browser จริง + ส่วน sideload ที่เหลือ) — งานต่อคือ phase 7 (tests ครบชุด), 8 (README + ไล่ acceptance criteria) ตามแผน ## เอกสารหลัก (อ่านก่อนเริ่มงานใดๆ) diff --git a/docs/plans/officejs-implementation-plan.md b/docs/plans/officejs-implementation-plan.md index 688655a..330d70f 100644 --- a/docs/plans/officejs-implementation-plan.md +++ b/docs/plans/officejs-implementation-plan.md @@ -1,7 +1,7 @@ # Implementation Plan: Word Versioning Add-in (Office.js + .NET 8) > แผนงานสร้างระบบจาก spec: [`specs/spec-officejs.md`](../../specs/spec-officejs.md) -> สถานะ: Phase 0–5 เสร็จ (backend ครบ + add-in ครบทั้ง services และ UI — เหลือตรวจ sideload บน Word จริง) — ต่อไป: phase 6 (web portal) +> สถานะ: Phase 0–6 เสร็จ (backend + add-in + web portal — ตรวจอัตโนมัติผ่านทั้งหมด เหลือยืนยันด้วยมือ: click-through บน browser จริง + ส่วน sideload ที่เหลือ) — ต่อไป: phase 7 (tests ครบชุด), 8 (README + AC) > วันที่แผน: 2026-08-18 (ฉบับแก้ไข v2: เพิ่ม FR-9 ถึง FR-11 — ระบบ template, สร้างเอกสาร v1 ผ่านเว็บ และ field sync) --- @@ -397,22 +397,34 @@ POST /api/documents/generate { templateId, values: { : } } **เป้า:** เว็บ Nuxt + Tailwind สำหรับจัดการ template และสร้างเอกสาร ทุกหน้ารองรับ light/dark theme ### 6.1 พื้นฐาน -- [ ] `useAuth.ts` — OIDC PKCE + **browser redirect ปกติ** (top-level page ใช้ได้เลย ไม่ต้องมี Dialog API), รับ code ที่ route callback, แลก token, silent refresh, token ใน memory -- [ ] `useApi.ts` — fetch wrapper + Bearer + auto refresh ตอน 401 -- [ ] `useTheme.ts` — toggle light/dark (สลับ class บน ``), persist ใน localStorage, default ตาม `prefers-color-scheme` -- [ ] Layout หลัก: header + theme toggle + สถานะ login (ทุกหน้าใช้ร่วม) +- [x] `useAuth.ts` — OIDC PKCE + **browser redirect ปกติ** (top-level page ใช้ได้เลย ไม่ต้องมี Dialog API), รับ code ที่ route callback, แลก token, silent refresh, token ใน memory +- [x] `useApi.ts` — fetch wrapper + Bearer + auto refresh ตอน 401 +- [x] `useTheme.ts` — toggle light/dark (สลับ class บน ``), persist ใน localStorage, default ตาม `prefers-color-scheme` +- [x] Layout หลัก: header + theme toggle + สถานะ login (ทุกหน้าใช้ร่วม) ### 6.2 จัดการ template (FR-9 UI) -- [ ] `templates/index.vue` — ตารางรายการ (name, description, จำนวน fields, active) + ปุ่ม deactivate/activate -- [ ] `templates/new.vue` + `templates/[id]/edit.vue` — upload/แทนที่ไฟล์ .docx → โชว์ **field list ที่ระบบ detect อัตโนมัติ** → ฟอร์มตั้ง label/type (text/number/date)/required/ลำดับ → บันทึก +- [x] `templates/index.vue` — ตารางรายการ (name, description, จำนวน fields, active) + ปุ่ม deactivate/activate +- [x] `templates/new.vue` + `templates/[id]/edit.vue` — upload/แทนที่ไฟล์ .docx → โชว์ **field list ที่ระบบ detect อัตโนมัติ** → ฟอร์มตั้ง label/type (text/number/date)/required/ลำดับ → บันทึก ### 6.3 สร้างเอกสาร (FR-10 UI) -- [ ] `index.vue` — card รายการ template ที่ active → เลือกแล้วไปหน้าสร้าง -- [ ] `documents/create/[templateId].vue` — **dynamic form สร้างจาก field defs** (input type ตาม field type: text/number/date picker), validate required/type ฝั่ง client + แสดง error จาก backend -- [ ] กดสร้าง → แสดงผล: documentId, version 1 + **ปุ่มดาวน์โหลด .docx** + คำแนะนำสั้นๆ ("เปิดไฟล์นี้ใน Word แล้วใช้ add-in ต่อ") +- [x] `index.vue` — card รายการ template ที่ active → เลือกแล้วไปหน้าสร้าง +- [x] `documents/create/[templateId].vue` — **dynamic form สร้างจาก field defs** (input type ตาม field type: text/number/date picker), validate required/type ฝั่ง client + แสดง error จาก backend +- [x] กดสร้าง → แสดงผล: documentId, version 1 + **ปุ่มดาวน์โหลด .docx** + คำแนะนำสั้นๆ ("เปิดไฟล์นี้ใน Word แล้วใช้ add-in ต่อ") + +หมายเหตุจากการทำจริง (2026-08-20): +- โครงไฟล์ตาม Nuxt 4 (dir `app/`): `app/composables`, `app/utils`, `app/components`, `app/plugins` auto-import — **import ข้ามโฟลเดอร์ต้องใช้ alias `~/utils/...`** (relative `./oidc` จาก composables ไม่ resolve — เจอตอน build แรก) +- **นโยบาย session ของเว็บ (กติกา #4 + แผนข้อ 5):** access/refresh/id token เก็บใน memory เท่านั้น; สิ่งที่ persist มีแค่ refresh token + id token (ไว้ restore หลัง reload และ logout แบบมี `id_token_hint`) ใน sessionStorage key `wv-session` + PKCE pending (`wv-oidc-pending`) ต้องข้าม navigation จึงจำเป็น — sessionStorage อายุสั้นตามที่แผนอนุญาต +- restore session ทำใน `app/plugins/auth.client.ts` (**async plugin** → รอผลก่อน render หน้าแรก กัน UI กระพริบไปหน้า login); สถานะ login ใน header ครอบ `` กัน hydration mismatch; ทุกหน้าครอบเนื้อหาด้วย `AuthGate` (ยังไม่ login → ปุ่มเข้าระบบ) +- `useApi`: Bearer ทุก request, 401 → force refresh → retry ครั้งเดียว; refresh พัง → เคลียร์ session แล้ว AuthGate สลับไปหน้า login เอง (พฤติกรรมเดียวกับ AC-9 ของ add-in) — และ `filenameFrom()` อ่านชื่อไฟล์จาก `Content-Disposition` รองรับ `filename*=UTF-8` ของ .NET (ชื่อไทย) +- ตรวจอัตโนมัติผ่านหมด (2026-08-20): `npm run build` exit 0 · SSR ทุก route (/, /templates, /templates/new, /templates/{id}/edit, /auth/callback ±query, /documents/create/{id}) ตอบ 200 พร้อมข้อความถูกต้อง · **เดิน PKCE จริงด้วยพารามิเตอร์เหมือน `useAuth` ทุกตัว** (redirect `/auth/callback`, scope=openid, S256) → Keycloak คืน code+state ตรง → แลก token ได้ `aud=word-versioning-web` + refresh/id token → ยิง `GET /api/templates` 200 → CORS preflight จาก 5173 ได้ 204 + allow-origin ถูก → refresh_token grant ผ่าน (path ที่ restoreSession ใช้) · **จำลอง request ทั้งลำดับที่หน้าเว็บส่ง** (multipart สร้าง template → PUT field defs → generate → download) → docx ค่าครบ + `SavedByAddIn`/`DocumentTrackingId` stamp ถูก +- 🩹 **รัน backend เจอ 500 "No password has been provided"**: `dotnet run --no-launch-profile` ตัด `ASPNETCORE_ENVIRONMENT=Development` (ปกติตั้งโดย launchSettings) ทิ้ง → ไปอ่าน appsettings.json ที่ DB password ว่าง — ต้องใส่ env นี้เองทุกครั้งที่ใช้ `--no-launch-profile` +- ดาวน์โหลดในเว็บใช้ blob + `a[download]` ได้ตามปกติ (browser จริง — ต่างจาก webview ของ Word ที่ต้องใช้ presigned link ตามกติกา #12) +- ⚠️ **เหลือตรวจด้วยมือ (ทำแทนไม่ได้):** เปิด browser จริง → login ผ่าน Keycloak → สร้าง/แก้ template → กรอกฟอร์มสร้างเอกสาร → ดาวน์โหลด → เปิดใน Word แล้ว add-in เห็น v1 ทันที (AC-11/12 เต็มรูปแบบ) + theme toggle จำค่าหลัง reload — ใน dev DB มี template "สัญญาทดสอบเว็บ" (เกิดจากสคริปต์ตรวจ) ไว้ click-through ได้ทันที และลบผ่านหน้าเว็บได้เอง **DoD:** login ผ่าน Keycloak บนเว็บได้ → สร้าง/แก้ template จากไฟล์จริง detect fields ถูก → กรอก form สร้างเอกสาร ได้ docx ครบค่า → เปิดใน Word แล้ว add-in รู้จักทันทีเห็น v1 (AC-11, 12 เต็มรูปแบบ) + theme toggle ทำงานทุกหน้าและจำค่าได้หลัง reload +> 🟡 implement + ตรวจอัตโนมัติผ่านหมด (2026-08-20 — รายละเอียดในหมายเหตุ) · **ส่วน "เปิดใน Word แล้ว add-in รู้จักทันที" ต้องยืนยันด้วยมือ** ร่วมกับ AC checklist ที่เหลือใน Phase 8 + --- ## Phase 7: Tests diff --git a/web/.env.example b/web/.env.example new file mode 100644 index 0000000..89544e6 --- /dev/null +++ b/web/.env.example @@ -0,0 +1,9 @@ +# ค่า default ของ local dev อยู่ใน nuxt.config.ts (runtimeConfig.public) — ไฟล์นี้ใช้ override ผ่าน env +# คัดลอกเป็น .env แล้วแก้ได้เลย (Nuxt อ่าน NUXT_PUBLIC_* อัตโนมัติ) + +# backend API (ไม่มี slash ท้าย) +NUXT_PUBLIC_API_BASE_URL=http://localhost:5075 + +# Keycloak realm (ผ่าน https:8443 ด้วย cert ของ office-addin-dev-certs) +NUXT_PUBLIC_KEYCLOAK_AUTHORITY=https://localhost:8443/realms/word-versioning +NUXT_PUBLIC_KEYCLOAK_CLIENT_ID=word-versioning-web diff --git a/web/app/app.vue b/web/app/app.vue index 08cb52c..c02e3b4 100644 --- a/web/app/app.vue +++ b/web/app/app.vue @@ -1,22 +1,73 @@