// ตัวอย่างรองรับค่าจาก 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 }); 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: { 'Content-Type': 'application/json', 'x-api-key': EXTERNAL_API_KEY, } }); // เช็ค 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}` }, { status: 502 } ); } } catch (error) { console.error(error) 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 }); } /////// !! ตรวจสอบสิทธิ์ผู้ใช้ 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 ตั้งแต่แต่แรก และเอา ENOENT ออก 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) }