Merge pull request 'Barcode generation feature/13-barcode_generation' (#21) from feature/13-barcode_generation into dev
continuous-integration/drone/push Build is failing

Reviewed-on: #21
This commit was merged in pull request #21.
This commit is contained in:
niggl committed 2021-02-09 16:43:45 +00:00
commit ff36b4871f
6 files changed
+301 -242

No files matched your search

+2
View File
@@ -40,7 +40,9 @@
"license": "CC-BY-NC-SA-4.0",
"dependencies": {
"@odit/class-validator-jsonschema": "^2.1.1",
"async-helpers": "^0.3.17",
"axios": "^0.21.1",
"bwip-js": "^2.0.12",
"cheerio": "^1.0.0-rc.5",
"class-transformer": "0.3.1",
"class-validator": "^0.13.1",
+205 -198
View File
@@ -1,199 +1,206 @@
import axios from 'axios';
import cheerio from "cheerio";
import fs from "fs";
import Handlebars from 'handlebars';
import i18next from "i18next";
import Backend from 'i18next-fs-backend';
import mime from "mime-types";
import path from 'path';
import { PDFDocument } from 'pdf-lib';
import puppeteer from "puppeteer";
import { config } from './config';
import { Runner } from './models/Runner';
import { RunnerGroup } from './models/RunnerGroup';
/**
* This class is responsible for all things pdf creation.
* This uses the html templates from src/templates.
*/
export class PdfCreator {
private templateDir = path.join(__dirname, '/templates');
private browser;
private static interpolations = { eventname: config.eventname, sponsoring_receipt_minimum_amount: config.sponsoring_receipt_minimum_amount, currency_symbol: config.currency_symbol }
/**
* Main constructor.
* Initializes i18n(ext), Handlebars and puppeteer.
*/
constructor() {
this.init();
}
/**
* Main constructor.
* Initializes i18n(ext), Handlebars and puppeteer.
*/
public async init() {
const minimal_args = [
'--autoplay-policy=user-gesture-required',
'--disable-background-networking',
'--disable-background-timer-throttling',
'--disable-backgrounding-occluded-windows',
'--disable-breakpad',
'--disable-client-side-phishing-detection',
'--disable-component-update',
'--disable-default-apps',
'--disable-dev-shm-usage',
'--disable-domain-reliability',
'--disable-extensions',
'--disable-features=AudioServiceOutOfProcess',
'--disable-hang-monitor',
'--disable-ipc-flooding-protection',
'--disable-notifications',
'--disable-offer-store-unmasked-wallet-cards',
'--disable-popup-blocking',
'--disable-print-preview',
'--disable-prompt-on-repost',
'--disable-renderer-backgrounding',
'--disable-speech-api',
'--disable-sync',
'--hide-scrollbars',
'--ignore-gpu-blacklist',
'--metrics-recording-only',
'--mute-audio',
'--no-default-browser-check',
'--no-first-run',
'--no-pings',
'--no-zygote',
'--password-store=basic',
'--use-gl=swiftshader',
'--no-sandbox'
];
await i18next
.use(Backend)
.init({
fallbackLng: 'en',
lng: 'en',
backend: {
loadPath: path.join(__dirname, '/locales/{{lng}}.json')
}
});
await Handlebars.registerHelper('__',
function (str) {
return i18next.t(str, PdfCreator.interpolations).toString();
}
);
this.browser = await puppeteer.launch({ headless: true, args: minimal_args });
}
/**
* Generate sponsoring contract pdfs.
* @param runner The runner you want to generate the contracts for.
* @param locale The locale used for the contracts (default:en)
*/
public async generateSponsoringContract(runners: Runner[], locale: string = "en"): Promise<Buffer> {
if (runners.length == 1 && Object.keys(runners[0]).length == 0) {
runners[0] = this.generateEmptyRunner();
}
if (runners.length > 50) {
let pdf_promises = new Array<Promise<Buffer>>();
let i, j;
for (i = 0, j = runners.length; i < j; i += 50) {
let chunk = runners.slice(i, i + 50);
pdf_promises.push(this.generateSponsoringContract(chunk, locale));
}
const pdfs = await Promise.all(pdf_promises);
return await this.mergePdfs(pdfs);
}
await i18next.changeLanguage(locale);
const template_source = fs.readFileSync(`${this.templateDir}/sponsoring_contract.html`, 'utf8');
const template = Handlebars.compile(template_source);
const result = template({ runners })
const pdf = await this.renderPdf(result, { format: "A5", landscape: true });
return pdf
}
/**
* Converts all images in html to base64.
* Works with image files in the template directory or images from urls.
* @param html The html string whoms images shall get replaced.
*/
public async imgToBase64(html): Promise<string> {
const $ = cheerio.load(html)
$('img').each(async (index, element) => {
let imgsrc = $(element).attr("src");
const img_type = mime.lookup(imgsrc);
if (!(img_type.includes("image"))) {
throw new Error("File is not image mime type");
}
let image;
if (imgsrc.startsWith("http")) {
image = (await axios.get(imgsrc)).data;
image = Buffer.from(image).toString('base64');
}
else {
if (imgsrc.startsWith("./")) {
imgsrc = imgsrc.replace("./", "");
}
image = fs.readFileSync(`${this.templateDir}/${imgsrc}`, { encoding: "base64" });
}
image = `data:${img_type};base64,${image}`
$(element).attr("src", image)
})
return $.html();
}
/**
* This method manages the creation of pdfs via puppeteer.
* @param html The HTML that should get rendered.
* @param options Puppeteer PDF option (eg: {format: "A4"})
*/
public async renderPdf(html: string, options): Promise<any> {
html = await this.imgToBase64(html);
let page = await this.browser.newPage();
await page.setContent(html);
const pdf = await page.pdf(options);
await page.close();
return pdf;
}
/**
* Merges multiple pdfs into one.
* @param pdfs The pdfs you want to merge as an buffer array.
* @returns The merged pdf as a buffer.
*/
private async mergePdfs(pdfs: Buffer[]): Promise<Buffer> {
const mergedPdf = await PDFDocument.create();
for (const pdfBuffer of pdfs) {
const pdf = await PDFDocument.load(pdfBuffer);
const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
copiedPages.forEach((page) => {
mergedPdf.addPage(page);
});
}
return <Buffer>(await mergedPdf.save());
}
/**
* Generates a new dummy runner with halfspaces for all strings.
* Can be used to generate empty sponsoring contracts.
* @returns A new runner object that apears to be empty.
*/
private generateEmptyRunner(): Runner {
let group = new RunnerGroup();
group.id = 0;
group.name = " ";
let runner = new Runner();
runner.id = 0;
runner.firstname = " ";
runner.lastname = " ";
runner.group = group;
return runner;
}
import axios from 'axios';
import cheerio from "cheerio";
import fs from "fs";
import Handlebars from 'handlebars';
import i18next from "i18next";
import Backend from 'i18next-fs-backend';
import mime from "mime-types";
import path from 'path';
import { PDFDocument } from 'pdf-lib';
import puppeteer from "puppeteer";
import { awaitAsyncHandlebarHelpers, helpers } from './asyncHelpers';
import { config } from './config';
import { Runner } from './models/Runner';
import { RunnerGroup } from './models/RunnerGroup';
/**
* This class is responsible for all things pdf creation.
* This uses the html templates from src/templates.
*/
export class PdfCreator {
private templateDir = path.join(__dirname, '/templates');
private browser;
private static interpolations = { eventname: config.eventname, sponsoring_receipt_minimum_amount: config.sponsoring_receipt_minimum_amount, currency_symbol: config.currency_symbol }
/**
* Main constructor.
* Initializes i18n(ext), Handlebars and puppeteer.
*/
constructor() {
this.init();
}
/**
* Main constructor.
* Initializes i18n(ext), Handlebars and puppeteer.
*/
public async init() {
const minimal_args = [
'--autoplay-policy=user-gesture-required',
'--disable-background-networking',
'--disable-background-timer-throttling',
'--disable-backgrounding-occluded-windows',
'--disable-breakpad',
'--disable-client-side-phishing-detection',
'--disable-component-update',
'--disable-default-apps',
'--disable-dev-shm-usage',
'--disable-domain-reliability',
'--disable-extensions',
'--disable-features=AudioServiceOutOfProcess',
'--disable-hang-monitor',
'--disable-ipc-flooding-protection',
'--disable-notifications',
'--disable-offer-store-unmasked-wallet-cards',
'--disable-popup-blocking',
'--disable-print-preview',
'--disable-prompt-on-repost',
'--disable-renderer-backgrounding',
'--disable-speech-api',
'--disable-sync',
'--hide-scrollbars',
'--ignore-gpu-blacklist',
'--metrics-recording-only',
'--mute-audio',
'--no-default-browser-check',
'--no-first-run',
'--no-pings',
'--no-zygote',
'--password-store=basic',
'--use-gl=swiftshader',
'--no-sandbox'
];
await i18next
.use(Backend)
.init({
fallbackLng: 'en',
lng: 'en',
backend: {
loadPath: path.join(__dirname, '/locales/{{lng}}.json')
}
});
await Handlebars.registerHelper(helpers);
await Handlebars.registerHelper('__',
function (str) {
return i18next.t(str, PdfCreator.interpolations).toString();
}
);
this.browser = await puppeteer.launch({ headless: true, args: minimal_args });
}
/**
* Generate sponsoring contract pdfs.
* @param runner The runner you want to generate the contracts for.
* @param locale The locale used for the contracts (default:en)
*/
public async generateSponsoringContract(runners: Runner[], locale: string = "en", codeformat: string = config.codeformat): Promise<Buffer> {
if (runners.length == 1 && Object.keys(runners[0]).length == 0) {
runners[0] = this.generateEmptyRunner();
}
if (runners.length > 50) {
let pdf_promises = new Array<Promise<Buffer>>();
let i, j;
for (i = 0, j = runners.length; i < j; i += 50) {
let chunk = runners.slice(i, i + 50);
pdf_promises.push(this.generateSponsoringContract(chunk, locale));
}
const pdfs = await Promise.all(pdf_promises);
return await this.mergePdfs(pdfs);
}
await i18next.changeLanguage(locale);
const template_source = fs.readFileSync(`${this.templateDir}/sponsoring_contract.html`, 'utf8');
const template = Handlebars.compile(template_source);
let result = template({ runners, codeformat });
result = await awaitAsyncHandlebarHelpers(result);
const pdf = await this.renderPdf(result, { format: "A5", landscape: true });
return pdf
}
/**
* Converts all images in html to base64.
* Works with image files in the template directory or images from urls.
* @param html The html string whoms images shall get replaced.
*/
public async imgToBase64(html): Promise<string> {
const $ = cheerio.load(html)
$('img').each(async (index, element) => {
let imgsrc = $(element).attr("src");
if (imgsrc.startsWith("data:image")) {
return;
}
const img_type = mime.lookup(imgsrc);
if (!(img_type.includes("image"))) {
throw new Error("File is not image mime type");
}
let image;
if (imgsrc.startsWith("http")) {
image = (await axios.get(imgsrc)).data;
image = Buffer.from(image).toString('base64');
}
else {
if (imgsrc.startsWith("./")) {
imgsrc = imgsrc.replace("./", "");
}
image = fs.readFileSync(`${this.templateDir}/${imgsrc}`, { encoding: "base64" });
}
image = `data:${img_type};base64,${image}`
$(element).attr("src", image)
})
return $.html();
}
/**
* This method manages the creation of pdfs via puppeteer.
* @param html The HTML that should get rendered.
* @param options Puppeteer PDF option (eg: {format: "A4"})
*/
public async renderPdf(html: string, options): Promise<any> {
html = await this.imgToBase64(html);
let page = await this.browser.newPage();
await page.setContent(html);
const pdf = await page.pdf(options);
await page.close();
return pdf;
}
/**
* Merges multiple pdfs into one.
* @param pdfs The pdfs you want to merge as an buffer array.
* @returns The merged pdf as a buffer.
*/
private async mergePdfs(pdfs: Buffer[]): Promise<Buffer> {
const mergedPdf = await PDFDocument.create();
for (const pdfBuffer of pdfs) {
const pdf = await PDFDocument.load(pdfBuffer);
const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
copiedPages.forEach((page) => {
mergedPdf.addPage(page);
});
}
return <Buffer>(await mergedPdf.save());
}
/**
* Generates a new dummy runner with halfspaces for all strings.
* Can be used to generate empty sponsoring contracts.
* @returns A new runner object that apears to be empty.
*/
private generateEmptyRunner(): Runner {
let group = new RunnerGroup();
group.id = 0;
group.name = " ";
let runner = new Runner();
runner.id = 0;
runner.firstname = " ";
runner.lastname = " ";
runner.group = group;
return runner;
}
}
+44
View File
@@ -0,0 +1,44 @@
import AsyncHelpers from "async-helpers";
import bwipjs from "bwip-js";
export const asyncHelpers = new AsyncHelpers();
async function generateBarcode(str, options, emtpy, cb) {
let res = await generateBase64Barcode(options.toString(), str.toString());
cb(null, res);
}
generateBarcode.async = true;
asyncHelpers.set('--bc', generateBarcode);
export const helpers = asyncHelpers.get({ wrap: true });
export async function generateBase64Barcode(type: string, content: string): Promise<String> {
let options = {
bcid: type,
text: content,
scale: 4,
height: 10,
width: 10,
includetext: true,
textxalign: 'center',
}
if (type != "qrcode") {
delete options.width;
}
try {
const barcode: Buffer = await bwipjs.toBuffer(options);
return `data:image/png;base64,${barcode.toString('base64')}`;
}
catch {
return errorimage;
}
}
export async function awaitAsyncHandlebarHelpers(input: string): Promise<string> {
return await new Promise((resolve, reject) => {
asyncHelpers.resolveIds(input, (err, data) => {
if (err) { reject(err) }
resolve(data)
});
});
}
const errorimage = "data:image/png;base64,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 Line truncated
+19 -18
View File
@@ -1,19 +1,20 @@
import { config as configDotenv } from 'dotenv';
configDotenv();
export const config = {
internal_port: parseInt(process.env.APP_PORT) || 4010,
development: process.env.NODE_ENV === "production",
version: process.env.VERSION || require('../package.json').version,
eventname: process.env.EVENT_NAME || "Please set the event name",
currency_symbol: process.env.CURRENCY_SYMBOL || "€",
sponsoring_receipt_minimum_amount: process.env.SPONSORING_RECEIPT_MINIMUM_AMOUNT || "10"
}
let errors = 0
if (typeof config.internal_port !== "number") {
errors++
}
if (typeof config.development !== "boolean") {
errors++
}
import { config as configDotenv } from 'dotenv';
configDotenv();
export const config = {
internal_port: parseInt(process.env.APP_PORT) || 4010,
development: process.env.NODE_ENV === "production",
version: process.env.VERSION || require('../package.json').version,
eventname: process.env.EVENT_NAME || "Please set the event name",
currency_symbol: process.env.CURRENCY_SYMBOL || "€",
sponsoring_receipt_minimum_amount: process.env.SPONSORING_RECEIPT_MINIMUM_AMOUNT || "10",
codeformat: process.env.CODEFORMAT || "qrcode"
}
let errors = 0
if (typeof config.internal_port !== "number") {
errors++
}
if (typeof config.development !== "boolean") {
errors++
}
export let e = errors
+2 -2
View File
@@ -15,7 +15,7 @@ export class PdfController {
@Post('/contracts')
@OpenAPI({ description: "Generate Sponsoring contract pdfs from runner objects.<br>You can choose your prefered locale by passing the 'locale' query-param.<br> If you provide more than 100 runenrs this could take a moment or two (we tested up to 1000 runners in about 70sec so far)." })
async generateContracts(@Body({ validate: true, options: { limit: "500mb" } }) runners: Runner | Runner[], @Res() res: any, @QueryParam("locale") locale: string) {
async generateContracts(@Body({ validate: true, options: { limit: "500mb" } }) runners: Runner | Runner[], @Res() res: any, @QueryParam("locale") locale: string, @QueryParam("codeformat") codeformat: string) {
if (!this.initialized) {
await this.pdf.init();
this.initialized = true;
@@ -23,7 +23,7 @@ export class PdfController {
if (!Array.isArray(runners)) {
runners = [runners];
}
const contracts = await this.pdf.generateSponsoringContract(runners, locale);
const contracts = await this.pdf.generateSponsoringContract(runners, locale, codeformat);
res.setHeader('content-type', 'application/pdf');
return contracts;
}
+29 -24
View File
@@ -29,31 +29,36 @@
<div class="sheet">
<img id="header_img" width="100%" src="sponsoringheader.png" />
<div style=" padding: 0 1rem 0 1rem;">
<div class="columns" style="padding-bottom: 0;">
<div class="column is-two-fifths">
<p style="font-size: large; font-weight: bold;">{{__ "sponsoring_title"}}</p>
</div>
<div class="column">
<p style="font-size: x-small; vertical-align: revert; margin-top: auto;">{{__ "please_use_blockletters"}}
</p>
</div>
</div>
<p> {{__ "sponsoring_subtitle"}} </p>
<div class="columns" style="padding-top: 0;">
<div class="column is-8">
<span style="border-bottom: 1px solid; width: 100%; display: block;">{{this.firstname}}
{{this.middlename}}</span>
<p style="font-size: x-small; display: block;">{{__ "firstname"}}</p>
</div>
<div class="column is-2">
<span style="border-bottom: 1px solid; width: 100%; display: block;">{{this.id}}</span>
<p style="font-size: x-small; display: block;">ID</p>
</div>
<div class="column">
<!-- CODE Here -->
</div>
</div>
<div class="columns">
<div class="column is-10">
<div class="columns" style="padding-bottom: 0;">
<div class="column is-two-fifths">
<p style="font-size: large; font-weight: bold;">{{__ "sponsoring_title"}}</p>
</div>
<div class="column">
<p style="font-size: x-small; vertical-align: revert; margin-top: auto;">{{__ "please_use_blockletters"}}
</p>
</div>
</div>
<p> {{__ "sponsoring_subtitle"}} </p>
<div class="columns">
<div class="column is-9">
<span style="border-bottom: 1px solid; width: 100%; display: block;">{{this.firstname}}
{{this.middlename}}</span>
<p style="font-size: x-small; display: block;">{{__ "firstname"}}</p>
</div>
<div class="column is-3">
<span style="border-bottom: 1px solid; width: 100%; display: block;">{{this.id}}</span>
<p style="font-size: x-small; display: block;">ID</p>
</div>
</div>
</div>
<div class="column">
<img style="vertical-align: revert; margin-top: auto; object-fit: cover; max-height: 2cm;"
src="{{--bc this.id ../codeformat}}" />
</div>
</div>
<div class="columns" style="padding-top: 1rem;">
<div class="column is-6">
<span style="border-bottom: 1px solid; width: 100%; display: block;">{{this.lastname}}</span>
<p style="font-size: x-small; display: block;">{{__ "lastname"}}</p>