Merge pull request 'Barcode generation feature/13-barcode_generation' (#21) from feature/13-barcode_generation into dev
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is failing
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	continuous-integration/drone/push Build is failing
				
			Reviewed-on: #21
This commit was merged in pull request #21.
	This commit is contained in:
		@@ -40,7 +40,9 @@
 | 
				
			|||||||
  "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",
 | 
				
			||||||
    "axios": "^0.21.1",
 | 
					    "axios": "^0.21.1",
 | 
				
			||||||
 | 
					    "bwip-js": "^2.0.12",
 | 
				
			||||||
    "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",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,199 +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 { config } from './config';
 | 
					import { awaitAsyncHandlebarHelpers, helpers } from './asyncHelpers';
 | 
				
			||||||
import { Runner } from './models/Runner';
 | 
					import { config } from './config';
 | 
				
			||||||
import { RunnerGroup } from './models/RunnerGroup';
 | 
					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.
 | 
					/**
 | 
				
			||||||
 */
 | 
					 * This class is responsible for all things pdf creation.
 | 
				
			||||||
export class PdfCreator {
 | 
					 * This uses the html templates from src/templates.
 | 
				
			||||||
    private templateDir = path.join(__dirname, '/templates');
 | 
					 */
 | 
				
			||||||
    private browser;
 | 
					export class PdfCreator {
 | 
				
			||||||
    private static interpolations = { eventname: config.eventname, sponsoring_receipt_minimum_amount: config.sponsoring_receipt_minimum_amount, currency_symbol: config.currency_symbol }
 | 
					    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.
 | 
					    /**
 | 
				
			||||||
     */
 | 
					     * Main constructor.
 | 
				
			||||||
    constructor() {
 | 
					     * Initializes i18n(ext), Handlebars and puppeteer.
 | 
				
			||||||
        this.init();
 | 
					     */
 | 
				
			||||||
    }
 | 
					    constructor() {
 | 
				
			||||||
 | 
					        this.init();
 | 
				
			||||||
    /**
 | 
					    }
 | 
				
			||||||
     * Main constructor.
 | 
					
 | 
				
			||||||
     * Initializes i18n(ext), Handlebars and puppeteer.
 | 
					    /**
 | 
				
			||||||
     */
 | 
					     * Main constructor.
 | 
				
			||||||
    public async init() {
 | 
					     * Initializes i18n(ext), Handlebars and puppeteer.
 | 
				
			||||||
        const minimal_args = [
 | 
					     */
 | 
				
			||||||
            '--autoplay-policy=user-gesture-required',
 | 
					    public async init() {
 | 
				
			||||||
            '--disable-background-networking',
 | 
					        const minimal_args = [
 | 
				
			||||||
            '--disable-background-timer-throttling',
 | 
					            '--autoplay-policy=user-gesture-required',
 | 
				
			||||||
            '--disable-backgrounding-occluded-windows',
 | 
					            '--disable-background-networking',
 | 
				
			||||||
            '--disable-breakpad',
 | 
					            '--disable-background-timer-throttling',
 | 
				
			||||||
            '--disable-client-side-phishing-detection',
 | 
					            '--disable-backgrounding-occluded-windows',
 | 
				
			||||||
            '--disable-component-update',
 | 
					            '--disable-breakpad',
 | 
				
			||||||
            '--disable-default-apps',
 | 
					            '--disable-client-side-phishing-detection',
 | 
				
			||||||
            '--disable-dev-shm-usage',
 | 
					            '--disable-component-update',
 | 
				
			||||||
            '--disable-domain-reliability',
 | 
					            '--disable-default-apps',
 | 
				
			||||||
            '--disable-extensions',
 | 
					            '--disable-dev-shm-usage',
 | 
				
			||||||
            '--disable-features=AudioServiceOutOfProcess',
 | 
					            '--disable-domain-reliability',
 | 
				
			||||||
            '--disable-hang-monitor',
 | 
					            '--disable-extensions',
 | 
				
			||||||
            '--disable-ipc-flooding-protection',
 | 
					            '--disable-features=AudioServiceOutOfProcess',
 | 
				
			||||||
            '--disable-notifications',
 | 
					            '--disable-hang-monitor',
 | 
				
			||||||
            '--disable-offer-store-unmasked-wallet-cards',
 | 
					            '--disable-ipc-flooding-protection',
 | 
				
			||||||
            '--disable-popup-blocking',
 | 
					            '--disable-notifications',
 | 
				
			||||||
            '--disable-print-preview',
 | 
					            '--disable-offer-store-unmasked-wallet-cards',
 | 
				
			||||||
            '--disable-prompt-on-repost',
 | 
					            '--disable-popup-blocking',
 | 
				
			||||||
            '--disable-renderer-backgrounding',
 | 
					            '--disable-print-preview',
 | 
				
			||||||
            '--disable-speech-api',
 | 
					            '--disable-prompt-on-repost',
 | 
				
			||||||
            '--disable-sync',
 | 
					            '--disable-renderer-backgrounding',
 | 
				
			||||||
            '--hide-scrollbars',
 | 
					            '--disable-speech-api',
 | 
				
			||||||
            '--ignore-gpu-blacklist',
 | 
					            '--disable-sync',
 | 
				
			||||||
            '--metrics-recording-only',
 | 
					            '--hide-scrollbars',
 | 
				
			||||||
            '--mute-audio',
 | 
					            '--ignore-gpu-blacklist',
 | 
				
			||||||
            '--no-default-browser-check',
 | 
					            '--metrics-recording-only',
 | 
				
			||||||
            '--no-first-run',
 | 
					            '--mute-audio',
 | 
				
			||||||
            '--no-pings',
 | 
					            '--no-default-browser-check',
 | 
				
			||||||
            '--no-zygote',
 | 
					            '--no-first-run',
 | 
				
			||||||
            '--password-store=basic',
 | 
					            '--no-pings',
 | 
				
			||||||
            '--use-gl=swiftshader',
 | 
					            '--no-zygote',
 | 
				
			||||||
            '--no-sandbox'
 | 
					            '--password-store=basic',
 | 
				
			||||||
        ];
 | 
					            '--use-gl=swiftshader',
 | 
				
			||||||
 | 
					            '--no-sandbox'
 | 
				
			||||||
        await i18next
 | 
					        ];
 | 
				
			||||||
            .use(Backend)
 | 
					        await i18next
 | 
				
			||||||
            .init({
 | 
					            .use(Backend)
 | 
				
			||||||
                fallbackLng: 'en',
 | 
					            .init({
 | 
				
			||||||
                lng: 'en',
 | 
					                fallbackLng: 'en',
 | 
				
			||||||
                backend: {
 | 
					                lng: 'en',
 | 
				
			||||||
                    loadPath: path.join(__dirname, '/locales/{{lng}}.json')
 | 
					                backend: {
 | 
				
			||||||
                }
 | 
					                    loadPath: path.join(__dirname, '/locales/{{lng}}.json')
 | 
				
			||||||
            });
 | 
					                }
 | 
				
			||||||
        await Handlebars.registerHelper('__',
 | 
					            });
 | 
				
			||||||
            function (str) {
 | 
					
 | 
				
			||||||
                return i18next.t(str, PdfCreator.interpolations).toString();
 | 
					        await Handlebars.registerHelper(helpers);
 | 
				
			||||||
            }
 | 
					        await Handlebars.registerHelper('__',
 | 
				
			||||||
        );
 | 
					            function (str) {
 | 
				
			||||||
        this.browser = await puppeteer.launch({ headless: true, args: minimal_args });
 | 
					                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)
 | 
					    /**
 | 
				
			||||||
     */
 | 
					     * Generate sponsoring contract pdfs.
 | 
				
			||||||
    public async generateSponsoringContract(runners: Runner[], locale: string = "en"): Promise<Buffer> {
 | 
					     * @param runner The runner you want to generate the contracts for.
 | 
				
			||||||
        if (runners.length == 1 && Object.keys(runners[0]).length == 0) {
 | 
					     * @param locale The locale used for the contracts (default:en)
 | 
				
			||||||
            runners[0] = this.generateEmptyRunner();
 | 
					     */
 | 
				
			||||||
        }
 | 
					    public async generateSponsoringContract(runners: Runner[], locale: string = "en", codeformat: string = config.codeformat): Promise<Buffer> {
 | 
				
			||||||
        if (runners.length > 50) {
 | 
					        if (runners.length == 1 && Object.keys(runners[0]).length == 0) {
 | 
				
			||||||
            let pdf_promises = new Array<Promise<Buffer>>();
 | 
					            runners[0] = this.generateEmptyRunner();
 | 
				
			||||||
            let i, j;
 | 
					        }
 | 
				
			||||||
            for (i = 0, j = runners.length; i < j; i += 50) {
 | 
					        if (runners.length > 50) {
 | 
				
			||||||
                let chunk = runners.slice(i, i + 50);
 | 
					            let pdf_promises = new Array<Promise<Buffer>>();
 | 
				
			||||||
                pdf_promises.push(this.generateSponsoringContract(chunk, locale));
 | 
					            let i, j;
 | 
				
			||||||
            }
 | 
					            for (i = 0, j = runners.length; i < j; i += 50) {
 | 
				
			||||||
            const pdfs = await Promise.all(pdf_promises);
 | 
					                let chunk = runners.slice(i, i + 50);
 | 
				
			||||||
            return await this.mergePdfs(pdfs);
 | 
					                pdf_promises.push(this.generateSponsoringContract(chunk, locale));
 | 
				
			||||||
        }
 | 
					            }
 | 
				
			||||||
        await i18next.changeLanguage(locale);
 | 
					            const pdfs = await Promise.all(pdf_promises);
 | 
				
			||||||
        const template_source = fs.readFileSync(`${this.templateDir}/sponsoring_contract.html`, 'utf8');
 | 
					            return await this.mergePdfs(pdfs);
 | 
				
			||||||
        const template = Handlebars.compile(template_source);
 | 
					        }
 | 
				
			||||||
        const result = template({ runners })
 | 
					        await i18next.changeLanguage(locale);
 | 
				
			||||||
        const pdf = await this.renderPdf(result, { format: "A5", landscape: true });
 | 
					        const template_source = fs.readFileSync(`${this.templateDir}/sponsoring_contract.html`, 'utf8');
 | 
				
			||||||
        return pdf
 | 
					        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 });
 | 
				
			||||||
     * Converts all images in html to base64.
 | 
					        return pdf
 | 
				
			||||||
     * 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> {
 | 
					     * Converts all images in html to base64.
 | 
				
			||||||
        const $ = cheerio.load(html)
 | 
					     * Works with image files in the template directory or images from urls.
 | 
				
			||||||
        $('img').each(async (index, element) => {
 | 
					     * @param html The html string whoms images shall get replaced.
 | 
				
			||||||
            let imgsrc = $(element).attr("src");
 | 
					     */
 | 
				
			||||||
            const img_type = mime.lookup(imgsrc);
 | 
					    public async imgToBase64(html): Promise<string> {
 | 
				
			||||||
 | 
					        const $ = cheerio.load(html)
 | 
				
			||||||
            if (!(img_type.includes("image"))) {
 | 
					        $('img').each(async (index, element) => {
 | 
				
			||||||
                throw new Error("File is not image mime type");
 | 
					            let imgsrc = $(element).attr("src");
 | 
				
			||||||
            }
 | 
					            if (imgsrc.startsWith("data:image")) {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
            let image;
 | 
					            }
 | 
				
			||||||
            if (imgsrc.startsWith("http")) {
 | 
					            const img_type = mime.lookup(imgsrc);
 | 
				
			||||||
                image = (await axios.get(imgsrc)).data;
 | 
					
 | 
				
			||||||
                image = Buffer.from(image).toString('base64');
 | 
					            if (!(img_type.includes("image"))) {
 | 
				
			||||||
            }
 | 
					                throw new Error("File is not image mime type");
 | 
				
			||||||
            else {
 | 
					            }
 | 
				
			||||||
                if (imgsrc.startsWith("./")) {
 | 
					
 | 
				
			||||||
                    imgsrc = imgsrc.replace("./", "");
 | 
					            let image;
 | 
				
			||||||
                }
 | 
					            if (imgsrc.startsWith("http")) {
 | 
				
			||||||
                image = fs.readFileSync(`${this.templateDir}/${imgsrc}`, { encoding: "base64" });
 | 
					                image = (await axios.get(imgsrc)).data;
 | 
				
			||||||
            }
 | 
					                image = Buffer.from(image).toString('base64');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            image = `data:${img_type};base64,${image}`
 | 
					            else {
 | 
				
			||||||
            $(element).attr("src", image)
 | 
					                if (imgsrc.startsWith("./")) {
 | 
				
			||||||
        })
 | 
					                    imgsrc = imgsrc.replace("./", "");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
        return $.html();
 | 
					                image = fs.readFileSync(`${this.templateDir}/${imgsrc}`, { encoding: "base64" });
 | 
				
			||||||
    }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					            image = `data:${img_type};base64,${image}`
 | 
				
			||||||
     * This method manages the creation of pdfs via puppeteer.
 | 
					            $(element).attr("src", image)
 | 
				
			||||||
     * @param html The HTML that should get rendered.
 | 
					        })
 | 
				
			||||||
     * @param options Puppeteer PDF option (eg: {format: "A4"})
 | 
					
 | 
				
			||||||
     */
 | 
					        return $.html();
 | 
				
			||||||
    public async renderPdf(html: string, options): Promise<any> {
 | 
					    }
 | 
				
			||||||
        html = await this.imgToBase64(html);
 | 
					
 | 
				
			||||||
        let page = await this.browser.newPage();
 | 
					    /**
 | 
				
			||||||
        await page.setContent(html);
 | 
					     * This method manages the creation of pdfs via puppeteer.
 | 
				
			||||||
        const pdf = await page.pdf(options);
 | 
					     * @param html The HTML that should get rendered.
 | 
				
			||||||
        await page.close();
 | 
					     * @param options Puppeteer PDF option (eg: {format: "A4"})
 | 
				
			||||||
        return pdf;
 | 
					     */
 | 
				
			||||||
    }
 | 
					    public async renderPdf(html: string, options): Promise<any> {
 | 
				
			||||||
 | 
					        html = await this.imgToBase64(html);
 | 
				
			||||||
    /**
 | 
					        let page = await this.browser.newPage();
 | 
				
			||||||
     * Merges multiple pdfs into one.
 | 
					        await page.setContent(html);
 | 
				
			||||||
     * @param pdfs The pdfs you want to merge as an buffer array.
 | 
					        const pdf = await page.pdf(options);
 | 
				
			||||||
     * @returns The merged pdf as a buffer.
 | 
					        await page.close();
 | 
				
			||||||
     */
 | 
					        return pdf;
 | 
				
			||||||
    private async mergePdfs(pdfs: Buffer[]): Promise<Buffer> {
 | 
					    }
 | 
				
			||||||
        const mergedPdf = await PDFDocument.create();
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
        for (const pdfBuffer of pdfs) {
 | 
					     * Merges multiple pdfs into one.
 | 
				
			||||||
            const pdf = await PDFDocument.load(pdfBuffer);
 | 
					     * @param pdfs The pdfs you want to merge as an buffer array.
 | 
				
			||||||
            const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
 | 
					     * @returns The merged pdf as a buffer.
 | 
				
			||||||
            copiedPages.forEach((page) => {
 | 
					     */
 | 
				
			||||||
                mergedPdf.addPage(page);
 | 
					    private async mergePdfs(pdfs: Buffer[]): Promise<Buffer> {
 | 
				
			||||||
            });
 | 
					        const mergedPdf = await PDFDocument.create();
 | 
				
			||||||
        }
 | 
					
 | 
				
			||||||
 | 
					        for (const pdfBuffer of pdfs) {
 | 
				
			||||||
        return <Buffer>(await mergedPdf.save());
 | 
					            const pdf = await PDFDocument.load(pdfBuffer);
 | 
				
			||||||
    }
 | 
					            const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
 | 
				
			||||||
 | 
					            copiedPages.forEach((page) => {
 | 
				
			||||||
    /**
 | 
					                mergedPdf.addPage(page);
 | 
				
			||||||
     * 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.
 | 
					
 | 
				
			||||||
     */
 | 
					        return <Buffer>(await mergedPdf.save());
 | 
				
			||||||
    private generateEmptyRunner(): Runner {
 | 
					    }
 | 
				
			||||||
        let group = new RunnerGroup();
 | 
					
 | 
				
			||||||
        group.id = 0;
 | 
					    /**
 | 
				
			||||||
        group.name = " ";
 | 
					     * Generates a new dummy runner with halfspaces for all strings.
 | 
				
			||||||
        let runner = new Runner();
 | 
					     * Can be used to generate empty sponsoring contracts.
 | 
				
			||||||
        runner.id = 0;
 | 
					     * @returns A new runner object that apears to be empty.
 | 
				
			||||||
        runner.firstname = " ";
 | 
					     */
 | 
				
			||||||
        runner.lastname = " ";
 | 
					    private generateEmptyRunner(): Runner {
 | 
				
			||||||
        runner.group = group;
 | 
					        let group = new RunnerGroup();
 | 
				
			||||||
        return runner;
 | 
					        group.id = 0;
 | 
				
			||||||
    }
 | 
					        group.name = " ";
 | 
				
			||||||
 | 
					        let runner = new Runner();
 | 
				
			||||||
 | 
					        runner.id = 0;
 | 
				
			||||||
 | 
					        runner.firstname = " ";
 | 
				
			||||||
 | 
					        runner.lastname = " ";
 | 
				
			||||||
 | 
					        runner.group = group;
 | 
				
			||||||
 | 
					        return runner;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										44
									
								
								src/asyncHelpers.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/asyncHelpers.ts
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -1,19 +1,20 @@
 | 
				
			|||||||
import { config as configDotenv } from 'dotenv';
 | 
					import { config as configDotenv } from 'dotenv';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
configDotenv();
 | 
					configDotenv();
 | 
				
			||||||
export const config = {
 | 
					export const config = {
 | 
				
			||||||
    internal_port: parseInt(process.env.APP_PORT) || 4010,
 | 
					    internal_port: parseInt(process.env.APP_PORT) || 4010,
 | 
				
			||||||
    development: process.env.NODE_ENV === "production",
 | 
					    development: process.env.NODE_ENV === "production",
 | 
				
			||||||
    version: process.env.VERSION || require('../package.json').version,
 | 
					    version: process.env.VERSION || require('../package.json').version,
 | 
				
			||||||
    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"
 | 
				
			||||||
let errors = 0
 | 
					}
 | 
				
			||||||
if (typeof config.internal_port !== "number") {
 | 
					let errors = 0
 | 
				
			||||||
    errors++
 | 
					if (typeof config.internal_port !== "number") {
 | 
				
			||||||
}
 | 
					    errors++
 | 
				
			||||||
if (typeof config.development !== "boolean") {
 | 
					}
 | 
				
			||||||
    errors++
 | 
					if (typeof config.development !== "boolean") {
 | 
				
			||||||
}
 | 
					    errors++
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
export let e = errors
 | 
					export let e = errors
 | 
				
			||||||
@@ -15,7 +15,7 @@ export class PdfController {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @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) {
 | 
					    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;
 | 
				
			||||||
@@ -23,7 +23,7 @@ export class PdfController {
 | 
				
			|||||||
        if (!Array.isArray(runners)) {
 | 
					        if (!Array.isArray(runners)) {
 | 
				
			||||||
            runners = [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');
 | 
					        res.setHeader('content-type', 'application/pdf');
 | 
				
			||||||
        return contracts;
 | 
					        return contracts;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,31 +29,36 @@
 | 
				
			|||||||
  <div class="sheet">
 | 
					  <div class="sheet">
 | 
				
			||||||
    <img id="header_img" width="100%" src="sponsoringheader.png" />
 | 
					    <img id="header_img" width="100%" src="sponsoringheader.png" />
 | 
				
			||||||
    <div style=" padding: 0 1rem 0 1rem;">
 | 
					    <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="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">
 | 
					        <div class="column is-6">
 | 
				
			||||||
          <span style="border-bottom: 1px solid; width: 100%; display: block;">{{this.lastname}}</span>
 | 
					          <span style="border-bottom: 1px solid; width: 100%; display: block;">{{this.lastname}}</span>
 | 
				
			||||||
          <p style="font-size: x-small; display: block;">{{__ "lastname"}}</p>
 | 
					          <p style="font-size: x-small; display: block;">{{__ "lastname"}}</p>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user