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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
- 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 <noreply@anthropic.com>
- 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 <noreply@anthropic.com>
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 <noreply@anthropic.com>