api upload template

This commit is contained in:
DESKTOP-2S5P7D1\Windows 10 2024-09-20 12:07:44 +07:00
parent ba8175204c
commit 58f8e928a0
6 changed files with 3742 additions and 3104 deletions

View file

@ -1,18 +1,18 @@
import express from "express"
export const docxTemplateRoute = express.Router()
import express from 'express'
export const docxTemplateRoute = express.Router();
import {mimeToExtension,templateData} from './report-template'
import fs from 'fs'
import { createReport } from 'docx-templates';
import qrcode from 'yaqrcode'
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';
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.
* 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)
@ -20,34 +20,44 @@ const TEMPLATE_FOLDER_NAME = "templates/docx"
* @param {String} outputMediaType output extension
* @return {Promise<Uint8Array>} output buffer after apply template.
*/
export async function docxTemplateX(base: string=".", tdata: templateData, outputMediaType: string="docx"): Promise<Uint8Array> {
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 };
},
}
});
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)
}
export async function docxTemplateX(template: Buffer, tdata: templateData, outputMediaType: string = "docx"): Promise<Uint8Array> {
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
@ -63,32 +73,39 @@ export async function docxTemplateX(base: string=".", tdata: templateData, outpu
* applicatin/json:
* schema:
* type: array
* items:
* 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);
}
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:
@ -128,26 +145,175 @@ 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']);
let outputMediaType = mimeToExtension(req.headers['accept']);
console.log('content-type: ', inputType);
console.log('accept: ', outputMediaType);
let buffer = await docxTemplateX('.', 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);
}
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"])
console.log("content-type: ", inputType)
console.log("accept: ", outputMediaType)
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)
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}.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"])
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}.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)
}
})

View file

@ -1,48 +1,46 @@
export interface templateData {
template: string;
reportName: string;
data: object
template: string
reportName: string
data: object
}
export interface IDictionary<TValue> {
[key: string]: TValue;
[key: string]: TValue
}
export function mimeToExtension(mime: string): string {
const mimeList: IDictionary<string> = {
"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<string> = {
"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
* tags:
* name: report-template
* description: API Template docx xlsx Mail Merge Template soffice
*/
*/
/** javascript-obfuscator:disable
* @swagger
@ -66,4 +64,3 @@ export function mimeToExtension(mime: string): string {
* type: object
* description: value for template
*/

View file

@ -1,93 +1,51 @@
{
"openapi": "3.1.0",
"info": {
"title": "Report Server",
"version": "0.8.1",
"description": "Technical preview releases - Report Server <br/>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"
"openapi": "3.1.0",
"info": {
"title": "Report Server",
"version": "0.8.1",
"description": "Technical preview releases - Report Server <br/>Advance create and convert document API for microservice era. ",
"license": {
"name": "by oom@Frappet",
"url": "https://frappet.com"
}
},
{
"url": "https://bma-ehr.frappet.synology.me/"
},
{
"url": "http://localhost:3000"
},
{
"url": "http://192.168.2.101:3001"
}
],
"paths": {
"/api/v1/report-template/convert": {
"post": {
"summary": "แปลงฟอร์แม็ตเอกสารเช่นจาก docx เป็น pdf ให้ตั้งค่า Media type เป็นชนิดไฟล์ที่ต้องการ",
"tags": [
"office-convert"
],
"parameters": [
{
"name": "report-name",
"in": "header",
"description": "ชื่อไฟล์ที่ต้องการหลังแปลง",
"required": true,
"schema": {
"type": "string",
"example": "report"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/octet-stream": {
"servers": [
{
"url": "https://report-server.frappet.synology.me"
},
{
"url": "https://bma-ehr.frappet.synology.me/"
},
{
"url": "http://localhost:3000"
},
{
"url": "http://192.168.2.101:3001"
}
],
"paths": {
"/api/v1/report-template/convert": {
"post": {
"summary": "แปลงฟอร์แม็ตเอกสารเช่นจาก docx เป็น pdf ให้ตั้งค่า Media type เป็นชนิดไฟล์ที่ต้องการ",
"tags": [
"office-convert"
],
"parameters": [
{
"name": "report-name",
"in": "header",
"description": "ชื่อไฟล์ที่ต้องการหลังแปลง",
"required": true,
"schema": {
"type": "string",
"format": "binary"
"example": "report"
}
}
}
},
"responses": {
"201": {
"description": "file was converted.",
],
"requestBody": {
"required": true,
"content": {
"application/pdf": {
"schema": {
"type": "string",
"format": "binary"
}
},
"application/vnd.oasis.opendocument.text": {
"schema": {
"type": "string",
"format": "binary"
}
},
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": {
"schema": {
"type": "string",
"format": "binary"
}
},
"application/msword": {
"schema": {
"type": "string",
"format": "binary"
}
},
"image/png": {
"schema": {
"type": "string",
"format": "binary"
}
},
"image/jpeg": {
"application/octet-stream": {
"schema": {
"type": "string",
"format": "binary"
@ -95,242 +53,474 @@
}
}
},
"400": {
"description": "Invalid format"
},
"500": {
"description": "Server error"
}
}
}
},
"/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"
"responses": {
"201": {
"description": "file was converted.",
"content": {
"application/pdf": {
"schema": {
"type": "string",
"format": "binary"
}
},
"example": [
"hello"
]
"application/vnd.oasis.opendocument.text": {
"schema": {
"type": "string",
"format": "binary"
}
},
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": {
"schema": {
"type": "string",
"format": "binary"
}
},
"application/msword": {
"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"
}
},
"500": {
"description": "Server error"
}
}
},
"post": {
"summary": "สร้างเอกสารโดยใช้ template จากไฟล์ docx จะแทนค่าตัวแปรในเอกสาร หรือจะแปลงเป็นฟอร์แม็ตอื่นได้ด้วยรองรับ docx pdf odt, ค่า template เป็นชื่อของ template, reportName เป็นชื่อไฟล์ที่ต้องการ",
"tags": [
"report-template"
],
"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": "พลสิทธิ์"
"/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"
}
}
},
"responses": {
"201": {
"description": "เอกสารถูกสร้างขึ้น",
"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/vnd.openxmlformats-officedocument.wordprocessingml.document": {
"application/json": {
"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"
"$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": "พลสิทธิ์"
}
]
}
}
}
}
},
"500": {
"description": "Server error"
}
}
}
},
"/api/v1/report-template/xlsx": {
"get": {
"summary": "แสดงรายการ xlsx template",
"tags": [
"report-template"
],
"responses": {
"200": {
"description": "array of template",
"content": {
"applicatin/json": {
"schema": {
"type": "array",
"items": {
"type": "string"
"responses": {
"201": {
"description": "เอกสารถูกสร้างขึ้น",
"content": {
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": {
"schema": {
"type": "string",
"format": "binary"
}
},
"example": [
"hello"
]
"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"
}
},
"500": {
"description": "Server error"
}
}
},
"post": {
"summary": "สร้างเอกสารจาก xlsx template แล้วส่งกลับมาเป็น xlsx pdf odt , ค่า template เป็นชื่อของ template ที่ใช้งาน, reportName เป็นชื่อไฟล์ที่ต้องการ",
"tags": [
"report-template"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"/api/v1/report-template/docx/upload": {
"post": {
"summary": "อัพไฟล์ docx",
"tags": [
"report-template"
],
"parameters": [
{
"name": "report_name",
"in": "query",
"description": "ชื่อไฟล์",
"required": true,
"schema": {
"$ref": "#/components/schemas/templateData"
},
"example": {
"template": "hello",
"reportName": "xlsx-report",
"data": {
"docNo": "๑๒๓๔๕",
"me": "กระผม",
"prefix": "นาย",
"name": "สรวิชญ์",
"surname": "พลสิทธิ์",
"position": "Chief Technology Officer",
"org": {
"type": "บริษัท",
"name": "เฟรปเป้ที",
"url": "https://frappet.com"
},
"employees": [
{
"id": 1,
"name": "ภาวิชญ์",
"surname": "พลสิทธิ์",
"score": 80
},
{
"id": 2,
"name": "วิชญาภา",
"surname": "พลสิทธิ์",
"score": 50
},
{
"id": 3,
"name": "ฐิตาภา",
"surname": "พลสิทธิ์",
"score": 90
},
{
"id": 4,
"name": "สรวิชญ์ พลสิทธิ์",
"surname": "พลสิทธิ์",
"score": 99
"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"
}
}
}
},
"/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"
}
}
}
},
"/api/v1/report-template/xlsx": {
"get": {
"summary": "แสดงรายการ xlsx template",
"tags": [
"report-template"
],
"responses": {
"200": {
"description": "array of template",
"content": {
"applicatin/json": {
"schema": {
"type": "array",
"items": {
"type": "string"
}
},
"example": [
"hello"
]
}
}
},
"500": {
"description": "Server error"
}
}
},
"responses": {
"201": {
"description": "เอกสารถูกสร้างขึ้น created.",
"post": {
"summary": "สร้างเอกสารจาก xlsx template แล้วส่งกลับมาเป็น xlsx 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/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
"application/json": {
"schema": {
"type": "string",
"format": "binary"
"$ref": "#/components/schemas/templateData"
},
"example": {
"template": "hello",
"reportName": "xlsx-report",
"data": {
"docNo": "๑๒๓๔๕",
"me": "กระผม",
"prefix": "นาย",
"name": "สรวิชญ์",
"surname": "พลสิทธิ์",
"position": "Chief Technology Officer",
"org": {
"type": "บริษัท",
"name": "เฟรปเป้ที",
"url": "https://frappet.com"
},
"employees": [
{
"id": 1,
"name": "ภาวิชญ์",
"surname": "พลสิทธิ์",
"score": 80
},
{
"id": 2,
"name": "วิชญาภา",
"surname": "พลสิทธิ์",
"score": 50
},
{
"id": 3,
"name": "ฐิตาภา",
"surname": "พลสิทธิ์",
"score": 90
},
{
"id": 4,
"name": "สรวิชญ์ พลสิทธิ์",
"surname": "พลสิทธิ์",
"score": 99
}
]
}
}
},
"application/pdf": {
"schema": {
"type": "string",
"format": "binary"
}
}
},
"responses": {
"201": {
"description": "เอกสารถูกสร้างขึ้น created.",
"content": {
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
"schema": {
"type": "string",
"format": "binary"
}
},
"application/pdf": {
"schema": {
"type": "string",
"format": "binary"
}
},
"application/vnd.oasis.opendocument.spreadsheet": {
"schema": {
"type": "string",
"format": "binary"
}
},
"image/png": {
"schema": {
"type": "string",
"format": "binary"
}
},
"image/jpeg": {
"schema": {
"type": "string",
"format": "binary"
}
}
},
"application/vnd.oasis.opendocument.spreadsheet": {
"schema": {
"type": "string",
"format": "binary"
}
},
"image/png": {
"schema": {
"type": "string",
"format": "binary"
}
},
"image/jpeg": {
}
},
"500": {
"description": "Server error"
}
}
}
},
"/api/v1/report-template/xlsx/upload": {
"post": {
"summary": "อัพไฟล์ xlsx",
"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"
@ -338,48 +528,112 @@
}
}
},
"500": {
"description": "Server error"
"responses": {
"201": {
"description": "file was converted.",
"content": {
"application/octet-stream": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
},
"400": {
"description": "Invalid format"
},
"500": {
"description": "Server error"
}
}
}
},
"/api/v1/report-template/xlsx/download": {
"post": {
"summary": "โหลดไฟล์ xlsx",
"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": "xlsx-report"
}
}
}
},
"responses": {
"201": {
"description": "เอกสารถูกสร้างขึ้น",
"content": {
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
},
"500": {
"description": "Server error"
}
}
}
}
}
},
"components": {
"schemas": {
"templateData": {
"type": "object",
"required": [
"template",
"reportName",
"data",
"finished"
],
"properties": {
"template": {
"type": "string",
"description": "name of template"
},
"reportName": {
"type": "string",
"description": "name of report for download"
},
"data": {
"type": "object",
"description": "value for template"
}
}
}
}
},
"tags": [
{
"name": "office-convert",
"description": "ใช้แปลงไฟล์จากเอกสารที่ libreoffice แปลงได้ เช่น docx เป็น pdf"
},
{
"name": "report-template",
"description": "API สำหรับสร้างเอกสารจาก Template docx หรือ xlsx การทำงานคล้ายการทำ Mail Merge ทำให้ยูสเซอร์ทั่วไปแก้ Template ได้ง่าย สามารถแปลงไฟล์นามสกุลอื่นๆที่ soffice รองรับ"
}
]
}
"components": {
"schemas": {
"templateData": {
"type": "object",
"required": [
"template",
"reportName",
"data",
"finished"
],
"properties": {
"template": {
"type": "string",
"description": "name of template"
},
"reportName": {
"type": "string",
"description": "name of report for download"
},
"data": {
"type": "object",
"description": "value for template"
}
}
}
}
},
"tags": [
{
"name": "office-convert",
"description": "ใช้แปลงไฟล์จากเอกสารที่ libreoffice แปลงได้ เช่น docx เป็น pdf"
},
{
"name": "report-template",
"description": "API สำหรับสร้างเอกสารจาก Template docx หรือ xlsx การทำงานคล้ายการทำ Mail Merge ทำให้ยูสเซอร์ทั่วไปแก้ Template ได้ง่าย สามารถแปลงไฟล์นามสกุลอื่นๆที่ soffice รองรับ"
}
]
}

View file

@ -1,14 +1,14 @@
import express from 'express'
export const xlsxTemplateRoute = express.Router();
import express from "express"
export const xlsxTemplateRoute = express.Router()
import {mimeToExtension,templateData} from './report-template'
import { ExcelTemplate } from 'xlsx-template-next'
import fs from 'fs'
import { LibreOfficeFileConverter } from 'libreoffice-file-converter';
import { mimeToExtension, templateData } from "./report-template"
import { ExcelTemplate } from "xlsx-template-next"
import fs from "fs"
import { LibreOfficeFileConverter } from "libreoffice-file-converter"
const TEMPLATE_FOLDER_NAME = "templates/xlsx"
/**
* xlsxTemplate Uses xlsx-template-next to convert input data and template to output buffer.
* xlsxTemplate Uses xlsx-template-next 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)
@ -17,30 +17,27 @@ const TEMPLATE_FOLDER_NAME = "templates/xlsx"
* @param {Number} tab tab page of spread sheet , default = 1
* @return {Promise<Uint8Array>} output buffer after apply template.
*/
export async function xlsxTemplateX(base: string=".", tdata: templateData, outputMediaType: string="xlsx",tab:number=1): Promise<Uint8Array> {
try {
const template = new ExcelTemplate();
let templateBuff = await fs.promises.readFile(`${base}/${TEMPLATE_FOLDER_NAME}/${tdata.template}.xlsx`)
await template.load(templateBuff);
await template.process(tab,tdata.data)
const buffer = await template.build({ type: 'uint8array' })
if (outputMediaType === "xlsx")
return buffer as Uint8Array
const libreOfficeFileConverter = new LibreOfficeFileConverter({
childProcessOptions: {
timeout: 60 * 1000,
},
});
const lbuffer = await libreOfficeFileConverter.convertBuffer(Buffer.from(buffer as Uint8Array), outputMediaType);
return lbuffer
} catch (e) {
throw (e)
}
}
/** javascript-obfuscator:disable
export async function xlsxTemplateX(templateBuff: Buffer, tdata: templateData, outputMediaType: string = "xlsx", tab: number = 1): Promise<Uint8Array> {
try {
const template = new ExcelTemplate()
// let templateBuff = await fs.promises.readFile(`${base}/${TEMPLATE_FOLDER_NAME}/${tdata.template}.xlsx`)
await template.load(templateBuff)
await template.process(tab, tdata.data)
const buffer = await template.build({ type: "uint8array" })
if (outputMediaType === "xlsx") return buffer as Uint8Array
const libreOfficeFileConverter = new LibreOfficeFileConverter({
childProcessOptions: {
timeout: 60 * 1000,
},
})
const lbuffer = await libreOfficeFileConverter.convertBuffer(Buffer.from(buffer as Uint8Array), outputMediaType)
return lbuffer
} catch (e) {
throw e
}
}
/** javascript-obfuscator:disable
* @swagger
* /api/v1/report-template/xlsx:
* get:
@ -53,23 +50,23 @@ export async function xlsxTemplateX(base: string=".", tdata: templateData, outpu
* applicatin/json:
* schema:
* type: array
* items:
* items:
* type: string
* example: ["hello"]
* 500:
* 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
@ -78,12 +75,20 @@ xlsxTemplateRoute.get("/", async function (req, res) {
* post:
* summary: สร้างเอกสารจาก xlsx template xlsx 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'
* $ref: '#/components/schemas/templateData'
* example:
* template: hello
* reportName: xlsx-report
@ -117,26 +122,167 @@ 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']);
console.log('content-type: ', inputType);
console.log('accept: ', outputMediaType);
let buffer = await xlsxTemplateX(".",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';
res.end(res.statusMessage);
console.error('Error during apply template: ', 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"])
console.log("content-type: ", inputType)
console.log("accept: ", outputMediaType)
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) {
res.statusCode = 500
res.statusMessage = "Internal Server Error"
res.end(res.statusMessage)
console.error("Error during apply template: ", ex)
}
})
/** javascript-obfuscator:disable
* @swagger
* /api/v1/report-template/xlsx/upload:
* post:
* summary: อัพไฟล์ xlsx
* 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
* 400:
* description: Invalid format
* 500:
* description: Server error
*
*/
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)
// 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)
}
})
/** javascript-obfuscator:disable
* @swagger
* /api/v1/report-template/xlsx/download:
* post:
* summary: โหลดไฟล์ xlsx
* 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: xlsx-report
* responses:
* 201:
* description: เอกสารถูกสร้างขึ้น
* content:
* application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
* schema:
* type: string
* format: binary
* 500:
* description: Server error
*
*/
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)
}
})

5229
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,39 +1,41 @@
{
"name": "report-server-ts",
"version": "1.0.0",
"description": "docx-template มีปัญหากับ bun คาดว่าเป็นปัญหาจาก eval",
"scripts": {
"dev": "PORT=3001 nodemon app.ts",
"swaggergen": "ts-node libs/create-swagger-spec.ts ",
"build": "ts-node libs/create-swagger-spec.ts && tsc && cp libs/swagger-specs.json dist/libs",
"serve": "PORT=3000 node dist/app.js",
"obfuscator": "tsc && javascript-obfuscator ./dist --output ./dist2 && cp libs/swagger-specs.json dist/libs",
"preview": "PORT=3000 node dist2/app.js",
"build:docker": "ts-node libs/create-swagger-spec.ts && tsc && javascript-obfuscator ./dist --output ./dist2&& cp libs/swagger-specs.json dist2/libs && docker build -t docker.frappet.com/demo/report-server .",
"push:docker": "docker push docker.frappet.com/demo/report-server"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"cors": "^2.8.5",
"docx-templates": "^4.13.0",
"express": "^4.19.2",
"libreoffice-file-converter": "^2.3.3",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.1",
"xlsx-template-next": "^1.0.3",
"yaqrcode": "^0.2.1"
},
"devDependencies": {
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/node": "^20.14.9",
"@types/swagger-jsdoc": "^6.0.4",
"@types/swagger-ui-express": "^4.1.6",
"javascript-obfuscator": "^4.1.1",
"nodemon": "^3.1.4",
"ts-node": "^10.9.2",
"typescript": "^5.2.2"
"name": "report-server-ts",
"version": "1.0.0",
"description": "docx-template มีปัญหากับ bun คาดว่าเป็นปัญหาจาก eval",
"scripts": {
"dev": "PORT=3001 nodemon app.ts",
"swaggergen": "ts-node libs/create-swagger-spec.ts ",
"build": "ts-node libs/create-swagger-spec.ts && tsc && cp libs/swagger-specs.json dist/libs",
"serve": "PORT=3000 node dist/app.js",
"obfuscator": "tsc && javascript-obfuscator ./dist --output ./dist2 && cp libs/swagger-specs.json dist/libs",
"preview": "PORT=3000 node dist2/app.js",
"build:docker": "ts-node libs/create-swagger-spec.ts && tsc && javascript-obfuscator ./dist --output ./dist2&& cp libs/swagger-specs.json dist2/libs && docker build -t docker.frappet.com/demo/report-server .",
"push:docker": "docker push docker.frappet.com/demo/report-server"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^1.7.7",
"cors": "^2.8.5",
"docx-templates": "^4.13.0",
"express": "^4.19.2",
"libreoffice-file-converter": "^2.3.3",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.1",
"xlsx-template-next": "^1.0.3",
"yaqrcode": "^0.2.1"
},
"devDependencies": {
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/node": "^20.14.9",
"@types/swagger-jsdoc": "^6.0.4",
"@types/swagger-ui-express": "^4.1.6",
"javascript-obfuscator": "^4.1.1",
"nodemon": "^3.1.4",
"ts-node": "^10.9.2",
"typescript": "^5.2.2"
}
}
}