Barcode generation feature/13-barcode_generation #21

Merged
niggl merged 13 commits from feature/13-barcode_generation into dev 2021-02-09 16:43:48 +00:00
6 changed files with 301 additions and 242 deletions

View File

@ -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",

View File

@ -8,9 +8,11 @@ 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 { config } from './config'; import { config } from './config';
import { Runner } from './models/Runner'; import { Runner } from './models/Runner';
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.
@ -68,7 +70,6 @@ export class PdfCreator {
'--use-gl=swiftshader', '--use-gl=swiftshader',
'--no-sandbox' '--no-sandbox'
]; ];
await i18next await i18next
.use(Backend) .use(Backend)
.init({ .init({
@ -78,6 +79,8 @@ export class PdfCreator {
loadPath: path.join(__dirname, '/locales/{{lng}}.json') loadPath: path.join(__dirname, '/locales/{{lng}}.json')
} }
}); });
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();
@ -91,7 +94,7 @@ export class PdfCreator {
* @param runner The runner you want to generate the contracts for. * @param runner The runner you want to generate the contracts for.
* @param locale The locale used for the contracts (default:en) * @param locale The locale used for the contracts (default:en)
*/ */
public async generateSponsoringContract(runners: Runner[], locale: string = "en"): Promise<Buffer> { public async generateSponsoringContract(runners: Runner[], locale: string = "en", codeformat: string = config.codeformat): Promise<Buffer> {
if (runners.length == 1 && Object.keys(runners[0]).length == 0) { if (runners.length == 1 && Object.keys(runners[0]).length == 0) {
runners[0] = this.generateEmptyRunner(); runners[0] = this.generateEmptyRunner();
} }
@ -108,7 +111,8 @@ export class PdfCreator {
await i18next.changeLanguage(locale); await i18next.changeLanguage(locale);
const template_source = fs.readFileSync(`${this.templateDir}/sponsoring_contract.html`, 'utf8'); const template_source = fs.readFileSync(`${this.templateDir}/sponsoring_contract.html`, 'utf8');
const template = Handlebars.compile(template_source); const template = Handlebars.compile(template_source);
const result = template({ runners }) let result = template({ runners, codeformat });
result = await awaitAsyncHandlebarHelpers(result);
const pdf = await this.renderPdf(result, { format: "A5", landscape: true }); const pdf = await this.renderPdf(result, { format: "A5", landscape: true });
return pdf return pdf
} }
@ -122,6 +126,9 @@ export class PdfCreator {
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")) {
return;
}
const img_type = mime.lookup(imgsrc); const img_type = mime.lookup(imgsrc);
if (!(img_type.includes("image"))) { if (!(img_type.includes("image"))) {

44
src/asyncHelpers.ts Normal file

File diff suppressed because one or more lines are too long

View File

@ -7,7 +7,8 @@ export const config = {
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 let errors = 0
if (typeof config.internal_port !== "number") { if (typeof config.internal_port !== "number") {

View File

@ -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;
} }

View File

@ -29,6 +29,8 @@
<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">
<div class="column is-10">
<div class="columns" style="padding-bottom: 0;"> <div class="columns" style="padding-bottom: 0;">
<div class="column is-two-fifths"> <div class="column is-two-fifths">
<p style="font-size: large; font-weight: bold;">{{__ "sponsoring_title"}}</p> <p style="font-size: large; font-weight: bold;">{{__ "sponsoring_title"}}</p>
@ -39,21 +41,24 @@
</div> </div>
</div> </div>
<p> {{__ "sponsoring_subtitle"}} </p> <p> {{__ "sponsoring_subtitle"}} </p>
<div class="columns" style="padding-top: 0;"> <div class="columns">
<div class="column is-8"> <div class="column is-9">
<span style="border-bottom: 1px solid; width: 100%; display: block;">{{this.firstname}} <span style="border-bottom: 1px solid; width: 100%; display: block;">{{this.firstname}}
{{this.middlename}}</span> {{this.middlename}}</span>
<p style="font-size: x-small; display: block;">{{__ "firstname"}}</p> <p style="font-size: x-small; display: block;">{{__ "firstname"}}</p>
</div> </div>
<div class="column is-2"> <div class="column is-3">
<span style="border-bottom: 1px solid; width: 100%; display: block;">{{this.id}}</span> <span style="border-bottom: 1px solid; width: 100%; display: block;">{{this.id}}</span>
<p style="font-size: x-small; display: block;">ID</p> <p style="font-size: x-small; display: block;">ID</p>
</div> </div>
</div>
</div>
<div class="column"> <div class="column">
<!-- CODE Here --> <img style="vertical-align: revert; margin-top: auto; object-fit: cover; max-height: 2cm;"
src="{{--bc this.id ../codeformat}}" />
</div> </div>
</div> </div>
<div class="columns"> <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>