import express from "express" export const docxTemplateRoute = express.Router() import { mimeToExtension, templateOption } from "./report-template" import fs from "fs" import { createReport } from "docx-templates" import qrcode from "yaqrcode" import axios from "axios" // แก้ package.json ของ LibreOfficeFileConverter // https://github.com/microsoft/TypeScript/issues/52363#issuecomment-1659179354 import { LibreOfficeFileConverter } from "libreoffice-file-converter" const TEMPLATE_FOLDER_NAME = "templates/docx" /** * docxTemplate Uses docx-template to convert input data and template to output buffer. * You have to handle exception throw by function * template keep in folder templates * @param {Buffer|String} t template in buffer format or path to file * @param {templateOption} tdata Template Information in JSON format * @param {String} outputMediaType output extension * @return {Promise} output buffer after apply template. */ export async function docxTemplateX( t: Buffer | String, tdata: templateOption, outputMediaType: string = "docx" ): Promise { try { const template = Buffer.isBuffer(t) ? t : await fs.promises.readFile(String(t)) const buffer = await createReport({ template, data: tdata.data, additionalJsContext: { qrCode: ( contents: string, caption: string, size: number, width: number, height: number ) => { const dataUrl = qrcode(contents, { size: size }) const data = dataUrl.slice("data:image/gif;base64,".length) return { width, height, data, extension: ".gif", caption } }, addImageByUrl: async ( imageUrl: string, width: number, height: number, caption: string ) => { const response = await axios.get(imageUrl, { responseType: "arraybuffer", }) const imageData = Buffer.from(response.data).toString("base64") // Convert image to base64 const ext = ".png" // Assuming PNG format; adjust based on actual image type return { width, height, data: imageData, extension: ext, caption, } }, }, }) if (outputMediaType === "docx") return buffer const libreOfficeFileConverter = new LibreOfficeFileConverter({ childProcessOptions: { timeout: 60 * 1000, }, }) const lbuffer = await libreOfficeFileConverter.convert({ buffer: Buffer.from(buffer), format: outputMediaType, input: "buffer", output: "buffer", }) return lbuffer } catch (e) { throw e } } /** javascript-obfuscator:disable * @swagger * /api/v1/report-template/docx: * get: * summary: แสดงรายการ template ที่มีในโฟลเดอร์ templates * tags: [report-template] * responses: * 200: * description: array of template * content: * applicatin/json: * schema: * type: array * items: * type: string * example: ["hello"] * 500: * description: Server error */ docxTemplateRoute.get("/", async function (req, res) { try { const fileList = await fs.promises.readdir(`./${TEMPLATE_FOLDER_NAME}`) const templateList = fileList.map((f) => f.split(".docx")[0]) res.send(templateList) } catch (ex) { res.statusCode = 500 res.statusMessage = "Internal Server Error" res.end(res.statusMessage) console.error("Error during get template list: ", ex) } }) /** javascript-obfuscator:disable * @swagger * /api/v1/report-template/docx: * post: * summary: สร้างเอกสารโดยใช้ template จากไฟล์ docx จะแทนค่าตัวแปรในเอกสาร หรือจะแปลงเป็นฟอร์แม็ตอื่นได้ด้วยรองรับ docx pdf odt, ค่า template เป็นชื่อของ template, reportName เป็นชื่อไฟล์ที่ต้องการ * tags: [report-template] * parameters: * - name: folder * in: query * description: ชื่อโฟลเดอร์ * required: false * schema: * type: string * example: test * requestBody: * required: true * content: * application/json: * schema: * $ref: '#/components/schemas/templateOption' * example: * template: hello * reportName: docx-report * data: {"docNo":"๑๒๓๔๕","me": "กระผม","prefix": "นาย","name": "สรวิชญ์","surname": "พลสิทธิ์","position": "Chief Technology Officer","org": {"type": "บริษัท","name": "เฟรปเป้ที","url": "https://frappet.com"},"employees": [{"name": "ภาวิชญ์","surname": "พลสิทธิ์"},{"name": "วิชญาภา","surname": "พลสิทธิ์"}]} * responses: * 201: * description: เอกสารถูกสร้างขึ้น * content: * application/vnd.openxmlformats-officedocument.wordprocessingml.document: * schema: * type: string * format: binary * application/pdf: * schema: * type: string * format: binary * application/vnd.oasis.opendocument.text: * schema: * type: string * format: binary * image/png: * schema: * type: string * format: binary * image/jpeg: * schema: * type: string * format: binary * 500: * description: Server error * */ docxTemplateRoute.post("/", async function (req, res) { try { if (!req.headers["content-type"] || !req.headers["accept"]) throw new Error("Require header content-type, accept") let inputType = mimeToExtension(req.headers["content-type"]) // application/json let outputMediaType = mimeToExtension(req.headers["accept"]) const include_folder = req.query["folder"] ? "/" + req.query["folder"] : "" console.log(req.body) let buffer = await docxTemplateX( `./${TEMPLATE_FOLDER_NAME}${include_folder}/${req.body.template}.docx`, req.body, outputMediaType ) res.statusCode = 201 res.setHeader("Content-Type", req.headers["accept"]) res.setHeader( "Content-Disposition", `attachment;filename=${req.body.reportName}.${outputMediaType}` ) res.setHeader("Content-Length", buffer.length) res.end(buffer) } catch (ex) { if (ex instanceof SyntaxError) { res.statusCode = 400 res.statusMessage = ex.message res.end(res.statusMessage) console.error("report-template/docx: ", ex) } else { res.statusCode = 500 res.statusMessage = "Internal Server Error during POST report-template/docx" res.end(res.statusMessage) console.error("report-template/docx: ", ex) } } }) /** javascript-obfuscator:disable * @swagger * /api/v1/report-template/docx/upload: * post: * summary: อัพไฟล์ docx * tags: [report-template] * parameters: * - name: report_name * in: query * description: ชื่อไฟล์ * required: true * schema: * type: string * example: report * - name: folder * in: query * description: ชื่อโฟลเดอร์ * required: false * schema: * type: string * example: test * requestBody: * required: true * content: * application/octet-stream: * schema: * type: string * format: binary * responses: * 201: * description: file was converted. * content: * application/vnd.openxmlformats-officedocument.wordprocessingml.document: * schema: * type: string * format: binary * application/msword: * schema: * type: string * format: binary * application/pdf: * schema: * type: string * format: binary * image/png: * schema: * type: string * format: binary * image/jpeg: * schema: * type: string * format: binary * 400: * description: Invalid format * 500: * description: Server error * */ docxTemplateRoute.post("/upload", async function (req, res) { try { if ( !req.headers["content-type"] || !req.headers["accept"] || !req.query["report_name"] || req.headers["content-type"] !== "application/octet-stream" ) { res.statusCode = 400 res.statusMessage = "Require header: content-type(application/octet-stream) accept" res.end(res.statusMessage) return } // Determine the output media type and report name from headers let outputMediaType = mimeToExtension(req.headers["accept"]) let reportName = req.query["report_name"] // Save the converted file to disk if (req.query["folder"]) { // Ensure the template folder exists await fs.promises.mkdir(`TEMPLATE_FOLDER_NAME/${req.query["folder"]}`, { recursive: true, }) await fs.promises.writeFile( `./${TEMPLATE_FOLDER_NAME}/${req.query["folder"]}/${reportName}.docx`, req.body ) } else { // Ensure the template folder exists await fs.promises.mkdir(TEMPLATE_FOLDER_NAME, { recursive: true }) await fs.promises.writeFile( `./${TEMPLATE_FOLDER_NAME}/${reportName}.docx`, req.body ) } // Send a response to the client res.statusCode = 201 res.json({ message: "File converted and saved successfully" }) } catch (ex) { res.statusCode = 500 res.statusMessage = "Internal Server Error" res.end(res.statusMessage) console.error(`Error during convert with soffice:`, ex) } }) /** javascript-obfuscator:disable * @swagger * /api/v1/report-template/docx/download: * post: * summary: โหลดไฟล์ docx * tags: [report-template] * parameters: * - name: folder * in: query * description: ชื่อโฟลเดอร์ * required: false * schema: * type: string * example: test * requestBody: * required: true * content: * application/json: * schema: * $ref: '#/components/schemas/templateOption' * example: * template: docx-report * responses: * 201: * description: เอกสารถูกสร้างขึ้น * content: * application/vnd.openxmlformats-officedocument.wordprocessingml.document: * schema: * type: string * format: binary * application/vnd.oasis.opendocument.text: * schema: * type: string * format: binary * 500: * description: Server error * */ docxTemplateRoute.post("/download", async function (req, res) { try { if ( !req.headers["content-type"] || !req.headers["accept"] || !req.body.template ) throw new Error("Require header content-type, accept") let inputType = mimeToExtension(req.headers["content-type"]) let outputMediaType = mimeToExtension(req.headers["accept"]) let buffer = null if (req.query["folder"]) { buffer = await fs.promises.readFile( `./${TEMPLATE_FOLDER_NAME}/${req.query["folder"]}/${req.body.template}.docx` ) } else { buffer = await fs.promises.readFile( `./${TEMPLATE_FOLDER_NAME}/${req.body.template}.docx` ) } res.statusCode = 201 res.setHeader("Content-Type", req.headers["accept"]) res.setHeader( "Content-Disposition", `attachment;filename=${req.body.template}.${outputMediaType}` ) res.setHeader("Content-Length", buffer.length) res.end(buffer) } catch (ex) { res.statusCode = 500 res.statusMessage = "Internal Server Error during get docx template list" res.end(res.statusMessage) console.error("Error during apply template: ", ex) } })