import express from "express" export const docxTemplateRoute = express.Router() import { mimeToExtension, templateData } from "./report-template" import fs from "fs" import { createReport } from "docx-templates" const qrcode = require("yaqrcode") const axios = require("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 {String} base base path of caller relate to template-docx foler (no trail slash) * @param {templateData} tdata Template Information in JSON format * @param {String} outputMediaType output extension * @return {Promise} output buffer after apply template. */ export async function docxTemplateX(template: Buffer, tdata: templateData, outputMediaType: string = "docx"): Promise { try { // let template = await fs.promises.readFile(`${base}/${TEMPLATE_FOLDER_NAME}/${tdata.template}.docx`) 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.convertBuffer(Buffer.from(buffer), outputMediaType) 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/templateData' * 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"]) let outputMediaType = mimeToExtension(req.headers["accept"]) let template = null // Save the converted file to disk if (req.query["folder"]) { template = await fs.promises.readFile(`./${TEMPLATE_FOLDER_NAME}/${req.query["folder"]}/${req.body.template}.docx`) } else { template = await fs.promises.readFile(`./${TEMPLATE_FOLDER_NAME}/${req.body.template}.docx`) } let buffer = await docxTemplateX(template, 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) { res.statusCode = 500 res.statusMessage = "Internal Server Error during get docx template list" res.end(res.statusMessage) console.error("Error during apply template: ", 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/octet-stream: * schema: * type: string * format: binary * application/msword: * 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/templateData' * 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) } })