diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c49c9a..2156fdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,46 @@ All notable changes to this project will be documented in this file. Dates are displayed in UTC. +#### [v0.5.0](https://git.odit.services/lfk/document-server/compare/v0.4.3...v0.5.0) + +- Added translations [`ac572f1`](https://git.odit.services/lfk/document-server/commit/ac572f1ea31cb66985e04cb5d56cc67f521e990d) +- Added translations [`7fea1ca`](https://git.odit.services/lfk/document-server/commit/7fea1ca78ff6fdbb38dee0edd9918eaeb1264d18) +- Sorted translations 🌍 [`2278e4a`](https://git.odit.services/lfk/document-server/commit/2278e4ad06947b540323856ea1e71022562ea719) +- Added front certificate design [`8b71608`](https://git.odit.services/lfk/document-server/commit/8b71608792f00084df1e71956e503f102cea290d) +- 📖New license file version [CI SKIP] [skip ci] [`3fc6124`](https://git.odit.services/lfk/document-server/commit/3fc612488d1f231d83d6a5823a13dc4817e6a588) +- Addest first coupple of test improvements [`a580841`](https://git.odit.services/lfk/document-server/commit/a5808419738563fec3a7d8d35f4ce20d76c017fb) +- Pinned depencencies (and bumped some) [`479e28c`](https://git.odit.services/lfk/document-server/commit/479e28c46c6b3b8ba1622bcf5712695a94c2ae89) +- Added new basic certificate endpoint [`0af9b81`](https://git.odit.services/lfk/document-server/commit/0af9b81b38e1c28261c012375af2c91808e65621) +- Added function for generateing runner certificates [`955e118`](https://git.odit.services/lfk/document-server/commit/955e11846b5385c1a0b6f0b54b9951f0768ff414) +- 🧾New changelog file version [CI SKIP] [skip ci] [`f220e70`](https://git.odit.services/lfk/document-server/commit/f220e70743534c1fc66ff6f50e3693182d35990a) +- Now formatting currency ans distance [`e0add84`](https://git.odit.services/lfk/document-server/commit/e0add846bb37b435da9807bdc76c70793002896e) +- Fixed bg image opacity overlay [`8d6ea4d`](https://git.odit.services/lfk/document-server/commit/8d6ea4dbf9763994f0f44c4ad9b32a7b1d16f11a) +- Pinned dev dependencies [`82159be`](https://git.odit.services/lfk/document-server/commit/82159bed536bbafd20746927f5a7f61f76959637) +- Now calculateing total donations (perdistance) [`6b23dea`](https://git.odit.services/lfk/document-server/commit/6b23dea47745e98371a65a4f577f2e20cfdfe597) +- Added backside table [`3ca2237`](https://git.odit.services/lfk/document-server/commit/3ca22379535bba72d05e0b3fafe22d3a4b2f549d) +- Added template strings [`6767c3b`](https://git.odit.services/lfk/document-server/commit/6767c3b2d1e991e2f4f74b8d423a72e240ffcb8c) +- Added template strings [`2b21957`](https://git.odit.services/lfk/document-server/commit/2b2195727b15b8666edf0d925f2e68a98030153d) +- Fixed background opacity [`2a4cfdb`](https://git.odit.services/lfk/document-server/commit/2a4cfdb2f88ad3ac1ebc925199a440756e9e9d3a) +- Now with embedded background [`64fce5b`](https://git.odit.services/lfk/document-server/commit/64fce5bd019a00bf34c1ebd133c1904bb577b67b) +- Made footer text configureable [`63c7beb`](https://git.odit.services/lfk/document-server/commit/63c7beb8b9cdc564186c5b86a4f305c8575f5b9f) +- 🚀Bumped version to v0.5.0 [`f623c0a`](https://git.odit.services/lfk/document-server/commit/f623c0a7cd06f707ac488456c9e8a051d3ceae46) +- Merge pull request 'Generate runner certificates feature/36-runner_certificates' (#41) from feature/36-runner_certificates into dev [`d3f7d1a`](https://git.odit.services/lfk/document-server/commit/d3f7d1a6c9858d7fdf09c696622962e6f8471e78) +- disabled testing for now [`cec8930`](https://git.odit.services/lfk/document-server/commit/cec893032dea9f312e37841232a9434e19b79003) +- Added missing interpolations [`b43aeec`](https://git.odit.services/lfk/document-server/commit/b43aeec0cf40a9c37a10072062ab5d93102f6c81) +- 🧾New changelog file version [CI SKIP] [skip ci] [`f1084b5`](https://git.odit.services/lfk/document-server/commit/f1084b59a74dcc5981fd314721c36726706f386c) +- disabled testing for now [`e75f151`](https://git.odit.services/lfk/document-server/commit/e75f15142e293349a071a7cdcc53cc10780304f6) +- Removed temporary background-image fix [`5ba26c4`](https://git.odit.services/lfk/document-server/commit/5ba26c4cbfae7d3f31d3709aaeb372c14de78fa9) +- Fixed page size+background image [`b82a32a`](https://git.odit.services/lfk/document-server/commit/b82a32ae3ee3256402be5dde0ada903f2c19a8cc) +- Fixed typo [`1d12de7`](https://git.odit.services/lfk/document-server/commit/1d12de7045b5e8324dc0ddc421944e70ffc2ec73) +- Documented new env var [`5a98688`](https://git.odit.services/lfk/document-server/commit/5a98688d60eed34644391ecde638949fe5a46c65) + #### [v0.4.3](https://git.odit.services/lfk/document-server/compare/v0.4.2...v0.4.3) +> 30 March 2021 + +- Merge pull request 'Release 0.4.3' (#40) from dev into main [`c8dc998`](https://git.odit.services/lfk/document-server/commit/c8dc998ecdccc7fc4348ecc0db552a3d7bc2eb52) - 🧾New changelog file version [CI SKIP] [skip ci] [`289a0d8`](https://git.odit.services/lfk/document-server/commit/289a0d8671575dda911c64f79d24726d3bbee071) +- 🧾New changelog file version [CI SKIP] [skip ci] [`3df3d26`](https://git.odit.services/lfk/document-server/commit/3df3d26708aab12590cd9c1f697cfdea8017ace4) - 🧾New changelog file version [CI SKIP] [skip ci] [`457ea26`](https://git.odit.services/lfk/document-server/commit/457ea26cf8124009084415d12c7a0e31912a3eb1) - 🚀Bumped version to v0.4.3 [`c3beb3e`](https://git.odit.services/lfk/document-server/commit/c3beb3e1032492f9a8304c4b099a700ad0d1d2cf) - Pipeline mtu fix [`c2d2b66`](https://git.odit.services/lfk/document-server/commit/c2d2b66f2f6fbd30c2027fd3dab393db5219eb44) diff --git a/README.md b/README.md index d3bd447..dfc827b 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ The basic generation mechanism makes the templates and routes interchangeable (i | SPONOR_LOGOS | Array | 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. | 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). ## Templates diff --git a/licenses.md b/licenses.md index 625a297..72671f9 100644 --- a/licenses.md +++ b/licenses.md @@ -1,6 +1,6 @@ # @odit/class-validator-jsonschema **Author**: Aleksi Pekkala -**Repo**: git@github.com:epiphone/class-validator-jsonschema.git +**Repo**: git@github.com:epiphone/class-validator-jsonschema **License**: MIT **Description**: Convert class-validator-decorated classes into JSON schema ## License Text @@ -29,7 +29,7 @@ SOFTWARE. # async-helpers **Author**: Brian Woodward (https://github.com/doowb) -**Repo**: doowb/async-helpers +**Repo**: https://github.com/doowb/async-helpers **License**: MIT **Description**: Use async helpers in templates with engines that typically only handle sync helpers. Handlebars and Lodash have been tested. ## License Text @@ -58,7 +58,7 @@ THE SOFTWARE. # axios **Author**: Matt Zabriskie -**Repo**: https://github.com/axios/axios.git +**Repo**: https://github.com/axios/axios **License**: MIT **Description**: Promise based HTTP client for the browser and node.js ## License Text @@ -85,7 +85,7 @@ THE SOFTWARE. # bwip-js **Author**: Mark Warren -**Repo**: https://github.com/metafloor/bwip-js.git +**Repo**: https://github.com/metafloor/bwip-js **License**: MIT **Description**: JavaScript barcode generator supporting over 100 types and standards. ## License Text @@ -117,7 +117,7 @@ THE SOFTWARE. # cheerio **Author**: Matt Mueller (mat.io) -**Repo**: git://github.com/cheeriojs/cheerio.git +**Repo**: https://github.comcheeriojs/cheerio **License**: MIT **Description**: Tiny, fast, and elegant implementation of core jQuery designed specifically for the server ## License Text @@ -144,8 +144,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # class-transformer -**Author**: [object Object] -**Repo**: https://github.com/typestack/class-transformer.git +**Author**: Umed Khudoiberdiev +**Repo**: https://github.com/typestack/class-transformer **License**: MIT **Description**: Proper decorator-based transformation / serialization / deserialization of plain javascript objects to class constructors ## License Text @@ -173,7 +173,7 @@ THE SOFTWARE. # class-validator **Author**: TypeStack contributors -**Repo**: https://github.com/typestack/class-validator.git +**Repo**: https://github.com/typestack/class-validator **License**: MIT **Description**: Decorator-based property validation for classes. ## License Text @@ -201,7 +201,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # consola -**Author**: undefined +**Author**: ? **Repo**: nuxt/consola **License**: MIT **Description**: Elegant Console Logger for Node.js and Browser @@ -239,8 +239,8 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # dotenv -**Author**: undefined -**Repo**: git://github.com/motdotla/dotenv.git +**Author**: ? +**Repo**: https://github.commotdotla/dotenv **License**: BSD-2-Clause **Description**: Loads environment variables from .env file ## License Text @@ -303,7 +303,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # handlebars **Author**: Yehuda Katz -**Repo**: https://github.com/wycats/handlebars.js.git +**Repo**: https://github.com/wycats/handlebars.js **License**: MIT **Description**: Handlebars provides the power necessary to let you build semantic templates effectively with no frustration ## License Text @@ -330,7 +330,7 @@ THE SOFTWARE. # i18next **Author**: Jan Mühlemann (https://github.com/jamuhl) -**Repo**: https://github.com/i18next/i18next.git +**Repo**: https://github.com/i18next/i18next **License**: MIT **Description**: i18next internationalization framework ## License Text @@ -358,15 +358,15 @@ SOFTWARE. # i18next-fs-backend -**Author**: undefined -**Repo**: git@github.com:i18next/i18next-fs-backend.git +**Author**: ? +**Repo**: git@github.com:i18next/i18next-fs-backend **License**: undefined **Description**: i18next-fs-backend is a backend layer for i18next using in Node.js and for Deno to load translations from the filesystem. ## License Text # mime-types -**Author**: undefined +**Author**: ? **Repo**: jshttp/mime-types **License**: MIT **Description**: The ultimate javascript content-type utility. @@ -398,7 +398,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # pdf-lib **Author**: Andrew Dillon -**Repo**: git+https://github.com/Hopding/pdf-lib.git +**Repo**: git+https://github.com/Hopding/pdf-lib **License**: MIT **Description**: Create and modify PDF files with JavaScript ## License Text @@ -427,7 +427,7 @@ SOFTWARE. # puppeteer **Author**: The Chromium Authors -**Repo**: github:puppeteer/puppeteer +**Repo**: https://github.com/puppeteer/puppeteer **License**: Apache-2.0 **Description**: A high-level API to control headless Chrome over the DevTools Protocol ## License Text @@ -636,8 +636,8 @@ SOFTWARE. # reflect-metadata -**Author**: [object Object] -**Repo**: https://github.com/rbuckton/reflect-metadata.git +**Author**: Ron Buckton +**Repo**: https://github.com/rbuckton/reflect-metadata **License**: Apache-2.0 **Description**: Polyfill for Metadata Reflection API ## License Text @@ -698,8 +698,8 @@ If the Work includes a "NOTICE" text file as part of its distribution, then any END OF TERMS AND CONDITIONS # routing-controllers -**Author**: [object Object] -**Repo**: https://github.com/typestack/routing-controllers.git +**Author**: Umed Khudoiberdiev +**Repo**: https://github.com/typestack/routing-controllers **License**: MIT **Description**: Create structured, declarative and beautifully organized class-based controllers with heavy decorators usage for Express / Koa using TypeScript. ## License Text @@ -762,8 +762,8 @@ OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # @types/express -**Author**: undefined -**Repo**: https://github.com/DefinitelyTyped/DefinitelyTyped.git +**Author**: ? +**Repo**: https://github.com/DefinitelyTyped/DefinitelyTyped **License**: MIT **Description**: TypeScript definitions for Express ## License Text @@ -791,8 +791,8 @@ OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # @types/node -**Author**: undefined -**Repo**: https://github.com/DefinitelyTyped/DefinitelyTyped.git +**Author**: ? +**Repo**: https://github.com/DefinitelyTyped/DefinitelyTyped **License**: MIT **Description**: TypeScript definitions for Node.js ## License Text @@ -820,8 +820,8 @@ OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # @types/puppeteer -**Author**: undefined -**Repo**: https://github.com/DefinitelyTyped/DefinitelyTyped.git +**Author**: ? +**Repo**: https://github.com/DefinitelyTyped/DefinitelyTyped **License**: MIT **Description**: TypeScript definitions for puppeteer ## License Text @@ -849,8 +849,8 @@ OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # cp-cli -**Author**: undefined -**Repo**: git+https://github.com/screendriver/cp-cli.git +**Author**: ? +**Repo**: https://github.com/screendriver/cp-cli **License**: MIT **Description**: A 'cp' CLI util for Node.js ## License Text @@ -878,16 +878,16 @@ SOFTWARE. # faker -**Author**: undefined -**Repo**: http://github.com/Marak/Faker.js.git +**Author**: ? +**Repo**: http://github.com/Marak/Faker.js **License**: MIT **Description**: Generate massive amounts of fake contextual data ## License Text # nodemon -**Author**: [object Object] -**Repo**: https://github.com/remy/nodemon.git +**Author**: Remy Sharp +**Repo**: https://github.com/remy/nodemon **License**: MIT **Description**: Simple monitor script for use during development of a node.js app. ## License Text @@ -915,8 +915,8 @@ SOFTWARE. # release-it -**Author**: [object Object] -**Repo**: https://github.com/release-it/release-it.git +**Author**: Lars Kappert +**Repo**: https://github.com/release-it/release-it **License**: MIT **Description**: Generic CLI tool to automate versioning and package publishing related tasks. ## License Text @@ -945,7 +945,7 @@ SOFTWARE. # rimraf **Author**: Isaac Z. Schlueter (http://blog.izs.me/) -**Repo**: git://github.com/isaacs/rimraf.git +**Repo**: git://github.com/isaacs/rimraf **License**: ISC **Description**: A deep deletion module for node (like `rm -rf`) ## License Text @@ -968,15 +968,15 @@ IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # start-server-and-test **Author**: Gleb Bahmutov -**Repo**: https://github.com/bahmutov/start-server-and-test.git +**Repo**: https://github.com/bahmutov/start-server-and-test **License**: MIT **Description**: Starts server, waits for URL, then runs test command; when the tests end, shuts down server ## License Text # ts-node -**Author**: [object Object] -**Repo**: git://github.com/TypeStrong/ts-node.git +**Author**: Blake Embrey +**Repo**: https://github.comTypeStrong/ts-node **License**: MIT **Description**: TypeScript execution environment and REPL for node.js, with source map support ## License Text @@ -1005,7 +1005,7 @@ THE SOFTWARE. # typescript **Author**: Microsoft Corp. -**Repo**: https://github.com/Microsoft/TypeScript.git +**Repo**: https://github.com/Microsoft/TypeScript **License**: Apache-2.0 **Description**: TypeScript is a language for application scale JavaScript development ## License Text diff --git a/package.json b/package.json index c704978..a3bf208 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@odit/lfk-document-server", - "version": "0.4.3", + "version": "0.5.0", "description": "The document generation server for the LfK! runner system. This generates certificates, sponsoring aggreements and more", "main": "src/app.ts", "scripts": { @@ -8,9 +8,7 @@ "build": "rimraf ./dist && tsc && cp-cli ./src/templates ./dist/templates && cp-cli ./src/locales ./dist/locales", "licenses:export": "license-exporter --markdown", "release": "release-it --only-version", - "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" + "translations:sort": "node sort_translations.js" }, "repository": { "type": "git", @@ -41,40 +39,40 @@ ], "license": "CC-BY-NC-SA-4.0", "dependencies": { - "@odit/class-validator-jsonschema": "^2.1.1", - "async-helpers": "^0.3.17", - "axios": "^0.21.1", - "bwip-js": "^2.0.12", - "cheerio": "^1.0.0-rc.5", + "@odit/class-validator-jsonschema": "2.1.1", + "async-helpers": "0.3.17", + "axios": "0.21.1", + "bwip-js": "2.1.1", + "cheerio": "1.0.0-rc.5", "class-transformer": "0.3.1", - "class-validator": "^0.13.1", - "consola": "^2.15.0", - "cors": "^2.8.5", - "dotenv": "^8.2.0", - "express": "^4.17.1", - "handlebars": "^4.7.6", - "i18next": "^19.8.7", - "i18next-fs-backend": "^1.0.8", - "mime-types": "^2.1.28", - "pdf-lib": "^1.16.0", - "puppeteer": "^7.0.1", - "reflect-metadata": "^0.1.13", + "class-validator": "0.13.1", + "consola": "2.15.3", + "cors": "2.8.5", + "dotenv": "8.2.0", + "express": "4.17.1", + "handlebars": "4.7.7", + "i18next": "20.1.0", + "i18next-fs-backend": "1.1.1", + "mime-types": "2.1.29", + "pdf-lib": "1.16.0", + "puppeteer": "8.0.0", + "reflect-metadata": "0.1.13", "routing-controllers": "0.9.0-alpha.6", "routing-controllers-openapi": "2.2.0" }, "devDependencies": { - "@odit/license-exporter": "^0.0.10", - "@types/express": "^4.17.11", - "@types/node": "^14.14.22", - "@types/puppeteer": "^5.4.3", - "cp-cli": "^2.0.0", - "faker": "^5.3.1", - "nodemon": "^2.0.7", + "@odit/license-exporter": "0.0.11", + "@types/express": "4.17.11", + "@types/node": "14.14.22", + "@types/puppeteer": "5.4.3", + "cp-cli": "2.0.0", + "faker": "5.3.1", + "nodemon": "2.0.7", "release-it": "^14.2.2", - "rimraf": "^3.0.2", - "start-server-and-test": "^1.12.0", - "ts-node": "^9.1.1", - "typescript": "^4.1.3" + "rimraf": "3.0.2", + "start-server-and-test": "1.12.0", + "ts-node": "9.1.1", + "typescript": "4.1.3" }, "release-it": { "git": { diff --git a/src/PdfCreator.ts b/src/PdfCreator.ts index 549cf4f..f929c83 100644 --- a/src/PdfCreator.ts +++ b/src/PdfCreator.ts @@ -10,6 +10,7 @@ import { PDFDocument } from 'pdf-lib'; import puppeteer from "puppeteer"; import { awaitAsyncHandlebarHelpers, helpers } from './asyncHelpers'; import { config } from './config'; +import { CertificateRunner } from './models/CertificateRunner'; import { Runner } from './models/Runner'; import { RunnerCard } from './models/RunnerCard'; import { RunnerGroup } from './models/RunnerGroup'; @@ -97,6 +98,18 @@ export class PdfCreator { 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 }); } @@ -153,13 +166,37 @@ export class PdfCreator { await i18next.changeLanguage(locale); 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" }) + let result = template({ cards, cards_swapped, eventname: config.eventname, codeformat: codeformat }) result = await awaitAsyncHandlebarHelpers(result); - fs.writeFileSync("lelelelele.tmp", result); const pdf = await this.renderPdf(result, { format: "A4", landscape: false }); 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 { + if (runners.length > 50) { + let pdf_promises = new Array>(); + 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. * Works with image files in the template directory or images from urls. @@ -167,6 +204,7 @@ export class PdfCreator { */ public async imgToBase64(html): Promise { const $ = cheerio.load(html) + $('img').each(async (index, element) => { let imgsrc = $(element).attr("src"); if (imgsrc.startsWith("data:image")) { @@ -192,7 +230,7 @@ export class PdfCreator { image = `data:${img_type};base64,${image}` $(element).attr("src", image) - }) + }); return $.html(); } diff --git a/src/config.ts b/src/config.ts index ed07fd0..aea575a 100644 --- a/src/config.ts +++ b/src/config.ts @@ -13,6 +13,7 @@ export const config = { sponor_logos: getSponsorLogos(), api_key: getApiKey(), disclaimer_text: process.env.DISCLAIMER_TEXT || "", + donations_footer_text: process.env.DONATIONS_FOOTER_TEXT || "", contracts_per_runner: parseInt(process.env.CONTRACTS_PER_RUNNER) || 1, } let errors = 0 diff --git a/src/controllers/PdfController.ts b/src/controllers/PdfController.ts index 619dc81..665f36f 100644 --- a/src/controllers/PdfController.ts +++ b/src/controllers/PdfController.ts @@ -1,5 +1,6 @@ import { Authorized, Body, JsonController, Post, QueryParam, Res } from 'routing-controllers'; import { OpenAPI } from 'routing-controllers-openapi'; +import { CertificateRunner } from '../models/CertificateRunner'; import { Runner } from '../models/Runner'; import { RunnerCard } from '../models/RunnerCard'; import { PdfCreator } from '../PdfCreator'; @@ -54,6 +55,25 @@ export class PdfController { return contracts; } + @Post('/certificates') + @OpenAPI({ description: "Generate runner certificate pdfs from certificate runner objects.
You can choose your prefered locale by passing the 'locale' query-param.
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[] { let response = new Array(); for (let runner of runners) { @@ -68,6 +88,26 @@ export class PdfController { return response; } + private mapCertificatRunnersGroupNames(runners: CertificateRunner[]): CertificateRunner[] { + let response = new Array(); + 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[] { let response = new Array(); for (let card of cards) { diff --git a/src/locales/de.json b/src/locales/de.json index 7fd4d49..abd0e37 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -1,9 +1,14 @@ { "address": "Adresse", + "betrag-km": "Betrag/KM", "city": "Stadt", "date": "Datum", "firstname": "Vorname", + "fuer-den-guten-zweck-zurueckgelegt": "für den guten Zweck zurückgelegt", + "gesamt": "Gesamt", + "gesamtbetrag": "Gesamtbetrag", "group": "Team/Klasse", + "hat-beim-eventname": "Hat beim {{eventname}}", "house_number": "Hausnummer", "id": "ID", "lastname": "Nachname", @@ -12,9 +17,12 @@ "postalcode": "Postleitzahl", "signature": "Unterschrift", "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_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_title": "Sponsoringerklärung", - "street": "Straße" + "sponsorings": "Sponsorings", + "street": "Straße", + "urkunde": "Urkunde" } \ No newline at end of file diff --git a/src/locales/en.json b/src/locales/en.json index 317b9b6..b1b0691 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1,19 +1,28 @@ { "address": "Address", + "betrag-km": "Amount/KM", "city": "City", "date": "date", "firstname": "First name", + "fuer-den-guten-zweck-zurueckgelegt": "for our good cuse at the {{eventname}}", + "gesamt": "Combined", + "gesamtbetrag": "Total", "group": "Team/class", + "hat-beim-eventname": "Ran", "house_number": "House number", + "id": "ID", "lastname": "Last name", "location": "Location", "please_use_blockletters": "Please write in BLOCK LETTERS.", "postalcode": "Postal code", "signature": "Signature", "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_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_title": "Sponsoring contract", - "street": "Street" + "sponsorings": "Donations", + "street": "Street", + "urkunde": "Certifcate" } \ No newline at end of file diff --git a/src/models/CertificateRunner.ts b/src/models/CertificateRunner.ts index a30b356..ef6168a 100644 --- a/src/models/CertificateRunner.ts +++ b/src/models/CertificateRunner.ts @@ -1,5 +1,5 @@ import { - IsArray + IsArray, IsNumber, IsOptional } from "class-validator"; import { DistanceDonation } from './DistanceDonation'; import { Runner } from './Runner'; @@ -13,4 +13,13 @@ export class CertificateRunner extends Runner { */ @IsArray() distanceDonations: DistanceDonation[]; + + @IsNumber() + @IsOptional() + donationPerDistanceTotal?: number = 0; + + @IsNumber() + @IsOptional() + donationTotal?: number = 0; + } diff --git a/src/templates/certficate_background.png b/src/templates/certficate_background.png new file mode 100644 index 0000000..1190792 Binary files /dev/null and b/src/templates/certficate_background.png differ diff --git a/src/templates/certificate_footer.png b/src/templates/certificate_footer.png new file mode 100644 index 0000000..9472ac4 Binary files /dev/null and b/src/templates/certificate_footer.png differ diff --git a/src/templates/runner_certificate.html b/src/templates/runner_certificate.html new file mode 100644 index 0000000..16f3412 --- /dev/null +++ b/src/templates/runner_certificate.html @@ -0,0 +1,102 @@ + + + + + Sponsoring contract + + + + + + {{#each runners}} +
+
+

{{__ "urkunde"}}

+
+
+

{{this.firstname}} + {{this.middlename}} {{this.lastname}} +

+

{{__ "hat-beim-eventname"}}

+

{{--format_kilometers this.distance}}km

+

{{__ "fuer-den-guten-zweck-zurueckgelegt"}}

+
+
+ +
+
+
+
+

{{__ "sponsorings"}}

+
+
+ + + + + + + + {{#each this.distanceDonations}} + + + + + + {{/each}} + + + + + + +
{{__ "sponsor-in"}}{{__ "betrag-km"}}{{__ "gesamtbetrag"}}
{{this.donor.firstname}} {{this.donor.middlename}} {{this.donor.lastname}}{{--format_currency this.amountPerDistance}} {{../../currency_symbol}}{{--format_currency this.amount}} {{../../currency_symbol}}
{{__ "gesamt"}}{{--format_currency this.donationPerDistanceTotal}} {{../currency_symbol}}{{--format_currency this.donationTotal}} {{../currency_symbol}}
+
+
+

+ {{../donations_footer_text}} +

+
+
+ {{/each}} + + + \ No newline at end of file diff --git a/src/tests/speedtest.ts b/src/tests/speedtest.ts index 0ed4372..2899da0 100644 --- a/src/tests/speedtest.ts +++ b/src/tests/speedtest.ts @@ -1,10 +1,15 @@ import axios from "axios" 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 { RunnerCard } from '../models/RunnerCard' import { RunnerGroup } from '../models/RunnerGroup' const baseurl = "http://localhost:4010" +const key = config.api_key; axios.interceptors.request.use((config) => { config.headers['request-startTime'] = process.hrtime() @@ -46,6 +51,36 @@ function generateCards(amount: number): RunnerCard[] { return cards; } +function generateCertificateRunners(amount: number): CertificateRunner[] { + let runners: CertificateRunner[] = new Array(); + 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 { const multiply = [1, 3]; id = id.toString(); @@ -64,15 +99,20 @@ function idToEan13(id): string { } async function postContracts(runners: Runner[]): Promise { - 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'])) } async function postCards(cards: RunnerCard[]): Promise { - 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'])) } +async function postCertificates(runners: CertificateRunner[]): Promise { + 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 { let measurements = new Array(); console.log("#### Testing contracts ####"); @@ -97,16 +137,30 @@ async function testCards(sizes): Promise { return measurements; } +async function testCertificates(sizes): Promise { + let measurements = new Array(); + 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() { - const sizes = [0, 1, 10, 50, 100, 200, 500, 1000] + const sizes = [1, 10, 50, 100] 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); + const certificateResults = await testCertificates(sizes); console.log("####### Results #######"); console.table(contractResults); console.table(cardResults); + console.table(certificateResults); } main();