import { NextResponse } from "next/server"; import { DocumentTemplate, templateContentControls, setCustomDocProps, getDocProps, templateDynamicContents } from "@/lib/lib-tmpl"; import { generateHmacSha256, verifyHmacSha256, signDataEd25519, verifyDataEd25519, sortObjectKeys } from '@/lib/lib-utils'; export const runtime = "nodejs"; const DOCX_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; // cap เดียวกับ N4 (app/actions/files.ts) — route นี้เปิดกว้างให้ภายนอกยิงได้ // ต้องเช็คขนาดก่อนอ่านเข้าหน่วยความจำ กัน DoS (bodySizeLimit ครอบเฉพาะ Server Actions) const MAX_UPLOAD_BYTES = 100 * 1024 * 1024; // 100MB export async function POST(request: Request) { try { const formData = await request.formData(); const template = formData.get("template"); const dataFile = formData.get("data"); const dataJson = formData.get("dataJson"); const TEST_SECRET = (typeof formData.get("secret") === "string") ? String(formData.get("secret")) : "super_secret"; const TEST_PRIVATE_KEY_HEX = (typeof formData.get("private_key") === "string") ? String(formData.get("private_key")) : "8f2077b57b39399b6a7828f35da03a63ade8e3a36a6bf437b091a8bdb3d18ee1"; console.log(`secret:${TEST_SECRET}`) console.log(`private:${TEST_PRIVATE_KEY_HEX}`) // ----------------------------- // ตรวจสอบ Template // ----------------------------- if (!(template instanceof File)) { return NextResponse.json( { success: false, error: 'Missing DOCX file in field "template".', }, { status: 400 }, ); } if ( template.type !== DOCX_CONTENT_TYPE && !template.name.toLowerCase().endsWith(".docx") ) { return NextResponse.json( { success: false, error: 'Field "template" must be a .docx file.', }, { status: 400 }, ); } if (template.size > MAX_UPLOAD_BYTES) { return NextResponse.json( { success: false, error: 'Field "template" exceeds the 100MB size limit.', }, { status: 400 }, ); } // ----------------------------- // อ่าน JSON // ----------------------------- let templateData: DocumentTemplate; // JSON ผิด syntax เป็นความผิดของฝั่งผู้เรียก — คืน 400 ไม่ใช่ 500 // (catch กลางท้ายฟังก์ชันสงวนไว้ให้ error ของระบบเท่านั้น) try { if (typeof dataJson === "string" && dataJson.length > 0) { // กรณีส่ง JSON ผ่าน form field templateData = JSON.parse(dataJson) as DocumentTemplate; } else if (dataFile instanceof File) { if (dataFile.size > MAX_UPLOAD_BYTES) { return NextResponse.json( { success: false, error: 'Field "data" exceeds the 100MB size limit.', }, { status: 400 }, ); } // กรณี upload data.json const text = await dataFile.text(); templateData = JSON.parse(text) as DocumentTemplate; } else { return NextResponse.json( { success: false, error: 'Missing JSON data. Send either "data" file or "dataJson" field.', }, { status: 400 }, ); } } catch (error) { return NextResponse.json( { success: false, error: `Invalid JSON data (${error instanceof Error ? error.message : "parse error" }).`, }, { status: 400 }, ); } // JSON ต้องเป็น object if ( templateData === null || typeof templateData !== "object" || Array.isArray(templateData) ) { return NextResponse.json( { success: false, error: "JSON data must be an object.", }, { status: 400 }, ); } // ----------------------------- // Template File → Buffer // ----------------------------- let templateBuffer: Buffer = Buffer.from( await template.arrayBuffer(), ); //Calculate hash const originalTicket_data = templateData?.info?.ticket_data //expect valid json string // console.log("templateData",templateData?.info?.ticket_data) if (originalTicket_data && typeof originalTicket_data === 'string') { try { const ttlSeconds: number = 300 const originalTicketObj = JSON.parse(originalTicket_data)//sort and add expire to ticket data if (originalTicketObj && typeof originalTicketObj === 'object') { console.log("Found valid ticket data: hash it") // คำนวณค่า expire (ปัจจุบัน + จำนวนวินาทีที่ต้องการให้หมดอายุ) // หาร 1000 เพราะต้องการหน่วยวินาที (sec) ไม่ใช่มิลลิวินาที (ms) const expireTimestamp = Math.floor(Date.now() / 1000) + ttlSeconds; const fullObj = { ...originalTicketObj, exp: expireTimestamp }; const sortedObj = sortObjectKeys(fullObj); // แปลงกลับเป็น JSON string เพื่อนำไปใช้ส่งและทำ Hash const ticket_data = JSON.stringify(sortedObj); // console.log("check secret:",TEST_SECRET,TEST_PRIVATE_KEY_HEX,ticket_data) const HmacSha256 = generateHmacSha256(ticket_data, TEST_SECRET) const Ed25519 = signDataEd25519(ticket_data, TEST_PRIVATE_KEY_HEX) templateBuffer = setCustomDocProps(templateBuffer, { ticket_data, HmacSha256, Ed25519 }) } } catch (error) { console.error("Set custom property fail",error); } } // Apply Dynamic Content if (templateData?.dc && typeof templateData?.dc === "object") { console.log("Apply Dynamic Content") templateBuffer = Buffer.from(await templateDynamicContents(templateBuffer, templateData.dc)) } // Apply Content Control if (templateData?.cc && typeof templateData?.cc === "object") { console.log("Apply Content Control") templateBuffer = templateContentControls(templateBuffer, templateData.cc); } const output = templateBuffer; // ----------------------------- // ส่ง DOCX กลับ // ----------------------------- return new NextResponse(output as BodyInit, { status: 200, headers: { "Content-Type": DOCX_CONTENT_TYPE, "Content-Disposition": 'attachment; filename="generated-document.docx"', "Content-Length": String(output.length), }, }); } catch (error) { console.error(error); return NextResponse.json( { success: false, error: error instanceof Error ? error.message : "Internal Server Error", }, { status: 500 }, ); } }