feat: add GET /api/documents/latest returning latest version file with stamped ticket
All checks were successful
Deploy eCourt Note Web to Server / Validate Database Schema (Drizzle) (push) Successful in 4s
Deploy eCourt Note Web to Server / Build Web Docker Image (push) Successful in 1m23s
Deploy eCourt Note Web to Server / Deploy eCourt Note Web to Server (push) Successful in 5s
Deploy eCourt Note Web to Server / Notify Deployment Status (push) Successful in 0s
All checks were successful
Deploy eCourt Note Web to Server / Validate Database Schema (Drizzle) (push) Successful in 4s
Deploy eCourt Note Web to Server / Build Web Docker Image (push) Successful in 1m23s
Deploy eCourt Note Web to Server / Deploy eCourt Note Web to Server (push) Successful in 5s
Deploy eCourt Note Web to Server / Notify Deployment Status (push) Successful in 0s
Server-to-server endpoint for the e-Court backend (x-api-key against DOCVER_API_KEY or EXTERNAL_API_KEY): - type=pdf returns the latest version's PDF bytes (404 when absent) - type=docx stamps fresh ticket custom properties (exp + HmacSha256 via addinTicketSecret(), Ed25519 when PRIVATE_KEY_HEX is set) into an in-memory copy before returning — MinIO object is never modified - docId comes from fid inside the ticket_data query param, which must include fid/uid/uname so the stamped file stays redeemable by the add-in at /api/documents/token (buildStampedTicketData drops null values because parseDocverTicket rejects tid:null) - adds parseTicketDataParam, parseLatestTypeParam, requireDocumentApiKey, findLatestVersionForDoc, getVersionObjectBytes + unit tests (320 pass) - syncs PLAN-DOCVER.md, OpenAPI spec and .env.example Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
parent
5bc8d9a817
commit
0b6ee8cc84
12 changed files with 806 additions and 9 deletions
|
|
@ -1376,4 +1376,60 @@ export async function applyTemplate(templateBuffer:Buffer,templateData:DocumentT
|
|||
}
|
||||
return templateBuffer
|
||||
|
||||
}
|
||||
|
||||
// ─── ticket stamp ของ GET /api/documents/latest (กลุ่ม docver) ────────────────
|
||||
// recipe เดียวกับ applyTemplate ด้านบนแต่แยกเป็น fn เองเพราะที่มาของ ticket
|
||||
// ต่างกัน: ของ route นั้นคือ query param จากผู้เรียก (ไม่ใช่ templateData.info)
|
||||
// และห้ามกลืน error แบบ applyTemplate — การ stamp คือตัวประสงค์ของ endpoint
|
||||
// ล้ม = throw ให้ route ตอบ 500 (คืนไฟล์ไม่มี stamp เงียบ ๆ = add-in เปิดไม่ได้)
|
||||
|
||||
/** เติม exp ใหม่ (now + ttlSeconds) + เรียง key ตามตัวอักษรแล้วคืน JSON string
|
||||
* ที่จะ stamp — HMAC/ลายเซ็นต้องคำนวณบน string ดิบนี้เป๊ะ ๆ (ห้าม trim/normalize)
|
||||
* · ตัด key ที่ค่า null/undefined ทิ้งก่อน เพราะ parser ฝั่ง redeem
|
||||
* (parseDocverTicket ของ utils/docver-ticket.ts) ไม่ยอม tid:null (ต้องเป็น
|
||||
* string หรือไม่มี key เลย) — ตัวรับเข้าของ route ใช้ null แทน "ไม่มี" */
|
||||
export function buildStampedTicketData(
|
||||
ticket: Record<string, unknown>,
|
||||
ttlSeconds: number,
|
||||
nowMs: number = Date.now(),
|
||||
): string {
|
||||
const withoutNulls = Object.fromEntries(
|
||||
Object.entries(ticket).filter(([, value]) => value !== null && value !== undefined),
|
||||
);
|
||||
// หาร 1000 เพราะต้องการหน่วยวินาที (sec) ไม่ใช่มิลลิวินาที (ms)
|
||||
const expireTimestamp = Math.floor(nowMs / 1000) + ttlSeconds;
|
||||
const fullObj = { ...withoutNulls, exp: expireTimestamp };
|
||||
const sortedObj = sortObjectKeys(fullObj);
|
||||
return JSON.stringify(sortedObj);
|
||||
}
|
||||
|
||||
export type StampTicketOptions = {
|
||||
/** secret ของ HMAC — ฝั่ง caller ของกลุ่ม docver ใช้ addinTicketSecret() เพื่อ
|
||||
* ตรงกับฝั่ง verify ของ add-in / POST /api/documents/token ทาง ticket */
|
||||
secret: string;
|
||||
/** hex ของ private key (seed 32 หรือ 64 byte) — ไม่ตั้ง = ไม่ stamp Ed25519
|
||||
* (chain ของ add-in verify แค่ HmacSha256 — Ed25519 เป็นข้อมูลเสริม) */
|
||||
privateKeyHex?: string | null;
|
||||
};
|
||||
|
||||
/** stamp custom properties `ticket_data`/`HmacSha256`(+`Ed25519`) ลงสำเนาในหน่วย
|
||||
* ความจำของ .docx — setCustomDocProps คืน Buffer ใหม่ ไม่แตะไฟล์ต้นทาง
|
||||
* (non-destructive) และ upsert ตามชื่อ จึงเขียนทับ stamp เดิมของ template ได้ ·
|
||||
* ล้มทุกกรณี = throw */
|
||||
export function stampTicketProps(
|
||||
docxBuffer: Buffer,
|
||||
ticketData: string,
|
||||
options: StampTicketOptions,
|
||||
): { buffer: Buffer; hmacSha256: string; ed25519: string | null } {
|
||||
const hmacSha256 = generateHmacSha256(ticketData, options.secret);
|
||||
const privateKeyHex = options.privateKeyHex?.trim() || null;
|
||||
const ed25519 = privateKeyHex !== null ? signEd25519(ticketData, privateKeyHex) : null;
|
||||
const buffer = setCustomDocProps(
|
||||
docxBuffer,
|
||||
ed25519 === null
|
||||
? { ticket_data: ticketData, HmacSha256: hmacSha256 }
|
||||
: { ticket_data: ticketData, HmacSha256: hmacSha256, Ed25519: ed25519 },
|
||||
);
|
||||
return { buffer, hmacSha256, ed25519 };
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue