Some checks failed
Deploy eCourt Note Web to Server / Validate Database Schema (Drizzle) (push) Successful in 5s
Deploy eCourt Note Web to Server / Build Web Docker Image (push) Failing after 1m11s
Deploy eCourt Note Web to Server / Deploy eCourt Note Web to Server (push) Has been skipped
Deploy eCourt Note Web to Server / Notify Deployment Status (push) Failing after 0s
178 lines
No EOL
6.7 KiB
TypeScript
178 lines
No EOL
6.7 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { DocumentTemplate, templateContentControls, setCustomDocProps, templateDynamicContents } from "@/lib/lib-tmpl";
|
|
import { generateHmacSha256, signEd25519, 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
|
|
const TICKET_SEC = Number(process.env.TICKET_SEC||'300')
|
|
|
|
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 = String(formData.get("secret")||"")
|
|
const TEST_PRIVATE_KEY_HEX = String(formData.get("private_key")||"")
|
|
|
|
// ตรวจสอบ Template
|
|
if (!(template instanceof File)) {
|
|
return NextResponse.json(
|
|
{
|
|
error: 'Missing DOCX file in field "template".',
|
|
},
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
if (template.type !== DOCX_CONTENT_TYPE &&
|
|
!template.name.toLowerCase().endsWith(".docx") &&
|
|
template.size > MAX_UPLOAD_BYTES
|
|
) {
|
|
return NextResponse.json(
|
|
{
|
|
error: 'Field "template" must be a .docx file and not 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(
|
|
{
|
|
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(
|
|
{
|
|
error:
|
|
'Missing JSON data. Send either "data" file or "dataJson" field.',
|
|
},
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
} catch (error) {
|
|
return NextResponse.json(
|
|
{
|
|
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(
|
|
{
|
|
error: "JSON data must be an object.",
|
|
},
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
// -----------------------------
|
|
// Template File → Buffer
|
|
// -----------------------------
|
|
|
|
let templateBuffer: Buffer = Buffer.from(
|
|
await template.arrayBuffer(),
|
|
);
|
|
const output = await applyTemplate(templateBuffer,templateData,TEST_SECRET,TEST_PRIVATE_KEY_HEX);
|
|
|
|
// -----------------------------
|
|
// ส่ง DOCX กลับ
|
|
// -----------------------------
|
|
if(templateData?.info?.ticket_data)
|
|
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(
|
|
{
|
|
|
|
error:
|
|
error instanceof Error
|
|
? error.message
|
|
: "Internal Server Error",
|
|
},
|
|
{ status: 500 },
|
|
);
|
|
}
|
|
}
|
|
|
|
export async function applyTemplate(templateBuffer:Buffer,templateData:DocumentTemplate,
|
|
secret:string,private_key:string):Promise<Buffer<ArrayBufferLike>>
|
|
{
|
|
//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 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) + TICKET_SEC;
|
|
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, secret)
|
|
|
|
const Ed25519 = signEd25519(ticket_data, private_key)
|
|
templateBuffer = setCustomDocProps(templateBuffer, { ticket_data, HmacSha256, Ed25519 })
|
|
}
|
|
} catch (error) { //just skip
|
|
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);
|
|
}
|
|
return templateBuffer
|
|
|
|
} |