From 3c35c1e5526f20217b1387b59daf82af8bce29e3 Mon Sep 17 00:00:00 2001 From: oom Date: Fri, 28 Feb 2025 11:43:17 +0700 Subject: [PATCH] format with prettier --- app.ts | 48 +++--- libs/convert-libs.ts | 112 +++++++------- libs/create-swagger-spec.ts | 48 +++--- libs/report-template.ts | 88 +++++------ libs/xlsx-template-lib.ts | 283 +++++++++++++++++++++--------------- test-run/docx-template.ts | 43 +++--- test-run/grafana_pdf.js | 220 ++++++++++++++-------------- test-run/html-template.ts | 47 +++--- test-run/url_pup2pdf.ts | 74 +++++----- test-run/xlsx-template.ts | 43 +++--- 10 files changed, 540 insertions(+), 466 deletions(-) diff --git a/app.ts b/app.ts index 9f0f739..d1a00a1 100644 --- a/app.ts +++ b/app.ts @@ -1,31 +1,35 @@ /* -* Report Server -* Web API สำหรับสร้างรายงาน กำหนด Path และเพิ่มฟีเจอร์ได้ -* จะใช้ Node.js เป็น reverse proxy ไปหา pandoc ที่ swan ขึ้นมา -* demo frontent อยู่ในโฟลเดอร์ public -*/ + * Report Server + * Web API สำหรับสร้างรายงาน กำหนด Path และเพิ่มฟีเจอร์ได้ + * จะใช้ Node.js เป็น reverse proxy ไปหา pandoc ที่ swan ขึ้นมา + * demo frontent อยู่ในโฟลเดอร์ public + */ import cors from "cors" import swaggerspecs from "./libs/swagger-specs.json" import swaggerUi from "swagger-ui-express" -import express, { Express, Request, Response } from 'express' -import { docxTemplateRoute } from './libs/docx-templates-lib' -import { xlsxTemplateRoute } from './libs/xlsx-template-lib' +import express, { Express, Request, Response } from "express" +import { docxTemplateRoute } from "./libs/docx-templates-lib" +import { xlsxTemplateRoute } from "./libs/xlsx-template-lib" import { htmlTemplateRoute } from "./libs/html-templates-lib" -import { convertTemplateRoute } from './libs/convert-libs' +import { convertTemplateRoute } from "./libs/convert-libs" const app: Express = express() -const port: number = Number(process.env.PORT) || 80; -app.use(cors()); -app.use(express.json({ limit: "200mb" })); //‘application/json’ -app.use(express.raw({ limit: "200mb" })); //‘application/octet-stream’ -app.use(express.urlencoded({ extended: true, limit: "200mb" })); -app.use("/swagger", swaggerUi.serve, swaggerUi.setup(swaggerspecs,{ explorer: true })); -app.get('/', (req: Request, res: Response) => { +const port: number = Number(process.env.PORT) || 80 +app.use(cors()) +app.use(express.json({ limit: "200mb" })) //‘application/json’ +app.use(express.raw({ limit: "200mb" })) //‘application/octet-stream’ +app.use(express.urlencoded({ extended: true, limit: "200mb" })) +app.use( + "/swagger", + swaggerUi.serve, + swaggerUi.setup(swaggerspecs, { explorer: true }) +) +app.get("/", (req: Request, res: Response) => { res.json({ - message: 'Hello report-template API !!', + message: "Hello report-template API !!", }) }) -app.use('/api/v1/report-template/docx', docxTemplateRoute); -app.use('/api/v1/report-template/xlsx', xlsxTemplateRoute); -app.use('/api/v1/report-template/html', htmlTemplateRoute); -app.use('/api/v1/report-template/convert', convertTemplateRoute); -app.listen(port, () => console.log(`Application is running on port ${port}`)) \ No newline at end of file +app.use("/api/v1/report-template/docx", docxTemplateRoute) +app.use("/api/v1/report-template/xlsx", xlsxTemplateRoute) +app.use("/api/v1/report-template/html", htmlTemplateRoute) +app.use("/api/v1/report-template/convert", convertTemplateRoute) +app.listen(port, () => console.log(`Application is running on port ${port}`)) diff --git a/libs/convert-libs.ts b/libs/convert-libs.ts index a31e39c..c1ea489 100644 --- a/libs/convert-libs.ts +++ b/libs/convert-libs.ts @@ -1,14 +1,14 @@ -import express from 'express' -export const convertTemplateRoute = express.Router(); -import {mimeToExtension} from './report-template' -import { LibreOfficeFileConverter } from 'libreoffice-file-converter'; +import express from "express" +export const convertTemplateRoute = express.Router() +import { mimeToExtension } from "./report-template" +import { LibreOfficeFileConverter } from "libreoffice-file-converter" /** javascript-obfuscator:disable * @swagger * tags: * name: office-convert * description: ใช้แปลงไฟล์จากเอกสารที่ libreoffice แปลงได้ เช่น docx เป็น pdf -*/ + */ /** javascript-obfuscator:disable * @swagger @@ -19,7 +19,7 @@ import { LibreOfficeFileConverter } from 'libreoffice-file-converter'; * parameters: * - name: report-name * in: header - * description: ชื่อไฟล์ที่ต้องการหลังแปลง + * description: ชื่อไฟล์ที่ต้องการหลังแปลง * required: true * schema: * type: string @@ -66,49 +66,59 @@ import { LibreOfficeFileConverter } from 'libreoffice-file-converter'; * */ convertTemplateRoute.post("/", async function (req, res) { - try { - if (!req.headers['content-type'] || - !req.headers['accept'] || - !req.headers['report-name'] || - req.headers['content-type'] !== "application/octet-stream") { - res.statusCode = 400; - res.statusMessage = 'Require header: content-type(application/octet-stream) accept, report-name'; - res.end(res.statusMessage); - console.log(req.headers['content-type'],req.headers['accept'],req.headers['report-name']) - return - } - let outputMediaType = mimeToExtension(req.headers['accept']); - let reportName = req.headers['report-name'] - console.log('convert output: ' + outputMediaType); - const libreOfficeFileConverter = new LibreOfficeFileConverter({ - childProcessOptions: { - timeout: 60 * 1000, - }, - }); - //const buffer = await libreOfficeFileConverter.convertBuffer(req.body, outputMediaType); - const buffer = await libreOfficeFileConverter.convert({ - buffer:Buffer.from(req.body), - format: outputMediaType, - input: "buffer", - output: "buffer" - }) - res.statusCode = 201; - res.setHeader('Content-Type', req.headers['accept']); - res.setHeader('Content-Disposition', `attachment;filename=${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/convert: ", ex) - }else{ - res.statusCode = 500 - res.statusMessage = "Internal Server Error during POST report-template/convert" - res.end(res.statusMessage) - console.error("report-template/html: ", ex) - } + try { + if ( + !req.headers["content-type"] || + !req.headers["accept"] || + !req.headers["report-name"] || + req.headers["content-type"] !== "application/octet-stream" + ) { + res.statusCode = 400 + res.statusMessage = + "Require header: content-type(application/octet-stream) accept, report-name" + res.end(res.statusMessage) + console.log( + req.headers["content-type"], + req.headers["accept"], + req.headers["report-name"] + ) + return } - }) - \ No newline at end of file + let outputMediaType = mimeToExtension(req.headers["accept"]) + let reportName = req.headers["report-name"] + console.log("convert output: " + outputMediaType) + const libreOfficeFileConverter = new LibreOfficeFileConverter({ + childProcessOptions: { + timeout: 60 * 1000, + }, + }) + //const buffer = await libreOfficeFileConverter.convertBuffer(req.body, outputMediaType); + const buffer = await libreOfficeFileConverter.convert({ + buffer: Buffer.from(req.body), + format: outputMediaType, + input: "buffer", + output: "buffer", + }) + res.statusCode = 201 + res.setHeader("Content-Type", req.headers["accept"]) + res.setHeader( + "Content-Disposition", + `attachment;filename=${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/convert: ", ex) + } else { + res.statusCode = 500 + res.statusMessage = + "Internal Server Error during POST report-template/convert" + res.end(res.statusMessage) + console.error("report-template/html: ", ex) + } + } +}) diff --git a/libs/create-swagger-spec.ts b/libs/create-swagger-spec.ts index 71bbba9..c625f2a 100644 --- a/libs/create-swagger-spec.ts +++ b/libs/create-swagger-spec.ts @@ -1,30 +1,32 @@ // https://swagger.io/docs/specification/about/ import swaggerJsdoc from "swagger-jsdoc" -import fs from 'fs' +import fs from "fs" const swaggerOptions = { - definition: { - openapi: "3.1.0", - info: { - title: "Report Server", - version: "0.8.1", - description: - "Technical preview releases - Report Server
Advance create and convert document API for microservice era. ", - license: { - name: "by oom@Frappet", - url: "https://frappet.com", - }, - }, - servers: [ - { url: "https://report-server.frappet.synology.me" }, - { url: "https://bma-ehr.frappet.synology.me/" }, - { url: "http://localhost:3001" }, - ], + definition: { + openapi: "3.1.0", + info: { + title: "Report Server", + version: "0.8.1", + description: + "Technical preview releases - Report Server
Advance create and convert document API for microservice era. ", + license: { + name: "by oom@Frappet", + url: "https://frappet.com", + }, }, - apis: ["./libs/*.ts"], -}; + servers: [ + { url: "https://report-server.frappet.synology.me" }, + { url: "https://bma-ehr.frappet.synology.me/" }, + { url: "http://localhost:3001" }, + ], + }, + apis: ["./libs/*.ts"], +} export function createSpec() { - const swaggerSpecs = swaggerJsdoc(swaggerOptions); - fs.promises.writeFile("libs/swagger-specs.json", JSON.stringify(swaggerSpecs, null, 2)) + const swaggerSpecs = swaggerJsdoc(swaggerOptions) + fs.promises.writeFile( + "libs/swagger-specs.json", + JSON.stringify(swaggerSpecs, null, 2) + ) } createSpec() - diff --git a/libs/report-template.ts b/libs/report-template.ts index ed5339f..b9bec74 100644 --- a/libs/report-template.ts +++ b/libs/report-template.ts @@ -1,17 +1,17 @@ -import { PDFOptions } from 'puppeteer' -/** +import { PDFOptions } from "puppeteer" +/** * @prop {string} template template ID * @prop {string} reportName outputname * @prop {htmlTemplateOption} htmlOption? support only html-template * @prop {object} data json data for apply template */ export interface templateOption { - template: string - reportName: string - htmlOption?:htmlTemplateOption - data: object + template: string + reportName: string + htmlOption?: htmlTemplateOption + data: object } -/** +/** * @prop {number} navigationTimeout page.setDefaultNavigationTimeout(navigationTimeout) * @prop {number} querySelector Element of page * @prop {number} waitUntil 'networkidle0' or 'networkidle0' @@ -21,48 +21,48 @@ export interface templateOption { * @prop {PDFOptions} pdfOption PdfOptions of Puppeteer */ export interface htmlTemplateOption { - navigationTimeout?:number - querySelector?: string - waitUntil?:string - preloadWait?:number - preloadScroll?:number - preloadLoop?:number - pdfOption?: PDFOptions + navigationTimeout?: number + querySelector?: string + waitUntil?: string + preloadWait?: number + preloadScroll?: number + preloadLoop?: number + pdfOption?: PDFOptions } - - export interface IDictionary { - [key: string]: TValue + [key: string]: TValue } export function mimeToExtension(mime: string): string { - const mimeList: IDictionary = { - "application/pdf": "pdf", - "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "docx", - "application/msword": "doc", - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "xlsx", - "application/vnd.ms-excel": "xls", - "application/vnd.ms-powerpoint": "ppt", - "application/vnd.openxmlformats-officedocument.presentationml.presentation": "pptx", - "application/vnd.oasis.opendocument.text": "odt", - "application/vnd.oasis.opendocument.spreadsheet": "ods", - "application/vnd.oasis.opendocument.presentation": "odp", - "text/html": "html", - "application/json": "json", - "text/csv": "csv", - "text/markdown": "md", - "text/plain": "txt", - "application/rtf": "rtf", - "image/png": "png", - "image/jpeg": "jpeg", - } - if (mimeList[mime]) { - return mimeList[mime] - } else if (mime.match(/^application\/x\./)) { - return mime.substr(mime.indexOf(".") + 1) - } else { - return mime - } + const mimeList: IDictionary = { + "application/pdf": "pdf", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document": + "docx", + "application/msword": "doc", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "xlsx", + "application/vnd.ms-excel": "xls", + "application/vnd.ms-powerpoint": "ppt", + "application/vnd.openxmlformats-officedocument.presentationml.presentation": + "pptx", + "application/vnd.oasis.opendocument.text": "odt", + "application/vnd.oasis.opendocument.spreadsheet": "ods", + "application/vnd.oasis.opendocument.presentation": "odp", + "text/html": "html", + "application/json": "json", + "text/csv": "csv", + "text/markdown": "md", + "text/plain": "txt", + "application/rtf": "rtf", + "image/png": "png", + "image/jpeg": "jpeg", + } + if (mimeList[mime]) { + return mimeList[mime] + } else if (mime.match(/^application\/x\./)) { + return mime.substr(mime.indexOf(".") + 1) + } else { + return mime + } } /** javascript-obfuscator:disable * @swagger diff --git a/libs/xlsx-template-lib.ts b/libs/xlsx-template-lib.ts index da65758..2faa074 100644 --- a/libs/xlsx-template-lib.ts +++ b/libs/xlsx-template-lib.ts @@ -17,34 +17,40 @@ const TEMPLATE_FOLDER_NAME = "templates/xlsx" * @param {Number} tab tab page of spread sheet , default = 1 * @return {Promise} output buffer after apply template. */ -export async function xlsxTemplateX(t: Buffer|String, tdata: templateOption, outputMediaType: string = "xlsx", tab: number = 1): Promise { - try { - const templateBuff = Buffer.isBuffer(t)?t: await fs.promises.readFile(String(t)) - const template = new ExcelTemplate() - await template.load(templateBuff) - await template.process(tab, tdata.data) - const buffer = await template.build({ type: "uint8array" }) as Uint8Array - if (outputMediaType === "xlsx") return buffer +export async function xlsxTemplateX( + t: Buffer | String, + tdata: templateOption, + outputMediaType: string = "xlsx", + tab: number = 1 +): Promise { + try { + const templateBuff = Buffer.isBuffer(t) + ? t + : await fs.promises.readFile(String(t)) + const template = new ExcelTemplate() + await template.load(templateBuff) + await template.process(tab, tdata.data) + const buffer = (await template.build({ type: "uint8array" })) as Uint8Array + if (outputMediaType === "xlsx") return buffer - const libreOfficeFileConverter = new LibreOfficeFileConverter({ - childProcessOptions: { - timeout: 60 * 1000, - }, - }) - //const lbuffer = await libreOfficeFileConverter.convertBuffer(Buffer.from(buffer as Uint8Array), outputMediaType) + const libreOfficeFileConverter = new LibreOfficeFileConverter({ + childProcessOptions: { + timeout: 60 * 1000, + }, + }) + //const lbuffer = await libreOfficeFileConverter.convertBuffer(Buffer.from(buffer as Uint8Array), outputMediaType) - const lbuffer = await libreOfficeFileConverter.convert({ - buffer:Buffer.from(buffer), - format: outputMediaType, - input: "buffer", - output: "buffer" - }) + const lbuffer = await libreOfficeFileConverter.convert({ + buffer: Buffer.from(buffer), + format: outputMediaType, + input: "buffer", + output: "buffer", + }) - - return lbuffer - } catch (e) { - throw e - } + return lbuffer + } catch (e) { + throw e + } } /** javascript-obfuscator:disable @@ -67,16 +73,16 @@ export async function xlsxTemplateX(t: Buffer|String, tdata: templateOption, out * description: Server error */ xlsxTemplateRoute.get("/", async function (req, res) { - try { - const fileList = await fs.promises.readdir(`./${TEMPLATE_FOLDER_NAME}`) - const templateList = fileList.map(f => f.split(".xlsx")[0]) - res.send(templateList) - } catch (ex) { - res.statusCode = 500 - res.statusMessage = "Internal Server Error during get xlsx template list" - res.end(res.statusMessage) - console.error("Error during get template list: ", ex) - } + try { + const fileList = await fs.promises.readdir(`./${TEMPLATE_FOLDER_NAME}`) + const templateList = fileList.map((f) => f.split(".xlsx")[0]) + res.send(templateList) + } catch (ex) { + res.statusCode = 500 + res.statusMessage = "Internal Server Error during get xlsx template list" + res.end(res.statusMessage) + console.error("Error during get template list: ", ex) + } }) /** javascript-obfuscator:disable @@ -136,36 +142,45 @@ xlsxTemplateRoute.get("/", async function (req, res) { * */ xlsxTemplateRoute.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}.xlsx`) - } else { - template = await fs.promises.readFile(`./${TEMPLATE_FOLDER_NAME}/${req.body.template}.xlsx`) - } - let buffer = await xlsxTemplateX(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) { - if(ex instanceof SyntaxError){ - res.statusCode = 400 - res.statusMessage = ex.message - res.end(res.statusMessage) - console.error("report-template/xlsx: ", ex) - }else{ - res.statusCode = 500 - res.statusMessage = "Internal Server Error during POST report-template/xlsx" - res.end(res.statusMessage) - console.error("report-template/xlsx: ", ex) - } - } + 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}.xlsx` + ) + } else { + template = await fs.promises.readFile( + `./${TEMPLATE_FOLDER_NAME}/${req.body.template}.xlsx` + ) + } + let buffer = await xlsxTemplateX(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) { + if (ex instanceof SyntaxError) { + res.statusCode = 400 + res.statusMessage = ex.message + res.end(res.statusMessage) + console.error("report-template/xlsx: ", ex) + } else { + res.statusCode = 500 + res.statusMessage = + "Internal Server Error during POST report-template/xlsx" + res.end(res.statusMessage) + console.error("report-template/xlsx: ", ex) + } + } }) /** javascript-obfuscator:disable @@ -211,39 +226,57 @@ xlsxTemplateRoute.post("/", async function (req, res) { * */ xlsxTemplateRoute.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) - console.log(req.headers["content-type"], req.headers["accept"], req.query["report_name"]) - return - } - // Determine the output media type and report name from headers - let outputMediaType = mimeToExtension(req.headers["accept"]) - let reportName = req.query["report_name"] - console.log("convert output: " + outputMediaType) + 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) + console.log( + req.headers["content-type"], + req.headers["accept"], + req.query["report_name"] + ) + return + } + // Determine the output media type and report name from headers + let outputMediaType = mimeToExtension(req.headers["accept"]) + let reportName = req.query["report_name"] + console.log("convert output: " + outputMediaType) - // 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}.xlsx`, 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}.xlsx`, req.body) - } + // 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}.xlsx`, + 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}.xlsx`, + 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) - } + // 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 @@ -281,27 +314,39 @@ xlsxTemplateRoute.post("/upload", async function (req, res) { * */ xlsxTemplateRoute.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"]) - console.log("content-type: ", inputType) - console.log("accept: ", outputMediaType) - let buffer = null - if (req.query["folder"]) { - buffer = await fs.promises.readFile(`./${TEMPLATE_FOLDER_NAME}/${req.query["folder"]}/${req.body.template}.xlsx`) - } else { - buffer = await fs.promises.readFile(`./${TEMPLATE_FOLDER_NAME}/${req.body.template}.xlsx`) - } - 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 xlsx template list" - res.end(res.statusMessage) - console.error("Error during apply template: ", ex) - } + 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"]) + console.log("content-type: ", inputType) + console.log("accept: ", outputMediaType) + let buffer = null + if (req.query["folder"]) { + buffer = await fs.promises.readFile( + `./${TEMPLATE_FOLDER_NAME}/${req.query["folder"]}/${req.body.template}.xlsx` + ) + } else { + buffer = await fs.promises.readFile( + `./${TEMPLATE_FOLDER_NAME}/${req.body.template}.xlsx` + ) + } + 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 xlsx template list" + res.end(res.statusMessage) + console.error("Error during apply template: ", ex) + } }) diff --git a/test-run/docx-template.ts b/test-run/docx-template.ts index ae119f6..ba7cb23 100644 --- a/test-run/docx-template.ts +++ b/test-run/docx-template.ts @@ -1,22 +1,25 @@ - // npx ts-node docx-template.ts -import * as readline from 'node:readline/promises'; // This uses the promise-based APIs -import { stdin as input, stdout as output } from 'node:process'; -import { docxTemplateX} from '../libs/docx-templates-lib'; -import {templateOption} from '../libs/report-template' -import fs from 'fs'; -(async ()=>{ - const rl = readline.createInterface({ input, output }); - const e = await rl.question('Output extension(docx,pdf,odt,png,jpeg): '); - const ext =e?e:"docx" - const dpath = await rl.question('JSON data path(./docx.json): '); - const datapath = dpath?dpath:"./docx.json" - const data_raw = fs.readFileSync(datapath); - const tdata:templateOption = JSON.parse(data_raw.toString()); - const bpath = await rl.question('templates path(../templates/docx): '); - const basepath = bpath?bpath:"../templates/docx" - //const template = await fs.promises.readFile(`${basepath}/${tdata.template}.docx`) - let buffer = await docxTemplateX(`${basepath}/${tdata.template}.docx`, tdata,ext) - fs.writeFileSync(".output/"+tdata.reportName+"."+ext, buffer); - rl.close(); +import * as readline from "node:readline/promises" // This uses the promise-based APIs +import { stdin as input, stdout as output } from "node:process" +import { docxTemplateX } from "../libs/docx-templates-lib" +import { templateOption } from "../libs/report-template" +import fs from "fs" +(async () => { + const rl = readline.createInterface({ input, output }) + const e = await rl.question("Output extension(docx,pdf,odt,png,jpeg): ") + const ext = e ? e : "docx" + const dpath = await rl.question("JSON data path(./docx.json): ") + const datapath = dpath ? dpath : "./docx.json" + const data_raw = fs.readFileSync(datapath) + const tdata: templateOption = JSON.parse(data_raw.toString()) + const bpath = await rl.question("templates path(../templates/docx): ") + const basepath = bpath ? bpath : "../templates/docx" + //const template = await fs.promises.readFile(`${basepath}/${tdata.template}.docx`) + let buffer = await docxTemplateX( + `${basepath}/${tdata.template}.docx`, + tdata, + ext + ) + fs.writeFileSync(".output/" + tdata.reportName + "." + ext, buffer) + rl.close() })() diff --git a/test-run/grafana_pdf.js b/test-run/grafana_pdf.js index 1624df5..096d68f 100644 --- a/test-run/grafana_pdf.js +++ b/test-run/grafana_pdf.js @@ -1,65 +1,65 @@ -'use strict'; +"use strict" -const puppeteer = require('puppeteer'); +const puppeteer = require("puppeteer") //const fetch = require('node-fetch'); -const fs = require('fs'); +const fs = require("fs") -console.log("Script grafana_pdf.js started..."); +console.log("Script grafana_pdf.js started...") /* const url = process.argv[2]; const auth_string = process.argv[3]; let outfile = process.argv[4]; */ -const url = 'https://bma-dashboard.frappet.synology.me/d/5EwyjelSk/1408ef66-0081-5b3f-aa00-5e70aa9bdbf1?orgId=1&kiosk=true' -const auth_string = 'admin:xxx'; -let outfile = "./url_gf.pdf"; +const url = + "https://bma-dashboard.frappet.synology.me/d/5EwyjelSk/1408ef66-0081-5b3f-aa00-5e70aa9bdbf1?orgId=1&kiosk=true" +const auth_string = "admin:xxx" +let outfile = "./url_gf.pdf" -const width_px = parseInt(process.env.PDF_WIDTH_PX, 10) || 1200; -console.log("PDF width set to:", width_px); +const width_px = parseInt(process.env.PDF_WIDTH_PX, 10) || 1200 +console.log("PDF width set to:", width_px) -const auth_header = 'Basic ' + Buffer.from(auth_string).toString('base64'); +const auth_header = "Basic " + Buffer.from(auth_string).toString("base64") -(async () => { - try { - console.log("URL provided:", url); - console.log("Checking URL accessibility..."); - const response = await fetch(url, { - method: 'GET', - headers: {'Authorization': auth_header} - }); +;(async () => { + try { + console.log("URL provided:", url) + console.log("Checking URL accessibility...") + const response = await fetch(url, { + method: "GET", + headers: { Authorization: auth_header }, + }) - if (!response.ok) { - throw new Error(`Unable to access URL. HTTP status: ${response.status}`); - } + if (!response.ok) { + throw new Error(`Unable to access URL. HTTP status: ${response.status}`) + } - const contentType = response.headers.get('content-type'); - if (!contentType || !contentType.includes('text/html')) { - throw new Error("The URL provided is not a valid Grafana instance."); - } + const contentType = response.headers.get("content-type") + if (!contentType || !contentType.includes("text/html")) { + throw new Error("The URL provided is not a valid Grafana instance.") + } - let finalUrl = url; - if(process.env.FORCE_KIOSK_MODE === 'true') { - console.log("Checking if kiosk mode is enabled.") - if (!finalUrl.includes('&kiosk')) { - console.log("Kiosk mode not enabled. Enabling it.") - finalUrl += '&kiosk=true'; - } - console.log("Kiosk mode enabled.") - } + let finalUrl = url + if (process.env.FORCE_KIOSK_MODE === "true") { + console.log("Checking if kiosk mode is enabled.") + if (!finalUrl.includes("&kiosk")) { + console.log("Kiosk mode not enabled. Enabling it.") + finalUrl += "&kiosk=true" + } + console.log("Kiosk mode enabled.") + } + console.log("Starting browser...") + const browser = await puppeteer.launch({ + executablePath: process.env.PUPPETEER_EXECUTABLE_PATH, + headless: true, + // args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-gpu'] + }) - console.log("Starting browser..."); - const browser = await puppeteer.launch({ - executablePath: process.env.PUPPETEER_EXECUTABLE_PATH, - headless: true, - // args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-gpu'] - }); + const page = await browser.newPage() + console.log("Browser started...") - const page = await browser.newPage(); - console.log("Browser started..."); - -/* + /* await page.setExtraHTTPHeaders({'Authorization': auth_header}); await page.setDefaultNavigationTimeout(process.env.PUPPETEER_NAVIGATION_TIMEOUT || 120000); @@ -70,10 +70,10 @@ const auth_header = 'Basic ' + Buffer.from(auth_string).toString('base64'); isMobile: false }); */ - console.log("Navigating to URL..."); - await page.goto(finalUrl, {waitUntil: 'networkidle0'}); - console.log("Page loaded..."); -/* + console.log("Navigating to URL...") + await page.goto(finalUrl, { waitUntil: "networkidle0" }) + console.log("Page loaded...") + /* await page.evaluate(() => { let infoCorners = document.getElementsByClassName('panel-info-corner'); for (let el of infoCorners) { @@ -85,10 +85,10 @@ const auth_header = 'Basic ' + Buffer.from(auth_string).toString('base64'); } }); */ - let dashboardName = 'output_grafana'; - let date = new Date().toISOString().split('T')[0]; - let addRandomStr = false; -/* + let dashboardName = "output_grafana" + let date = new Date().toISOString().split("T")[0] + let addRandomStr = false + /* if (process.env.EXTRACT_DATE_AND_DASHBOARD_NAME_FROM_HTML_PANEL_ELEMENTS === 'true') { console.log("Extracting dashboard name and date from the HTML page..."); let scrapedDashboardName = await page.evaluate(() => { @@ -207,61 +207,65 @@ const auth_header = 'Basic ' + Buffer.from(auth_string).toString('base64'); } */ - const totalHeight = await page.evaluate(() => { - const scrollableSection = document.querySelector('.scrollbar-view'); - return scrollableSection ? scrollableSection.firstElementChild.scrollHeight : null; - }); + const totalHeight = await page.evaluate(() => { + const scrollableSection = document.querySelector(".scrollbar-view") + return scrollableSection + ? scrollableSection.firstElementChild.scrollHeight + : null + }) - if (!totalHeight) { - throw new Error("Unable to determine the page height. The selector '.scrollbar-view' might be incorrect or missing."); - } else { - console.log("Page height adjusted to:", totalHeight); - } - - let x = await page.evaluate(async () => { - const scrollableSection = document.querySelector('.scrollbar-view'); - if (scrollableSection) { - const childElement = scrollableSection.firstElementChild; - let scrollPosition = 0; - let viewportHeight = window.innerHeight; - - while (scrollPosition < childElement.scrollHeight) { - scrollableSection.scrollBy(0, viewportHeight); - await new Promise(resolve => setTimeout(resolve, 500)); - scrollPosition += viewportHeight; - } - return scrollPosition - } - return 0 - }); - console.log("scrollPosition="+x) - - await page.setViewport({ - width: width_px, - height: totalHeight, - deviceScaleFactor: 2, - isMobile: false - }); - - console.log("Generating PDF..."); - await page.pdf({ - path: outfile, - width: width_px + 'px', - height: totalHeight + 'px', - printBackground: true, - scale: 1, - displayHeaderFooter: false, - margin: {top: 0, right: 0, bottom: 0, left: 0} - }); - console.log(`PDF generated: ${outfile}`); - - await browser.close(); - console.log("Browser closed."); - - //process.send({ success: true, path: outfile }); - } catch (error) { - console.error("Error during PDF generation:", error.message); - //process.send({ success: false, error: error.message }); - process.exit(1); + if (!totalHeight) { + throw new Error( + "Unable to determine the page height. The selector '.scrollbar-view' might be incorrect or missing." + ) + } else { + console.log("Page height adjusted to:", totalHeight) } -})(); + + let x = await page.evaluate(async () => { + const scrollableSection = document.querySelector(".scrollbar-view") + if (scrollableSection) { + const childElement = scrollableSection.firstElementChild + let scrollPosition = 0 + let viewportHeight = window.innerHeight + + while (scrollPosition < childElement.scrollHeight) { + scrollableSection.scrollBy(0, viewportHeight) + await new Promise((resolve) => setTimeout(resolve, 500)) + scrollPosition += viewportHeight + } + return scrollPosition + } + return 0 + }) + console.log("scrollPosition=" + x) + + await page.setViewport({ + width: width_px, + height: totalHeight, + deviceScaleFactor: 2, + isMobile: false, + }) + + console.log("Generating PDF...") + await page.pdf({ + path: outfile, + width: width_px + "px", + height: totalHeight + "px", + printBackground: true, + scale: 1, + displayHeaderFooter: false, + margin: { top: 0, right: 0, bottom: 0, left: 0 }, + }) + console.log(`PDF generated: ${outfile}`) + + await browser.close() + console.log("Browser closed.") + + //process.send({ success: true, path: outfile }); + } catch (error) { + console.error("Error during PDF generation:", error.message) + //process.send({ success: false, error: error.message }); + process.exit(1) + } +})() diff --git a/test-run/html-template.ts b/test-run/html-template.ts index 84b7076..4d5db0c 100644 --- a/test-run/html-template.ts +++ b/test-run/html-template.ts @@ -1,25 +1,26 @@ // npx ts-node html-templates.ts -import * as readline from 'node:readline/promises'; // This uses the promise-based APIs -import { stdin as input, stdout as output } from 'node:process'; -import { htmlTemplateX} from '../libs/html-templates-lib'; -import {templateOption} from '../libs/report-template' -import fs from 'fs'; -(async ()=>{ - const rl = readline.createInterface({ input, output }); - const e = await rl.question('Output extension(pdf,png,jpeg): '); - const ext =e?e:"pdf" - const dpath = await rl.question('JSON data path(./html.json): '); - const datapath = dpath?dpath:"./html.json" - const data_raw = fs.readFileSync(datapath); - const tdata:templateOption = JSON.parse(data_raw.toString()); - const bpath = await rl.question('templates path(../templates/html): '); - const basepath = bpath?bpath:"../templates/html" -// const template = await fs.promises.readFile(`${basepath}/${tdata.template}.docx`) - let url = "https://bma-dashboard.frappet.synology.me/d/ANtkJay4z/4Lic4Li54LmJ4Lie4Li04LiB4Liy4Lij?orgId=1&kiosk" - //let url = "https://pantip.com" - // let url = "https://google.com" +import * as readline from "node:readline/promises" // This uses the promise-based APIs +import { stdin as input, stdout as output } from "node:process" +import { htmlTemplateX } from "../libs/html-templates-lib" +import { templateOption } from "../libs/report-template" +import fs from "fs" +;(async () => { + const rl = readline.createInterface({ input, output }) + const e = await rl.question("Output extension(pdf,png,jpeg): ") + const ext = e ? e : "pdf" + const dpath = await rl.question("JSON data path(./html.json): ") + const datapath = dpath ? dpath : "./html.json" + const data_raw = fs.readFileSync(datapath) + const tdata: templateOption = JSON.parse(data_raw.toString()) + const bpath = await rl.question("templates path(../templates/html): ") + const basepath = bpath ? bpath : "../templates/html" + // const template = await fs.promises.readFile(`${basepath}/${tdata.template}.docx`) + let url = + "https://bma-dashboard.frappet.synology.me/d/ANtkJay4z/4Lic4Li54LmJ4Lie4Li04LiB4Liy4Lij?orgId=1&kiosk" + //let url = "https://pantip.com" + // let url = "https://google.com" - let buffer = await htmlTemplateX(url, tdata,ext) - fs.writeFileSync(".output/"+tdata.reportName+"."+ext, buffer); - rl.close(); -})() \ No newline at end of file + let buffer = await htmlTemplateX(url, tdata, ext) + fs.writeFileSync(".output/" + tdata.reportName + "." + ext, buffer) + rl.close() +})() diff --git a/test-run/url_pup2pdf.ts b/test-run/url_pup2pdf.ts index 5f98627..45b6811 100644 --- a/test-run/url_pup2pdf.ts +++ b/test-run/url_pup2pdf.ts @@ -1,57 +1,62 @@ // ใช้เพื่อทดสอบ puppeteer -import puppeteer from 'puppeteer' -(async () => { +import puppeteer from "puppeteer" +;(async () => { //const targetUrl = "https://bma-dashboard.frappet.synology.me/d/5EwyjelSk/1408ef66-0081-5b3f-aa00-5e70aa9bdbf1?orgId=1&kiosk=true" //const targetUrl = "https://bma-dashboard.frappet.synology.me/d/OLZwQhPVz/4Liq4Lit4Lia4LmB4LiC4LmI4LiH4LiC4Lix4LiZ?orgId=1&kiosk" const targetUrl = "https://blognone.com" - const width_px = Number(process.env.PDF_WIDTH_PX) || 1200; + const width_px = Number(process.env.PDF_WIDTH_PX) || 1200 const browser = await puppeteer.launch({ // executablePath: '/usr/bin/chromium', headless: true, //args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-gpu'] - }); + }) + + const page = await browser.newPage() + page.setDefaultNavigationTimeout(120000) - const page = await browser.newPage(); - page.setDefaultNavigationTimeout(120000); - await page.setViewport({ width: width_px, height: 800, deviceScaleFactor: 2, - isMobile: false - }); - - await page.goto(targetUrl, { waitUntil: 'networkidle0' }); + isMobile: false, + }) + + await page.goto(targetUrl, { waitUntil: "networkidle0" }) //console.log("Page loaded..."); let x = await page.evaluate(async () => { - const scrollableSection = - document.querySelector('.scrollbar-view')?document.querySelector('.scrollbar-view'):document.body + const scrollableSection = document.querySelector(".scrollbar-view") + ? document.querySelector(".scrollbar-view") + : document.body if (scrollableSection) { - const childElement = scrollableSection.firstElementChild; - let scrollPosition = 0; - let viewportHeight = window.innerHeight; + const childElement = scrollableSection.firstElementChild + let scrollPosition = 0 + let viewportHeight = window.innerHeight if (childElement) while (scrollPosition < childElement.scrollHeight) { - scrollableSection.scrollBy(0, viewportHeight); - await new Promise(resolve => setTimeout(resolve, 500)); - scrollPosition += viewportHeight; + scrollableSection.scrollBy(0, viewportHeight) + await new Promise((resolve) => setTimeout(resolve, 500)) + scrollPosition += viewportHeight } return scrollPosition } return 0 - }); + }) console.log("scrollPosition=" + x) const totalHeight = await page.evaluate(() => { - const scrollableSection = document.querySelector('.scrollbar-view');//only Grafana ? - return scrollableSection ? scrollableSection.firstElementChild?.scrollHeight : document.body.scrollHeight; - }); + const scrollableSection = document.querySelector(".scrollbar-view") //only Grafana ? + return scrollableSection + ? scrollableSection.firstElementChild?.scrollHeight + : document.body.scrollHeight + }) if (!totalHeight) { - throw new Error("Unable to determine the page height. The selector '.scrollbar-view' might be incorrect or missing."); + throw new Error( + "Unable to determine the page height. The selector '.scrollbar-view' might be incorrect or missing." + ) } else { - console.log("Page height adjusted to:", totalHeight); + console.log("Page height adjusted to:", totalHeight) } console.log("set viewport ") @@ -59,13 +64,13 @@ import puppeteer from 'puppeteer' width: width_px, height: totalHeight, deviceScaleFactor: 2, - isMobile: false - }); + isMobile: false, + }) await page.screenshot({ - path: 'url_pup.png', + path: "url_pup.png", fullPage: true, - type: 'png' // | 'jpeg' | 'webp' + type: "png", // | 'jpeg' | 'webp' }) await page.pdf({ path: ".outputurl_prop.pdf", @@ -75,10 +80,7 @@ import puppeteer from 'puppeteer' printBackground: true, scale: 1, displayHeaderFooter: false, - margin: { top: 0, right: 0, bottom: 0, left: 0 } - }); - await browser.close(); - -})(); - - + margin: { top: 0, right: 0, bottom: 0, left: 0 }, + }) + await browser.close() +})() diff --git a/test-run/xlsx-template.ts b/test-run/xlsx-template.ts index a4eecb8..8e72693 100644 --- a/test-run/xlsx-template.ts +++ b/test-run/xlsx-template.ts @@ -1,22 +1,25 @@ - // npx ts-node xlsx-template.ts -import * as readline from 'node:readline/promises'; // This uses the promise-based APIs -import { stdin as input, stdout as output } from 'node:process'; -import {xlsxTemplateX} from '../libs/xlsx-template-lib' -import {templateOption} from '../libs/report-template' -import fs from 'fs'; -(async ()=>{ - const rl = readline.createInterface({ input, output }); - const e = await rl.question('Output extension(xlsx,pdf,ods,png,jpeg): '); - const ext =e?e:"xlsx" - const dpath = await rl.question('JSON data path(./xlsx.json): '); - const datapath = dpath?dpath:"./xlsx.json" - const data_raw = fs.readFileSync(datapath); - const tdata:templateOption = JSON.parse(data_raw.toString()); - const bpath = await rl.question('template path(../templates/xlsx): '); - const basepath = bpath?bpath:"../templates/xlsx" - // const template = await fs.promises.readFile(`${basepath}/${tdata.template}.xlsx`) - let buffer = await xlsxTemplateX(`${basepath}/${tdata.template}.xlsx`,tdata,ext) - fs.writeFileSync(tdata.reportName+"."+ext, buffer); - rl.close(); +import * as readline from "node:readline/promises" // This uses the promise-based APIs +import { stdin as input, stdout as output } from "node:process" +import { xlsxTemplateX } from "../libs/xlsx-template-lib" +import { templateOption } from "../libs/report-template" +import fs from "fs" +;(async () => { + const rl = readline.createInterface({ input, output }) + const e = await rl.question("Output extension(xlsx,pdf,ods,png,jpeg): ") + const ext = e ? e : "xlsx" + const dpath = await rl.question("JSON data path(./xlsx.json): ") + const datapath = dpath ? dpath : "./xlsx.json" + const data_raw = fs.readFileSync(datapath) + const tdata: templateOption = JSON.parse(data_raw.toString()) + const bpath = await rl.question("template path(../templates/xlsx): ") + const basepath = bpath ? bpath : "../templates/xlsx" + // const template = await fs.promises.readFile(`${basepath}/${tdata.template}.xlsx`) + let buffer = await xlsxTemplateX( + `${basepath}/${tdata.template}.xlsx`, + tdata, + ext + ) + fs.writeFileSync(tdata.reportName + "." + ext, buffer) + rl.close() })()