From f7178d212ad93321f48d6d75343538e0488eda6e Mon Sep 17 00:00:00 2001 From: oom Date: Fri, 28 Feb 2025 11:42:24 +0700 Subject: [PATCH] add prettier --- .prettierrc | 3 + README.md | 2 - libs/create-swagger-spec.ts | 12 +- libs/docx-templates-lib.ts | 309 +++++++++++++++++++++--------------- libs/html-templates-lib.ts | 280 +++++++++++++++++--------------- 5 files changed, 339 insertions(+), 267 deletions(-) create mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..dceda2d --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "semi": false +} \ No newline at end of file diff --git a/README.md b/README.md index 9f385c0..79287d4 100644 --- a/README.md +++ b/README.md @@ -83,8 +83,6 @@ curl -X 'POST' \ "querySelector": ".scrollbar-view" } }' -o html-grafana.pdf - - # url to png curl -X 'POST' 'http://localhost:3001/api/v1/report-template/html' \ -H 'accept: image/png' -H 'Content-Type: application/json' \ diff --git a/libs/create-swagger-spec.ts b/libs/create-swagger-spec.ts index 4c867b2..71bbba9 100644 --- a/libs/create-swagger-spec.ts +++ b/libs/create-swagger-spec.ts @@ -15,16 +15,16 @@ const swaggerOptions = { }, }, servers: [ - {url: "https://report-server.frappet.synology.me"}, - {url:"https://bma-ehr.frappet.synology.me/"}, - {url: "http://localhost:3001"}, + { url: "https://report-server.frappet.synology.me" }, + { url: "https://bma-ehr.frappet.synology.me/" }, + { url: "http://localhost:3001" }, ], }, apis: ["./libs/*.ts"], }; -export function createSpec(){ +export function createSpec() { const swaggerSpecs = swaggerJsdoc(swaggerOptions); - fs.promises.writeFile("libs/swagger-specs.json",JSON.stringify(swaggerSpecs,null,2)) -} + fs.promises.writeFile("libs/swagger-specs.json", JSON.stringify(swaggerSpecs, null, 2)) +} createSpec() diff --git a/libs/docx-templates-lib.ts b/libs/docx-templates-lib.ts index 4924036..4296e7c 100644 --- a/libs/docx-templates-lib.ts +++ b/libs/docx-templates-lib.ts @@ -11,7 +11,6 @@ import axios from "axios" 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 @@ -21,49 +20,68 @@ const TEMPLATE_FOLDER_NAME = "templates/docx" * @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 +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 - } + 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 @@ -86,16 +104,16 @@ export async function docxTemplateX(t: Buffer|String, tdata: templateOption, out * 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) - } + 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 @@ -151,31 +169,40 @@ docxTemplateRoute.get("/", async function (req, res) { * */ 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) - } - } + 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 @@ -237,37 +264,51 @@ docxTemplateRoute.post("/", async function (req, res) { * */ 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"] + 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) - } + // 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) - } + // 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 @@ -309,25 +350,37 @@ docxTemplateRoute.post("/upload", async function (req, res) { * */ 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) - } + 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) + } }) diff --git a/libs/html-templates-lib.ts b/libs/html-templates-lib.ts index 869b27c..61e7620 100644 --- a/libs/html-templates-lib.ts +++ b/libs/html-templates-lib.ts @@ -3,8 +3,8 @@ export const htmlTemplateRoute = express.Router() import { mimeToExtension, templateOption } from "./report-template" // import fs from "fs" //import { chromium } from 'playwright' -import puppeteer, {PDFOptions} from 'puppeteer' -import Handlebars from 'handlebars' +import puppeteer, { PDFOptions } from "puppeteer" +import Handlebars from "handlebars" //import { createReport } from "docx-templates" // แก้ package.json ของ LibreOfficeFileConverter @@ -13,12 +13,12 @@ import Handlebars from 'handlebars' const TEMPLATE_FOLDER_NAME = "templates/html" function wait(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)) } /** * docxTemplate Uses docx-template to convert input data and template to output buffer. - * SPA and lazy load page may not fully render(eg. pantip.com). + * SPA and lazy load page may not fully render(eg. pantip.com). * You have to handle exception throw by function * handlebars template support only content from Buffer * @param {Buffer|String} t template in buffer format or url to web page @@ -26,117 +26,127 @@ function wait(ms: number) { * @param {String} outputMediaType output extension, support pdf, jpeg, png * @return {Promise} output buffer after apply template. */ -export async function htmlTemplateX(t: Buffer | String, templOpt: templateOption, outputMediaType: string = "pdf"): Promise { - try { - if (!["pdf", "jpeg", "png"].find((e) => e === outputMediaType)) { - throw "FormatError" - } - const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox'] }); - const page = await browser.newPage(); - if(templOpt.htmlOption?.navigationTimeout) - page.setDefaultNavigationTimeout(120000); +export async function htmlTemplateX( + t: Buffer | String, + templOpt: templateOption, + outputMediaType: string = "pdf" +): Promise { + try { + if (!["pdf", "jpeg", "png"].find((e) => e === outputMediaType)) { + throw "FormatError" + } + const browser = await puppeteer.launch({ + headless: true, + args: ["--no-sandbox"], + }) + const page = await browser.newPage() + if (templOpt.htmlOption?.navigationTimeout) + page.setDefaultNavigationTimeout(120000) - if (typeof t === 'string') { + if (typeof t === "string") { + switch (templOpt.htmlOption?.waitUntil) { + case "networkidle0": + await page.goto(t, { waitUntil: "networkidle0" }) + break + case "networkidle2": + await page.goto(t, { waitUntil: "networkidle2" }) + break + default: + await page.goto(t) + } + } else { + if (templOpt.data) { + const template = Handlebars.compile(t.toString()) + const html = template(templOpt.data) + await page.setContent(html) + } else { + await page.setContent(t.toString()) + } + } + const totalHeight = await page.evaluate(async (_templOpt) => { + //force scroll for lazy load page + const _scroll = _templOpt.htmlOption?.preloadScroll ?? 1000 + const _wait = _templOpt.htmlOption?.preloadWait ?? 400 + const _loop = _templOpt.htmlOption?.preloadLoop ?? 4 - switch(templOpt.htmlOption?.waitUntil){ - case 'networkidle0': await page.goto(t, { waitUntil: 'networkidle0' }) - break - case 'networkidle2': await page.goto(t, { waitUntil: 'networkidle2' }) - break - default: await page.goto(t) - } - } else { - if (templOpt.data) { - const template = Handlebars.compile(t.toString()); - const html = template(templOpt.data); - await page.setContent(html); - } else { - await page.setContent(t.toString()) - } - } - const totalHeight = await page.evaluate(async (_templOpt) => { - //force scroll for lazy load page - const _scroll = _templOpt.htmlOption?.preloadScroll??1000 - const _wait = _templOpt.htmlOption?.preloadWait??400 - const _loop =_templOpt.htmlOption?.preloadLoop??4 + for (let i = 0; i < _loop; i++) { + window.scrollBy(0, _scroll) + await new Promise((resolve) => setTimeout(resolve, _wait)) + } + //extra wait + await new Promise((resolve) => setTimeout(resolve, _wait * 2)) - for (let i = 0; i < _loop; i++) { - window.scrollBy(0, _scroll); - await new Promise(resolve => setTimeout(resolve, _wait)); - } - //extra wait - await new Promise(resolve => setTimeout(resolve, _wait*2)); + let scrollableSection = + _templOpt.htmlOption?.querySelector && + document.querySelector(_templOpt.htmlOption.querySelector) + ? document.querySelector(_templOpt.htmlOption.querySelector) + : null - let scrollableSection = - (_templOpt.htmlOption?.querySelector && - document.querySelector(_templOpt.htmlOption.querySelector)) ? - document.querySelector(_templOpt.htmlOption.querySelector) : null + const childElement = scrollableSection ? scrollableSection : document.body - const childElement = scrollableSection ? scrollableSection : document.body - - if (scrollableSection == null) - scrollableSection = document.body - //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 childElement.scrollHeight - }, templOpt); + if (scrollableSection == null) scrollableSection = document.body + //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 childElement.scrollHeight + }, templOpt) - console.log("totalHeight") - if (!totalHeight) { - throw new Error(`Unable to determine the page height ${totalHeight}. The selector may not correct or no body tag`); - } else { - console.log("Page height adjusted to:", totalHeight); - } + console.log("totalHeight") + if (!totalHeight) { + throw new Error( + `Unable to determine the page height ${totalHeight}. The selector may not correct or no body tag` + ) + } else { + console.log("Page height adjusted to:", totalHeight) + } - await page.setViewport({ - width: Number(templOpt.htmlOption?.pdfOption?.width??1200), - height: totalHeight, - deviceScaleFactor: 2, - isMobile: false - }); + await page.setViewport({ + width: Number(templOpt.htmlOption?.pdfOption?.width ?? 1200), + height: totalHeight, + deviceScaleFactor: 2, + isMobile: false, + }) - - ///// output to photo end here - if (outputMediaType === "png" || outputMediaType === "jpeg") { - const photoBuffer = await page.screenshot({ - // path: 'url_pup.png', - fullPage: true, - type: outputMediaType // 'webp' - }) - await browser.close(); - return photoBuffer - } - ///// output to PDF - //TODO overide option from htmlTemplateOption - let o:PDFOptions ={ - // path: './url_prop.pdf', - // format:"A4", - width: 1200, - height: totalHeight, - printBackground: true, - scale: 1, - displayHeaderFooter: false, - margin: { top: 5, right: 5, bottom: 5, left: 5 } - } - //Murge with default - if(templOpt.htmlOption?.pdfOption) - o = {...o,...templOpt.htmlOption.pdfOption} - const {path,...pdfOption} = o //remove path if exists - console.log("pdfOption:",pdfOption) - const buffer = await page.pdf(pdfOption); - await browser.close(); - return buffer - } catch (e) { - //console.log(e) - throw e - } + ///// output to photo end here + if (outputMediaType === "png" || outputMediaType === "jpeg") { + const photoBuffer = await page.screenshot({ + // path: 'url_pup.png', + fullPage: true, + type: outputMediaType, // 'webp' + }) + await browser.close() + return photoBuffer + } + ///// output to PDF + //TODO overide option from htmlTemplateOption + let o: PDFOptions = { + // path: './url_prop.pdf', + // format:"A4", + width: 1200, + height: totalHeight, + printBackground: true, + scale: 1, + displayHeaderFooter: false, + margin: { top: 5, right: 5, bottom: 5, left: 5 }, + } + //Murge with default + if (templOpt.htmlOption?.pdfOption) + o = { ...o, ...templOpt.htmlOption.pdfOption } + const { path, ...pdfOption } = o //remove path if exists + console.log("pdfOption:", pdfOption) + const buffer = await page.pdf(pdfOption) + await browser.close() + return buffer + } catch (e) { + //console.log(e) + throw e + } } /** javascript-obfuscator:disable @@ -178,28 +188,36 @@ export async function htmlTemplateX(t: Buffer | String, templOpt: templateOption * */ htmlTemplateRoute.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"]) - let buffer = await htmlTemplateX(req.body.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/html: ", ex) - } else { - res.statusCode = 500 - res.statusMessage = "Internal Server Error during POST report-template/html" - res.end(res.statusMessage) - console.error("report-template/html: ", 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"]) // application/json + let outputMediaType = mimeToExtension(req.headers["accept"]) + let buffer = await htmlTemplateX( + req.body.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/html: ", ex) + } else { + res.statusCode = 500 + res.statusMessage = + "Internal Server Error during POST report-template/html" + res.end(res.statusMessage) + console.error("report-template/html: ", ex) + } + } }) -