Generate runner certificates feature/36-runner_certificates #41
No files matched your search
@@ -37,6 +37,7 @@ The basic generation mechanism makes the templates and routes interchangeable (i
|
|||||||
| 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.
|
| 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.
|
||||||
| API_KEY | String(min length: 64) | Random generated string | The api key you want to use for auth (query-param `key`), has to be at least 64 chars long.
|
| API_KEY | String(min length: 64) | Random generated string | The api key you want to use for auth (query-param `key`), has to be at least 64 chars long.
|
||||||
| DISCLAIMER_TEXT | String | N/A | A disclaimer that will get displayed on the bottom of each sponsoring contract. R/N You can only provide the disclaimer for one language.
|
| DISCLAIMER_TEXT | String | N/A | A disclaimer that will get displayed on the bottom of each sponsoring contract. R/N You can only provide the disclaimer for one language.
|
||||||
|
| DONATIONS_FOOTER_TEXT | String | N/A | A text that will get displayed on the bottom of each runner certificate's second page. R/N You can only provide the text for one language.
|
||||||
| CONTRACTS_PER_RUNNER | Number | 1 | The amount of contracts that get created per runner (per request).
|
| CONTRACTS_PER_RUNNER | Number | 1 | The amount of contracts that get created per runner (per request).
|
||||||
|
|
||||||
## Templates
|
## Templates
|
||||||
|
|||||||
+29
-31
@@ -8,9 +8,7 @@
|
|||||||
"build": "rimraf ./dist && tsc && cp-cli ./src/templates ./dist/templates && cp-cli ./src/locales ./dist/locales",
|
"build": "rimraf ./dist && tsc && cp-cli ./src/templates ./dist/templates && cp-cli ./src/locales ./dist/locales",
|
||||||
"licenses:export": "license-exporter --markdown",
|
"licenses:export": "license-exporter --markdown",
|
||||||
"release": "release-it --only-version",
|
"release": "release-it --only-version",
|
||||||
"translations:sort": "node sort_translations.js",
|
"translations:sort": "node sort_translations.js"
|
||||||
"test:speed": "start-server-and-test dev http://localhost:4010/docs/openapi.json test:speed:run",
|
|
||||||
"test:speed:run": "ts-node src/tests/speedtest.ts"
|
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -41,40 +39,40 @@
|
|||||||
],
|
],
|
||||||
"license": "CC-BY-NC-SA-4.0",
|
"license": "CC-BY-NC-SA-4.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@odit/class-validator-jsonschema": "^2.1.1",
|
"@odit/class-validator-jsonschema": "2.1.1",
|
||||||
"async-helpers": "^0.3.17",
|
"async-helpers": "0.3.17",
|
||||||
"axios": "^0.21.1",
|
"axios": "0.21.1",
|
||||||
"bwip-js": "^2.0.12",
|
"bwip-js": "2.1.1",
|
||||||
"cheerio": "^1.0.0-rc.5",
|
"cheerio": "1.0.0-rc.5",
|
||||||
"class-transformer": "0.3.1",
|
"class-transformer": "0.3.1",
|
||||||
"class-validator": "^0.13.1",
|
"class-validator": "0.13.1",
|
||||||
"consola": "^2.15.0",
|
"consola": "2.15.3",
|
||||||
"cors": "^2.8.5",
|
"cors": "2.8.5",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "8.2.0",
|
||||||
"express": "^4.17.1",
|
"express": "4.17.1",
|
||||||
"handlebars": "^4.7.6",
|
"handlebars": "4.7.7",
|
||||||
"i18next": "^19.8.7",
|
"i18next": "20.1.0",
|
||||||
"i18next-fs-backend": "^1.0.8",
|
"i18next-fs-backend": "1.1.1",
|
||||||
"mime-types": "^2.1.28",
|
"mime-types": "2.1.29",
|
||||||
"pdf-lib": "^1.16.0",
|
"pdf-lib": "1.16.0",
|
||||||
"puppeteer": "^7.0.1",
|
"puppeteer": "8.0.0",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "0.1.13",
|
||||||
"routing-controllers": "0.9.0-alpha.6",
|
"routing-controllers": "0.9.0-alpha.6",
|
||||||
"routing-controllers-openapi": "2.2.0"
|
"routing-controllers-openapi": "2.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@odit/license-exporter": "^0.0.10",
|
"@odit/license-exporter": "0.0.11",
|
||||||
"@types/express": "^4.17.11",
|
"@types/express": "4.17.11",
|
||||||
"@types/node": "^14.14.22",
|
"@types/node": "14.14.22",
|
||||||
"@types/puppeteer": "^5.4.3",
|
"@types/puppeteer": "5.4.3",
|
||||||
"cp-cli": "^2.0.0",
|
"cp-cli": "2.0.0",
|
||||||
"faker": "^5.3.1",
|
"faker": "5.3.1",
|
||||||
"nodemon": "^2.0.7",
|
"nodemon": "2.0.7",
|
||||||
"release-it": "^14.2.2",
|
"release-it": "^14.2.2",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "3.0.2",
|
||||||
"start-server-and-test": "^1.12.0",
|
"start-server-and-test": "1.12.0",
|
||||||
"ts-node": "^9.1.1",
|
"ts-node": "9.1.1",
|
||||||
"typescript": "^4.1.3"
|
"typescript": "4.1.3"
|
||||||
},
|
},
|
||||||
"release-it": {
|
"release-it": {
|
||||||
"git": {
|
"git": {
|
||||||
|
|||||||
+41
-3
@@ -10,6 +10,7 @@ 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 { CertificateRunner } from './models/CertificateRunner';
|
||||||
import { Runner } from './models/Runner';
|
import { Runner } from './models/Runner';
|
||||||
import { RunnerCard } from './models/RunnerCard';
|
import { RunnerCard } from './models/RunnerCard';
|
||||||
import { RunnerGroup } from './models/RunnerGroup';
|
import { RunnerGroup } from './models/RunnerGroup';
|
||||||
@@ -97,6 +98,18 @@ export class PdfCreator {
|
|||||||
return config.sponor_logos[index];
|
return config.sponor_logos[index];
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
await Handlebars.registerHelper('--format_kilometers',
|
||||||
|
function (str) {
|
||||||
|
let meters = parseInt(str);
|
||||||
|
return ((meters / 1000).toFixed(3).toString())
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await Handlebars.registerHelper('--format_currency',
|
||||||
|
function (str) {
|
||||||
|
let meters = parseInt(str);
|
||||||
|
return ((meters / 100).toFixed(2).toString())
|
||||||
|
}
|
||||||
|
);
|
||||||
this.browser = await puppeteer.launch({ headless: true, args: minimal_args });
|
this.browser = await puppeteer.launch({ headless: true, args: minimal_args });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,13 +166,37 @@ export class PdfCreator {
|
|||||||
await i18next.changeLanguage(locale);
|
await i18next.changeLanguage(locale);
|
||||||
const template_source = fs.readFileSync(`${this.templateDir}/runner_card.html`, 'utf8');
|
const template_source = fs.readFileSync(`${this.templateDir}/runner_card.html`, 'utf8');
|
||||||
const template = Handlebars.compile(template_source);
|
const template = Handlebars.compile(template_source);
|
||||||
let result = template({ cards, cards_swapped, eventname: "LfK! 2069", codeformat: "qrcode" })
|
let result = template({ cards, cards_swapped, eventname: config.eventname, codeformat: codeformat })
|
||||||
result = await awaitAsyncHandlebarHelpers(result);
|
result = await awaitAsyncHandlebarHelpers(result);
|
||||||
fs.writeFileSync("lelelelele.tmp", result);
|
|
||||||
const pdf = await this.renderPdf(result, { format: "A4", landscape: false });
|
const pdf = await this.renderPdf(result, { format: "A4", landscape: false });
|
||||||
return pdf
|
return pdf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 generateRunnerCertficates(runners: CertificateRunner[], locale: string = "en"): Promise<Buffer> {
|
||||||
|
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.generateRunnerCertficates(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}/runner_certificate.html`, 'utf8');
|
||||||
|
const template = Handlebars.compile(template_source);
|
||||||
|
let result = template({ runners, eventname: config.eventname, currency_symbol: config.currency_symbol, donations_footer_text: config.donations_footer_text });
|
||||||
|
result = await awaitAsyncHandlebarHelpers(result);
|
||||||
|
const pdf = await this.renderPdf(result, { format: "A4", landscape: false, printBackground: true });
|
||||||
|
return pdf;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts all images in html to base64.
|
* Converts all images in html to base64.
|
||||||
* Works with image files in the template directory or images from urls.
|
* Works with image files in the template directory or images from urls.
|
||||||
@@ -167,6 +204,7 @@ export class PdfCreator {
|
|||||||
*/
|
*/
|
||||||
public async imgToBase64(html): Promise<string> {
|
public async imgToBase64(html): Promise<string> {
|
||||||
const $ = cheerio.load(html)
|
const $ = cheerio.load(html)
|
||||||
|
|
||||||
$('img').each(async (index, element) => {
|
$('img').each(async (index, element) => {
|
||||||
let imgsrc = $(element).attr("src");
|
let imgsrc = $(element).attr("src");
|
||||||
if (imgsrc.startsWith("data:image")) {
|
if (imgsrc.startsWith("data:image")) {
|
||||||
@@ -192,7 +230,7 @@ export class PdfCreator {
|
|||||||
|
|
||||||
image = `data:${img_type};base64,${image}`
|
image = `data:${img_type};base64,${image}`
|
||||||
$(element).attr("src", image)
|
$(element).attr("src", image)
|
||||||
})
|
});
|
||||||
|
|
||||||
return $.html();
|
return $.html();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export const config = {
|
|||||||
sponor_logos: getSponsorLogos(),
|
sponor_logos: getSponsorLogos(),
|
||||||
api_key: getApiKey(),
|
api_key: getApiKey(),
|
||||||
disclaimer_text: process.env.DISCLAIMER_TEXT || "",
|
disclaimer_text: process.env.DISCLAIMER_TEXT || "",
|
||||||
|
donations_footer_text: process.env.DONATIONS_FOOTER_TEXT || "",
|
||||||
contracts_per_runner: parseInt(process.env.CONTRACTS_PER_RUNNER) || 1,
|
contracts_per_runner: parseInt(process.env.CONTRACTS_PER_RUNNER) || 1,
|
||||||
}
|
}
|
||||||
let errors = 0
|
let errors = 0
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Authorized, Body, JsonController, Post, QueryParam, Res } from 'routing-controllers';
|
import { Authorized, Body, JsonController, Post, QueryParam, Res } from 'routing-controllers';
|
||||||
import { OpenAPI } from 'routing-controllers-openapi';
|
import { OpenAPI } from 'routing-controllers-openapi';
|
||||||
|
import { CertificateRunner } from '../models/CertificateRunner';
|
||||||
import { Runner } from '../models/Runner';
|
import { Runner } from '../models/Runner';
|
||||||
import { RunnerCard } from '../models/RunnerCard';
|
import { RunnerCard } from '../models/RunnerCard';
|
||||||
import { PdfCreator } from '../PdfCreator';
|
import { PdfCreator } from '../PdfCreator';
|
||||||
@@ -54,6 +55,25 @@ export class PdfController {
|
|||||||
return contracts;
|
return contracts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('/certificates')
|
||||||
|
@OpenAPI({ description: "Generate runner certificate pdfs from certificate 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 generateCertificates(@Body({ validate: true, options: { limit: "500mb" } }) runners: CertificateRunner[], @Res() res: any, @QueryParam("locale") locale: string, @QueryParam("download") download: boolean) {
|
||||||
|
if (!this.initialized) {
|
||||||
|
await this.pdf.init();
|
||||||
|
this.initialized = true;
|
||||||
|
}
|
||||||
|
if (!Array.isArray(runners)) {
|
||||||
|
runners = [runners];
|
||||||
|
}
|
||||||
|
runners = this.mapCertificatRunnersGroupNames(runners)
|
||||||
|
const certificates = await this.pdf.generateRunnerCertficates(runners, locale);
|
||||||
|
res.setHeader('content-type', 'application/pdf');
|
||||||
|
if (download) {
|
||||||
|
res.setHeader('Content-Disposition', 'attachment; filename="certificates.pdf"')
|
||||||
|
}
|
||||||
|
return certificates;
|
||||||
|
}
|
||||||
|
|
||||||
private mapRunnerGroupNames(runners: Runner[]): Runner[] {
|
private mapRunnerGroupNames(runners: Runner[]): Runner[] {
|
||||||
let response = new Array<Runner>();
|
let response = new Array<Runner>();
|
||||||
for (let runner of runners) {
|
for (let runner of runners) {
|
||||||
@@ -68,6 +88,26 @@ export class PdfController {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private mapCertificatRunnersGroupNames(runners: CertificateRunner[]): CertificateRunner[] {
|
||||||
|
let response = new Array<CertificateRunner>();
|
||||||
|
for (let runner of runners) {
|
||||||
|
if (!runner.group.parentGroup) {
|
||||||
|
runner.group.fullName = runner.group.name;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
runner.group.fullName = `${runner.group.parentGroup.name}/${runner.group.name}`;
|
||||||
|
}
|
||||||
|
runner.donationPerDistanceTotal = runner.distanceDonations.reduce(function (sum, current) {
|
||||||
|
return sum + current.amountPerDistance;
|
||||||
|
}, 0);
|
||||||
|
runner.donationTotal = runner.distanceDonations.reduce(function (sum, current) {
|
||||||
|
return sum + current.amount;
|
||||||
|
}, 0);
|
||||||
|
response.push(runner)
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
private mapCardGroupNames(cards: RunnerCard[]): RunnerCard[] {
|
private mapCardGroupNames(cards: RunnerCard[]): RunnerCard[] {
|
||||||
let response = new Array<RunnerCard>();
|
let response = new Array<RunnerCard>();
|
||||||
for (let card of cards) {
|
for (let card of cards) {
|
||||||
|
|||||||
+9
-1
@@ -1,9 +1,14 @@
|
|||||||
{
|
{
|
||||||
"address": "Adresse",
|
"address": "Adresse",
|
||||||
|
"betrag-km": "Betrag/KM",
|
||||||
"city": "Stadt",
|
"city": "Stadt",
|
||||||
"date": "Datum",
|
"date": "Datum",
|
||||||
"firstname": "Vorname",
|
"firstname": "Vorname",
|
||||||
|
"fuer-den-guten-zweck-zurueckgelegt": "für den guten Zweck zurückgelegt",
|
||||||
|
"gesamt": "Gesamt",
|
||||||
|
"gesamtbetrag": "Gesamtbetrag",
|
||||||
"group": "Team/Klasse",
|
"group": "Team/Klasse",
|
||||||
|
"hat-beim-eventname": "Hat beim {{eventname}}",
|
||||||
"house_number": "Hausnummer",
|
"house_number": "Hausnummer",
|
||||||
"id": "ID",
|
"id": "ID",
|
||||||
"lastname": "Nachname",
|
"lastname": "Nachname",
|
||||||
@@ -12,9 +17,12 @@
|
|||||||
"postalcode": "Postleitzahl",
|
"postalcode": "Postleitzahl",
|
||||||
"signature": "Unterschrift",
|
"signature": "Unterschrift",
|
||||||
"sponsor": "Sponsor",
|
"sponsor": "Sponsor",
|
||||||
|
"sponsor-in": "Sponsor:in",
|
||||||
"sponsoring_address_condition": "Muss ausgefüllt werden, wenn Sie eine Spendenquittung benötigen - Spendenquittungen können erst ab einem Gesamtbetrag von {{sponsoring_receipt_minimum_amount}}{{currency_symbol}} ausgestellt werden",
|
"sponsoring_address_condition": "Muss ausgefüllt werden, wenn Sie eine Spendenquittung benötigen - Spendenquittungen können erst ab einem Gesamtbetrag von {{sponsoring_receipt_minimum_amount}}{{currency_symbol}} ausgestellt werden",
|
||||||
"sponsoring_amount_per_distance": "mit einem Betrag von _____{{currency_symbol}} pro gelaufenem Kilometer zu unterstützen.",
|
"sponsoring_amount_per_distance": "mit einem Betrag von _____{{currency_symbol}} pro gelaufenem Kilometer zu unterstützen.",
|
||||||
"sponsoring_subtitle": "Ich/Wir sind bereit anlässlich des {{eventname}}",
|
"sponsoring_subtitle": "Ich/Wir sind bereit anlässlich des {{eventname}}",
|
||||||
"sponsoring_title": "Sponsoringerklärung",
|
"sponsoring_title": "Sponsoringerklärung",
|
||||||
"street": "Straße"
|
"sponsorings": "Sponsorings",
|
||||||
|
"street": "Straße",
|
||||||
|
"urkunde": "Urkunde"
|
||||||
}
|
}
|
||||||
+10
-1
@@ -1,19 +1,28 @@
|
|||||||
{
|
{
|
||||||
"address": "Address",
|
"address": "Address",
|
||||||
|
"betrag-km": "Amount/KM",
|
||||||
"city": "City",
|
"city": "City",
|
||||||
"date": "date",
|
"date": "date",
|
||||||
"firstname": "First name",
|
"firstname": "First name",
|
||||||
|
"fuer-den-guten-zweck-zurueckgelegt": "for our good cuse at the {{eventname}}",
|
||||||
|
"gesamt": "Combined",
|
||||||
|
"gesamtbetrag": "Total",
|
||||||
"group": "Team/class",
|
"group": "Team/class",
|
||||||
|
"hat-beim-eventname": "Ran",
|
||||||
"house_number": "House number",
|
"house_number": "House number",
|
||||||
|
"id": "ID",
|
||||||
"lastname": "Last name",
|
"lastname": "Last name",
|
||||||
"location": "Location",
|
"location": "Location",
|
||||||
"please_use_blockletters": "Please write in BLOCK LETTERS.",
|
"please_use_blockletters": "Please write in BLOCK LETTERS.",
|
||||||
"postalcode": "Postal code",
|
"postalcode": "Postal code",
|
||||||
"signature": "Signature",
|
"signature": "Signature",
|
||||||
"sponsor": "sponsor",
|
"sponsor": "sponsor",
|
||||||
|
"sponsor-in": "Donor",
|
||||||
"sponsoring_address_condition": "You have to provide an address if you want a donation receipt - Donation receipts can't be issued for total donation amounts under {{sponsoring_receipt_minimum_amount}}{{currency_symbol}}",
|
"sponsoring_address_condition": "You have to provide an address if you want a donation receipt - Donation receipts can't be issued for total donation amounts under {{sponsoring_receipt_minimum_amount}}{{currency_symbol}}",
|
||||||
"sponsoring_amount_per_distance": "with the amount of _____{{currency_symbol}} per kilometer run.",
|
"sponsoring_amount_per_distance": "with the amount of _____{{currency_symbol}} per kilometer run.",
|
||||||
"sponsoring_subtitle": "On the ocation of the {{eventname}} I/We want to support",
|
"sponsoring_subtitle": "On the ocation of the {{eventname}} I/We want to support",
|
||||||
"sponsoring_title": "Sponsoring contract",
|
"sponsoring_title": "Sponsoring contract",
|
||||||
"street": "Street"
|
"sponsorings": "Donations",
|
||||||
|
"street": "Street",
|
||||||
|
"urkunde": "Certifcate"
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
IsArray
|
IsArray, IsNumber, IsOptional
|
||||||
} from "class-validator";
|
} from "class-validator";
|
||||||
import { DistanceDonation } from './DistanceDonation';
|
import { DistanceDonation } from './DistanceDonation';
|
||||||
import { Runner } from './Runner';
|
import { Runner } from './Runner';
|
||||||
@@ -13,4 +13,13 @@ export class CertificateRunner extends Runner {
|
|||||||
*/
|
*/
|
||||||
@IsArray()
|
@IsArray()
|
||||||
distanceDonations: DistanceDonation[];
|
distanceDonations: DistanceDonation[];
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
@IsOptional()
|
||||||
|
donationPerDistanceTotal?: number = 0;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
@IsOptional()
|
||||||
|
donationTotal?: number = 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
@@ -0,0 +1,102 @@
|
|||||||
|
<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;
|
||||||
|
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAqsAAASrCAYAAAC1y/HLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDIxLTAzLTMxVDEzOjUzOjUyKzAyOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyMS0wMy0zMVQxNzowMzo1OCswMjowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyMS0wMy0zMVQxNzowMzo1OCswMjowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2MjMzNzM0My1jYjBj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 truncated
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 11cm;
|
||||||
|
background-position: 5cm 5cm;
|
||||||
|
color: #000;
|
||||||
|
/* border-style: solid; */
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
p {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.A4 .sheet {
|
||||||
|
width: 210mm;
|
||||||
|
height: 296mm
|
||||||
|
}
|
||||||
|
|
||||||
|
.certificate-footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0cm;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
border: 1px solid black;
|
||||||
|
font-size: large;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="A4 landscape">
|
||||||
|
{{#each runners}}
|
||||||
|
<article class="sheet">
|
||||||
|
<header class="content has-text-centered">
|
||||||
|
<p style="font-size: 3cm; font-weight: bold;">{{__ "urkunde"}}</p>
|
||||||
|
</header>
|
||||||
|
<main class="content has-text-centered" style="padding-top: 3cm;">
|
||||||
|
<p style="width: 50%; font-size: 4vw; font-weight: bold; margin-bottom: 0; display: inline;">{{this.firstname}}
|
||||||
|
{{this.middlename}} {{this.lastname}}
|
||||||
|
</p>
|
||||||
|
<p style="font-size: 1cm; margin-bottom: 0;">{{__ "hat-beim-eventname"}}</p>
|
||||||
|
<p style="font-size: 2cm; font-weight: bold; margin-bottom: 0;">{{--format_kilometers this.distance}}km</p>
|
||||||
|
<p style="font-size: 1cm;">{{__ "fuer-den-guten-zweck-zurueckgelegt"}}</p>
|
||||||
|
</main>
|
||||||
|
<footer class="certificate-footer">
|
||||||
|
<img src="./certificate_footer.png">
|
||||||
|
</footer>
|
||||||
|
</article>
|
||||||
|
<article class="sheet">
|
||||||
|
<header class="content has-text-centered">
|
||||||
|
<p style="font-size: 2.5cm; font-weight: bold;">{{__ "sponsorings"}}</p>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<table style="border: solid; width: 17cm;">
|
||||||
|
<thead>
|
||||||
|
<td class=".td-head">{{__ "sponsor-in"}}</td>
|
||||||
|
<td>{{__ "betrag-km"}}</td>
|
||||||
|
<td>{{__ "gesamtbetrag"}}</td>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{#each this.distanceDonations}}
|
||||||
|
<tr>
|
||||||
|
<td>{{this.donor.firstname}} {{this.donor.middlename}} {{this.donor.lastname}}</td>
|
||||||
|
<td>{{--format_currency this.amountPerDistance}} {{../../currency_symbol}}</td>
|
||||||
|
<td>{{--format_currency this.amount}} {{../../currency_symbol}}</td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<td>{{__ "gesamt"}}</td>
|
||||||
|
<td>{{--format_currency this.donationPerDistanceTotal}} {{../currency_symbol}}</td>
|
||||||
|
<td>{{--format_currency this.donationTotal}} {{../currency_symbol}}</td>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</main>
|
||||||
|
<footer class="certificate-footer">
|
||||||
|
<p>
|
||||||
|
{{../donations_footer_text}}
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
</article>
|
||||||
|
{{/each}}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
+57
-3
@@ -1,10 +1,15 @@
|
|||||||
import axios from "axios"
|
import axios from "axios"
|
||||||
import faker from "faker"
|
import faker from "faker"
|
||||||
|
import { config } from '../config'
|
||||||
|
import { CertificateRunner } from '../models/CertificateRunner'
|
||||||
|
import { DistanceDonation } from '../models/DistanceDonation'
|
||||||
|
import { Donor } from '../models/Donor'
|
||||||
import { Runner } from '../models/Runner'
|
import { Runner } from '../models/Runner'
|
||||||
import { RunnerCard } from '../models/RunnerCard'
|
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"
|
||||||
|
const key = config.api_key;
|
||||||
|
|
||||||
axios.interceptors.request.use((config) => {
|
axios.interceptors.request.use((config) => {
|
||||||
config.headers['request-startTime'] = process.hrtime()
|
config.headers['request-startTime'] = process.hrtime()
|
||||||
@@ -46,6 +51,36 @@ function generateCards(amount: number): RunnerCard[] {
|
|||||||
return cards;
|
return cards;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generateCertificateRunners(amount: number): CertificateRunner[] {
|
||||||
|
let runners: CertificateRunner[] = new Array<CertificateRunner>();
|
||||||
|
let group = new RunnerGroup();
|
||||||
|
let runner = new CertificateRunner();
|
||||||
|
let donor = new Donor();
|
||||||
|
let donation = new DistanceDonation();
|
||||||
|
for (var i = 0; i < amount; i++) {
|
||||||
|
group.name = faker.company.bsBuzz();
|
||||||
|
group.id = Math.floor(Math.random() * (9999999 - 1) + 1);
|
||||||
|
|
||||||
|
donor.firstname = faker.name.firstName();
|
||||||
|
donor.lastname = faker.name.lastName();
|
||||||
|
donor.id = Math.floor(Math.random() * (9999999 - 1) + 1);
|
||||||
|
|
||||||
|
runner.firstname = faker.name.firstName();
|
||||||
|
runner.lastname = faker.name.lastName();
|
||||||
|
runner.id = Math.floor(Math.random() * (9999999 - 1) + 1);
|
||||||
|
runner.distance = Math.floor(Math.random() * (9999999 - 1) + 1);
|
||||||
|
|
||||||
|
donation.id = Math.floor(Math.random() * (9999999 - 1) + 1);
|
||||||
|
donation.donor = donor;
|
||||||
|
donation.runner = runner;
|
||||||
|
donation.amountPerDistance = Math.floor(Math.random() * (10000 - 1) + 1);
|
||||||
|
|
||||||
|
runner.distanceDonations = [donation, donation]
|
||||||
|
runners.push(runner);
|
||||||
|
}
|
||||||
|
return runners;
|
||||||
|
}
|
||||||
|
|
||||||
function idToEan13(id): string {
|
function idToEan13(id): string {
|
||||||
const multiply = [1, 3];
|
const multiply = [1, 3];
|
||||||
id = id.toString();
|
id = id.toString();
|
||||||
@@ -64,15 +99,20 @@ function idToEan13(id): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function postContracts(runners: Runner[]): Promise<Measurement> {
|
async function postContracts(runners: Runner[]): Promise<Measurement> {
|
||||||
const res = await axios.post(`${baseurl}/contracts`, runners);
|
const res = await axios.post(`${baseurl}/contracts?key=${key}`, runners);
|
||||||
return new Measurement("contract", runners.length, parseInt(res.headers['request-duration']))
|
return new Measurement("contract", runners.length, parseInt(res.headers['request-duration']))
|
||||||
}
|
}
|
||||||
|
|
||||||
async function postCards(cards: RunnerCard[]): Promise<Measurement> {
|
async function postCards(cards: RunnerCard[]): Promise<Measurement> {
|
||||||
const res = await axios.post(`${baseurl}/cards`, cards);
|
const res = await axios.post(`${baseurl}/cards?key=${key}`, cards);
|
||||||
return new Measurement("card", cards.length, parseInt(res.headers['request-duration']))
|
return new Measurement("card", cards.length, parseInt(res.headers['request-duration']))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function postCertificates(runners: CertificateRunner[]): Promise<Measurement> {
|
||||||
|
const res = await axios.post(`${baseurl}/certificates?key=${key}`, runners);
|
||||||
|
return new Measurement("certificate", runners.length, parseInt(res.headers['request-duration']))
|
||||||
|
}
|
||||||
|
|
||||||
async function testContracts(sizes): Promise<Measurement[]> {
|
async function testContracts(sizes): Promise<Measurement[]> {
|
||||||
let measurements = new Array<Measurement>();
|
let measurements = new Array<Measurement>();
|
||||||
console.log("#### Testing contracts ####");
|
console.log("#### Testing contracts ####");
|
||||||
@@ -97,16 +137,30 @@ async function testCards(sizes): Promise<Measurement[]> {
|
|||||||
return measurements;
|
return measurements;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function testCertificates(sizes): Promise<Measurement[]> {
|
||||||
|
let measurements = new Array<Measurement>();
|
||||||
|
console.log("#### Testing Certificates ####");
|
||||||
|
|
||||||
|
for (let size of sizes) {
|
||||||
|
const m = await postCertificates(generateCertificateRunners(size));
|
||||||
|
console.log(m.toString());
|
||||||
|
measurements.push(m);
|
||||||
|
}
|
||||||
|
return measurements;
|
||||||
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const sizes = [0, 1, 10, 50, 100, 200, 500, 1000]
|
const sizes = [1, 10, 50, 100]
|
||||||
console.log("########### Speedtest ###########");
|
console.log("########### Speedtest ###########");
|
||||||
console.log(`Document server version (according to the api): ${(await axios.get("http://localhost:4010/version")).data.version}`);
|
console.log(`Document server version (according to the api): ${(await axios.get("http://localhost:4010/version")).data.version}`);
|
||||||
console.log("####### Running tests #######");
|
console.log("####### Running tests #######");
|
||||||
const contractResults = await testContracts(sizes);
|
const contractResults = await testContracts(sizes);
|
||||||
const cardResults = await testCards(sizes);
|
const cardResults = await testCards(sizes);
|
||||||
|
const certificateResults = await testCertificates(sizes);
|
||||||
console.log("####### Results #######");
|
console.log("####### Results #######");
|
||||||
console.table(contractResults);
|
console.table(contractResults);
|
||||||
console.table(cardResults);
|
console.table(cardResults);
|
||||||
|
console.table(certificateResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
main();
|
main();
|
||||||
|
|||||||
Reference in new issue
Block a user