Barcode generation feature/13-barcode_generation #21
@ -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",
|
||||||
|
@ -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
44
src/asyncHelpers.ts
Normal file
File diff suppressed because one or more lines are too long
@ -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") {
|
||||||
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user