Refactor document versioning to extract fields from uploaded .docx files on the server side
- Removed the `fields` parameter from the version upload form, as it is now extracted from the uploaded .docx file using the `extractContentControls` function. - Updated related types and interfaces to reflect the new structure of version fields. - Modified the OpenAPI specification to document the changes in the versioning process. - Adjusted tests to ensure compliance with the new behavior, including validation for the absence of the `fields` parameter. - Implemented a new function `extractDocumentSnapshot` to handle the extraction of content controls and document properties from .docx files. - Updated metadata and versioning services to align with the new extraction logic.
This commit is contained in:
parent
bc61f0fd2e
commit
cdae1ceedd
17 changed files with 380 additions and 179 deletions
|
|
@ -6,7 +6,8 @@
|
|||
// ไม่รับจาก URL/form อีก (lib/docver-token.ts)
|
||||
import { createHash, randomUUID } from "node:crypto";
|
||||
|
||||
import { toDocumentVersionDTO } from "@/db/schema";
|
||||
import { toDocumentVersionDTO, type DocumentVersionFields } from "@/db/schema";
|
||||
import { addinTicketSecret } from "@/lib/addin-ticket";
|
||||
import {
|
||||
docverError,
|
||||
DOCVER_MAX_UPLOAD_BYTES,
|
||||
|
|
@ -16,7 +17,9 @@ import {
|
|||
parseVersionUploadForm,
|
||||
sanitizeFilePartName,
|
||||
} from "@/lib/document-versioning";
|
||||
import { extractContentControls } from "@/lib/lib-tmpl";
|
||||
import { requireDocverBearer } from "@/lib/docver-token";
|
||||
import { extractDocumentSnapshot } from "@/lib/lib-tmpl";
|
||||
import {
|
||||
findDocumentByDocId,
|
||||
insertVersionRecord,
|
||||
|
|
@ -31,6 +34,7 @@ export const dynamic = "force-dynamic";
|
|||
|
||||
/** POST — อัปโหลด version ใหม่ (เอกสารสร้างแบบ implicit ถ้าเป็น version แรก)
|
||||
* ลำดับ: Bearer token → validate text field ก่อนแตะไฟล์ → cap ขนาดก่อน buffer → magic bytes →
|
||||
* แกะ fields จากไฟล์ (lib-tmpl เหมือน /api/template/extract) →
|
||||
* putObject MinIO → insert DB (tx) — DB ล้ม = เก็บกวาด object (pattern N4)
|
||||
* response = UploadVersionResult เป๊ะ โดย versionNumber เป็นค่า authoritative
|
||||
* จาก server (client เดา latest+1 แล้วแก้เองเมื่ออัปโหลดชนกัน) */
|
||||
|
|
@ -92,6 +96,21 @@ export async function POST(request: Request) {
|
|||
return docverError("เนื้อไฟล์ .pdf ไม่ถูกต้อง", 400);
|
||||
}
|
||||
|
||||
// fields snapshot แกะจากไฟล์เองฝั่ง server — **เก็บทั้งก้อนเหมือน return ของ
|
||||
// /api/template/extract** ({cc, bookmark, properties, verify} ผ่าน extractDocumentSnapshot
|
||||
// ของ lib/lib-tmpl.ts ตัวเดียวกับ route นั้น — 2026-10-30 ถอด form field "fields"
|
||||
// ที่ add-in อ่านผ่าน Office.js ออกจาก contract): มองเห็น CC ทุกชนิดเสมอไม่ขึ้นกับ
|
||||
// เวอร์ชัน WordApi ของ host · verify.HmacSha256 ตรวจด้วย secret ของ add-in ticket
|
||||
// ฝั่ง server (env chain เดียวกับ lib/addin-ticket.ts) · ZIP ไม่มี word/document.xml,
|
||||
// parse ไม่ได้ หรือ ticket_data ไม่ใช่ JSON = 400
|
||||
let fields: DocumentVersionFields;
|
||||
try {
|
||||
fields = extractDocumentSnapshot(docxBytes, { secret: addinTicketSecret() });
|
||||
} catch (err) {
|
||||
console.error("docver: แกะข้อมูลจากไฟล์ไม่สำเร็จ", err);
|
||||
return docverError("เนื้อไฟล์ .docx ไม่ถูกต้อง (แกะข้อมูลไม่สำเร็จ)", 400);
|
||||
}
|
||||
|
||||
const contentHash = createHash("sha256").update(docxBytes).digest("hex");
|
||||
const versionId = randomUUID();
|
||||
// ชื่อแสดงผลตอนดาวน์โหลด: field "fileName" ของผู้เรียก (add-in ส่งมาเอง) →
|
||||
|
|
@ -119,7 +138,7 @@ export async function POST(request: Request) {
|
|||
clientVersionId: parsed.form.clientVersionId,
|
||||
contentHash,
|
||||
message: parsed.form.message,
|
||||
fields: parsed.form.fields,
|
||||
fields,
|
||||
templateId: auth.claims.template,
|
||||
fileName: docxDisplayName,
|
||||
docxObjectKey: keys.docx,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { NextResponse } from "next/server";
|
||||
import { extractContentControls, extractBookmark, templateContentControls, setCustomDocProps, getDocProps,templateDynamicContents } from "@/lib/lib-tmpl";
|
||||
import { verifyEd25519, verifyHmacSha256 } from "@/lib/lib-utils";
|
||||
import { extractDocumentSnapshot } from "@/lib/lib-tmpl";
|
||||
// export const runtime = "nodejs";
|
||||
|
||||
const DOCX_CONTENT_TYPE =
|
||||
|
|
@ -40,32 +39,13 @@ export async function POST(request: Request) {
|
|||
// File → Buffer
|
||||
const buffer = Buffer.from(await file.arrayBuffer());
|
||||
|
||||
// ใช้ lib-tmpl.ts เดิม
|
||||
// const data = extractContentControls(buffer);
|
||||
|
||||
const cc = extractContentControls(buffer);
|
||||
const bookmark = extractBookmark(buffer)
|
||||
const properties = getDocProps(buffer)
|
||||
let verify: Record<string, unknown> ={}
|
||||
const ticket_data = properties?.custom?.ticket_data
|
||||
const HmacSha256 = String(properties?.custom?.HmacSha256)
|
||||
const Ed25519 = String(properties?.custom?.Ed25519)
|
||||
|
||||
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['HmacSha256'] = verifyHmacSha256(ticket_data,TEST_SECRET,HmacSha256)
|
||||
}
|
||||
if(TEST_PUBLIC_KEY_HEX&& Ed25519){
|
||||
verify["Ed25519"]=verifyEd25519(ticket_data,Ed25519,TEST_PUBLIC_KEY_HEX)
|
||||
}
|
||||
}
|
||||
|
||||
let returnData : Record<string, unknown> = {cc,bookmark,properties,verify}
|
||||
// ชุดแกะข้อมูล `{cc, bookmark, properties, verify}` จาก lib-tmpl.ts — ตัวเดียวกับที่
|
||||
// ใช้เก็บเป็น fields ของ version ตอน POST /api/documents/versions (2026-10-30) ·
|
||||
// secret/public_key เป็นของผู้เรียกเพื่อ "ทดสอบ verify" เฉพาะฝั่งนี้
|
||||
const returnData = extractDocumentSnapshot(buffer, {
|
||||
secret: TEST_SECRET || undefined,
|
||||
publicKeyHex: TEST_PUBLIC_KEY_HEX || undefined,
|
||||
});
|
||||
return NextResponse.json(returnData);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue