Support TS, swagger,Template(xls,docx),PDF convert

This commit is contained in:
Sorawit Bholsithi 2023-10-07 17:29:53 +07:00
parent b221deb1cf
commit 8d7654423a
84 changed files with 3604 additions and 1 deletions

94
libs/convert-libs.ts Normal file
View file

@ -0,0 +1,94 @@
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 x
*/
/** javascript-obfuscator:disable
* @swagger
* /api/v1/report-template/convert:
* post:
* summary: Create document from template
* tags: [office-convert]
* parameters:
* - name: report_name
* in: header
* description: นามสกุลไฟล์ที่อัปโหลดเข้ามา( soffice )
* required: true
* schema:
* type: string
* example: report
* requestBody:
* required: true
* content:
* application/octet-stream:
* schema:
* type: string
* format: binary
* responses:
* 201:
* description: file was converted.
* 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
* text/html:
* schema:
* type: string
* 500:
* description: Server error
*
*/
convertTemplateRoute.post("/convert", 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('output: ' + outputMediaType);
const libreOfficeFileConverter = new LibreOfficeFileConverter({
childProcessOptions: {
timeout: 60 * 1000,
},
});
const buffer = await libreOfficeFileConverter.convertBuffer(req.body, outputMediaType);
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) {
res.statusCode = 500;
res.statusMessage = 'Internal Server Error';
res.end(res.statusMessage);
console.error(`Error during convert with soffice:`, ex);
}
})

View file

@ -0,0 +1,36 @@
// https://swagger.io/docs/specification/about/
import swaggerJsdoc from "swagger-jsdoc"
import fs from 'fs'
const swaggerOptions = {
definition: {
openapi: "3.1.0",
info: {
title: "Report Server",
version: "0.1.0",
description:
"Advance Create and convert document API for microservice.",
license: {
name: "by Frappet",
url: "https://frappet.com",
},
},
servers: [
{
url: "https://report-server.frappet.synology.me",
},
{
url: "http://localhost:3000",
},
{
url: "http://192.168.2.100:3000",
},
],
},
apis: ["./libs/*.ts"],
};
export function createSpec(){
const swaggerSpecs = swaggerJsdoc(swaggerOptions);
fs.promises.writeFile("libs/swagger-specs.json",JSON.stringify(swaggerSpecs,null,2))
}
createSpec()

147
libs/docx-templates-lib.ts Normal file
View file

@ -0,0 +1,147 @@
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'
// แก้ package.json ของ LibreOfficeFileConverter
// https://github.com/microsoft/TypeScript/issues/52363#issuecomment-1659179354
import { LibreOfficeFileConverter } from 'libreoffice-file-converter';
const TEMPLATE_FOLDER_NAME = "templates/docx"
/**
* docxTemplate Uses docx-template to convert input data and template to output buffer.
* You have to handle exception throw by function
* template keep in folder templates
* @param {String} base base path of caller relate to template-docx foler (no trail slash)
* @param {templateData} tdata Template Information in JSON format
* @param {String} outputMediaType output extension
* @return {Promise<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)
}
}
/** javascript-obfuscator:disable
* @swagger
* /api/v1/report-template/docx:
* get:
* summary: list docx 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
*/
docxTemplateRoute.get("/", async function (req, res) {
try {
const fileList = await fs.promises.readdir(`./${TEMPLATE_FOLDER_NAME}`)
const templateList = fileList.map((f) => f.split('.docx')[0])
res.send(templateList)
} catch (ex) {
res.statusCode = 500;
res.statusMessage = 'Internal Server Error';
res.end(res.statusMessage);
console.error('Error during get template list: ', ex);
}
})
/** javascript-obfuscator:disable
* @swagger
* /api/v1/report-template/docx:
* post:
* summary: Create document from docx template output can be docx pdf odt
* 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": "พลสิทธิ์"}]}
* responses:
* 201:
* description: เอกสารถูกสร้างขึ้น
* content:
* application/vnd.openxmlformats-officedocument.wordprocessingml.document:
* schema:
* type: string
* format: binary
* application/pdf:
* schema:
* type: string
* format: binary
* application/vnd.oasis.opendocument.text:
* schema:
* type: string
* format: binary
* 500:
* description: Server error
*
*/
docxTemplateRoute.post("/", async function (req, res) {
try {
if (!req.headers['content-type'] || !req.headers['accept'])
throw new Error("Require header content-type, accept")
let inputType = mimeToExtension(req.headers['content-type']);
let outputMediaType = mimeToExtension(req.headers['accept']);
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);
}
})

67
libs/report-template.ts Normal file
View file

@ -0,0 +1,67 @@
export interface templateData {
template: string;
reportName: string;
data: object
}
export interface IDictionary<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"
}
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
* 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
*/

348
libs/swagger-specs.json Normal file
View file

@ -0,0 +1,348 @@
{
"openapi": "3.1.0",
"info": {
"title": "Report Server",
"version": "0.1.0",
"description": "Advance Create and convert document API for microservice.",
"license": {
"name": "by Frappet",
"url": "https://frappet.com"
}
},
"servers": [
{
"url": "https://report-server.frappet.synology.me"
},
{
"url": "http://localhost:3000"
},
{
"url": "http://192.168.2.100:3000"
}
],
"paths": {
"/api/v1/report-template/convert": {
"post": {
"summary": "Create document from template",
"tags": [
"office-convert"
],
"parameters": [
{
"name": "report_name",
"in": "header",
"description": "นามสกุลไฟล์ที่อัปโหลดเข้ามา(ตอนนี้ไม่จำเป็นต้องใช้เพราะ soffice ตรวจสอบเอง)",
"required": true,
"schema": {
"type": "string",
"example": "report"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/octet-stream": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
},
"responses": {
"201": {
"description": "file was converted.",
"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"
}
},
"text/html": {
"schema": {
"type": "string"
}
}
}
},
"500": {
"description": "Server error"
}
}
}
},
"/api/v1/report-template/docx": {
"get": {
"summary": "list docx 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"
}
}
},
"post": {
"summary": "Create document from docx template output can be docx pdf odt",
"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": "พลสิทธิ์"
}
]
}
}
}
}
},
"responses": {
"201": {
"description": "เอกสารถูกสร้างขึ้น",
"content": {
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": {
"schema": {
"type": "string",
"format": "binary"
}
},
"application/pdf": {
"schema": {
"type": "string",
"format": "binary"
}
},
"application/vnd.oasis.opendocument.text": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
},
"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"
}
}
},
"post": {
"summary": "สร้าองเอกสารจาก xlsx template แล้วส่งกลับมาเป็น xlsx pdf odt",
"tags": [
"report-template"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"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
}
]
}
}
}
}
},
"responses": {
"201": {
"description": "เอกสารถูกสร้างขึ้น created.",
"content": {
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": {
"schema": {
"type": "string",
"format": "binary"
}
},
"application/pdf": {
"schema": {
"type": "string",
"format": "binary"
}
},
"application/vnd.oasis.opendocument.text": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
},
"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 แปลงได้ x"
},
{
"name": "report-template",
"description": "API สำหรับสร้างเอกสารจาก Template docx หรือ xlsx การทำงานคล้ายการทำ Mail Merge ทำให้ยูสเซอร์ทั่วไปแก้ Template ได้ง่าย สามารถแปลงไฟล์นามสกุลอื่นๆที่ soffice รองรับ"
}
]
}

134
libs/xlsx-template-lib.ts Normal file
View file

@ -0,0 +1,134 @@
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';
const TEMPLATE_FOLDER_NAME = "templates/xlsx"
/**
* docxTemplate Uses docx-template to convert input data and template to output buffer.
* You have to handle exception throw by function
* template keep in folder templates
* @param {String} base base path of caller relate to template-docx foler (no trail slash)
* @param {templateData} tdata Template Information in JSON format
* @param {String} outputMediaType output extension
* @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
* @swagger
* /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
*/
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);
}
})
/** javascript-obfuscator:disable
* @swagger
* /api/v1/report-template/xlsx:
* post:
* summary: สร้าองเอกสารจาก xlsx template xlsx pdf odt
* tags: [report-template]
* requestBody:
* required: true
* content:
* application/json:
* 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}]}
* responses:
* 201:
* description: เอกสารถูกสร้างขึ้น created.
* content:
* application/vnd.openxmlformats-officedocument.wordprocessingml.document:
* schema:
* type: string
* format: binary
* application/pdf:
* schema:
* type: string
* format: binary
* application/vnd.oasis.opendocument.text:
* schema:
* type: string
* format: binary
* 500:
* description: Server error
*
*/
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);
}
})