Compare commits

..

No commits in common. "703eaa0e9d667b628eab4e8496689fe66238f896" and "8a90f63b0919376beefef6a52aae9a59337aea59" have entirely different histories.

7 changed files with 357 additions and 564 deletions

View File

@ -34,7 +34,6 @@ The basic generation mechanism makes the templates and routes interchangeable (i
| EVENT_NAME | String | "Please set the event name" | The event's name - used to generate pdf text. | EVENT_NAME | String | "Please set the event name" | The event's name - used to generate pdf text.
| CURRENCY_SYMBOL | String | "€" | The your currency's symbol - used to generate pdf text. | CURRENCY_SYMBOL | String | "€" | The your currency's symbol - used to generate pdf text.
| SPONSORING_RECEIPT_MINIMUM_AMOUNT | String | "10" | The mimimum total donation amount a sponsor has to donate to be able to receive a donation receipt - used to generate pdf text. | SPONSORING_RECEIPT_MINIMUM_AMOUNT | String | "10" | The mimimum total donation amount a sponsor has to donate to be able to receive a donation receipt - used to generate pdf text.
| SPONOR_LOGOS | Array<String> | Empty png | The sponsor images you want to loop through. You can provide them via http url, local file or base64-encoded image.
## Templates ## Templates
> The document server uses html templates to generate various pdf documents. > The document server uses html templates to generate various pdf documents.

View File

@ -1,265 +1,206 @@
import axios from 'axios'; import axios from 'axios';
import cheerio from "cheerio"; import cheerio from "cheerio";
import fs from "fs"; import fs from "fs";
import Handlebars from 'handlebars'; import Handlebars from 'handlebars';
import i18next from "i18next"; import i18next from "i18next";
import Backend from 'i18next-fs-backend'; import Backend from 'i18next-fs-backend';
import mime from "mime-types"; import mime from "mime-types";
import path from 'path'; import path from 'path';
import { PDFDocument } from 'pdf-lib'; import { PDFDocument } from 'pdf-lib';
import puppeteer from "puppeteer"; import puppeteer from "puppeteer";
import { awaitAsyncHandlebarHelpers, helpers } from './asyncHelpers'; import { awaitAsyncHandlebarHelpers, helpers } from './asyncHelpers';
import { config } from './config'; import { config } from './config';
import { Runner } from './models/Runner'; import { Runner } from './models/Runner';
import { RunnerCard } from './models/RunnerCard'; import { RunnerGroup } from './models/RunnerGroup';
import { RunnerGroup } from './models/RunnerGroup';
/**
/** * This class is responsible for all things pdf creation.
* This class is responsible for all things pdf creation. * This uses the html templates from src/templates.
* This uses the html templates from src/templates. */
*/ export class PdfCreator {
export class PdfCreator { private templateDir = path.join(__dirname, '/templates');
private templateDir = path.join(__dirname, '/templates'); private browser;
private browser; private static interpolations = { eventname: config.eventname, sponsoring_receipt_minimum_amount: config.sponsoring_receipt_minimum_amount, currency_symbol: config.currency_symbol }
private static interpolations = { eventname: config.eventname, sponsoring_receipt_minimum_amount: config.sponsoring_receipt_minimum_amount, currency_symbol: config.currency_symbol }
/**
/** * Main constructor.
* Main constructor. * Initializes i18n(ext), Handlebars and puppeteer.
* Initializes i18n(ext), Handlebars and puppeteer. */
*/ constructor() {
constructor() { this.init();
this.init(); }
}
/**
/** * Main constructor.
* Main constructor. * Initializes i18n(ext), Handlebars and puppeteer.
* Initializes i18n(ext), Handlebars and puppeteer. */
*/ public async init() {
public async init() { const minimal_args = [
const minimal_args = [ '--autoplay-policy=user-gesture-required',
'--autoplay-policy=user-gesture-required', '--disable-background-networking',
'--disable-background-networking', '--disable-background-timer-throttling',
'--disable-background-timer-throttling', '--disable-backgrounding-occluded-windows',
'--disable-backgrounding-occluded-windows', '--disable-breakpad',
'--disable-breakpad', '--disable-client-side-phishing-detection',
'--disable-client-side-phishing-detection', '--disable-component-update',
'--disable-component-update', '--disable-default-apps',
'--disable-default-apps', '--disable-dev-shm-usage',
'--disable-dev-shm-usage', '--disable-domain-reliability',
'--disable-domain-reliability', '--disable-extensions',
'--disable-extensions', '--disable-features=AudioServiceOutOfProcess',
'--disable-features=AudioServiceOutOfProcess', '--disable-hang-monitor',
'--disable-hang-monitor', '--disable-ipc-flooding-protection',
'--disable-ipc-flooding-protection', '--disable-notifications',
'--disable-notifications', '--disable-offer-store-unmasked-wallet-cards',
'--disable-offer-store-unmasked-wallet-cards', '--disable-popup-blocking',
'--disable-popup-blocking', '--disable-print-preview',
'--disable-print-preview', '--disable-prompt-on-repost',
'--disable-prompt-on-repost', '--disable-renderer-backgrounding',
'--disable-renderer-backgrounding', '--disable-speech-api',
'--disable-speech-api', '--disable-sync',
'--disable-sync', '--hide-scrollbars',
'--hide-scrollbars', '--ignore-gpu-blacklist',
'--ignore-gpu-blacklist', '--metrics-recording-only',
'--metrics-recording-only', '--mute-audio',
'--mute-audio', '--no-default-browser-check',
'--no-default-browser-check', '--no-first-run',
'--no-first-run', '--no-pings',
'--no-pings', '--no-zygote',
'--no-zygote', '--password-store=basic',
'--password-store=basic', '--use-gl=swiftshader',
'--use-gl=swiftshader', '--no-sandbox'
'--no-sandbox' ];
]; await i18next
await i18next .use(Backend)
.use(Backend) .init({
.init({ fallbackLng: 'en',
fallbackLng: 'en', lng: 'en',
lng: 'en', backend: {
backend: { loadPath: path.join(__dirname, '/locales/{{lng}}.json')
loadPath: path.join(__dirname, '/locales/{{lng}}.json') }
} });
});
await Handlebars.registerHelper(helpers);
await Handlebars.registerHelper(helpers); await Handlebars.registerHelper('__',
await Handlebars.registerHelper('__', function (str) {
function (str) { return i18next.t(str, PdfCreator.interpolations).toString();
return i18next.t(str, PdfCreator.interpolations).toString(); }
} );
); this.browser = await puppeteer.launch({ headless: true, args: minimal_args });
await Handlebars.registerHelper('--sponsor', }
function (str) {
const index = (parseInt(str) % config.sponor_logos.length); /**
if (isNaN(index)) { * Generate sponsoring contract pdfs.
return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wlseKgAAAABJRU5ErkJggg==" * @param runner The runner you want to generate the contracts for.
} * @param locale The locale used for the contracts (default:en)
return config.sponor_logos[index]; */
} public async generateSponsoringContract(runners: Runner[], locale: string = "en", codeformat: string = config.codeformat): Promise<Buffer> {
); if (runners.length == 1 && Object.keys(runners[0]).length == 0) {
this.browser = await puppeteer.launch({ headless: true, args: minimal_args }); runners[0] = this.generateEmptyRunner();
} }
if (runners.length > 50) {
/** let pdf_promises = new Array<Promise<Buffer>>();
* Generate sponsoring contract pdfs. let i, j;
* @param runner The runner you want to generate the contracts for. for (i = 0, j = runners.length; i < j; i += 50) {
* @param locale The locale used for the contracts (default:en) let chunk = runners.slice(i, i + 50);
*/ pdf_promises.push(this.generateSponsoringContract(chunk, locale));
public async generateSponsoringContract(runners: Runner[], locale: string = "en", codeformat: string = config.codeformat): Promise<Buffer> { }
if (runners.length == 1 && Object.keys(runners[0]).length == 0) { const pdfs = await Promise.all(pdf_promises);
runners[0] = this.generateEmptyRunner(); return await this.mergePdfs(pdfs);
} }
if (runners.length > 50) { await i18next.changeLanguage(locale);
let pdf_promises = new Array<Promise<Buffer>>(); const template_source = fs.readFileSync(`${this.templateDir}/sponsoring_contract.html`, 'utf8');
let i, j; const template = Handlebars.compile(template_source);
for (i = 0, j = runners.length; i < j; i += 50) { let result = template({ runners, codeformat });
let chunk = runners.slice(i, i + 50); result = await awaitAsyncHandlebarHelpers(result);
pdf_promises.push(this.generateSponsoringContract(chunk, locale)); const pdf = await this.renderPdf(result, { format: "A5", landscape: true });
} return pdf
const pdfs = await Promise.all(pdf_promises); }
return await this.mergePdfs(pdfs);
} /**
await i18next.changeLanguage(locale); * Converts all images in html to base64.
const template_source = fs.readFileSync(`${this.templateDir}/sponsoring_contract.html`, 'utf8'); * Works with image files in the template directory or images from urls.
const template = Handlebars.compile(template_source); * @param html The html string whoms images shall get replaced.
let result = template({ runners, codeformat }); */
result = await awaitAsyncHandlebarHelpers(result); public async imgToBase64(html): Promise<string> {
const pdf = await this.renderPdf(result, { format: "A5", landscape: true }); const $ = cheerio.load(html)
return pdf $('img').each(async (index, element) => {
} let imgsrc = $(element).attr("src");
if (imgsrc.startsWith("data:image")) {
/** return;
* Generate runner card pdfs. }
* @param cards The runner cars you want to generate the cards for. const img_type = mime.lookup(imgsrc);
* @param locale The locale used for the cards (default:en)
*/ if (!(img_type.includes("image"))) {
public async generateRunnerCards(cards: RunnerCard[], locale: string = "en", codeformat: string = config.codeformat): Promise<Buffer> { throw new Error("File is not image mime type");
if (cards.length > 10) { }
let pdf_promises = new Array<Promise<Buffer>>();
let i, j; let image;
for (i = 0, j = cards.length; i < j; i += 10) { if (imgsrc.startsWith("http")) {
let chunk = cards.slice(i, i + 10); image = (await axios.get(imgsrc)).data;
pdf_promises.push(this.generateRunnerCards(chunk, locale)); image = Buffer.from(image).toString('base64');
} }
const pdfs = await Promise.all(pdf_promises); else {
return await this.mergePdfs(pdfs); if (imgsrc.startsWith("./")) {
} imgsrc = imgsrc.replace("./", "");
const cards_swapped = this.swapArrayPairs(cards); }
await i18next.changeLanguage(locale); image = fs.readFileSync(`${this.templateDir}/${imgsrc}`, { encoding: "base64" });
const template_source = fs.readFileSync(`${this.templateDir}/runner_card.html`, 'utf8'); }
const template = Handlebars.compile(template_source);
let result = template({ cards, cards_swapped, eventname: "LfK! 2069", codeformat: "qrcode" }) image = `data:${img_type};base64,${image}`
result = await awaitAsyncHandlebarHelpers(result); $(element).attr("src", image)
fs.writeFileSync("lelelelele.tmp", result); })
const pdf = await this.renderPdf(result, { format: "A4", landscape: false });
return pdf return $.html();
} }
/** /**
* Converts all images in html to base64. * This method manages the creation of pdfs via puppeteer.
* Works with image files in the template directory or images from urls. * @param html The HTML that should get rendered.
* @param html The html string whoms images shall get replaced. * @param options Puppeteer PDF option (eg: {format: "A4"})
*/ */
public async imgToBase64(html): Promise<string> { public async renderPdf(html: string, options): Promise<any> {
const $ = cheerio.load(html) html = await this.imgToBase64(html);
$('img').each(async (index, element) => { let page = await this.browser.newPage();
let imgsrc = $(element).attr("src"); await page.setContent(html);
if (imgsrc.startsWith("data:image")) { const pdf = await page.pdf(options);
return; await page.close();
} return pdf;
const img_type = mime.lookup(imgsrc); }
if (!(img_type.includes("image"))) { /**
throw new Error("File is not image mime type"); * Merges multiple pdfs into one.
} * @param pdfs The pdfs you want to merge as an buffer array.
* @returns The merged pdf as a buffer.
let image; */
if (imgsrc.startsWith("http")) { private async mergePdfs(pdfs: Buffer[]): Promise<Buffer> {
image = (await axios.get(imgsrc)).data; const mergedPdf = await PDFDocument.create();
image = Buffer.from(image).toString('base64');
} for (const pdfBuffer of pdfs) {
else { const pdf = await PDFDocument.load(pdfBuffer);
if (imgsrc.startsWith("./")) { const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
imgsrc = imgsrc.replace("./", ""); copiedPages.forEach((page) => {
} mergedPdf.addPage(page);
image = fs.readFileSync(`${this.templateDir}/${imgsrc}`, { encoding: "base64" }); });
} }
image = `data:${img_type};base64,${image}` return <Buffer>(await mergedPdf.save());
$(element).attr("src", image) }
})
/**
return $.html(); * 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.
/** */
* This method manages the creation of pdfs via puppeteer. private generateEmptyRunner(): Runner {
* @param html The HTML that should get rendered. let group = new RunnerGroup();
* @param options Puppeteer PDF option (eg: {format: "A4"}) group.id = 0;
*/ group.name = "";
public async renderPdf(html: string, options): Promise<any> { let runner = new Runner();
html = await this.imgToBase64(html); runner.id = 0;
let page = await this.browser.newPage(); runner.firstname = "";
await page.setContent(html); runner.lastname = "";
const pdf = await page.pdf(options); runner.group = group;
await page.close(); return runner;
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;
}
/**
* Swaps pairs (0/1, 2/3, ...) of elements in an array recursively.
* If the last element has no partner it inserts an empty element at the end and swaps the two
* This is needed to generate pdfs with front- and backside that get printet on one paper.
* @param array The array which's pairs shall get switched.
* @returns Array with swapped pairs,
*/
private swapArrayPairs(array): Array<any> {
if (array.length == 1) {
return [null, array[0]];
}
if (array.length == 0) {
return null;
}
const rest = this.swapArrayPairs(array.slice(2))
if (!rest) {
return [array[1], array[0]]
}
return [array[1], array[0]].concat(rest);
}
} }

File diff suppressed because one or more lines are too long

View File

@ -8,8 +8,7 @@ export const config = {
eventname: process.env.EVENT_NAME || "Please set the event name", eventname: process.env.EVENT_NAME || "Please set the event name",
currency_symbol: process.env.CURRENCY_SYMBOL || "€", currency_symbol: process.env.CURRENCY_SYMBOL || "€",
sponsoring_receipt_minimum_amount: process.env.SPONSORING_RECEIPT_MINIMUM_AMOUNT || "10", sponsoring_receipt_minimum_amount: process.env.SPONSORING_RECEIPT_MINIMUM_AMOUNT || "10",
codeformat: process.env.CODEFORMAT || "qrcode", codeformat: process.env.CODEFORMAT || "qrcode"
sponor_logos: getSponsorLogos()
} }
let errors = 0 let errors = 0
if (typeof config.internal_port !== "number") { if (typeof config.internal_port !== "number") {
@ -18,13 +17,4 @@ if (typeof config.internal_port !== "number") {
if (typeof config.development !== "boolean") { if (typeof config.development !== "boolean") {
errors++ errors++
} }
function getSponsorLogos(): string[] {
try {
const logos = JSON.parse(process.env.SPONOR_LOGOS);
if (!Array.isArray(logos)) { throw new Error("Not an array.") }
return logos;
} catch (error) {
return ["data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wlseKgAAAABJRU5ErkJggg=="];
}
}
export let e = errors export let e = errors

View File

@ -1,46 +1,30 @@
import { Body, JsonController, Post, QueryParam, Res } from 'routing-controllers'; import { Body, JsonController, Post, QueryParam, Res } from 'routing-controllers';
import { OpenAPI } from 'routing-controllers-openapi'; import { OpenAPI } from 'routing-controllers-openapi';
import { Runner } from '../models/Runner'; import { Runner } from '../models/Runner';
import { RunnerCard } from '../models/RunnerCard'; import { PdfCreator } from '../PdfCreator';
import { PdfCreator } from '../PdfCreator';
/**
/** * The pdf controller handels all endpoints concerning pdf generation.
* The pdf controller handels all endpoints concerning pdf generation. * It therefore is the hearth of the document-generation server's endpoints.
* It therefore is the hearth of the document-generation server's endpoints. * All endpoints have to accept a locale query-param to support i18n.
* All endpoints have to accept a locale query-param to support i18n. */
*/ @JsonController()
@JsonController() export class PdfController {
export class PdfController { private pdf: PdfCreator = new PdfCreator();
private pdf: PdfCreator = new PdfCreator(); private initialized: boolean = false;
private initialized: boolean = false;
@Post('/contracts')
@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)." })
@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, @QueryParam("codeformat") codeformat: 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) {
if (!this.initialized) { await this.pdf.init();
await this.pdf.init(); this.initialized = true;
this.initialized = true; }
} if (!Array.isArray(runners)) {
if (!Array.isArray(runners)) { runners = [runners];
runners = [runners]; }
} const contracts = await this.pdf.generateSponsoringContract(runners, locale, codeformat);
const contracts = await this.pdf.generateSponsoringContract(runners, locale, codeformat); res.setHeader('content-type', 'application/pdf');
res.setHeader('content-type', 'application/pdf'); return contracts;
return contracts; }
} }
@Post('/cards')
@OpenAPI({ description: "Generate runner card pdfs from runner card objects.<br>You can choose your prefered locale by passing the 'locale' query-param." })
async generateCards(@Body({ validate: true, options: { limit: "500mb" } }) cards: RunnerCard | RunnerCard[], @Res() res: any, @QueryParam("locale") locale: string) {
if (!this.initialized) {
await this.pdf.init();
this.initialized = true;
}
if (!Array.isArray(cards)) {
cards = [cards];
}
const contracts = await this.pdf.generateRunnerCards(cards, locale);
res.setHeader('content-type', 'application/pdf');
return contracts;
}
}

View File

@ -1,71 +0,0 @@
<html>
<head>
<meta charset="utf8">
<title>Sponsoring contract</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
<style>
.sheet {
margin: 0;
overflow: hidden;
position: relative;
box-sizing: border-box;
page-break-after: always;
padding: 1.2cm 2cm 1.2cm 2cm
}
body.A4 .sheet {
width: 210mm;
height: 296mm
}
.runnercard {
border: 1px solid;
height: 5.5cm;
overflow: hidden;
}
</style>
</head>
<body class="A4 landscape">
<div class="sheet">
<div class="columns is-multiline">
{{#each cards}}
<div class="column is-half runnercard">
<p class="title is-5" style="text-align: center; padding-bottom: 0; margin-top: -0.75rem;">{{../eventname}}</p>
<p style="text-align: center; margin-top: -1.5rem; font-size: small;">lauf-fuer-kaya.de - am 01.01.2021</p>
<p style="font-size: small;">Mit unterstützung von:</p>
<div class="columns" style="height: 6rem; overflow: hidden;">
<div class="column is-two-thirds">
<!--SPONSOR LOGO HERE-->
<img style="vertical-align: revert; margin-top: auto; object-fit: cover; max-height: 2cm;"
src="{{--sponsor this.id}}" />
</div>
<div class="column is-one-third">
<!--BARCODE HERE-->
<img style="vertical-align: revert; margin-top: auto; object-fit: cover; max-height: 2cm;"
src="{{--bc this.id ../codeformat}}" />
</div>
</div>
<p>{{this.runner.lastname}}, {{this.runner.firstname}} {{this.runner.middlename}}</p>
<p>{{this.runner.group.name}}</p>
</div>
{{/each}}
</div>
</div>
<div class="sheet">
<div class="columns is-multiline">
{{#each cards_swapped}}
<div class="column is-half runnercard" style="justify-content: center; align-items: center; text-align: center;">
<!--SPONSOR LOGO FIRST-->
<div style="height: 2cm; padding: 0 0 2.25cm 0">
<img style="object-fit: cover; max-height: 2cm;" src="{{--sponsor this.id}}" />
</div>
<img style="object-fit: cover; max-height: 2.5cm; position: relative;" src="{{--bc this.id ../codeformat}}" />
</div>
{{/each}}
</div>
</div>
</body>
</html>

View File

@ -1,128 +1,79 @@
import axios from "axios" import axios from "axios"
import faker from "faker" import faker from "faker"
import { Runner } from '../models/Runner' import { Runner } from '../models/Runner'
import { RunnerCard } from '../models/RunnerCard' import { RunnerGroup } from '../models/RunnerGroup'
import { RunnerGroup } from '../models/RunnerGroup'
const baseurl = "http://localhost:4010"
const baseurl = "http://localhost:4010"
axios.interceptors.request.use((config) => {
axios.interceptors.request.use((config) => { config.headers['request-startTime'] = process.hrtime()
config.headers['request-startTime'] = process.hrtime() return config
return config })
})
axios.interceptors.response.use((response) => {
axios.interceptors.response.use((response) => { const start = response.config.headers['request-startTime']
const start = response.config.headers['request-startTime'] const end = process.hrtime(start)
const end = process.hrtime(start) const milliseconds = Math.round((end[0] * 1000) + (end[1] / 1000000))
const milliseconds = Math.round((end[0] * 1000) + (end[1] / 1000000)) response.headers['request-duration'] = milliseconds
response.headers['request-duration'] = milliseconds return response
return response })
})
function generateRunners(amount: number): Runner[] {
function generateRunners(amount: number): Runner[] { let runners: Runner[] = new Array<Runner>();
let runners: Runner[] = new Array<Runner>(); let group = new RunnerGroup();
let group = new RunnerGroup(); let runner = new Runner();
let runner = new Runner(); for (var i = 0; i < amount; i++) {
for (var i = 0; i < amount; i++) { group.name = faker.company.bsBuzz();
group.name = faker.company.bsBuzz(); group.id = Math.floor(Math.random() * (9999999 - 1) + 1);
group.id = Math.floor(Math.random() * (9999999 - 1) + 1); runner.firstname = faker.name.firstName();
runner.firstname = faker.name.firstName(); runner.lastname = faker.name.lastName();
runner.lastname = faker.name.lastName(); runner.id = Math.floor(Math.random() * (9999999 - 1) + 1);
runner.id = Math.floor(Math.random() * (9999999 - 1) + 1); runners.push(runner);
runners.push(runner); }
} return runners;
return runners; }
}
async function postContracts(runners: Runner[]): Promise<Measurement> {
function generateCards(amount: number): RunnerCard[] { const res = await axios.post(`${baseurl}/contracts`, runners);
let cards: RunnerCard[] = new Array<RunnerCard>(); return new Measurement("contract", runners.length, parseInt(res.headers['request-duration']))
let card = new RunnerCard(); }
for (let runner of generateRunners(amount)) {
card.id = runner.id; async function testContracts(sizes): Promise<Measurement[]> {
card.code = idToEan13(card.id); let measurements = new Array<Measurement>();
card.runner = runner; console.log("#### Testing contracts ####");
cards.push(card);
} for (let size of sizes) {
return cards; const m = await postContracts(generateRunners(size));
} console.log(m.toString());
measurements.push(m);
function idToEan13(id): string { }
const multiply = [1, 3]; return measurements;
id = id.toString(); }
if (id.length > 12) { async function main() {
throw new Error("id too long"); const sizes = [0, 1, 10, 50, 100, 200, 500, 1000]
} console.log("########### Speedtest ###########");
while (id.length < 12) { id = '0' + id; } console.log(`Document server version (according to the api): ${(await axios.get("http://localhost:4010/version")).data.version}`);
console.log("####### Running tests #######");
let total = 0; const contractResults = await testContracts(sizes);
id.split('').forEach((letter, index) => { console.log("####### Results #######");
total += parseInt(letter, 10) * multiply[index % 2]; console.table(contractResults);
}); }
const checkSum = (Math.ceil(total / 10) * 10) - total;
return id + checkSum.toString(); main();
}
class Measurement {
async function postContracts(runners: Runner[]): Promise<Measurement> { public type: string;
const res = await axios.post(`${baseurl}/contracts`, runners); public inputcount: number;
return new Measurement("contract", runners.length, parseInt(res.headers['request-duration'])) public responsetime: number;
}
constructor(type: string, input: number, time: number) {
async function postCards(cards: RunnerCard[]): Promise<Measurement> { this.type = type;
const res = await axios.post(`${baseurl}/cards`, cards); this.inputcount = input;
return new Measurement("card", cards.length, parseInt(res.headers['request-duration'])) this.responsetime = time;
} }
async function testContracts(sizes): Promise<Measurement[]> { public toString(): string {
let measurements = new Array<Measurement>(); return `It took ${this.responsetime}ms to generate ${this.inputcount} pdfs for the type ${this.type}.`
console.log("#### Testing contracts ####"); }
for (let size of sizes) {
const m = await postContracts(generateRunners(size));
console.log(m.toString());
measurements.push(m);
}
return measurements;
}
async function testCards(sizes): Promise<Measurement[]> {
let measurements = new Array<Measurement>();
console.log("#### Testing Cards ####");
for (let size of sizes) {
const m = await postCards(generateCards(size));
console.log(m.toString());
measurements.push(m);
}
return measurements;
}
async function main() {
const sizes = [0, 1, 10, 50, 100, 200, 500, 1000]
console.log("########### Speedtest ###########");
console.log(`Document server version (according to the api): ${(await axios.get("http://localhost:4010/version")).data.version}`);
console.log("####### Running tests #######");
const contractResults = await testContracts(sizes);
const cardResults = await testCards(sizes);
console.log("####### Results #######");
console.table(contractResults);
console.table(cardResults);
}
main();
class Measurement {
public type: string;
public inputcount: number;
public responsetime: number;
constructor(type: string, input: number, time: number) {
this.type = type;
this.inputcount = input;
this.responsetime = time;
}
public toString(): string {
return `It took ${this.responsetime}ms to generate ${this.inputcount} pdfs for the type ${this.type}.`
}
} }