Multi-thread support for applyTemplate
This commit is contained in:
parent
f7e84e3ee7
commit
bfb07c633d
10 changed files with 1260 additions and 154 deletions
|
|
@ -7,34 +7,34 @@ import { NextRequest, NextResponse } from 'next/server';
|
|||
|
||||
import path from 'path';
|
||||
import fs from 'fs/promises';
|
||||
const EXTERNAL_API_KEY = process.env.EXTERNAL_API_KEY||'';
|
||||
const TMPL_PATH = process.env.TMPL_PATH || "./public/tmpl";
|
||||
const DOCEDIT_BASE_URL = process.env.DOCEDIT_BASE_URL || '';
|
||||
const EXTERNAL_API_KEY = process.env.EXTERNAL_API_KEY || '';
|
||||
const TMPL_PATH = process.env.TMPL_PATH || "./public/tmpl";
|
||||
const DOCEDIT_BASE_URL = process.env.DOCEDIT_BASE_URL || '';
|
||||
|
||||
async function processRequest(fid: string,tid:string,uid:string,uname:string):Promise<NextResponse<unknown>>
|
||||
{
|
||||
async function processRequest(fid: string, tid: string, uid: string, uname: string): Promise<NextResponse<unknown>> {
|
||||
//กัน path traversal attack
|
||||
const TID = path.basename(tid)
|
||||
const FID = path.basename(fid)
|
||||
const ticketObj = {fid:FID,tid:TID,uid,uname}
|
||||
const ticket_data = JSON.stringify({fid:FID,tid:TID,uid,uname})
|
||||
const filePath = path.join(TMPL_PATH,`${FID}.json`);
|
||||
const ticketObj = { fid: FID, tid: TID, uid, uname }
|
||||
const ticket_data = JSON.stringify({ fid: FID, tid: TID, uid, uname })
|
||||
const filePath = path.join(TMPL_PATH, `${FID}.json`);
|
||||
// สำหรับการ demo ในแอปเดียวเท่านั้น สามารถคำนวนหา base path ได้ด้วยวิธีนี้
|
||||
// const host = headers().get("host"); // e.g., localhost:3000
|
||||
// const proto = headers().get("x-forwarded-proto") || "http";
|
||||
// const baseUrl = process.env.NEXT_PUBLIC_BASE_PATH || ""
|
||||
// const fullUrl = `${proto}://${host}${baseUrl}`;
|
||||
// const DOCEDIT_BASE_URL = process.env.DOCEDIT_BASE_URL || fullUrl;
|
||||
if(!DOCEDIT_BASE_URL || !EXTERNAL_API_KEY) {
|
||||
if (!DOCEDIT_BASE_URL || !EXTERNAL_API_KEY) {
|
||||
return NextResponse.json({ error: 'Server must set Environment variable DOCEDIT_BASE_URL or EXTERNAL_API_KEY' }, { status: 500 });
|
||||
}
|
||||
if (!FID||!TID||!uid||!uname) {
|
||||
if (!FID || !TID || !uid || !uname) {
|
||||
return NextResponse.json({ error: 'Missing fid, tid, uid or uname' }, { status: 400 });
|
||||
}
|
||||
|
||||
|
||||
try { //ตรวจสอบ docx เวอร์ชั่นล่าสุดก่อนถ้าไม่พบค่อยเอาจากฐานข้อมูลมาสร้าง raw Json
|
||||
const params = new URLSearchParams({type:'docx',ticket_data});
|
||||
const params = new URLSearchParams({ type: 'docx', ticket_data });
|
||||
console.log(`search ${DOCEDIT_BASE_URL}/api/document/latest?${params.toString()}`);
|
||||
const lastestDocxResponse = await fetch(`${DOCEDIT_BASE_URL}/api/document/latest?${params.toString()}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
|
|
@ -42,34 +42,51 @@ async function processRequest(fid: string,tid:string,uid:string,uname:string):Pr
|
|||
'x-api-key': EXTERNAL_API_KEY,
|
||||
}
|
||||
});
|
||||
|
||||
if(!lastestDocxResponse.ok && lastestDocxResponse.status !== 404){//เกิดปัญหาที่ไม่ใช่ Not Found
|
||||
// เช็ค Content-Type และ Status ก่อน
|
||||
const contentType = lastestDocxResponse.headers.get('content-type') || '';
|
||||
if (lastestDocxResponse.ok && !!contentType.includes('text/html')) {
|
||||
console.log('Fund latest docx from docedit, returning stream to browser');
|
||||
const stream = lastestDocxResponse.body;
|
||||
//ส่ง Binary Stream กลับไปยัง Browser พร้อม Header กำหนดประเภทไฟล์
|
||||
const download_file = `${FID}_${uid}_latest`
|
||||
return new NextResponse(stream as unknown as BodyInit, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type':
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'Content-Disposition': `attachment; filename="${encodeURIComponent(download_file)}.docx"`,
|
||||
'Cache-Control': 'no-store, max-age=0',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (lastestDocxResponse.status !== 404) {//เกิดปัญหาที่ไม่ใช่ Not Found น่าจะเป็นปัญหาที่ gateway
|
||||
const errorText = await lastestDocxResponse.text();
|
||||
console.error('download-docx:Invalid latest docx Response:', errorText);
|
||||
return NextResponse.json(
|
||||
{error:`Error from docedit latest: Expected docx stream but received ${lastestDocxResponse.status} ${lastestDocxResponse.statusText}`},
|
||||
{ error: `Error from docedit latest: Expected docx stream but received ${lastestDocxResponse.status} ${lastestDocxResponse.statusText}` },
|
||||
{ status: 502 }
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return NextResponse.json({error:'Internal Server Error'}, { status: 500 });
|
||||
}
|
||||
console.log('No version found, generating docx from template and JSON data...');
|
||||
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
|
||||
}
|
||||
console.log(`Not found latest docx from docedit, fallback to generate from template and database: ${filePath}`);
|
||||
|
||||
try { // ไม่พบเวอร์ชั้นให้สร้าง docx จาก template และ json(ฐานข้อมูล)
|
||||
// อ่านไฟล์ JSON จำลองการอ่านจากฐานข้อมูล
|
||||
const rawJson = await fs.readFile(filePath, 'utf-8');
|
||||
const jsonData = JSON.parse(rawJson);
|
||||
if (!jsonData || typeof jsonData !== 'object' ) {
|
||||
return NextResponse.json({error:'Invalid data format'}, { status: 400 });
|
||||
if (!jsonData || typeof jsonData !== 'object') {
|
||||
return NextResponse.json({ error: 'Invalid data format' }, { status: 400 });
|
||||
}
|
||||
/////// !! ตรวจสอบสิทธิ์ผู้ใช้ uid ว่ามีสิทธิ์เข้าถึงไฟล์ fid หรือไม่เพื่อกันการการยิง API ตรงๆ ไม่ผ่าน Frontend !!//
|
||||
/////// ไม่มี session return 401, ถ้าเจอแต่ไม่มีสิทธิ์ให้ return 403 , อ่านจากฐานข้อมูล ถ้าไม่พบข้อมูลให้ return 404,
|
||||
|
||||
// ข้อมูลจำเป็นต้องมี jsonData.info.ticket_data ถ้าไม่มีให้สร้างขึ้นมาเอง
|
||||
if(!Object.hasOwn(jsonData, 'info'))
|
||||
jsonData['info']={}
|
||||
if (!Object.hasOwn(jsonData, 'info'))
|
||||
jsonData['info'] = {}
|
||||
jsonData.info['ticket_data'] = JSON.stringify(ticketObj)
|
||||
// sign เองตรงนี้ให้อยู่ในรูปแบบ data.info={ticket_data,HmacSha256,Ed25519} หรือว่างไว้ให้ docedit สร้างให้
|
||||
|
||||
|
|
@ -90,7 +107,7 @@ async function processRequest(fid: string,tid:string,uid:string,uname:string):Pr
|
|||
console.error('download-docx:Invalid docx Response:', errorText);
|
||||
|
||||
return NextResponse.json(
|
||||
{error:`Error from docedit generate: Expected docx stream but received ${contentType} (Status ${docxResponse.status})`},
|
||||
{ error: `Error from docedit generate: Expected docx stream but received ${contentType} (Status ${docxResponse.status})` },
|
||||
{ status: 502 }
|
||||
);
|
||||
}
|
||||
|
|
@ -108,26 +125,25 @@ async function processRequest(fid: string,tid:string,uid:string,uname:string):Pr
|
|||
'Cache-Control': 'no-store, max-age=0',
|
||||
},
|
||||
});
|
||||
} catch (error:any) {
|
||||
if ( error.code === 'ENOENT') {// ไฟล์ JSON ไม่พบ ถ้าใช้ฐานข้อมูลจริงให้ return 404 แทน
|
||||
} catch (error: any) {
|
||||
if (error.code === 'ENOENT') {// ไฟล์ JSON ไม่พบ ถ้าใช้ฐานข้อมูลจริงให้ return 404 ตั้งแต่แต่แรก และเอา ENOENT ออก
|
||||
return NextResponse.json(
|
||||
{ error: `Data not found: ${filePath}`},
|
||||
{ error: `Data not found: ${filePath}` },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
console.error(error)
|
||||
return NextResponse.json({error:'Internal Server Error'}, { status: 500 });
|
||||
}
|
||||
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest)
|
||||
{
|
||||
export async function GET(request: NextRequest) {
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const fid = searchParams.get('fid') || '';
|
||||
const tid = searchParams.get('tid') || '';
|
||||
const uid = searchParams.get('uid') || '';
|
||||
const uname = searchParams.get('uname') || '';
|
||||
return await processRequest(fid,tid,uid,uname)
|
||||
return await processRequest(fid, tid, uid, uname)
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
|
|
@ -136,5 +152,5 @@ export async function POST(request: NextRequest) {
|
|||
const tid = String(formData.get('tid'))
|
||||
const uid = String(formData.get('uid'))
|
||||
const uname = String(formData.get('uname'))
|
||||
return await processRequest(fid,tid,uid,uname)
|
||||
return await processRequest(fid, tid, uid, uname)
|
||||
}
|
||||
|
|
@ -6,8 +6,11 @@ import { NextRequest, NextResponse } from 'next/server';
|
|||
import {readFile} from 'fs/promises';
|
||||
import path from 'path';
|
||||
import {DocumentTemplate,applyTemplate} from "@/lib/lib-tmpl";
|
||||
import { docxPool } from '@/lib/docx-pool';
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
const EXTERNAL_API_KEY = process.env.EXTERNAL_API_KEY || 'secret-api-key-1234';
|
||||
|
||||
const EXTERNAL_API_KEY = process.env.EXTERNAL_API_KEY || '';
|
||||
const PRIVATE_KEY_HEX = process.env.PRIVATE_KEY_HEX ||''
|
||||
const SECRET = process.env.SECRET || 'super_secret'
|
||||
const TMPL_PATH = process.env.TMPL_PATH || "./public/tmpl";
|
||||
|
|
@ -64,7 +67,16 @@ export async function POST(request: NextRequest) {
|
|||
const tmplPath = path.join(TMPL_PATH, `${path.basename(tid)}.docx`);
|
||||
let templateBuffer: Buffer = await readFile(tmplPath)
|
||||
|
||||
const output = await applyTemplate(templateBuffer, templateData, SECRET, PRIVATE_KEY_HEX, TICKET_SEC);
|
||||
// const output = await applyTemplate(templateBuffer, templateData, SECRET, PRIVATE_KEY_HEX, TICKET_SEC);
|
||||
const output = await docxPool.run({
|
||||
templateBuffer,
|
||||
templateData,
|
||||
secret: SECRET,
|
||||
privateKeyHex: PRIVATE_KEY_HEX,
|
||||
ticketSec: TICKET_SEC
|
||||
});
|
||||
|
||||
const outputBuffer = Buffer.from(output);
|
||||
|
||||
// สร้าง ReadableStream ของไฟล์ .docx
|
||||
// ในโค้ดจริง ส่วนนี้จะดึง Binary Buffer จาก Library เช่น docx, docxtemplater หรืออ่านจาก File System
|
||||
|
|
@ -72,7 +84,7 @@ export async function POST(request: NextRequest) {
|
|||
|
||||
const docxStream = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(output);
|
||||
controller.enqueue(outputBuffer);
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue