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
|
|
@ -1,4 +1,4 @@
|
|||
import { generateHmacSha256, signEd25519, sortObjectKeys } from '@/lib/lib-utils';
|
||||
import { generateHmacSha256, signEd25519, sortObjectKeys, verifyEd25519, verifyHmacSha256 } from '@/lib/lib-utils';
|
||||
import createReport from 'docx-templates';
|
||||
import PizzaZip from "pizzip";
|
||||
import {
|
||||
|
|
@ -1217,6 +1217,71 @@ export function getDocProps(
|
|||
}
|
||||
|
||||
|
||||
//**************************** document snapshot *******************************************
|
||||
|
||||
/** DocPropsResult หลังทำ JSON-safe (Date → ISO string) — ชนิดของ `properties` ใน
|
||||
* DocumentExtractSnapshot ทั้งฝั่ง response ของ /api/template/extract และ column
|
||||
* `fields` jsonb ของ document_versions (ค่าข้าม server→client เป็น string เสมอ) */
|
||||
export interface DocPropsJson {
|
||||
standard?: Record<string, string | undefined>;
|
||||
extended?: Record<string, number | string | undefined>;
|
||||
custom?: Record<string, string | number | boolean | null>;
|
||||
}
|
||||
|
||||
/** รูป return ของ /api/template/extract — ใช้ร่วม 2 ที่: route นั้นคืนทั้งก้อนตรง ๆ
|
||||
* และ POST /api/documents/versions เก็บทั้งก้อนเป็น fields ของ version (2026-10-30) */
|
||||
export interface DocumentExtractSnapshot {
|
||||
/** ค่า Content Control `{ [tag]: text }` */
|
||||
cc: ExtractedData;
|
||||
/** ข้อความใน bookmark `{ [name]: text }` */
|
||||
bookmark: ExtractedData;
|
||||
/** document properties (standard/extended/custom) */
|
||||
properties: DocPropsJson;
|
||||
/** ผลตรวจ ticket ที่ stamp มากับไฟล์ — exp (ฟอร์แมตไทย) + HmacSha256/Ed25519 เฉพาะเมื่อ
|
||||
* ผู้เรียกส่ง secret/publicKeyHex มาให้ใช้ตรวจ */
|
||||
verify: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/** แกะข้อมูลจาก docx ทั้งชุด `{cc, bookmark, properties, verify}` — logic ย้ายมาจาก
|
||||
* app/api/template/extract/route.ts แบบเดิม (แถวต่อแถว) เพื่อให้ return ของ extract
|
||||
* กับ fields ของ version ที่สร้างใหม่ตรงกันเป๊ะโดยการก่อสร้าง
|
||||
* · secret/publicKeyHex = ของผู้เรียกเพื่อ "ทดสอบ verify" (extract) หรือ secret ของ
|
||||
* add-in ticket ฝั่ง server (ตอนสร้าง version) — ไม่ส่ง = ไม่มี key นั้นใน verify
|
||||
* · ticket_data ที่ไม่ใช่ JSON จะ throw ผู้เรียกจัดการเอง (extract = 500 / versions = 400) */
|
||||
export function extractDocumentSnapshot(
|
||||
docxBuffer: Buffer,
|
||||
options: { secret?: string; publicKeyHex?: string } = {},
|
||||
): DocumentExtractSnapshot {
|
||||
const cc = extractContentControls(docxBuffer);
|
||||
const bookmark = extractBookmark(docxBuffer);
|
||||
// JSON-safe ทันที (Date → ISO string): response ของ extract กับ jsonb ของ version ได้
|
||||
// ค่าชนิดเดียวกัน (NextResponse.json / drizzle serialize Date เป็น ISO อยู่แล้ว —
|
||||
// ผลลัพธ์ JSON ไม่ต่างจากเดิม เปลี่ยนแค่ชนิดฝั่ง TS)
|
||||
const properties = JSON.parse(JSON.stringify(getDocProps(docxBuffer))) as DocPropsJson;
|
||||
|
||||
const 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 (options.secret && HmacSha256) {
|
||||
verify['HmacSha256'] = verifyHmacSha256(ticket_data, options.secret, HmacSha256);
|
||||
}
|
||||
if (options.publicKeyHex && Ed25519) {
|
||||
verify["Ed25519"] = verifyEd25519(ticket_data, Ed25519, options.publicKeyHex);
|
||||
}
|
||||
}
|
||||
|
||||
return { cc, bookmark, properties, verify };
|
||||
}
|
||||
|
||||
|
||||
//**************************** dynamic content *******************************************
|
||||
// sdtLocked(ห้ามลบ CC), contentLocked(ลบได้ห้ามแก้), sdtContentLocked(ห้ามทุกอย่าง)
|
||||
type LockType = 'none' | 'sdtLocked' | 'contentLocked' | 'sdtContentLocked';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue