// ตัวอย่างรองรับค่าจาก Frontend ที่เป็น Vanilla JavaScript และ Classic Framework // โดย ผ่าน Form(POST) หรือ การกดลิงค์(GET) เปลี่ยนหน้าเพื่อดาว์นโหลดไฟล์ // จะมีข้อมูล info.ticket_data ที่สมบูรณ์แล้ว({fid,tid,uid,uname})แล้วเเรียก /api/template/generate เพื่อสร้างเอกสาร docx // import { headers } from "next/headers"; 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 || ''; async function processRequest(fid: string,tid:string,uid:string,uname:string):Promise> { //กัน 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`); // สำหรับการ 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) { return NextResponse.json({ error: 'Server must set Environment variable DOCEDIT_BASE_URL or EXTERNAL_API_KEY' }, { status: 500 }); } 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 lastestDocxResponse = await fetch(`${DOCEDIT_BASE_URL}/api/document/latest?${params.toString()}`, { method: 'GET', headers: { 'Content-Type': 'application/json', 'x-api-key': EXTERNAL_API_KEY, } }); if(!lastestDocxResponse.ok && lastestDocxResponse.status !== 404){//เกิดปัญหาที่ไม่ใช่ Not Found 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}`}, { 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...'); 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 }); } /////// !! ตรวจสอบสิทธิ์ผู้ใช้ uid ว่ามีสิทธิ์เข้าถึงไฟล์ fid หรือไม่เพื่อกันการการยิง API ตรงๆ ไม่ผ่าน Frontend !!// /////// ไม่มี session return 401, ถ้าเจอแต่ไม่มีสิทธิ์ให้ return 403 , อ่านจากฐานข้อมูล ถ้าไม่พบข้อมูลให้ return 404, // ข้อมูลจำเป็นต้องมี jsonData.info.ticket_data ถ้าไม่มีให้สร้างขึ้นมาเอง if(!Object.hasOwn(jsonData, 'info')) jsonData['info']={} jsonData.info['ticket_data'] = JSON.stringify(ticketObj) // sign เองตรงนี้ให้อยู่ในรูปแบบ data.info={ticket_data,HmacSha256,Ed25519} หรือว่างไว้ให้ docedit สร้างให้ //ยิงไปหาระบบ Docedit เพื่อเอา Stream const docxResponse = await fetch(`${DOCEDIT_BASE_URL}/api/template/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': EXTERNAL_API_KEY, }, body: JSON.stringify(jsonData), }); // เช็ค Content-Type และ Status ก่อน const contentType = docxResponse.headers.get('content-type') || ''; if (!docxResponse.ok || contentType.includes('text/html')) { const errorText = await docxResponse.text(); 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})`}, { status: 502 } ); } // 2. ดึง ReadableStream โดยตรงจาก Response ของระบบ const stream = docxResponse.body; // 3. ส่ง Binary Stream กลับไปยัง Browser พร้อม Header กำหนดประเภทไฟล์ const download_file = `${FID}_${uid}_xxx` 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', }, }); } catch (error:any) { if ( error.code === 'ENOENT') {// ไฟล์ JSON ไม่พบ ถ้าใช้ฐานข้อมูลจริงให้ return 404 แทน return NextResponse.json( { error: `Data not found: ${filePath}`}, { status: 404 } ); } console.error(error) return NextResponse.json({error:'Internal Server Error'}, { status: 500 }); } } 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) } export async function POST(request: NextRequest) { const formData = await request.formData(); const fid = String(formData.get('fid')) 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) }