Compare commits

...

255 Commits
v0.4.2 ... main

Author SHA1 Message Date
ac471b28a6
fix(services/templater): Update kilometer formatting to include dynamic separator based on locale
All checks were successful
Build release images / build-container (push) Successful in 4m8s
Build latest image / build-container (push) Successful in 4m14s
2025-05-27 16:51:09 +02:00
76d982fa04
fix(templates/certificates): Remove 'km' suffix from formatted distance output and calculate the suffix dynamicly
All checks were successful
Build release images / build-container (push) Successful in 3m49s
Build latest image / build-container (push) Successful in 4m9s
2025-05-26 16:59:42 +02:00
14795e1831
fix(templates/cards): Update placeholder text for unassigned runners
All checks were successful
Build latest image / build-container (push) Successful in 4m11s
Build release images / build-container (push) Successful in 4m15s
2025-05-26 16:52:44 +02:00
c48a1f855f
fix(docker): Switch to alpine baseimage for temp file support
All checks were successful
Build release images / build-container (push) Successful in 4m15s
Build latest image / build-container (push) Successful in 4m22s
2025-05-01 18:24:12 +02:00
92380802e9
perf(cards): Implement generation splitting support for large datasets
All checks were successful
Build release images / build-container (push) Successful in 4m17s
Build latest image / build-container (push) Successful in 4m19s
2025-05-01 18:11:46 +02:00
a38a0149b7
fix(templates/certificates): Add point at end of sentence
All checks were successful
Build latest image / build-container (push) Successful in 2m4s
Build release images / build-container (push) Successful in 2m27s
2025-04-25 17:33:43 +02:00
af587b0ac1
feat(certificate): update footer image
All checks were successful
Build latest image / build-container (push) Successful in 1m31s
Build release images / build-container (push) Successful in 1m34s
2025-04-25 16:46:28 +02:00
50e3eff294
feat(certificates): Update footer to generation
All checks were successful
Build latest image / build-container (push) Successful in 1m35s
Build release images / build-container (push) Successful in 1m43s
2025-04-25 16:41:32 +02:00
bc17f7256b
Merge branch 'main' of git.odit.services:lfk/document-server
All checks were successful
Build latest image / build-container (push) Successful in 1m34s
2025-04-25 15:50:45 +02:00
d2f3eea8a5
fix(templater): Fix epc generation 2025-04-25 15:50:28 +02:00
f902c61490
feat(certificate): update footer image
All checks were successful
Build release images / build-container (push) Successful in 2m13s
Build latest image / build-container (push) Successful in 2m19s
2025-04-23 11:57:44 +02:00
11e8cc5b1d
feat(certificate): Add SepaConfig to certificate generation and CombinedGroupName to runners
All checks were successful
Build release images / build-container (push) Successful in 1m57s
Build latest image / build-container (push) Successful in 2m22s
2025-04-17 22:22:50 +02:00
84155b7404
feat(templater): Add GenerateEPC method for generating EPC QR codes 2025-04-17 22:22:46 +02:00
45b37197ec
feat(models): Add CombinedGroupName to RunnerWithDonations and introduce SepaConfig struct 2025-04-17 22:22:43 +02:00
f65848924c
feat(templates): Update donation transfer text and add SEPA support in certificate templates 2025-04-17 22:22:37 +02:00
98d584867e
feat(config): Add SEPA fields to environment configuration 2025-04-17 22:22:29 +02:00
376e8de1a4
feat(config): Add CurrencyIdentifier to configuration 2025-04-17 22:01:02 +02:00
2911391fb9
feat(config): Add SEPA fields to configuration 2025-04-17 21:51:27 +02:00
6d2e0241c9
feat(templates): Added selfservice qr
All checks were successful
Build release images / build-container (push) Successful in 2m12s
Build latest image / build-container (push) Successful in 2m26s
2025-04-17 21:44:58 +02:00
afc5b1f0c6
fix(models): Add required SelfServiceLink field to RunnerWithDonations struct 2025-04-17 21:43:54 +02:00
4a76ee469b
fix(barcode): Use auto encoding for QR code generation to support all characters 2025-04-17 21:43:44 +02:00
b58bf700df
chore(static) Add base64 encoded image for new sponsors
All checks were successful
Build latest image / build-container (push) Successful in 2m2s
Build release images / build-container (push) Successful in 2m6s
2025-04-14 18:08:03 +02:00
efd3a35802
fix(templates): Update titles for runner and certificate templates 2025-04-14 17:56:49 +02:00
0f7e44a42a
fix(models): Correct typo in SponsoringReceiptMinimum mapstructure tag
All checks were successful
Build latest image / build-container (push) Successful in 2m6s
Build release images / build-container (push) Successful in 2m17s
2025-04-10 15:29:29 +02:00
f90e5d75fa
fix(contracts): Minimum was not read correctly
All checks were successful
Build release images / build-container (push) Successful in 2m17s
Build latest image / build-container (push) Successful in 2m27s
2025-04-10 15:16:22 +02:00
31d4ec5f27
fix(templates): Correct spacing in group name display
All checks were successful
Build release images / build-container (push) Successful in 1m51s
Build latest image / build-container (push) Successful in 2m13s
2025-03-26 19:29:32 +01:00
d61d4d6e7e
refactor(ci): Switch to actions
All checks were successful
Build latest image / build-container (push) Successful in 1m39s
2025-03-22 22:48:35 +01:00
606ce6b940
docs(swagger): Build new docs
Some checks failed
ci/woodpecker/tag/release Pipeline was successful
ci/woodpecker/push/build Pipeline failed
2024-12-17 17:51:51 +01:00
750fa70332
feat(models): Support nested groups 2024-12-17 17:51:11 +01:00
7d503edbc9
feat(templates): Support nested groups 2024-12-17 17:50:54 +01:00
5c9235df8d
fix(templates): Enable blank cards 2024-12-17 17:38:42 +01:00
11ea0858bb
feat(services): Logging
All checks were successful
ci/woodpecker/tag/release Pipeline was successful
ci/woodpecker/push/build Pipeline was successful
2024-12-17 16:23:52 +01:00
4d57cf827d
refactor(handler): Move array manipulation
All checks were successful
ci/woodpecker/push/build Pipeline was successful
2024-12-17 16:07:40 +01:00
df9f7fdc13
feat(handlers): Added info logging 2024-12-17 16:07:02 +01:00
cdd2b5e250
feat(logging): Debug logging 2024-12-17 15:48:12 +01:00
94b766f106
feat(logger): Log levels 2024-12-17 15:45:40 +01:00
a2e94f715b
refactor(logs): Replaced main logger with zap
All checks were successful
ci/woodpecker/push/build Pipeline was successful
2024-12-16 17:31:23 +01:00
f64daaf817
feat: Request-IDs for better debugging 2024-12-16 17:23:58 +01:00
b4bb732303
fix(config): typoed defaults
All checks were successful
ci/woodpecker/tag/release Pipeline was successful
ci/woodpecker/push/build Pipeline was successful
2024-12-16 17:02:49 +01:00
3dee3e72af
fix: Bad dependency
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/tag/release Pipeline was successful
2024-12-16 16:41:52 +01:00
f5914e5c38
style: Formatting
Some checks failed
ci/woodpecker/push/build Pipeline failed
2024-12-16 16:39:56 +01:00
5a5a7179e9
feat: Use CORS
Some checks failed
ci/woodpecker/push/build Pipeline failed
ci/woodpecker/tag/release Pipeline failed
2024-12-16 16:39:02 +01:00
6c57d63891
fix(config): Added SPONSORING_DISCLAIMER default
All checks were successful
ci/woodpecker/push/build Pipeline was successful
2024-12-16 16:31:08 +01:00
b502e2fbd5
fix(config): Defaults for everyone
Some checks failed
ci/woodpecker/push/build Pipeline failed
ci/woodpecker/tag/release Pipeline was successful
2024-12-16 16:30:02 +01:00
c9475d0093
feat(config): log config on boot
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/tag/release Pipeline was successful
2024-12-16 16:17:26 +01:00
1cc19e0085
fix(config): Default apikey
Some checks failed
ci/woodpecker/push/build Pipeline failed
ci/woodpecker/tag/release Pipeline was successful
2024-12-16 16:15:11 +01:00
b792806481
docs(swagger): New barcode padding docs
All checks were successful
ci/woodpecker/tag/release Pipeline was successful
ci/woodpecker/push/build Pipeline was successful
2024-12-12 19:17:29 +01:00
ea6a4a7080
refactor(barcode): Switch to inclusive padding 2024-12-12 19:16:55 +01:00
de6fe4991c
feat(barcode): Padding 2024-12-12 19:11:01 +01:00
1d068b2655
docs(swagger): New swaggerdoc
All checks were successful
ci/woodpecker/push/build Pipeline was successful
2024-12-12 18:50:21 +01:00
ef25adf5ed
refactor(models): Group now only has a name
Some checks failed
ci/woodpecker/push/build Pipeline failed
ci/woodpecker/tag/release Pipeline was successful
2024-12-12 18:48:39 +01:00
c09c00ec68
feat(pdfs): Set download names for pdf generation responses
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/tag/release Pipeline was successful
2024-12-12 18:24:07 +01:00
1f4981b0a9
fix(config): Don't fail on missing env
Some checks failed
ci/woodpecker/push/build Pipeline failed
ci/woodpecker/tag/release Pipeline was successful
2024-12-12 18:18:41 +01:00
c9f28612be
docs(README): Updated benchmark section
All checks were successful
ci/woodpecker/tag/release Pipeline was successful
2024-12-12 18:05:52 +01:00
c2d192d8a3
docs(README): Added performance results 2024-12-12 17:59:33 +01:00
31734596f6
feat(barcode): Implement cache 2024-12-12 17:59:14 +01:00
850fa0a760
feat(barcode): Baseline for barcode caching 2024-12-12 17:44:41 +01:00
1c0a9860fa
feat(container): Provide default redis 2024-12-12 17:44:26 +01:00
7c32f1aad2
perf(templates): Cache templates in map 2024-12-12 17:34:55 +01:00
b9e550d6f5
refactor(services): Move staticservice to struct and interface 2024-12-12 17:11:59 +01:00
2a4f126377
feat(barcodes): Make width and height configureable 2024-12-12 17:03:10 +01:00
5c932158e9
refactor(handlers): Use shared gotenberg 2024-12-12 16:53:51 +01:00
1296c9e399
refactor(pdf): Share templater 2024-12-12 16:52:06 +01:00
0f2452eca0
feat(handlers): Implement barcode generation 2024-12-12 16:49:33 +01:00
b6cc98a165
refactor(services): Extract barcode generation 2024-12-12 16:43:14 +01:00
c5da33f10f
chore: Formatting 2024-12-12 16:33:23 +01:00
69f4de6739
docs(swagger): Move security annotations 2024-12-12 16:33:10 +01:00
649ac2a3c2
docs(swagger): Added barcode generation docs 2024-12-12 16:31:07 +01:00
5587fdaaa8
feat(barcodes): Baseline for implementation 2024-12-12 16:30:46 +01:00
8f676f08a9
refactor: Switch to pdf subpath 2024-12-12 16:28:06 +01:00
28de60d375 Merge pull request 'go' (#49) from go into main
Reviewed-on: #49
Reviewed-by: Philipp Dormann <philipp@noreply.git.odit.services>
2024-12-12 15:11:30 +00:00
d19029b5ad
docs(swagger): Updated swagger metadata 2024-12-11 19:45:05 +01:00
1dfd96869d
docs(swagger): Swagger auth 2024-12-11 19:39:18 +01:00
a1ba28cacb
docs(dev): Updated air commands 2024-12-11 19:33:06 +01:00
d2bace87af
fix(dev): Add air config for linux/macos 2024-12-11 19:31:48 +01:00
9d507b9572
feat(ci): Added ci config 2024-12-11 19:30:02 +01:00
e89c17806f
docs: Updated readme 2024-12-11 19:29:04 +01:00
924f76a100
refactor(images): Move sponsor images to folder and load them dynamicly 2024-12-11 19:22:50 +01:00
99ec0933ea
feat(container): New dev compose with just external services 2024-12-11 19:21:32 +01:00
cceca7f5e1
feat(container): Deliver default static stuff 2024-12-11 18:51:37 +01:00
54d294a8b4
feat(container): Added document-server 2024-12-11 18:51:06 +01:00
41291b9200
refactor(config): Load gotenberg url from env 2024-12-11 18:45:53 +01:00
4faf76a073
feat: Config from env 2024-12-11 18:30:46 +01:00
715eb8e1cb
chore(models): Removed unused param 2024-12-11 18:07:52 +01:00
2686bee1d1
refactor(auth): Switch to query auth for full link support (to be fixed in future) 2024-12-11 17:59:11 +01:00
57a3777891
fix(templates): Fix string style 2024-12-11 17:56:24 +01:00
f6dc33edb4
feat(v1): header auth for all endpoints 2024-12-11 17:55:29 +01:00
e2cd445aeb
feat(docs): Annotated required params 2024-12-11 17:45:51 +01:00
f5debf58fc
feat(docs): New swagger doc 2024-12-09 17:29:31 +01:00
11a9b51197
refactor(templates): Dynamic image loading 2024-12-09 17:28:53 +01:00
c5dc4f7e79
refactor(templates): Dynamic template loading 2024-12-09 17:22:37 +01:00
f9f30e96c7
refactor(templater): Format decimals according to locale 2024-12-09 17:15:06 +01:00
eff3354867
feat(templates): English certificate template 2024-12-09 17:13:01 +01:00
2bfff006ed
fix(templates): Added missing base64 image 2024-12-09 17:10:13 +01:00
53eab5db94
feat(certificate): Implement adding up 2024-12-09 17:08:01 +01:00
af73b35b18
fix(services): Updated templater comma logic 2024-12-09 17:03:13 +01:00
692f378eab
feat(templater): Implement unit formats 2024-12-09 16:59:39 +01:00
12f61e373f
fix(certificate): Expose german template 2024-12-09 16:48:59 +01:00
79a0062f60
feat(certificates): Endpoint with handler 2024-12-09 16:48:06 +01:00
561e7151c6
feat(certificate): German template 2024-12-09 16:44:46 +01:00
2108c88001
feat(certificates): Base types 2024-12-09 16:36:14 +01:00
2537235ce6
feat(static): Added more sponsor logos 2024-12-03 20:00:42 +01:00
4deda8adf1
feat(templates): Get static mounted images 2024-12-03 19:56:57 +01:00
d910a722df
feat(templates): Fallback image 2024-12-03 19:48:08 +01:00
e10448f1e3
feat(docs): Fresh openapi 2024-12-03 19:25:47 +01:00
f880e9f10c
feat(cards): Generate placeholders for cards without a pair 2024-12-03 19:20:28 +01:00
b179541532
fix(handlers): Return cards as pdf 2024-12-03 19:04:09 +01:00
5098d27ae9
refactor(templates): Add Card ids to card 2024-12-03 19:02:18 +01:00
d8fb9ea2fd
refactor(services): Barcodes always accept strings 2024-12-03 18:55:27 +01:00
5dbe7816cd
fix(card): Use correct template endpoints 2024-12-03 18:51:34 +01:00
1657a10dec
fix(templates): Use correct names 2024-12-03 18:45:27 +01:00
7d22a32cb4
feat(cards): Implement endpoint for card generation 2024-12-03 18:44:17 +01:00
ed4941b403
refactor(templates): Use go syntax for swapped cards 2024-12-03 18:43:46 +01:00
c227c291c9
refactor(handlers): Extract function 2024-12-03 18:36:55 +01:00
389922f22d
feat(model): Card Models 2024-12-03 18:27:24 +01:00
670290b60b
refactor(models): Rename Request model 2024-12-03 18:27:11 +01:00
3345571bd8
refactor(templates): Switched to golang templates 2024-12-03 18:23:20 +01:00
d51e78a442
feat(container): Dockerfile 2024-12-03 18:05:45 +01:00
bd70ac4542
refactor(models): Move options model to models 2024-12-03 18:03:25 +01:00
62f04f7d1c
feat(services): Support for multiple barcode formats 🥳 2024-12-03 18:02:15 +01:00
8812bf2410
feat(services): EAN 13 generation 2024-12-03 17:34:11 +01:00
323c0b0ff9
feat(templates): English Contract template 2024-12-03 16:29:58 +01:00
e9c28efd47
feat(handlers): Implemented contracts generation 2024-12-03 16:25:59 +01:00
e5f9eff54f
feat(templates): Working german sponsoring template 2024-12-03 16:25:43 +01:00
382a799038
feat(services): Implement pdf conversion through gotenberg 2024-12-03 16:25:17 +01:00
f4f5c8b63a
feat(services): Baseline templater 2024-12-03 16:24:57 +01:00
c0dd30f08c
feat(dev): Docker compose for gotenberg 2024-12-03 16:24:34 +01:00
3eb914d640
feat(template): First templates 2024-12-03 15:04:10 +01:00
ba7e02fa30
feat(contracts): Provide locale via post request 2024-12-03 14:47:53 +01:00
56e09dafb9
feat(dev): Automagic building of new api docs 2024-12-03 14:47:37 +01:00
145ebd8346
feat(contracts): Baseline handler 2024-12-02 17:30:59 +01:00
13e9c88a8e
feat(docs): First model for swagger 2024-12-02 17:26:03 +01:00
7d571ad46b
docs: Basic readme 2024-12-02 17:16:17 +01:00
491d31e2e9
feat(docs): Swagger generation 2024-12-02 17:13:05 +01:00
3f35d35016
refactor: Ignore temp stuff for air 2024-12-02 17:12:54 +01:00
aeeb7b3448
refactor: Go Baseline 2024-12-02 16:53:32 +01:00
f9b471b59e
refactor: Switch to new language 2024-12-02 16:43:54 +01:00
8f3faee573
Revert "updated templates"
This reverts commit 6ff2ba702b4df86a67a6adce0b5bbff6a1622569.
2024-12-02 15:57:04 +01:00
6ff2ba702b
updated templates 2024-11-21 17:51:23 +01:00
4ecefe8534
fix(ci): Switched to non-drone env vars 2024-02-13 17:25:07 +01:00
ab9295ee23
Frozen lockfile instead of offline
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-29 20:15:10 +02:00
29d4e21656
lockfile 2023-03-29 20:12:06 +02:00
490f24422b
Switched docker over to pnpm w/cache 2023-03-29 20:11:55 +02:00
67eb761a99
switched ci to pnpm cache 2023-03-29 20:10:34 +02:00
22978e5bbc
🚀Bumped version to v0.6.2
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-15 14:02:13 +01:00
0dbd7311ab
Added static img folder for directly serving images 2023-03-15 14:01:49 +01:00
cbda10e2a1
Added dockerignore for smaller build env 2023-03-15 13:56:53 +01:00
ee3c443216
Fixed docs not getting bundeled 2023-03-15 13:56:33 +01:00
bbe01cd231
🚀Bumped version to v0.6.1
All checks were successful
continuous-integration/drone/push Build is passing
2023-02-23 09:36:41 +01:00
10c68cfb37
Slimmed down docker build thx to copy
All checks were successful
continuous-integration/drone/push Build is passing
2023-02-23 09:27:00 +01:00
c21076a049
Dependency cleanup 2023-02-23 09:23:12 +01:00
fc774d5af3
Dependency Bumps 2023-02-23 09:18:08 +01:00
dd0b60b2ba
🚀Bumped version to v0.6.0
All checks were successful
continuous-integration/drone/push Build is passing
2023-02-23 09:03:09 +01:00
6add09c7df
Version pin 2023-02-23 09:02:45 +01:00
d064b51e1b
Added autochangelog 2023-02-23 09:02:34 +01:00
744f567b7c
Moved drone to kaniko
All checks were successful
continuous-integration/drone/push Build is passing
2023-02-23 09:00:15 +01:00
7dbce9e870
Nerf 2023-02-23 08:57:44 +01:00
72ed2495f6
🚀Bumped version to v0.6.0 2023-02-23 08:56:28 +01:00
05e0c63931
Bumped docker images 2023-02-23 08:56:08 +01:00
fcbcec85c5 🧾New changelog file version [CI SKIP] [skip ci] 2023-02-23 07:48:32 +00:00
b47b2f804f
Lockfile
Some checks failed
continuous-integration/drone/push Build is failing
2023-02-23 08:40:45 +01:00
5f4fc74cd9
Added image 2023-02-23 08:40:38 +01:00
300b8fd01a 🧾New changelog file version [CI SKIP] [skip ci] 2021-07-26 15:18:00 +00:00
8b951dfb3f Merge branch 'dev' of git.odit.services:lfk/document-server into dev
All checks were successful
continuous-integration/drone/push Build is passing
2021-07-26 17:09:58 +02:00
517e8a0819
Updated translation due to customer request 2021-07-26 17:09:46 +02:00
3af4b521d3
Changed default kilometer formatting to min 2 decimals, max 3 2021-07-26 17:06:17 +02:00
dca2829323 🧾New changelog file version [CI SKIP] [skip ci] 2021-07-19 16:17:13 +00:00
57a84c256a Merge pull request 'Fixed decimal separator' (#47) from dev into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #47
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-07-19 16:15:35 +00:00
b78534e1b0 🧾New changelog file version [CI SKIP] [skip ci] 2021-07-19 16:14:59 +00:00
9b85b54da5 Merge branch 'dev' of git.odit.services:lfk/document-server into dev
All checks were successful
continuous-integration/drone/push Build is passing
2021-07-19 18:14:03 +02:00
4b5a86282d 🚀Bumped version to v0.5.4 2021-07-19 18:13:53 +02:00
c9cb03ea95
Fixed decimal separator in docker 2021-07-19 18:13:37 +02:00
c7f57548f3 🧾New changelog file version [CI SKIP] [skip ci] 2021-07-19 15:53:52 +00:00
8d00307170 Merge pull request 'Hotfixes' (#46) from dev into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #46
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-07-19 15:53:40 +00:00
5e92b9a48f 🧾New changelog file version [CI SKIP] [skip ci] 2021-07-19 15:50:28 +00:00
01e1323555 🚀Bumped version to v0.5.3
All checks were successful
continuous-integration/drone/push Build is passing
2021-07-19 17:49:35 +02:00
f8465721cd 🧾New changelog file version [CI SKIP] [skip ci] 2021-07-19 15:41:59 +00:00
4cea7cb32f Merge branch 'dev' of git.odit.services:lfk/document-server into dev
All checks were successful
continuous-integration/drone/push Build is passing
2021-07-19 17:41:13 +02:00
72303b1105
Fix for runner donation array 2021-07-19 17:39:52 +02:00
451b7fbe05 🧾New changelog file version [CI SKIP] [skip ci] 2021-07-19 15:23:19 +00:00
2a3322612d
Fixed Locale comma format
All checks were successful
continuous-integration/drone/push Build is passing
2021-07-19 17:22:03 +02:00
4b4d66ae78 🧾New changelog file version [CI SKIP] [skip ci] 2021-07-04 11:42:27 +00:00
c935950eb0 Merge pull request 'v0.5.2: hotfix TypeError in Runner Certificate generation' (#45) from dev into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #45
2021-07-04 11:41:55 +00:00
573b921197 🧾New changelog file version [CI SKIP] [skip ci] 2021-07-04 11:40:08 +00:00
274c13e358
🚀Bumped version to v0.5.2
All checks were successful
continuous-integration/drone/push Build is passing
2021-07-04 13:39:06 +02:00
ff0421da2f 🧾New changelog file version [CI SKIP] [skip ci] 2021-07-04 11:34:41 +00:00
915baa6efa Merge branch 'bugfix/44-runner-certificates-result-in-a-status-500' into dev
All checks were successful
continuous-integration/drone/push Build is passing
close #44
2021-07-04 13:33:50 +02:00
bac004d74e wrap distanceDonations.reduce in array length check
ref #44
2021-07-04 13:32:53 +02:00
b7b7f6a0ae 🧾New changelog file version [CI SKIP] [skip ci] 2021-04-22 16:16:50 +00:00
11efdebacf Merge pull request 'Release 0.5.1' (#43) from dev into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #43
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-04-22 16:16:12 +00:00
0f2d6f58d6 🧾New changelog file version [CI SKIP] [skip ci] 2021-04-22 16:14:54 +00:00
df8bd1133b Merge branch 'dev' of git.odit.services:lfk/document-server into dev
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-22 18:14:02 +02:00
22fb3edd78 🚀Bumped version to v0.5.1 2021-04-22 18:13:50 +02:00
ded610f114 🧾New changelog file version [CI SKIP] [skip ci] 2021-04-22 16:13:22 +00:00
a4c8dade23 Updated docker-compose example🐳
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-22 18:12:38 +02:00
b6fc069042 Emoji+Chinese fixes🌍 2021-04-22 18:10:32 +02:00
60cc343adf 🧾New changelog file version [CI SKIP] [skip ci] 2021-04-22 15:55:26 +00:00
010f2046ad Added new config options to reamde
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-22 17:54:43 +02:00
c18cb7f135 Merge branch 'dev' of git.odit.services:lfk/document-server into dev 2021-04-22 17:50:46 +02:00
2e7c3e8a5b 🧾New changelog file version [CI SKIP] [skip ci] 2021-04-22 15:50:43 +00:00
ac9be793bd You can now configure the card's code format distinct from the others 2021-04-22 17:50:42 +02:00
c18fc4ec93 Merge branch 'dev' of https://git.odit.services/lfk/document-server into dev
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-22 17:49:40 +02:00
981bae4786 more typo fixes 2021-04-22 17:49:31 +02:00
754d0ca58c 🧾New changelog file version [CI SKIP] [skip ci] 2021-04-22 15:47:59 +00:00
fa26ed6012 typo fixes
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-22 17:47:19 +02:00
cc4a2b4ab4 🧾New changelog file version [CI SKIP] [skip ci] 2021-04-22 15:44:08 +00:00
e97e209746 Now laoding card subtitle from env
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-22 17:41:36 +02:00
8f30d8933f Fixed typo in translation 2021-04-22 17:35:02 +02:00
f78037c0f1 Fixed barcode generation for runenrcard pdfs🐞 2021-04-22 17:32:36 +02:00
3c02e13997 🧾New changelog file version [CI SKIP] [skip ci] 2021-04-16 19:10:33 +00:00
d8f3a6ed06 Dependenc bump 🔝
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-16 21:09:03 +02:00
2ee4c06055 🧾New changelog file version [CI SKIP] [skip ci] 2021-04-11 17:49:45 +00:00
76418f65e1 Quick callstack fix🛠
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-11 19:49:05 +02:00
a57e0909b9 🧾New changelog file version [CI SKIP] [skip ci] 2021-03-31 18:16:18 +00:00
a81db03ba3 Merge pull request 'Release 0.5.0' (#42) from dev into main
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #42
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-03-31 18:15:32 +00:00
7ae4750307 🧾New changelog file version [CI SKIP] [skip ci] 2021-03-31 17:20:16 +00:00
f623c0a7cd 🚀Bumped version to v0.5.0
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-31 19:15:06 +02:00
3fc612488d 📖New license file version [CI SKIP] [skip ci] 2021-03-31 17:11:04 +00:00
f220e70743 🧾New changelog file version [CI SKIP] [skip ci] 2021-03-31 17:10:05 +00:00
d3f7d1a6c9 Merge pull request 'Generate runner certificates feature/36-runner_certificates' (#41) from feature/36-runner_certificates into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #41
2021-03-31 17:09:24 +00:00
82159bed53 Pinned dev dependencies
ref #36
2021-03-31 17:19:48 +02:00
479e28c46c Pinned depencencies (and bumped some)
ref #36
2021-03-31 17:18:43 +02:00
e75f15142e disabled testing for now
ref #36
2021-03-31 17:15:57 +02:00
cec893032d disabled testing for now
ref #36
2021-03-31 17:15:29 +02:00
2278e4ad06 Sorted translations 🌍
ref #36
2021-03-31 17:14:20 +02:00
5a98688d60 Documented new env var
ref #36
2021-03-31 17:13:54 +02:00
63c7beb8b9 Made footer text configureable
ref #36
2021-03-31 17:12:41 +02:00
2a4cfdb2f8 Fixed background opacity
ref #36
2021-03-31 17:08:57 +02:00
a580841973 Addest first coupple of test improvements
ref #36
2021-03-31 16:43:59 +02:00
6b23dea477 Now calculateing total donations (perdistance)
ref #36
2021-03-31 16:31:44 +02:00
e0add846bb Now formatting currency ans distance
ref #36
2021-03-31 16:24:05 +02:00
1d12de7045 Fixed typo
ref #36
2021-03-31 16:17:48 +02:00
b43aeec0cf Added missing interpolations
ref #36
2021-03-31 16:16:37 +02:00
405bb20601 Merge branch 'feature/36-runner_certificates' of git.odit.services:lfk/document-server into feature/36-runner_certificates 2021-03-31 16:15:51 +02:00
ac572f1ea3 Added translations
ref #36
2021-03-31 16:15:49 +02:00
7fea1ca78f Added translations
ref #46
2021-03-31 16:15:42 +02:00
64fce5bd01 Now with embedded background
ref #36
2021-03-31 16:09:15 +02:00
5ba26c4cbf Removed temporary background-image fix
ref #36
2021-03-31 16:05:13 +02:00
b82a32ae3e Fixed page size+background image
ref #36
2021-03-31 15:42:52 +02:00
0af9b81b38 Added new basic certificate endpoint
ref #36
2021-03-31 15:22:16 +02:00
955e11846b Added function for generateing runner certificates
ref #36
2021-03-31 15:18:36 +02:00
d1577cd08d Merge branch 'feature/36-runner_certificates' of git.odit.services:lfk/document-server into feature/36-runner_certificates 2021-03-31 15:13:14 +02:00
6767c3b2d1 Added template strings
ref #36
2021-03-31 15:13:12 +02:00
2b2195727b Added template strings
ref #46
2021-03-31 15:13:08 +02:00
3ca2237953 Added backside table
ref #36
2021-03-31 15:09:14 +02:00
8d6ea4dbf9 Fixed bg image opacity overlay
ref #36
2021-03-31 15:00:12 +02:00
8b71608792 Added front certificate design
ref #36
2021-03-31 14:51:41 +02:00
f1084b59a7 🧾New changelog file version [CI SKIP] [skip ci] 2021-03-30 16:08:57 +00:00
c8dc998ecd Merge pull request 'Release 0.4.3' (#40) from dev into main
All checks were successful
continuous-integration/drone/tag Build is passing
continuous-integration/drone/push Build is passing
Reviewed-on: #40
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-03-30 16:07:37 +00:00
3df3d26708 🧾New changelog file version [CI SKIP] [skip ci] 2021-03-30 16:07:01 +00:00
922e762aa2 Merge branch 'dev' of git.odit.services:lfk/document-server into dev
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-30 18:06:02 +02:00
c3beb3e103 🚀Bumped version to v0.4.3 2021-03-30 18:05:48 +02:00
457ea26cf8 🧾New changelog file version [CI SKIP] [skip ci] 2021-03-30 16:05:37 +00:00
c2d2b66f2f Pipeline mtu fix
Some checks reported errors
continuous-integration/drone/push Build was killed
2021-03-30 18:04:54 +02:00
289a0d8671 🧾New changelog file version [CI SKIP] [skip ci] 2021-03-29 16:48:57 +00:00
94 changed files with 3502 additions and 13008 deletions

46
.air.linux.toml Normal file
View File

@ -0,0 +1,46 @@
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
args_bin = []
bin = "tmp\\main"
cmd = "go build -o ./tmp/main ."
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = false
poll_interval = 0
post_cmd = []
pre_cmd = []
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
main_only = false
time = false
[misc]
clean_on_exit = false
[screen]
clear_on_rebuild = false
keep_scroll = true

46
.air.windows.toml Normal file
View File

@ -0,0 +1,46 @@
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
args_bin = []
bin = "tmp\\main.exe"
cmd = "go build -o ./tmp/main.exe ."
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = false
poll_interval = 0
post_cmd = []
pre_cmd = []
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
main_only = false
time = false
[misc]
clean_on_exit = false
[screen]
clear_on_rebuild = false
keep_scroll = true

3
.dockerignore Normal file
View File

@ -0,0 +1,3 @@
tmp
docker-compose.yaml
.air*.toml

View File

@ -1,152 +0,0 @@
---
kind: secret
name: docker_username
get:
path: odit-registry-builder
name: username
---
kind: secret
name: docker_password
get:
path: odit-registry-builder
name: password
---
kind: secret
name: git_ssh
get:
path: odit-git-bot
name: sshkey
---
kind: pipeline
type: kubernetes
name: build:dev
clone:
disable: true
steps:
- name: clone
image: alpine/git
commands:
- git clone $DRONE_REMOTE_URL .
- git checkout dev
- name: build dev
image: plugins/docker
depends_on: [clone]
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: registry.odit.services/lfk/document-server
tags:
- dev
registry: registry.odit.services
mtu: 1000
- name: run changelog export
depends_on: ["clone"]
image: node:latest
commands:
- npx auto-changelog --commit-limit false -p -u --hide-credit
- name: push new changelog to repo
depends_on: ["run changelog export"]
image: appleboy/drone-git-push
settings:
branch: dev
commit: true
commit_message: 🧾New changelog file version [CI SKIP] [skip ci]
author_email: bot@odit.services
remote: git@git.odit.services:lfk/document-server.git
ssh_key:
from_secret: git_ssh
- name: run full license export
depends_on: ["clone"]
image: node:14.15.1-alpine3.12
commands:
- yarn
- yarn licenses:export
- name: push new licenses file to repo
depends_on: ["run full license export"]
image: appleboy/drone-git-push
settings:
branch: dev
commit: true
commit_message: 📖New license file version [CI SKIP] [skip ci]
author_email: bot@odit.services
remote: git@git.odit.services:lfk/document-server.git
skip_verify: true
ssh_key:
from_secret: git_ssh
trigger:
branch:
- dev
event:
- push
---
kind: pipeline
type: kubernetes
name: build:latest
clone:
disable: true
steps:
- name: clone
image: alpine/git
commands:
- git clone $DRONE_REMOTE_URL .
- git checkout dev
- git merge main
- git checkout main
- name: build latest
depends_on: ["clone"]
image: plugins/docker
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: registry.odit.services/lfk/document-server
tags:
- latest
registry: registry.odit.services
- name: push merge to repo
depends_on: ["clone"]
image: appleboy/drone-git-push
settings:
branch: dev
commit: false
remote: git@git.odit.services:lfk/document-server.git
ssh_key:
from_secret: git_ssh
trigger:
branch:
- main
event:
- push
---
kind: pipeline
type: kubernetes
name: build:tags
steps:
- name: build $DRONE_TAG
image: plugins/docker
depends_on: [clone]
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: registry.odit.services/lfk/document-server
tags:
- '${DRONE_TAG}'
registry: registry.odit.services
trigger:
event:
- tag

23
.env Normal file
View File

@ -0,0 +1,23 @@
LOGLEVEL=debug
PORT=3000
PRODUCION=false
APIKEY=lfk
EVENTNAME=Lauf für Kaya! 2025
CURRENCYSYMBOL=
GOTENBERG_BASEURL=http://localhost:3001
REDIS_ADDR=localhost:6379
CARD_SUBTITLE=Kaya ist cool
CARD_BARCODEFORMAT=ean13
# CARD_BARCODEPREFIX=
SPONSORING_RECEIPTMINIMUM=40
SPONSORING_DISCLAIMER=Kaya ist cool, aber pass auf, dass du nicht zu viel Geld sammelst!
SPONSORING_BARCODEFORMAT=code128
# SPONSORING_BARCODEPREFIX=
CERTIFICATE_FOOTER=Kaya ist cool, danke für deine Unterstützung!
SEPA_BIC=FNOMDEB2
SEPA_NAME=ODIT.Services
SEPA_IBAN=DE25100180000690238989

27
.gitea/workflows/dev.yaml Normal file
View File

@ -0,0 +1,27 @@
name: Build latest image
on:
push:
branches:
- main
jobs:
build-container:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to registry
uses: docker/login-action@v3
with:
registry: registry.odit.services
username: ${{ vars.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v6
with:
push: true
tags: |
${{ vars.REGISTRY }}/lfk/document-server:latest
platforms: linux/amd64,linux/arm64

View File

@ -0,0 +1,27 @@
name: Build release images
on:
push:
tags:
- "*.*.*"
jobs:
build-container:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to registry
uses: docker/login-action@v3
with:
registry: registry.odit.services
username: ${{ vars.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v6
with:
push: true
tags: |
${{ vars.REGISTRY }}/lfk/document-server:${{ github.ref_name }}
platforms: linux/amd64,linux/arm64

142
.gitignore vendored
View File

@ -1,141 +1 @@
# ---> VisualStudioCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/i18n-ally-custom-framework.yml
*.code-workspace
# Local History for Visual Studio Code
.history/
# ---> Node
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
yarn.lock
package-lock.json
build
*.sqlite
*.sqlite-jurnal
/docs
lib
/oss-attribution
*.tmp
*.pdf
tmp

View File

@ -1,11 +0,0 @@
{
"recommendations": [
"2gua.rainbow-brackets",
"christian-kohler.npm-intellisense",
"remimarsal.prettier-now",
"lokalise.i18n-ally",
],
"unwantedRecommendations": [
"antfu.i18n-ally"
]
}

View File

@ -1,8 +0,0 @@
languageIds:
- javascript
- html
keyMatchReg:
- '\{\{__ "([a-zA-Z0-9_]+)"\}\}'
monopoly: false
refactorTemplates:
- '{{__ "$1"}}'

27
.vscode/settings.json vendored
View File

@ -1,27 +0,0 @@
{
"editor.formatOnSave": true,
"typescript.format.enable": true,
"typescript.preferences.quoteStyle": "single",
"[json]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[typescript]": {
"editor.defaultFormatter": "vscode.typescript-language-features",
"editor.codeActionsOnSave": {
"source.organizeImports": true,
// "source.fixAll": true
}
},
"javascript.preferences.quoteStyle": "single",
"javascript.preferences.importModuleSpecifierEnding": "minimal",
"typescript.preferences.importModuleSpecifierEnding": "minimal",
"typescript.preferences.includePackageJsonAutoImports": "on",
"i18n-ally.localesPaths": "src/locales",
"i18n-ally.keystyle": "nested",
"i18n-ally.extract.keygenStrategy":"slug",
"i18n-ally.enabledFrameworks": [
"custom"
],
"i18n-ally.sourceLanguage": "en"
}

View File

@ -1,294 +0,0 @@
### Changelog
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [v0.4.2](https://git.odit.services/lfk/document-server/compare/v0.4.0...v0.4.2)
- 🚀Bumped version to v0.4.1 [`d17108f`](https://git.odit.services/lfk/document-server/commit/d17108f4b9f041727afcee5c171d5d8f9a3c677c)
- 🧾New changelog file version [CI SKIP] [skip ci] [`0ade575`](https://git.odit.services/lfk/document-server/commit/0ade57536e372a62356fe2f329f6cfe04bcfc397)
- Fixed faulty object mapping [`9584bfe`](https://git.odit.services/lfk/document-server/commit/9584bfed8ba5b8c856b55aa90969874d4d6751ff)
- 🧾New changelog file version [CI SKIP] [skip ci] [`8bac1fa`](https://git.odit.services/lfk/document-server/commit/8bac1fadd6b4257e5062a1b1673e122c5b535046)
- 🧾New changelog file version [CI SKIP] [skip ci] [`b8f0d1f`](https://git.odit.services/lfk/document-server/commit/b8f0d1fa60da4e3262623cecf4a78c8a9a322f8c)
- 🧾New changelog file version [CI SKIP] [skip ci] [`073433f`](https://git.odit.services/lfk/document-server/commit/073433f308f2f8e7ada97dd0b3896fb541e85711)
- 🧾New changelog file version [CI SKIP] [skip ci] [`a478081`](https://git.odit.services/lfk/document-server/commit/a47808172797e12800c46d17ccf4b458f4a506de)
- 🧾New changelog file version [CI SKIP] [skip ci] [`0839ff6`](https://git.odit.services/lfk/document-server/commit/0839ff63593cfb9e8339b2b2a86dbe7e2afb5dcb)
- Now using our own images to build stuff 🚀 [`96886c7`](https://git.odit.services/lfk/document-server/commit/96886c74bc397e1ee793e83e0d01a6bbaa953da3)
- 🚀Bumped version to v0.4.2 [`1cbe5a1`](https://git.odit.services/lfk/document-server/commit/1cbe5a1614c40fbf23f032be60039ddb1472ca94)
- Emergency bugfixes 🐞 [`3c42ca3`](https://git.odit.services/lfk/document-server/commit/3c42ca30427174c21e0db6426d8f0d91a1ebc501)
- Let's try the mtu fix [`c51ec74`](https://git.odit.services/lfk/document-server/commit/c51ec74d30334b156668de4719c77f2e9c6d023f)
#### [v0.4.0](https://git.odit.services/lfk/document-server/compare/v0.3.3...v0.4.0)
> 14 March 2021
- Merge pull request 'Alpha Release 0.4.0' (#38) from dev into main [`8d3cc34`](https://git.odit.services/lfk/document-server/commit/8d3cc34395922a271da1032310da6ec6f7a164f7)
- Updated ci with new kubernetes secrets 🚀🚀🚀 [`b180e04`](https://git.odit.services/lfk/document-server/commit/b180e0404537404d0564453fde5976f4de672a8b)
- Updated styleing [`03ed6d5`](https://git.odit.services/lfk/document-server/commit/03ed6d5bc18c5b26edd7b1d698c9de936e008f32)
- 🧾New changelog file version [CI SKIP] [skip ci] [`b612562`](https://git.odit.services/lfk/document-server/commit/b612562d347f5fafcf4ce94a4508b7c0bb9370c6)
- 🚀Bumped version to v0.4.0 [`85519bc`](https://git.odit.services/lfk/document-server/commit/85519bc2e4e8906732dc5f99e417014a14bad7d0)
- Merge pull request 'Configureable contracts amount feature/35-contracts_per_runner' (#37) from feature/35-contracts_per_runner into dev [`ed02306`](https://git.odit.services/lfk/document-server/commit/ed023067386551dc92d60ba63dff8bc4cc44ce00)
- Added disclaimer to template [`4e1e124`](https://git.odit.services/lfk/document-server/commit/4e1e124d0dc08f96aeb05dd1c975064fe785b80f)
- Changed ci pipeline type to kubernetes [`677bd86`](https://git.odit.services/lfk/document-server/commit/677bd861335302269baec96a59916475bbdd405f)
- Added config for amount of contracts per runner [`d01b4a0`](https://git.odit.services/lfk/document-server/commit/d01b4a0b99f3c1aa088b02d766d21bfb6b9e3a29)
- Added logic for generateing multiple contracts per runner [`d991940`](https://git.odit.services/lfk/document-server/commit/d9919404b5b90151318d20258a273ca6c7640393)
- MAde disclaimer configurable via env [`5e4d6f4`](https://git.odit.services/lfk/document-server/commit/5e4d6f44dab581483135594432bd8c8d65ba74ef)
- MAde disclaimer configurable via env [`885765a`](https://git.odit.services/lfk/document-server/commit/885765ac71b6a65c869ee841c383667f4947dcde)
- Documented the new config var [`31a5950`](https://git.odit.services/lfk/document-server/commit/31a59500fa77f1ad7c54fa297d00c8707ae6db9c)
- Merge pull request 'Configurable sponsoring disclaimer feature/33-disclaimer' (#34) from feature/33-disclaimer into dev [`e9d3574`](https://git.odit.services/lfk/document-server/commit/e9d35745997a9e948509ea67b59f5a7658f9d652)
- 🧾New changelog file version [CI SKIP] [skip ci] [`19fbf50`](https://git.odit.services/lfk/document-server/commit/19fbf50f6f6057c5321a999b6551d05db32b5fd8)
- Added reamde entry for the new env var [`9dd62ce`](https://git.odit.services/lfk/document-server/commit/9dd62cefa94bf3f0bf8e77fd25e7ca1ec4b30e2b)
#### [v0.3.3](https://git.odit.services/lfk/document-server/compare/v0.3.2...v0.3.3)
> 19 February 2021
- Merge pull request 'Alpha Release 0.3.3 - Download options' (#32) from dev into main [`21b5e04`](https://git.odit.services/lfk/document-server/commit/21b5e048ed5ed72f88304e5c737639c54cc71483)
- 🧾New changelog file version [CI SKIP] [skip ci] [`d1a29c1`](https://git.odit.services/lfk/document-server/commit/d1a29c1cbb8845a1adf8a414d158ebcabd48dc04)
- 🧾New changelog file version [CI SKIP] [skip ci] [`fd8b7e5`](https://git.odit.services/lfk/document-server/commit/fd8b7e56da4a1601141f63627e3990660c521ea6)
- Added download option to cards [`1ca5d3e`](https://git.odit.services/lfk/document-server/commit/1ca5d3ea078ef51818657e2b3c9f114c67bff86d)
- Added download header [`55877de`](https://git.odit.services/lfk/document-server/commit/55877de2aa46583f30d033467037e0454d1236be)
- 🧾New changelog file version [CI SKIP] [skip ci] [`c012b49`](https://git.odit.services/lfk/document-server/commit/c012b4943d96ee3a9442c65389c4dcea08642109)
- 🚀Bumped version to v0.3.3 [`ba566bc`](https://git.odit.services/lfk/document-server/commit/ba566bcc33ed920324cd9977b3f293a2c629f92c)
- 🧾New changelog file version [CI SKIP] [skip ci] [`a386c5b`](https://git.odit.services/lfk/document-server/commit/a386c5bef88da29e4447399d4edca0234b674ed5)
#### [v0.3.2](https://git.odit.services/lfk/document-server/compare/v0.3.0...v0.3.2)
> 18 February 2021
- Merge pull request 'Alpha Release 0.3.2' (#31) from dev into main [`e6f7dd2`](https://git.odit.services/lfk/document-server/commit/e6f7dd2be8d729f5fc7fbb458cc859b113f5aa86)
- 🚀Bumped version to v0.3.1 [`dcde424`](https://git.odit.services/lfk/document-server/commit/dcde424b77dcc9753859f94f7bcbe24fe3523c27)
- 📖New license file version [CI SKIP] [skip ci] [`92c5240`](https://git.odit.services/lfk/document-server/commit/92c52401b398f6a2f247c10879e17f6dc105aa8e)
- Now manually parsing runnergroup full names [`c290908`](https://git.odit.services/lfk/document-server/commit/c2909082a2dbb38041ae0fc695bd0fa1451b39ff)
- Implemented basic auth [`bdeadd2`](https://git.odit.services/lfk/document-server/commit/bdeadd274bc0f9c8cbab35a8a5605bef4c22ba6c)
- 📖New license file version [CI SKIP] [skip ci] [`9d7d044`](https://git.odit.services/lfk/document-server/commit/9d7d0443848522e5bdfdb6a80c836bea4bc200a1)
- 🧾New changelog file version [CI SKIP] [skip ci] [`cc6a53b`](https://git.odit.services/lfk/document-server/commit/cc6a53b25895594874acca370a1439d37bb280de)
- 🧾New changelog file version [CI SKIP] [skip ci] [`e306cdb`](https://git.odit.services/lfk/document-server/commit/e306cdb2c8e58fc1aef79b95cba5d4cc96ac7658)
- Added auth to openapi spec [`729f2d7`](https://git.odit.services/lfk/document-server/commit/729f2d7240b54ffe2d4db36cce29de0afdfc9417)
- Pinned routing controllers version as temp workaround for routing controllers openapi being broken again [`7ca7266`](https://git.odit.services/lfk/document-server/commit/7ca7266ea46965251c9df637a2556f2a1706e7e6)
- 🧾New changelog file version [CI SKIP] [skip ci] [`b7c6c6e`](https://git.odit.services/lfk/document-server/commit/b7c6c6e15708e471f5c3d0ca4cf11b1c08c88c9c)
- 🧾New changelog file version [CI SKIP] [skip ci] [`95099c5`](https://git.odit.services/lfk/document-server/commit/95099c5fbd7e6cb07c68151a998eebb0f00556f3)
- 🧾New changelog file version [CI SKIP] [skip ci] [`49590b8`](https://git.odit.services/lfk/document-server/commit/49590b897ed5ee5c9386d34408ac6868f43cc27c)
- 🚀Bumped version to v0.3.2 [`a9019e4`](https://git.odit.services/lfk/document-server/commit/a9019e4c67e7620b65650d60b0ebd57bc11a854e)
- Merge pull request 'Now using full group names feature/18-group_names' (#30) from feature/18-group_names into dev [`e0db6f6`](https://git.odit.services/lfk/document-server/commit/e0db6f6a78d0026a8485a57c88af1e9407bd68a4)
- 🧾New changelog file version [CI SKIP] [skip ci] [`adf11ab`](https://git.odit.services/lfk/document-server/commit/adf11ab1c356b6964230541331836abd363170b0)
- Updated templates with full group name [`0fcfb30`](https://git.odit.services/lfk/document-server/commit/0fcfb30d5c13266ca4faf7697308dfb7a0f91b4f)
- Merge pull request 'Alpha Release 0.3.1 - API Keys' (#29) from dev into main [`db91661`](https://git.odit.services/lfk/document-server/commit/db916615564813e8d21e3672581e4f3a4d748b89)
- Merge pull request 'API Key based auth feature/26-api_auth' (#27) from feature/26-api_auth into dev [`2d031da`](https://git.odit.services/lfk/document-server/commit/2d031dae035866a4aa247398ea68ff338ab58cbd)
- Added api key to env doc [`4543092`](https://git.odit.services/lfk/document-server/commit/454309278ef20a2b97248277b07a7b58a063618d)
- Fixed bug [`7be211f`](https://git.odit.services/lfk/document-server/commit/7be211f8b7b26f7f620df81af4ebde5eec2feec2)
#### [v0.3.0](https://git.odit.services/lfk/document-server/compare/v0.2.0...v0.3.0)
> 12 February 2021
- Merge pull request 'Alpha Release 0.3.0 - Runnercard generation' (#25) from dev into main [`406add3`](https://git.odit.services/lfk/document-server/commit/406add3d517473d01628b6405569de6cb85114e0)
- 🚀Bumped version to v0.2.0 [`491cdb8`](https://git.odit.services/lfk/document-server/commit/491cdb8d71a80ea196d16334c0c80b8f7cc859c5)
- Added card generation speed tests (part 1) [`68572b1`](https://git.odit.services/lfk/document-server/commit/68572b194eb740238be8101efed6fdb2a207f65b)
- Implemented first experimental speedtest [`e3a45a6`](https://git.odit.services/lfk/document-server/commit/e3a45a61ac3b2d691c2f75d36155896b7ed301d8)
- Added basic logic to generate two-sided runnercards [`d3a213c`](https://git.odit.services/lfk/document-server/commit/d3a213ce3326aeb96d924e16a31fc87bf82eb5b3)
- 🧾New changelog file version [CI SKIP] [skip ci] [`149bf18`](https://git.odit.services/lfk/document-server/commit/149bf1849db20b863ec998a72c77559ec401bc32)
- Fixed double-sided printing [`7f58dd6`](https://git.odit.services/lfk/document-server/commit/7f58dd694b53152069c2095b2e18dd3a46cd04dd)
- 🧾New changelog file version [CI SKIP] [skip ci] [`e74b9a4`](https://git.odit.services/lfk/document-server/commit/e74b9a4c9d041780e0cfd8c4d68a5b63f916e091)
- Added basic card generation function [`8fc6c71`](https://git.odit.services/lfk/document-server/commit/8fc6c7176ee92f813db1e1d4b3e5ef1b2f4e1aef)
- Beautified output a bit [`aefe549`](https://git.odit.services/lfk/document-server/commit/aefe5493b06c04cc2b20029e1f7fc5f15ec9c04e)
- Added barcode generatin [`5c075bc`](https://git.odit.services/lfk/document-server/commit/5c075bce8b94ff4482448c3cd56bdc28cbe0a7d9)
- Implemented runner generation using fakerjs [`0894446`](https://git.odit.services/lfk/document-server/commit/08944460854c78cd4368cf178b022c31b624c8d9)
- Styled front [`016f746`](https://git.odit.services/lfk/document-server/commit/016f746c7cec29ab391b3918c7589dea0cff9890)
- Now loading sponsor logos from env [`29376a7`](https://git.odit.services/lfk/document-server/commit/29376a7782ce39f04f856ec78775e65aa11f0ed7)
- Implmented sponsoring image selection from array [`68a1b8f`](https://git.odit.services/lfk/document-server/commit/68a1b8f3e0515e56c7c6069f7f3ef8db24c92674)
- 📖New license file version [CI SKIP] [skip ci] [`dbccbf6`](https://git.odit.services/lfk/document-server/commit/dbccbf68f4e22470b4fc93c894527cc78973e324)
- Added cards api endpoint [`929ac81`](https://git.odit.services/lfk/document-server/commit/929ac81515b3b426ff06f1d6d913bab930421a92)
- 🧾New changelog file version [CI SKIP] [skip ci] [`96204d8`](https://git.odit.services/lfk/document-server/commit/96204d809e852c1bcea808a9c8f5ca0f8475c87f)
- Fixed runnercard backside padding [`08e8587`](https://git.odit.services/lfk/document-server/commit/08e858726c1462b599ba9cb3f7fb057f35178b83)
- Added sizing for the real cards [`b92a6f7`](https://git.odit.services/lfk/document-server/commit/b92a6f7b2b98fb0074d5a563d9918295e9ec0274)
- 🧾New changelog file version [CI SKIP] [skip ci] [`8a90f63`](https://git.odit.services/lfk/document-server/commit/8a90f63b0919376beefef6a52aae9a59337aea59)
- 🚀Bumped version to v0.3.0 [`449a96b`](https://git.odit.services/lfk/document-server/commit/449a96b3027fe93d8042b30420245f66e92f14b8)
- Merge pull request 'Card generation feature/14-card_generation' (#24) from feature/14-card_generation into dev [`703eaa0`](https://git.odit.services/lfk/document-server/commit/703eaa0e9d667b628eab4e8496689fe66238f896)
- Added speedtest script to package [`75b8b28`](https://git.odit.services/lfk/document-server/commit/75b8b281b87d9b173093f16beae12d707ec05052)
- Fixed bug in array swapping function [`9697d53`](https://git.odit.services/lfk/document-server/commit/9697d53a1527854536f8ddf5426f7ca902772f51)
- Added **very** basic backside [`68f46a4`](https://git.odit.services/lfk/document-server/commit/68f46a45b5a51c8a8edafca852cb274af388fa76)
- Added card generation speed tests (part 2) [`d38923c`](https://git.odit.services/lfk/document-server/commit/d38923c4ad3a2cf8872e236dd42f078e2a0e1045)
- Implemented basic contracts tests in various sizes [`a1b0a19`](https://git.odit.services/lfk/document-server/commit/a1b0a1918db552eb385fedcbaa576ad493a1d605)
- tmp [`0d27916`](https://git.odit.services/lfk/document-server/commit/0d27916188114fa41e666170135de2b714ff113a)
- Added new env vars to readme [`cf0f583`](https://git.odit.services/lfk/document-server/commit/cf0f5839ee1e1b87f7b5bd5a299a35574fd1bb3c)
- Merge pull request 'Added speedtest feature/19-speed_test' (#23) from feature/19-speed_test into dev [`7ac8edb`](https://git.odit.services/lfk/document-server/commit/7ac8edb5cf4b7317a703faa32ded482a8c2b9b91)
#### [v0.2.0](https://git.odit.services/lfk/document-server/compare/v0.1.3...v0.2.0)
> 9 February 2021
- Merge pull request 'Alpha Release 0.2.0 - The barcode release' (#22) from dev into main [`b952ac4`](https://git.odit.services/lfk/document-server/commit/b952ac4d728952e1fb6d26b0929e3f946748b85b)
- Implemented async barcode generation using async helpers [`edc846a`](https://git.odit.services/lfk/document-server/commit/edc846ab05319a4e60422625678f204bc145884c)
- 📖New license file version [CI SKIP] [skip ci] [`fea0b1d`](https://git.odit.services/lfk/document-server/commit/fea0b1dc0582d2906bad22f2ff5e5aad90a1c885)
- Reworked template layout for barcode [`1c06689`](https://git.odit.services/lfk/document-server/commit/1c066898009883f510fa204c66800e5f6228a15d)
- 🧾New changelog file version [CI SKIP] [skip ci] [`7122fe7`](https://git.odit.services/lfk/document-server/commit/7122fe7dbe3fae64806492636255147078eb03c8)
- Merge pull request 'Barcode generation feature/13-barcode_generation' (#21) from feature/13-barcode_generation into dev [`ff36b48`](https://git.odit.services/lfk/document-server/commit/ff36b4871f2d696c0b86883d529365ee8f1c6132)
- Now with working code scaleing [`4b79b29`](https://git.odit.services/lfk/document-server/commit/4b79b29ee6319559c9d68ddb11f831d25f12b3da)
- Added basic barcode generation [`8072d0b`](https://git.odit.services/lfk/document-server/commit/8072d0b1940ef6f316ce78dcbcb9e5af5bab04e7)
- Now loading barcode format from env with overwrite via query param [`9a7c1d6`](https://git.odit.services/lfk/document-server/commit/9a7c1d64fdbdadbd104739133a87773e4d2bca01)
- Added fallback error image [`5023457`](https://git.odit.services/lfk/document-server/commit/502345782f26895ccf3089d15c3817709b62dfcc)
- First part of the handlebars barcode generation [`a35f8cf`](https://git.odit.services/lfk/document-server/commit/a35f8cfd3aa94923968fd77425c074844d28ec0d)
- Added todo [`75d2ac3`](https://git.odit.services/lfk/document-server/commit/75d2ac3c5f80f8440b6d48c33b15ef17565559b3)
- Removed promise [`e1ec193`](https://git.odit.services/lfk/document-server/commit/e1ec193a4ff1cd618da90f5f2d029ec848a6f669)
- 🧾New changelog file version [CI SKIP] [skip ci] [`03f63e3`](https://git.odit.services/lfk/document-server/commit/03f63e3777381a4475910e6fa4a3986f87b73f39)
- Fixed broken mime-type [`4187a8e`](https://git.odit.services/lfk/document-server/commit/4187a8e82015495c0e0362e957e236ed6935a908)
- Switched to using the current runner's id as the barcode text [`3e2b011`](https://git.odit.services/lfk/document-server/commit/3e2b011d2887d261fb9c36820982095d6dd6d847)
- Added barcode field to template [`27d1d69`](https://git.odit.services/lfk/document-server/commit/27d1d69360c8513079abcfe3a6fc2a50309a2b61)
#### [v0.1.3](https://git.odit.services/lfk/document-server/compare/v0.1.2...v0.1.3)
> 9 February 2021
- Merge pull request 'Alpha Release 0.1.3 - More env vars' (#20) from dev into main [`ecd02a1`](https://git.odit.services/lfk/document-server/commit/ecd02a1af7431d0bf615c4ec064f64e023946e49)
- 🚀Bumped version to v0.1.3 [`6a14232`](https://git.odit.services/lfk/document-server/commit/6a142328898d5b89fa11eaf033372971d1093b0c)
- 🧾New changelog file version [CI SKIP] [skip ci] [`ad9a8a4`](https://git.odit.services/lfk/document-server/commit/ad9a8a4fe0649d48db924771be8ecb4cbf5c162a)
- 🧾New changelog file version [CI SKIP] [skip ci] [`b6296b8`](https://git.odit.services/lfk/document-server/commit/b6296b8d97cda943dfb5e11bc9dfbb2f363f5b81)
- Merge pull request 'Load more stuff from env feature/16-env_vars' (#17) from feature/16-env_vars into dev [`bc4d16e`](https://git.odit.services/lfk/document-server/commit/bc4d16e6f8959ed35d7e87647de84584cdfddd7b)
- Added new env vars to config [`3bb322e`](https://git.odit.services/lfk/document-server/commit/3bb322ede5db15a147c0d7a8db2a68ccb7fa2112)
- Added new env vars to readme [`b77bb3a`](https://git.odit.services/lfk/document-server/commit/b77bb3ad9dba9d73c2c81215ba57936192155a9a)
- Now loading interpolation vars from config/env [`b4ebae2`](https://git.odit.services/lfk/document-server/commit/b4ebae283b472b2f0c6e28caed49b30edb119585)
- 🧾New changelog file version [CI SKIP] [skip ci] [`a306009`](https://git.odit.services/lfk/document-server/commit/a30600943d01116b99e946cb705a16d0372b5095)
#### [v0.1.2](https://git.odit.services/lfk/document-server/compare/v0.1.1...v0.1.2)
> 7 February 2021
- Merge pull request 'Alpha Release 0.1.2 - Hotfix release' (#15) from dev into main [`123cf8a`](https://git.odit.services/lfk/document-server/commit/123cf8ad48a45fa10dcd5208215a6e525f31115a)
- 🧾New changelog file version [CI SKIP] [skip ci] [`22b1e00`](https://git.odit.services/lfk/document-server/commit/22b1e0097efc865de9cc150cb0d0b99bf789b519)
- 🚀Bumped version to v0.1.2 [`7e507d4`](https://git.odit.services/lfk/document-server/commit/7e507d4cc415877ac0b25503dc0ff9ecdceabf42)
- PAtch: Copy locales [`f7dfd6d`](https://git.odit.services/lfk/document-server/commit/f7dfd6d0c3c69881338bc1f66d5d33ae9abff628)
#### [v0.1.1](https://git.odit.services/lfk/document-server/compare/v0.1.0...v0.1.1)
> 7 February 2021
- Merge pull request 'Alpha Release 0.1.1 - Hotfix release' (#12) from dev into main [`cbff307`](https://git.odit.services/lfk/document-server/commit/cbff3074d10110dbb64dda1ed516d2a18402eeca)
- 🧾New changelog file version [CI SKIP] [skip ci] [`4fca5af`](https://git.odit.services/lfk/document-server/commit/4fca5afdfa15d3260ac28c1feb89d50221b1f03f)
- Updated dockerfile & build for dockerized puppeteer [`ea8028d`](https://git.odit.services/lfk/document-server/commit/ea8028dfd54861335e9752334db335ad21e75cf2)
- 📖New license file version [CI SKIP] [skip ci] [`8d4e7a1`](https://git.odit.services/lfk/document-server/commit/8d4e7a16d1ebae6976ec183b761ca3e6a8946629)
- Revert "Now only using our mirror to build" [`fe0f45e`](https://git.odit.services/lfk/document-server/commit/fe0f45ea92099a65d998e342ef885af39d06915f)
- Now only using our mirror to build [`b07c5a9`](https://git.odit.services/lfk/document-server/commit/b07c5a96f2a670f2279a6575b03f9c553b59e0a9)
- 🧾New changelog file version [CI SKIP] [skip ci] [`9755e43`](https://git.odit.services/lfk/document-server/commit/9755e437c2b68c09e9b0ffa47c6bc1729e6bee14)
- 🧾New changelog file version [CI SKIP] [skip ci] [`603a814`](https://git.odit.services/lfk/document-server/commit/603a814ad4783c0b29c1f6a1db638f269b29f775)
- Switched to using our mirrored images for buildi [`dba765c`](https://git.odit.services/lfk/document-server/commit/dba765cb017998ad636f3f91fb00a8a277d3b225)
- 🚀Bumped version to v0.1.1 [`d1b07f3`](https://git.odit.services/lfk/document-server/commit/d1b07f39fd7e3c282a631924bf45367e866e667a)
- Pinned alpine version [`6ab3946`](https://git.odit.services/lfk/document-server/commit/6ab3946c31a7716d1c42fb217b0a0f7be8172dba)
#### v0.1.0
> 7 February 2021
- Merge pull request 'Alpha Release 0.1.0 - Contract generation' (#11) from dev into main [`bc2b6fa`](https://git.odit.services/lfk/document-server/commit/bc2b6fadd912f275bc05be29c6fbd87367d617df)
- 📖New license file version [CI SKIP] [skip ci] [`ee19efa`](https://git.odit.services/lfk/document-server/commit/ee19efa0e63f21a10fed1cf24beb16118bbe6cde)
- 🚀Bumped version to v0.0.2 [`de432c4`](https://git.odit.services/lfk/document-server/commit/de432c448129a8acb36fef6b40ef920eeb2f7887)
- First working (TM) template code [`a9e3360`](https://git.odit.services/lfk/document-server/commit/a9e3360ee258e4d48c8dbe3a55468409bb6ee0f7)
- Working Styleing [`c617c40`](https://git.odit.services/lfk/document-server/commit/c617c40e9d7e03382f647eafde9ff458db3c6c15)
- Removed base64 image [`e401d0e`](https://git.odit.services/lfk/document-server/commit/e401d0ec7219e88fbb277b2dcf456a22dae0d1e5)
- Updated the contract template to use external css [`6121b1e`](https://git.odit.services/lfk/document-server/commit/6121b1e3bfcf6fc9f88e0589205044c59859b4a7)
- Sorted locales [`1962499`](https://git.odit.services/lfk/document-server/commit/1962499d18685347302fd7d8491c6e3e7a355fcb)
- Switched to puppeteer for pdf generation [`5afd26e`](https://git.odit.services/lfk/document-server/commit/5afd26ea22cff3b8a339cf658ed37d2053db24ba)
- Added a coupple of real replacement strings to the sponsoring template [`041c0ed`](https://git.odit.services/lfk/document-server/commit/041c0ed6bbe5f8645e4e96b57c02d4e45b445402)
- Added automatic img to base64 conversion [`42443af`](https://git.odit.services/lfk/document-server/commit/42443af734ccc9ce202aec045a07775f4a054b92)
- Added i18n strings [`e345c36`](https://git.odit.services/lfk/document-server/commit/e345c36dd0b03f276259521aebf423853a81a04d)
- first working pdf generation from class 🎉 [`3af76a5`](https://git.odit.services/lfk/document-server/commit/3af76a53e3156bdc2d1d3ade6ff940db9d7b24b5)
- Added a bunch of english and german translations 🌎 [`784d7c6`](https://git.odit.services/lfk/document-server/commit/784d7c656fd2dfc726722459f3ccb7612021b882)
- Added translations using i18next [`75eb925`](https://git.odit.services/lfk/document-server/commit/75eb9252672883f7b6f89a812a37d2b96f9f626e)
- Switched to handlebars for templateing [`4acf3e3`](https://git.odit.services/lfk/document-server/commit/4acf3e39ce55c0ad9e697cf9598569ec1c1dcb44)
- Moved pdf creatior initialization to new function [`47a05fa`](https://git.odit.services/lfk/document-server/commit/47a05facb33c178e9bf3f99c25049abc010361c4)
- 🧾New changelog file version [CI SKIP] [skip ci] [`f3de80a`](https://git.odit.services/lfk/document-server/commit/f3de80af78c96bd697715a30dee7cae6806f9f9f)
- 🚀Bumped version to v0.1.0 [`6a42fe3`](https://git.odit.services/lfk/document-server/commit/6a42fe37d053a6a00005e3c5781edfcefe61183e)
- Merge pull request 'Sponsoring contract generation feature/5-sponsoring_contracts' (#10) from feature/5-sponsoring_contracts into dev [`84259d3`](https://git.odit.services/lfk/document-server/commit/84259d37d42234e1be75f3a5c95f4b442a9e5844)
- Added optimization args [`140fda1`](https://git.odit.services/lfk/document-server/commit/140fda11cc88cb93218116d4cbdbf13c014a659d)
- Added first parts of template [`13776d1`](https://git.odit.services/lfk/document-server/commit/13776d1ecb2db03f19dd0c01a930d4c98222cf6b)
- Added pdf merging for big requests (over 100 runners) [`785544c`](https://git.odit.services/lfk/document-server/commit/785544ca16cd4907a378eb2d48e4269778e3dccc)
- Fixed file broken in merge [`c07c6ae`](https://git.odit.services/lfk/document-server/commit/c07c6aeeab70589a4e1ddf025a07bde96c68ef6e)
- Now with workin i18n-ally config🥳 [`7c3813b`](https://git.odit.services/lfk/document-server/commit/7c3813b94d06ca38b83db90b7f9fba6c33716a05)
- Now with workin i18n-ally config🥳 [`b4232e5`](https://git.odit.services/lfk/document-server/commit/b4232e51e084f491d1a7fb4f929327e88f3b4c00)
- Now accepting arrays for sponsoring contract generation [`f833ae2`](https://git.odit.services/lfk/document-server/commit/f833ae222e68b7df9b8f8a8ec7ce17d679a9710f)
- Added full interpolation support to the i18n [`f755f4f`](https://git.odit.services/lfk/document-server/commit/f755f4f9fb1463af6bec3cf73b7fabf95ef3fb2e)
- Added option to generate empty sponsoring contracts [`1ced0e3`](https://git.odit.services/lfk/document-server/commit/1ced0e317544217599a42c6b270c9136dc73676d)
- Added translation sorting secript [`1e67672`](https://git.odit.services/lfk/document-server/commit/1e67672ef0d28f94a50b0377cca8e8ff2a87e983)
- Removed the example data from the fill-in fields [`5e12525`](https://git.odit.services/lfk/document-server/commit/5e1252545b7eafeee6c3a1d67567d00e5d615d85)
- Edited html template [`d58453f`](https://git.odit.services/lfk/document-server/commit/d58453faf9840037c583e029c41ef1b31a0bafa8)
- PDF Creator now accepts single instances of class [`78205ee`](https://git.odit.services/lfk/document-server/commit/78205ee8c7659ae0dc2e4a184554b042251d9271)
- PDF Creator now accepts single instances of class [`ba7cedd`](https://git.odit.services/lfk/document-server/commit/ba7cedd187dbc51a53ba169889d8e0ca22a97b5d)
- Updated template for i18n [`a596188`](https://git.odit.services/lfk/document-server/commit/a596188c00444c285565eecbb1f0bc668bb4706c)
- Parellized the chunks [`e92820e`](https://git.odit.services/lfk/document-server/commit/e92820e12f289b4fd28b4ebc985b5b711777fc35)
- Fixed the controller not waiting for initialization [`ee8ba99`](https://git.odit.services/lfk/document-server/commit/ee8ba99cc7778f89a8bb99400c7d1cf1a12898f7)
- Implemented language selection by query param [`6f81566`](https://git.odit.services/lfk/document-server/commit/6f81566cb82a0801a20523a7177996ea40cb3968)
- Added comments [`cd51e78`](https://git.odit.services/lfk/document-server/commit/cd51e78282278cae6cb86bd0c9616746612a97e3)
- Added example locales [`1969769`](https://git.odit.services/lfk/document-server/commit/19697692bcb6c0c1c978918916caccd95a8f2035)
- Fixed data validation problem [`bd6ec62`](https://git.odit.services/lfk/document-server/commit/bd6ec6215d362325c510611e4545b7643b918200)
- Removed fullname from groups (to be renabled later) [`388d8a2`](https://git.odit.services/lfk/document-server/commit/388d8a2dc6ee06d39e621e1e45ae595dae587483)
- Updated comments [`119102a`](https://git.odit.services/lfk/document-server/commit/119102aef8f43a68f537e7542c2f2cd3b41cd28e)
- Updated readme for handlebars [`05e4718`](https://git.odit.services/lfk/document-server/commit/05e471878f2321a1873b21730ae85ad6b8af5766)
- Updated readme [`f69e777`](https://git.odit.services/lfk/document-server/commit/f69e7779e42f18df7ec3ccb1cdf9dcf7fa845ee6)
- 🧾New changelog file version [CI SKIP] [skip ci] [`f4a34dd`](https://git.odit.services/lfk/document-server/commit/f4a34dd36b57bd122a650f2de4ea8e00816276ed)
- Typos [`95b882a`](https://git.odit.services/lfk/document-server/commit/95b882aceded275ad89446ad92a10cf36b166ab7)
- Added sentence about large requests to the /contracts openapi description [`4773a5f`](https://git.odit.services/lfk/document-server/commit/4773a5f18c866b6e9d50b421d7917416860cb6b5)
- Removed puppeteer args [`06dedc0`](https://git.odit.services/lfk/document-server/commit/06dedc0797b649f3fa8934f0d774ccae2b062849)
- Expanded max request body size to 500mb [`4f191dc`](https://git.odit.services/lfk/document-server/commit/4f191dcb52cd727e34770ab4304937cd1e7e480d)
- Now awaiting language change [`c07319b`](https://git.odit.services/lfk/document-server/commit/c07319b250db9452f6122e797b6059151031e2a9)
- 🧾New changelog file version [CI SKIP] [skip ci] [`42d6c40`](https://git.odit.services/lfk/document-server/commit/42d6c40d0c673f39c27e3d5a6484f01a4da77266)
- Formatting [`2d0b7ce`](https://git.odit.services/lfk/document-server/commit/2d0b7ce79ed47d4827096a2bbccfe3bbcd061810)
- Removed useless await [`4cd437b`](https://git.odit.services/lfk/document-server/commit/4cd437b6afe40e4c09027c0e2004a1130bc51870)
- Updated comments [`cf9cd29`](https://git.odit.services/lfk/document-server/commit/cf9cd298b638012e079be0a04d1b48efea20026f)
- Merge pull request 'Patch 0.0.2' (#9) from dev into main [`4585a83`](https://git.odit.services/lfk/document-server/commit/4585a83838b80552160c9d9e5be0af891eae39c8)
- Merge pull request 'Basic documentation feature/6-documentation' (#8) from feature/6-documentation into dev [`73915da`](https://git.odit.services/lfk/document-server/commit/73915da20321940e22145b4c5d830b9f700be566)
- 📖New license file version [CI SKIP] [skip ci] [`465efe0`](https://git.odit.services/lfk/document-server/commit/465efe03000ecc00d14479ac38710ffdba2cafb5)
- Typos [`68e34a9`](https://git.odit.services/lfk/document-server/commit/68e34a9054a195545b0bd10ce888f7274854fae6)
- Merge pull request 'Implemented the basics for templateing feature/3-pdf_templateing' (#4) from feature/3-pdf_templateing into dev [`15f1641`](https://git.odit.services/lfk/document-server/commit/15f16415c0ac5012df8126c8df6bd4da93ad0a20)
- Merge pull request 'Implemented the basic request classes feature/1-input_classes' (#2) from feature/1-input_classes into dev [`b5e79e5`](https://git.odit.services/lfk/document-server/commit/b5e79e51ef5d751a67e7a1763809ece91a7627f2)
- Added openapi viewers [`6435c8a`](https://git.odit.services/lfk/document-server/commit/6435c8a88e2ea3f4417ad5d8be9a07b9c043b41b)
- 📖New license file version [CI SKIP] [skip ci] [`d493e74`](https://git.odit.services/lfk/document-server/commit/d493e74bf39bc85de842ac7cd3e3d37ba8797a04)
- Initial commit [`0908bcd`](https://git.odit.services/lfk/document-server/commit/0908bcd63529b44463bc57ad7d6b8785564e4ddc)
- Added address class and errors [`d291cf0`](https://git.odit.services/lfk/document-server/commit/d291cf0d1bf375e96fda822543d5ceb29dc67010)
- Added drone file [`48d7cad`](https://git.odit.services/lfk/document-server/commit/48d7cad9f37485656cfb1c70abef158b5dd8d847)
- Added the donation classes [`1d73e4e`](https://git.odit.services/lfk/document-server/commit/1d73e4ed9ccb3dfb7709cb89957ed5bbcefad870)
- Added openapi related classes [`9d62225`](https://git.odit.services/lfk/document-server/commit/9d6222547811fd3c94b91496d9917b7df5115fc6)
- Added a bs example template [`f726a6e`](https://git.odit.services/lfk/document-server/commit/f726a6e699575b839d332c15e61c9008fe996fa9)
- Added a basic donor class [`cfa65e8`](https://git.odit.services/lfk/document-server/commit/cfa65e83cafcefbd6261a99ab7a4eaf099665a81)
- Added input class for contract generation [`63f9523`](https://git.odit.services/lfk/document-server/commit/63f952376698c3561754bfb206a64c58c1ae86f1)
- Initialized package [`b0427a6`](https://git.odit.services/lfk/document-server/commit/b0427a6d810656096c467881a302eef244f86d29)
- Added a simplified runnergroup class [`e29c17a`](https://git.odit.services/lfk/document-server/commit/e29c17a29a6ee84944f8d67d7b6f3c4292763c10)
- Resolved fun issues with promises [`4617f2c`](https://git.odit.services/lfk/document-server/commit/4617f2c5bdcb29534b46865ebe21d15e2b1cd72f)
- Added a runnercard class [`cb7325b`](https://git.odit.services/lfk/document-server/commit/cb7325bbf906b405955bd81e199abae5edc27d91)
- Added a basic readme [`df94b1b`](https://git.odit.services/lfk/document-server/commit/df94b1b750922989d870546150ff8d9ff8af4102)
- Added base app [`915ff92`](https://git.odit.services/lfk/document-server/commit/915ff92418a2859c11e0ce58b79220fb5ea06c7f)
- Added vscode workspace config [`ae466b4`](https://git.odit.services/lfk/document-server/commit/ae466b4d7e5b67c5cd1bc440214f477d15f768a2)
- Created a pdf class that takes care of the pdf wrapping [`7d5b575`](https://git.odit.services/lfk/document-server/commit/7d5b5750ad78fd979cb1b647d8b318a84513532a)
- Added editor and stageing infos to the README [`56b7227`](https://git.odit.services/lfk/document-server/commit/56b72275aca57b227c775c4a61e91fd03471f987)
- Added dockerfile and docker-compose [`b9d4cc3`](https://git.odit.services/lfk/document-server/commit/b9d4cc36199b8f0090d555e961a3454bf35f4bd6)
- Added loaders [`57afbd4`](https://git.odit.services/lfk/document-server/commit/57afbd4b6c48cf641b69a2fcbb28783ff84e1cfd)
- Added a certificaterunner class [`d08bdfd`](https://git.odit.services/lfk/document-server/commit/d08bdfd961f4128981b969cf149df7397a257193)
- Added a template section to the readme [`8141269`](https://git.odit.services/lfk/document-server/commit/8141269dd99f03b33019e5a8f4a010954c4fc44a)
- Created a barebones pdf controller [`64bd1ff`](https://git.odit.services/lfk/document-server/commit/64bd1ffc3ad8459c13154e2cd9a568f27baa2bf2)
- Moved distance to the main runner object [`1d1fa50`](https://git.odit.services/lfk/document-server/commit/1d1fa50327927328ce77af33c1abbf994df9ac8b)
- Added tsconfig [`e1f0378`](https://git.odit.services/lfk/document-server/commit/e1f03788dbdddcb1cb138556c1fed4da9ca104a8)
- Added basic dependencies [`43bb728`](https://git.odit.services/lfk/document-server/commit/43bb728d0d8b75239f1db765071d28a839300102)
- Added typeing to the buffer and stream conversion [`3fb8be2`](https://git.odit.services/lfk/document-server/commit/3fb8be22b74204accabed8a8c8fb5161c5ad7504)
- Added basic dependencies [`d450cea`](https://git.odit.services/lfk/document-server/commit/d450ceac74959bdfb694f9b12fb1fd14ff85b598)
- Added release-it config [`185e66f`](https://git.odit.services/lfk/document-server/commit/185e66fb5b2e4330233ff2e74101c4447c317b5a)
- Added config class [`278c563`](https://git.odit.services/lfk/document-server/commit/278c56386b0e9079a71d3339b40d08f046255a3e)
- Added Barebones pdf creator class [`557cc26`](https://git.odit.services/lfk/document-server/commit/557cc26f281da0b47621dff667435a788c42c103)
- Added a simple status controller [`1d62e7f`](https://git.odit.services/lfk/document-server/commit/1d62e7f14c8d6219607ed6ecf774ae152e362cc1)
- Added errorhandler middleware [`6539fd7`](https://git.odit.services/lfk/document-server/commit/6539fd785568971330639fb7055827417424c81a)
- Runners now use the runnergroup class instead of strings [`aae4f50`](https://git.odit.services/lfk/document-server/commit/aae4f507eafbfd51cd9972cf0fac4c5afafbb45a)
- Added files to gitignore [`66134c0`](https://git.odit.services/lfk/document-server/commit/66134c0e1e5dce2ebd9eae3f88a6ad893bad0283)
- Switched to the splitted functions [`3ca38ab`](https://git.odit.services/lfk/document-server/commit/3ca38abe9359e436fe0f80400ad0308ad9d64a4f)
- Cleaned up openapi stuff [`4efb629`](https://git.odit.services/lfk/document-server/commit/4efb62921bced79c817499b48bbc55b05e6371e5)
- Updated apidoc viewer settings [`36396af`](https://git.odit.services/lfk/document-server/commit/36396af50a1f7d1842effbd24e08a8d34946a375)
- Updated apidoc viewer settings [`4d45e0f`](https://git.odit.services/lfk/document-server/commit/4d45e0f80d5721c91de0b25334d040448d92e01d)
- 🧾New changelog file version [CI SKIP] [skip ci] [`8f250f7`](https://git.odit.services/lfk/document-server/commit/8f250f747a95feb246b1698a7c9be9ee19a1be12)
- Added missing dependencies [`c2d7141`](https://git.odit.services/lfk/document-server/commit/c2d714116e02bb4c2ff6501ba32417cd998e2f3e)
- Added license export and release scripts [`0c4e5a1`](https://git.odit.services/lfk/document-server/commit/0c4e5a182c91b99a657c83f2c24f4cfaae1ef48d)
- Added build script [`cbc421c`](https://git.odit.services/lfk/document-server/commit/cbc421cd838b6c874fbe50556350edeb08a7b882)
- Added dev script [`814b564`](https://git.odit.services/lfk/document-server/commit/814b564752aa95ed2c034a9a98c800b43452bf88)
- Adjusted template font styleing [`2c7b025`](https://git.odit.services/lfk/document-server/commit/2c7b0254ec7e49c31f8f33eed334ab4156052fb6)
- Added html-pdf package [`a7d4001`](https://git.odit.services/lfk/document-server/commit/a7d4001ad992d60e2af69e9bbed53366bc0a46d3)
- Added PDFs to gitignore [`9f3758d`](https://git.odit.services/lfk/document-server/commit/9f3758de39ef1d5b591a24868eb683caa742f694)
- Added version to config [`b1747f6`](https://git.odit.services/lfk/document-server/commit/b1747f6374c029e1e9c3a87584ea54b9d3714d92)
- fixed license-exporter call [`83c4bd6`](https://git.odit.services/lfk/document-server/commit/83c4bd62cbbb13ae33a790baa45cc15a77d99e41)
- Added version to config [`cff7011`](https://git.odit.services/lfk/document-server/commit/cff70110fde72c7fede0625872acdc0666703fa1)
- Added openapi dependency [`94e5e51`](https://git.odit.services/lfk/document-server/commit/94e5e51f8d421ea7fd64ded2281b89ff4a252b29)
- Added templates folder [`8a30265`](https://git.odit.services/lfk/document-server/commit/8a30265fc600dad17f584f95dd51db976945d4c5)

View File

@ -1,39 +1,16 @@
# Typescript Build
FROM registry.odit.services/hub/library/node:14.15.1-alpine3.12
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY package.json ./
RUN npm i -g pnpm
RUN pnpm i
COPY tsconfig.json ./
COPY src ./src
RUN pnpm run build
# final image
FROM registry.odit.services/hub/library/alpine:3.13.1
WORKDIR /app
RUN apk add --no-cache \
chromium \
nss \
freetype \
freetype-dev \
harfbuzz \
ca-certificates \
ttf-freefont \
nodejs \
yarn
# Tell Puppeteer to skip installing Chrome. We'll be using the installed package.
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
COPY go.mod go.sum ./
RUN go mod download
RUN addgroup -S pptruser && adduser -S -g pptruser pptruser \
&& mkdir -p /home/pptruser/Downloads /app \
&& chown -R pptruser:pptruser /home/pptruser \
&& chown -R pptruser:pptruser /app
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o server
# Run everything after as non-privileged user.
USER pptruser
FROM alpine:3.18
RUN mkdir -p /tmp && chmod 1777 /tmp
COPY package.json ./
RUN yarn
COPY --from=0 /app/dist app
ENTRYPOINT ["node", "app/app.js"]
COPY --from=builder /app/server /server
COPY static /static
ENTRYPOINT [ "/server" ]

362
LICENSE
View File

@ -1,362 +0,0 @@
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Creative
Commons Corporation ("Creative Commons") is not a law firm and does not provide
legal services or legal advice. Distribution of Creative Commons public licenses
does not create a lawyer-client or other relationship. Creative Commons makes
its licenses and related information available on an "as-is" basis. Creative
Commons gives no warranties regarding its licenses, any material licensed
under their terms and conditions, or any related information. Creative Commons
disclaims all liability for damages resulting from their use to the fullest
extent possible.
Using Creative Commons Public Licenses
Creative Commons public licenses provide a standard set of terms and conditions
that creators and other rights holders may use to share original works of
authorship and other material subject to copyright and certain other rights
specified in the public license below. The following considerations are for
informational purposes only, are not exhaustive, and do not form part of our
licenses.
Considerations for licensors: Our public licenses are intended for use by
those authorized to give the public permission to use material in ways otherwise
restricted by copyright and certain other rights. Our licenses are irrevocable.
Licensors should read and understand the terms and conditions of the license
they choose before applying it. Licensors should also secure all rights necessary
before applying our licenses so that the public can reuse the material as
expected. Licensors should clearly mark any material not subject to the license.
This includes other CC-licensed material, or material used under an exception
or limitation to copyright. More considerations for licensors : wiki.creativecommons.org/Considerations_for_licensors
Considerations for the public: By using one of our public licenses, a licensor
grants the public permission to use the licensed material under specified
terms and conditions. If the licensor's permission is not necessary for any
reasonfor example, because of any applicable exception or limitation to copyrightthen
that use is not regulated by the license. Our licenses grant only permissions
under copyright and certain other rights that a licensor has authority to
grant. Use of the licensed material may still be restricted for other reasons,
including because others have copyright or other rights in the material. A
licensor may make special requests, such as asking that all changes be marked
or described. Although not required by our licenses, you are encouraged to
respect those requests where reasonable. More considerations for the public
: wiki.creativecommons.org/Considerations_for_licensees
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public
License
By exercising the Licensed Rights (defined below), You accept and agree to
be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial-ShareAlike
4.0 International Public License ("Public License"). To the extent this Public
License may be interpreted as a contract, You are granted the Licensed Rights
in consideration of Your acceptance of these terms and conditions, and the
Licensor grants You such rights in consideration of benefits the Licensor
receives from making the Licensed Material available under these terms and
conditions.
Section 1 Definitions.
a. Adapted Material means material subject to Copyright and Similar Rights
that is derived from or based upon the Licensed Material and in which the
Licensed Material is translated, altered, arranged, transformed, or otherwise
modified in a manner requiring permission under the Copyright and Similar
Rights held by the Licensor. For purposes of this Public License, where the
Licensed Material is a musical work, performance, or sound recording, Adapted
Material is always produced where the Licensed Material is synched in timed
relation with a moving image.
b. Adapter's License means the license You apply to Your Copyright and Similar
Rights in Your contributions to Adapted Material in accordance with the terms
and conditions of this Public License.
c. BY-NC-SA Compatible License means a license listed at creativecommons.org/compatiblelicenses,
approved by Creative Commons as essentially the equivalent of this Public
License.
d. Copyright and Similar Rights means copyright and/or similar rights closely
related to copyright including, without limitation, performance, broadcast,
sound recording, and Sui Generis Database Rights, without regard to how the
rights are labeled or categorized. For purposes of this Public License, the
rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights.
e. Effective Technological Measures means those measures that, in the absence
of proper authority, may not be circumvented under laws fulfilling obligations
under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996,
and/or similar international agreements.
f. Exceptions and Limitations means fair use, fair dealing, and/or any other
exception or limitation to Copyright and Similar Rights that applies to Your
use of the Licensed Material.
g. License Elements means the license attributes listed in the name of a Creative
Commons Public License. The License Elements of this Public License are Attribution,
NonCommercial, and ShareAlike.
h. Licensed Material means the artistic or literary work, database, or other
material to which the Licensor applied this Public License.
i. Licensed Rights means the rights granted to You subject to the terms and
conditions of this Public License, which are limited to all Copyright and
Similar Rights that apply to Your use of the Licensed Material and that the
Licensor has authority to license.
j. Licensor means the individual(s) or entity(ies) granting rights under this
Public License.
k. NonCommercial means not primarily intended for or directed towards commercial
advantage or monetary compensation. For purposes of this Public License, the
exchange of the Licensed Material for other material subject to Copyright
and Similar Rights by digital file-sharing or similar means is NonCommercial
provided there is no payment of monetary compensation in connection with the
exchange.
l. Share means to provide material to the public by any means or process that
requires permission under the Licensed Rights, such as reproduction, public
display, public performance, distribution, dissemination, communication, or
importation, and to make material available to the public including in ways
that members of the public may access the material from a place and at a time
individually chosen by them.
m. Sui Generis Database Rights means rights other than copyright resulting
from Directive 96/9/EC of the European Parliament and of the Council of 11
March 1996 on the legal protection of databases, as amended and/or succeeded,
as well as other essentially equivalent rights anywhere in the world.
n. You means the individual or entity exercising the Licensed Rights under
this Public License. Your has a corresponding meaning.
Section 2 Scope.
a. License grant.
1. Subject to the terms and conditions of this Public License, the Licensor
hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive,
irrevocable license to exercise the Licensed Rights in the Licensed Material
to:
A. reproduce and Share the Licensed Material, in whole or in part, for NonCommercial
purposes only; and
B. produce, reproduce, and Share Adapted Material for NonCommercial purposes
only.
2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions
and Limitations apply to Your use, this Public License does not apply, and
You do not need to comply with its terms and conditions.
3. Term. The term of this Public License is specified in Section 6(a).
4. Media and formats; technical modifications allowed. The Licensor authorizes
You to exercise the Licensed Rights in all media and formats whether now known
or hereafter created, and to make technical modifications necessary to do
so. The Licensor waives and/or agrees not to assert any right or authority
to forbid You from making technical modifications necessary to exercise the
Licensed Rights, including technical modifications necessary to circumvent
Effective Technological Measures. For purposes of this Public License, simply
making modifications authorized by this Section 2(a)(4) never produces Adapted
Material.
5. Downstream recipients.
A. Offer from the Licensor Licensed Material. Every recipient of the Licensed
Material automatically receives an offer from the Licensor to exercise the
Licensed Rights under the terms and conditions of this Public License.
B. Additional offer from the Licensor Adapted Material. Every recipient
of Adapted Material from You automatically receives an offer from the Licensor
to exercise the Licensed Rights in the Adapted Material under the conditions
of the Adapter's License You apply.
C. No downstream restrictions. You may not offer or impose any additional
or different terms or conditions on, or apply any Effective Technological
Measures to, the Licensed Material if doing so restricts exercise of the Licensed
Rights by any recipient of the Licensed Material.
6. No endorsement. Nothing in this Public License constitutes or may be construed
as permission to assert or imply that You are, or that Your use of the Licensed
Material is, connected with, or sponsored, endorsed, or granted official status
by, the Licensor or others designated to receive attribution as provided in
Section 3(a)(1)(A)(i).
b. Other rights.
1. Moral rights, such as the right of integrity, are not licensed under this
Public License, nor are publicity, privacy, and/or other similar personality
rights; however, to the extent possible, the Licensor waives and/or agrees
not to assert any such rights held by the Licensor to the limited extent necessary
to allow You to exercise the Licensed Rights, but not otherwise.
2. Patent and trademark rights are not licensed under this Public License.
3. To the extent possible, the Licensor waives any right to collect royalties
from You for the exercise of the Licensed Rights, whether directly or through
a collecting society under any voluntary or waivable statutory or compulsory
licensing scheme. In all other cases the Licensor expressly reserves any right
to collect such royalties, including when the Licensed Material is used other
than for NonCommercial purposes.
Section 3 License Conditions.
Your exercise of the Licensed Rights is expressly made subject to the following
conditions.
a. Attribution.
1. If You Share the Licensed Material (including in modified form), You must:
A. retain the following if it is supplied by the Licensor with the Licensed
Material:
i. identification of the creator(s) of the Licensed Material and any others
designated to receive attribution, in any reasonable manner requested by the
Licensor (including by pseudonym if designated);
ii. a copyright notice;
iii. a notice that refers to this Public License;
iv. a notice that refers to the disclaimer of warranties;
v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable;
B. indicate if You modified the Licensed Material and retain an indication
of any previous modifications; and
C. indicate the Licensed Material is licensed under this Public License, and
include the text of, or the URI or hyperlink to, this Public License.
2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner
based on the medium, means, and context in which You Share the Licensed Material.
For example, it may be reasonable to satisfy the conditions by providing a
URI or hyperlink to a resource that includes the required information.
3. If requested by the Licensor, You must remove any of the information required
by Section 3(a)(1)(A) to the extent reasonably practicable.
b. ShareAlike.In addition to the conditions in Section 3(a), if You Share
Adapted Material You produce, the following conditions also apply.
1. The Adapter's License You apply must be a Creative Commons license with
the same License Elements, this version or later, or a BY-NC-SA Compatible
License.
2. You must include the text of, or the URI or hyperlink to, the Adapter's
License You apply. You may satisfy this condition in any reasonable manner
based on the medium, means, and context in which You Share Adapted Material.
3. You may not offer or impose any additional or different terms or conditions
on, or apply any Effective Technological Measures to, Adapted Material that
restrict exercise of the rights granted under the Adapter's License You apply.
Section 4 Sui Generis Database Rights.
Where the Licensed Rights include Sui Generis Database Rights that apply to
Your use of the Licensed Material:
a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract,
reuse, reproduce, and Share all or a substantial portion of the contents of
the database for NonCommercial purposes only;
b. if You include all or a substantial portion of the database contents in
a database in which You have Sui Generis Database Rights, then the database
in which You have Sui Generis Database Rights (but not its individual contents)
is Adapted Material, including for purposes of Section 3(b); and
c. You must comply with the conditions in Section 3(a) if You Share all or
a substantial portion of the contents of the database.
For the avoidance of doubt, this Section 4 supplements and does not replace
Your obligations under this Public License where the Licensed Rights include
other Copyright and Similar Rights.
Section 5 Disclaimer of Warranties and Limitation of Liability.
a. Unless otherwise separately undertaken by the Licensor, to the extent possible,
the Licensor offers the Licensed Material as-is and as-available, and makes
no representations or warranties of any kind concerning the Licensed Material,
whether express, implied, statutory, or other. This includes, without limitation,
warranties of title, merchantability, fitness for a particular purpose, non-infringement,
absence of latent or other defects, accuracy, or the presence or absence of
errors, whether or not known or discoverable. Where disclaimers of warranties
are not allowed in full or in part, this disclaimer may not apply to You.
b. To the extent possible, in no event will the Licensor be liable to You
on any legal theory (including, without limitation, negligence) or otherwise
for any direct, special, indirect, incidental, consequential, punitive, exemplary,
or other losses, costs, expenses, or damages arising out of this Public License
or use of the Licensed Material, even if the Licensor has been advised of
the possibility of such losses, costs, expenses, or damages. Where a limitation
of liability is not allowed in full or in part, this limitation may not apply
to You.
c. The disclaimer of warranties and limitation of liability provided above
shall be interpreted in a manner that, to the extent possible, most closely
approximates an absolute disclaimer and waiver of all liability.
Section 6 Term and Termination.
a. This Public License applies for the term of the Copyright and Similar Rights
licensed here. However, if You fail to comply with this Public License, then
Your rights under this Public License terminate automatically.
b. Where Your right to use the Licensed Material has terminated under Section
6(a), it reinstates:
1. automatically as of the date the violation is cured, provided it is cured
within 30 days of Your discovery of the violation; or
2. upon express reinstatement by the Licensor.
For the avoidance of doubt, this Section 6(b) does not affect any right the
Licensor may have to seek remedies for Your violations of this Public License.
c. For the avoidance of doubt, the Licensor may also offer the Licensed Material
under separate terms or conditions or stop distributing the Licensed Material
at any time; however, doing so will not terminate this Public License.
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License.
Section 7 Other Terms and Conditions.
a. The Licensor shall not be bound by any additional or different terms or
conditions communicated by You unless expressly agreed.
b. Any arrangements, understandings, or agreements regarding the Licensed
Material not stated herein are separate from and independent of the terms
and conditions of this Public License.
Section 8 Interpretation.
a. For the avoidance of doubt, this Public License does not, and shall not
be interpreted to, reduce, limit, restrict, or impose conditions on any use
of the Licensed Material that could lawfully be made without permission under
this Public License.
b. To the extent possible, if any provision of this Public License is deemed
unenforceable, it shall be automatically reformed to the minimum extent necessary
to make it enforceable. If the provision cannot be reformed, it shall be severed
from this Public License without affecting the enforceability of the remaining
terms and conditions.
c. No term or condition of this Public License will be waived and no failure
to comply consented to unless expressly agreed to by the Licensor.
d. Nothing in this Public License constitutes or may be interpreted as a limitation
upon, or waiver of, any privileges and immunities that apply to the Licensor
or You, including from the legal processes of any jurisdiction or authority.
Creative Commons is not a party to its public licenses. Notwithstanding, Creative
Commons may elect to apply one of its public licenses to material it publishes
and in those instances will be considered the "Licensor." The text of the
Creative Commons public licenses is dedicated to the public domain under the
CC0 Public Domain Dedication. Except for the limited purpose of indicating
that material is shared under a Creative Commons public license or as otherwise
permitted by the Creative Commons policies published at creativecommons.org/policies,
Creative Commons does not authorize the use of the trademark "Creative Commons"
or any other trademark or logo of Creative Commons without its prior written
consent including, without limitation, in connection with any unauthorized
modifications to any of its public licenses or any other arrangements, understandings,
or agreements concerning use of licensed material. For the avoidance of doubt,
this paragraph does not form part of the public licenses.
Creative Commons may be contacted at creativecommons.org.

178
README.md
View File

@ -1,82 +1,96 @@
# @lfk/document-server
The document generation server responsible for creating pdfs for sponsoring contracts, certificates and more.
This server doesn't interact with any database and can therefor be deployed on it's own.
The basic generation mechanism makes the templates and routes interchangeable (if you want to expand or modify it).
## Quickstart 🐳
> Use this to run the document server in docker.
1. Clone the repo or copy the docker-compose
2. Run in the folder that contains the docker-compose file: `docker-compose up -d`
3. Visit http://127.0.0.1:4010/docs to check if the server is running
## Dev Setup 🛠
> Local dev setup
1. Rename the .env.example file to .env (you can adjust app port and other settings, if needed)
2. Install Dependencies
```bash
yarn
```
3. Start the server
```bash
yarn dev
```
## ENV Vars
> You can provide them via .env file or docker env vars.
| Name | Type | Default | Description
| - | - | - | -
| APP_PORT | Number | 4010 | The port the backend server listens on. Is optional.
| NODE_ENV | String | dev | The apps env - influences debug info.
| EVENT_NAME | String | "Please set the event name" | The event's name - used to generate pdf text.
| CURRENCY_SYMBOL | String | "€" | The your currency's symbol - used to generate pdf text.
| SPONSORING_RECEIPT_MINIMUM_AMOUNT | String | "10" | The mimimum total donation amount a sponsor has to donate to be able to receive a donation receipt - used to generate pdf text.
| 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.
| 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.
| CONTRACTS_PER_RUNNER | Number | 1 | The amount of contracts that get created per runner (per request).
## Templates
> The document server uses html templates to generate various pdf documents.
> The templates are stored in src/templates by default.
We provide a set of default templates that we use for the ["Lauf für Kaya!" charity run](https://lauf-fuer-kaya.de).
We use handlebars for templateing utilizing i18next for translation - the i18n string format in the templates is : `{{__ "string"}}`
You can provide your own templates by replacing the ones we provided before compiling the project or by simply mounting your custom templates into the docker container.
The server currently needs the following templates to work:
* sponsoring_contract.html
### Sponsoring Contracts
| Template Data | Type | Optional | Description
| - | - | - | -
| runners | array(Runner) | ❌ | The runner objects. We generate a contract for each runner on a new DIN-A5 page.
## Recommended Editor
[Visual Studio Code](https://code.visualstudio.com/)
### Recommended Extensions
* will be automatically recommended via ./vscode/extensions.json
* we also provide a config for i18n-ally in the .vscode folder
## Staging
### Branches & Tags
* vX.Y.Z: Release tags created from the main branch
* The version numbers follow the semver standard
* A new release tag automaticly triggers the release ci pipeline
* main: Protected "release" branch
* The latest tag of the docker image get's build from this
* New releases get created as tags from this
* dev: Current dev branch for merging the different feature branches and bugfixes
* The dev tag of the docker image get's build from this
* Only push minor changes to this branch!
* To merge a feature branch into this please create a pull request
* feature/xyz: Feature branches - nameing scheme: `feature/issueid-title`
* bugfix/xyz: Branches for bugfixes - nameing scheme:`bugfix/issueid-title`
# ✒️ Document generation microservices
## Features
- 📝 HTML templates for pdf generation
- 📚 OpenAPI/Swagger documentation
- ⚡ High-performance with go and gotenberg
## 🚀 Quick Start
```bash
# Install dependencies
go mod download
# Run the server
air
```
## 📖 API Documentation
Swagger UI is available at: `/swagger`
## 🛠️ Development
The project uses:
- 🏃‍♂️ go as the language and build tool
- 🌐 gofiber for the web framework
- 📦 air for live reload
- 📝 swaggo for API documentation
- 📄 gotenberg for HTML to PDF conversion
### 📦 Use docker compose for external dependencies
```shell
docker compose -f docker-compose.dev.yaml up
```
### 🏃 Run via air
> Install air via `go install github.com/air-verse/air@latest`
```shell
# With the default air config
air
# With the config for linux/macOS
air -c .air.linux.toml
# With the config for windows
air -c .air.windows.toml
```
### ✒️ Update the swagger docs
> Install swag via `go install github.com/swaggo/swag/cmd/swag@latest`
```shell
swag init
```
### 🐋 Build container
```shell
# single arch
docker build -t registry.odit.services/lfk/document-server:latest .
# multiarch
docker buildx build --platform=linux/amd64,linux/arm64 -t registry.odit.services/lfk/document-server:latest --push .
```
## ⏱️ Benchmarks
### Barcode Generation
- Requests: 10000
- Concurrency: Unlimited
- Data: `123456789123`
- Width: 1000
- Height: 500
#### No cache (cold start)
| Format | Data | Requests/sec | Slowest | Fastest | Average |
| ------- | -------------- | ------------ | ------- | ------- | ------- |
| Code128 | `123456789123` | 763.3996 | 0.7995 | 0.0172 | 0.0654 |
| EAN13 | `123456789123` | 767.1170 | 0.7607 | 0.0171 | 0.0651 |
| QR | `123456789123` | 683.8472 | 0.6528 | 0.0178 | 0.0730 |
#### Redis cache (warm start)
| Format | Data | Requests/sec | Slowest | Fastest | Average |
| ------- | -------------- | ------------ | ------- | ------- | ------- |
| Code128 | `123456789123` | 15611.5521 | 0.0965 | 0.0006 | 0.0032 |
| EAN13 | `123456789123` | 14985.4401 | 0.0925 | 0.0006 | 0.0033 |
| QR | `123456789123` | 14306.2540 | 0.1143 | 0.0005 | 0.0035 |

9
docker-compose.dev.yaml Normal file
View File

@ -0,0 +1,9 @@
services:
gotenberg:
image: gotenberg/gotenberg:8
ports:
- "3001:3000"
redis:
image: docker.dragonflydb.io/dragonflydb/dragonfly
ports:
- "6379:6379"

15
docker-compose.yaml Normal file
View File

@ -0,0 +1,15 @@
services:
document-server:
build: .
ports:
- "3000:3000"
volumes:
- ./docker.env:/.env
gotenberg:
image: gotenberg/gotenberg:8
ports:
- "3001:3000"
redis:
image: docker.dragonflydb.io/dragonflydb/dragonfly
ports:
- "6379:6379"

View File

@ -1,15 +0,0 @@
version: "3"
services:
document_server:
build: .
ports:
- 4010:4010
environment:
APP_PORT: 4010
NODE_ENV: production
EVENT_NAME: "Lauf für Kaya! 2021"
CURRENCY_SYMBOL: "€"
API_KEY: RYRccAJ4SKZnZaEci6Nyk9Z6mw3sD94fyKJ74WNzi6hLkxGNyJDrKPkxBmPwvR4f
CONTRACTS_PER_RUNNER: 2
SPONSORING_RECEIPT_MINIMUM_AMOUNT: 50
DISCLAIMER_TEXT: "Rechtsgrundlage unserer Datenverarbeitung aufgrund freiwilliger Einwilligung ist Art. 6 Abs. 1 e), Abs. 3 DSGVO i.V.m. Art. 85 BayEUG. Mit Ihrer Unterschrift willigen Sie in unsere Datennutzung zum Zwecke des Lauf für Kaya! ein. Die Daten für Spendenquittungen"

18
docker.env Normal file
View File

@ -0,0 +1,18 @@
PORT=3000
PRODUCION=false
APIKEY=lfk
EVENTNAME=Lauf für Kaya! 2025
CURRENCYSYMBOL=
GOTENBERG_BASEURL=http://gotenberg:3000
REDIS_ADDR=redis:6379
CARD_SUBTITLE=Kaya ist cool
CARD_BARCODEFORMAT=ean13
# CARD_BARCODEPREFIX=
SPONSOING_RECEIPTMINIMUM=10
SPONSORING_DISCLAIMER=Kaya ist cool, aber pass auf, dass du nicht zu viel Geld sammelst!
SPONSORING_BARCODEFORMAT=code128
# SPONSORING_BARCODEPREFIX=
CERTIFICATE_FOOTER=Kaya ist cool, danke für deine Unterstützung!

437
docs/docs.go Normal file
View File

@ -0,0 +1,437 @@
// Package docs Code generated by swaggo/swag. DO NOT EDIT
package docs
import "github.com/swaggo/swag"
const docTemplate = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"termsOfService": "https://lauf-fuer-kaya.de/datenschutz",
"contact": {
"name": "ODIT.Services UG (haftungsbeschränkt)",
"url": "https://odit.services",
"email": "info@odit.services"
},
"license": {
"name": "CC BY-NC-SA 4.0"
},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/v1/barcodes/{type}/{content}": {
"get": {
"description": "Generate barcodes based on the provided data",
"produces": [
"image/png"
],
"tags": [
"barcodes"
],
"summary": "Generate barcodes",
"parameters": [
{
"enum": [
"ean13",
"code128"
],
"type": "string",
"description": "Barcode type",
"name": "type",
"in": "path",
"required": true
},
{
"minLength": 1,
"type": "string",
"description": "Barcode content",
"name": "content",
"in": "path",
"required": true
},
{
"maximum": 10000,
"minimum": 1,
"type": "integer",
"default": 1000,
"description": "Barcode width",
"name": "width",
"in": "query"
},
{
"maximum": 10000,
"minimum": 1,
"type": "integer",
"default": 1000,
"description": "Barcode height",
"name": "height",
"in": "query"
},
{
"maximum": 100,
"minimum": 0,
"type": "integer",
"default": 10,
"description": "Padding around the barcode (included in image size)",
"name": "padding",
"in": "query"
}
],
"responses": {}
}
},
"/v1/pdfs/cards": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Generate cards based on the provided data",
"consumes": [
"application/json"
],
"produces": [
"application/pdf"
],
"tags": [
"pdfs"
],
"summary": "Generate runner cards",
"parameters": [
{
"description": "Card data",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.CardRequest"
}
}
],
"responses": {}
}
},
"/v1/pdfs/certificates": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Generate certificates based on the provided data",
"consumes": [
"application/json"
],
"produces": [
"application/pdf"
],
"tags": [
"pdfs"
],
"summary": "Generate runner certificates",
"parameters": [
{
"description": "Certificate data",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.CertificateRequest"
}
}
],
"responses": {}
}
},
"/v1/pdfs/contracts": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Generate a contract based on the provided data",
"consumes": [
"application/json"
],
"produces": [
"application/pdf"
],
"tags": [
"pdfs"
],
"summary": "Generate a contract",
"parameters": [
{
"description": "Contract data",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.ContractRequest"
}
}
],
"responses": {}
}
}
},
"definitions": {
"models.Card": {
"type": "object",
"required": [
"code",
"id",
"runner"
],
"properties": {
"code": {
"type": "string"
},
"enabled": {
"type": "boolean",
"default": true
},
"id": {
"type": "integer"
},
"runner": {
"$ref": "#/definitions/models.Runner"
}
}
},
"models.CardRequest": {
"type": "object",
"required": [
"cards",
"locale"
],
"properties": {
"cards": {
"type": "array",
"items": {
"$ref": "#/definitions/models.Card"
}
},
"locale": {
"type": "string",
"enum": [
"en",
"de"
]
}
}
},
"models.CertificateRequest": {
"type": "object",
"required": [
"locale",
"runners"
],
"properties": {
"locale": {
"type": "string",
"enum": [
"en",
"de"
]
},
"runners": {
"type": "array",
"items": {
"$ref": "#/definitions/models.RunnerWithDonations"
}
}
}
},
"models.ContractRequest": {
"type": "object",
"required": [
"locale",
"runners"
],
"properties": {
"locale": {
"type": "string",
"enum": [
"en",
"de"
]
},
"runners": {
"type": "array",
"items": {
"$ref": "#/definitions/models.Runner"
}
}
}
},
"models.DistanceDonation": {
"type": "object",
"required": [
"amount_per_distance",
"donor",
"id"
],
"properties": {
"amount": {
"type": "integer"
},
"amount_per_distance": {
"type": "integer"
},
"donor": {
"$ref": "#/definitions/models.Donor"
},
"id": {
"type": "integer"
},
"paid_amount": {
"type": "integer"
}
}
},
"models.Donor": {
"type": "object",
"required": [
"first_name",
"id",
"last_name"
],
"properties": {
"first_name": {
"type": "string"
},
"id": {
"type": "integer"
},
"last_name": {
"type": "string"
},
"middle_name": {
"type": "string"
}
}
},
"models.Group": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
},
"parent_group": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
}
}
}
}
},
"models.Runner": {
"type": "object",
"required": [
"first_name",
"group",
"id",
"last_name"
],
"properties": {
"first_name": {
"type": "string"
},
"group": {
"$ref": "#/definitions/models.Group"
},
"id": {
"type": "integer"
},
"last_name": {
"type": "string"
},
"middle_name": {
"type": "string"
}
}
},
"models.RunnerWithDonations": {
"type": "object",
"required": [
"distance",
"first_name",
"group",
"id",
"last_name"
],
"properties": {
"distance": {
"type": "integer"
},
"distance_donations": {
"type": "array",
"items": {
"$ref": "#/definitions/models.DistanceDonation"
}
},
"first_name": {
"type": "string"
},
"group": {
"$ref": "#/definitions/models.Group"
},
"id": {
"type": "integer"
},
"last_name": {
"type": "string"
},
"middle_name": {
"type": "string"
},
"total_donations": {
"type": "integer"
},
"total_per_distance": {
"type": "integer"
}
}
}
},
"securityDefinitions": {
"ApiKeyAuth": {
"type": "apiKey",
"name": "key",
"in": "query"
}
}
}`
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "",
Host: "",
BasePath: "",
Schemes: []string{},
Title: "LfK Document Server API",
Description: "This is the API documentation for the LfK Document Server - a tool for pdf generation.",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
LeftDelim: "{{",
RightDelim: "}}",
}
func init() {
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}

410
docs/swagger.json Normal file
View File

@ -0,0 +1,410 @@
{
"swagger": "2.0",
"info": {
"description": "This is the API documentation for the LfK Document Server - a tool for pdf generation.",
"title": "LfK Document Server API",
"termsOfService": "https://lauf-fuer-kaya.de/datenschutz",
"contact": {
"name": "ODIT.Services UG (haftungsbeschränkt)",
"url": "https://odit.services",
"email": "info@odit.services"
},
"license": {
"name": "CC BY-NC-SA 4.0"
}
},
"paths": {
"/v1/barcodes/{type}/{content}": {
"get": {
"description": "Generate barcodes based on the provided data",
"produces": [
"image/png"
],
"tags": [
"barcodes"
],
"summary": "Generate barcodes",
"parameters": [
{
"enum": [
"ean13",
"code128"
],
"type": "string",
"description": "Barcode type",
"name": "type",
"in": "path",
"required": true
},
{
"minLength": 1,
"type": "string",
"description": "Barcode content",
"name": "content",
"in": "path",
"required": true
},
{
"maximum": 10000,
"minimum": 1,
"type": "integer",
"default": 1000,
"description": "Barcode width",
"name": "width",
"in": "query"
},
{
"maximum": 10000,
"minimum": 1,
"type": "integer",
"default": 1000,
"description": "Barcode height",
"name": "height",
"in": "query"
},
{
"maximum": 100,
"minimum": 0,
"type": "integer",
"default": 10,
"description": "Padding around the barcode (included in image size)",
"name": "padding",
"in": "query"
}
],
"responses": {}
}
},
"/v1/pdfs/cards": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Generate cards based on the provided data",
"consumes": [
"application/json"
],
"produces": [
"application/pdf"
],
"tags": [
"pdfs"
],
"summary": "Generate runner cards",
"parameters": [
{
"description": "Card data",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.CardRequest"
}
}
],
"responses": {}
}
},
"/v1/pdfs/certificates": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Generate certificates based on the provided data",
"consumes": [
"application/json"
],
"produces": [
"application/pdf"
],
"tags": [
"pdfs"
],
"summary": "Generate runner certificates",
"parameters": [
{
"description": "Certificate data",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.CertificateRequest"
}
}
],
"responses": {}
}
},
"/v1/pdfs/contracts": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Generate a contract based on the provided data",
"consumes": [
"application/json"
],
"produces": [
"application/pdf"
],
"tags": [
"pdfs"
],
"summary": "Generate a contract",
"parameters": [
{
"description": "Contract data",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.ContractRequest"
}
}
],
"responses": {}
}
}
},
"definitions": {
"models.Card": {
"type": "object",
"required": [
"code",
"id",
"runner"
],
"properties": {
"code": {
"type": "string"
},
"enabled": {
"type": "boolean",
"default": true
},
"id": {
"type": "integer"
},
"runner": {
"$ref": "#/definitions/models.Runner"
}
}
},
"models.CardRequest": {
"type": "object",
"required": [
"cards",
"locale"
],
"properties": {
"cards": {
"type": "array",
"items": {
"$ref": "#/definitions/models.Card"
}
},
"locale": {
"type": "string",
"enum": [
"en",
"de"
]
}
}
},
"models.CertificateRequest": {
"type": "object",
"required": [
"locale",
"runners"
],
"properties": {
"locale": {
"type": "string",
"enum": [
"en",
"de"
]
},
"runners": {
"type": "array",
"items": {
"$ref": "#/definitions/models.RunnerWithDonations"
}
}
}
},
"models.ContractRequest": {
"type": "object",
"required": [
"locale",
"runners"
],
"properties": {
"locale": {
"type": "string",
"enum": [
"en",
"de"
]
},
"runners": {
"type": "array",
"items": {
"$ref": "#/definitions/models.Runner"
}
}
}
},
"models.DistanceDonation": {
"type": "object",
"required": [
"amount_per_distance",
"donor",
"id"
],
"properties": {
"amount": {
"type": "integer"
},
"amount_per_distance": {
"type": "integer"
},
"donor": {
"$ref": "#/definitions/models.Donor"
},
"id": {
"type": "integer"
},
"paid_amount": {
"type": "integer"
}
}
},
"models.Donor": {
"type": "object",
"required": [
"first_name",
"id",
"last_name"
],
"properties": {
"first_name": {
"type": "string"
},
"id": {
"type": "integer"
},
"last_name": {
"type": "string"
},
"middle_name": {
"type": "string"
}
}
},
"models.Group": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
},
"parent_group": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
}
}
}
}
},
"models.Runner": {
"type": "object",
"required": [
"first_name",
"group",
"id",
"last_name"
],
"properties": {
"first_name": {
"type": "string"
},
"group": {
"$ref": "#/definitions/models.Group"
},
"id": {
"type": "integer"
},
"last_name": {
"type": "string"
},
"middle_name": {
"type": "string"
}
}
},
"models.RunnerWithDonations": {
"type": "object",
"required": [
"distance",
"first_name",
"group",
"id",
"last_name"
],
"properties": {
"distance": {
"type": "integer"
},
"distance_donations": {
"type": "array",
"items": {
"$ref": "#/definitions/models.DistanceDonation"
}
},
"first_name": {
"type": "string"
},
"group": {
"$ref": "#/definitions/models.Group"
},
"id": {
"type": "integer"
},
"last_name": {
"type": "string"
},
"middle_name": {
"type": "string"
},
"total_donations": {
"type": "integer"
},
"total_per_distance": {
"type": "integer"
}
}
}
},
"securityDefinitions": {
"ApiKeyAuth": {
"type": "apiKey",
"name": "key",
"in": "query"
}
}
}

278
docs/swagger.yaml Normal file
View File

@ -0,0 +1,278 @@
definitions:
models.Card:
properties:
code:
type: string
enabled:
default: true
type: boolean
id:
type: integer
runner:
$ref: '#/definitions/models.Runner'
required:
- code
- id
- runner
type: object
models.CardRequest:
properties:
cards:
items:
$ref: '#/definitions/models.Card'
type: array
locale:
enum:
- en
- de
type: string
required:
- cards
- locale
type: object
models.CertificateRequest:
properties:
locale:
enum:
- en
- de
type: string
runners:
items:
$ref: '#/definitions/models.RunnerWithDonations'
type: array
required:
- locale
- runners
type: object
models.ContractRequest:
properties:
locale:
enum:
- en
- de
type: string
runners:
items:
$ref: '#/definitions/models.Runner'
type: array
required:
- locale
- runners
type: object
models.DistanceDonation:
properties:
amount:
type: integer
amount_per_distance:
type: integer
donor:
$ref: '#/definitions/models.Donor'
id:
type: integer
paid_amount:
type: integer
required:
- amount_per_distance
- donor
- id
type: object
models.Donor:
properties:
first_name:
type: string
id:
type: integer
last_name:
type: string
middle_name:
type: string
required:
- first_name
- id
- last_name
type: object
models.Group:
properties:
name:
type: string
parent_group:
properties:
name:
type: string
required:
- name
type: object
required:
- name
type: object
models.Runner:
properties:
first_name:
type: string
group:
$ref: '#/definitions/models.Group'
id:
type: integer
last_name:
type: string
middle_name:
type: string
required:
- first_name
- group
- id
- last_name
type: object
models.RunnerWithDonations:
properties:
distance:
type: integer
distance_donations:
items:
$ref: '#/definitions/models.DistanceDonation'
type: array
first_name:
type: string
group:
$ref: '#/definitions/models.Group'
id:
type: integer
last_name:
type: string
middle_name:
type: string
total_donations:
type: integer
total_per_distance:
type: integer
required:
- distance
- first_name
- group
- id
- last_name
type: object
info:
contact:
email: info@odit.services
name: ODIT.Services UG (haftungsbeschränkt)
url: https://odit.services
description: This is the API documentation for the LfK Document Server - a tool
for pdf generation.
license:
name: CC BY-NC-SA 4.0
termsOfService: https://lauf-fuer-kaya.de/datenschutz
title: LfK Document Server API
paths:
/v1/barcodes/{type}/{content}:
get:
description: Generate barcodes based on the provided data
parameters:
- description: Barcode type
enum:
- ean13
- code128
in: path
name: type
required: true
type: string
- description: Barcode content
in: path
minLength: 1
name: content
required: true
type: string
- default: 1000
description: Barcode width
in: query
maximum: 10000
minimum: 1
name: width
type: integer
- default: 1000
description: Barcode height
in: query
maximum: 10000
minimum: 1
name: height
type: integer
- default: 10
description: Padding around the barcode (included in image size)
in: query
maximum: 100
minimum: 0
name: padding
type: integer
produces:
- image/png
responses: {}
summary: Generate barcodes
tags:
- barcodes
/v1/pdfs/cards:
post:
consumes:
- application/json
description: Generate cards based on the provided data
parameters:
- description: Card data
in: body
name: data
required: true
schema:
$ref: '#/definitions/models.CardRequest'
produces:
- application/pdf
responses: {}
security:
- ApiKeyAuth: []
summary: Generate runner cards
tags:
- pdfs
/v1/pdfs/certificates:
post:
consumes:
- application/json
description: Generate certificates based on the provided data
parameters:
- description: Certificate data
in: body
name: data
required: true
schema:
$ref: '#/definitions/models.CertificateRequest'
produces:
- application/pdf
responses: {}
security:
- ApiKeyAuth: []
summary: Generate runner certificates
tags:
- pdfs
/v1/pdfs/contracts:
post:
consumes:
- application/json
description: Generate a contract based on the provided data
parameters:
- description: Contract data
in: body
name: data
required: true
schema:
$ref: '#/definitions/models.ContractRequest'
produces:
- application/pdf
responses: {}
security:
- ApiKeyAuth: []
summary: Generate a contract
tags:
- pdfs
securityDefinitions:
ApiKeyAuth:
in: query
name: key
type: apiKey
swagger: "2.0"

69
go.mod Normal file
View File

@ -0,0 +1,69 @@
module git.odit.services/lfk/document-server
go 1.23.2
toolchain go1.23.3
require (
github.com/gofiber/fiber/v2 v2.52.5
github.com/gofiber/swagger v1.1.0
github.com/swaggo/swag v1.16.4
)
require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/boombuler/barcode v1.0.2 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hhrutter/lzw v1.0.0 // indirect
github.com/hhrutter/pkcs7 v0.2.0 // indirect
github.com/hhrutter/tiff v1.0.2 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/makiuchi-d/gozxing v0.1.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/oxplot/papersizes v0.0.0-20181201065918-90a3a5ae1915 // indirect
github.com/pdfcpu/pdfcpu v0.10.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/redis/go-redis/v9 v9.7.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.19.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/swaggo/files/v2 v2.0.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.61.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/image v0.26.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.24.0 // indirect
golang.org/x/tools v0.27.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

162
go.sum Normal file
View File

@ -0,0 +1,162 @@
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/boombuler/barcode v1.0.2 h1:79yrbttoZrLGkL/oOI8hBrUKucwOL0oOjUgEguGMcJ4=
github.com/boombuler/barcode v1.0.2/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
github.com/gofiber/swagger v1.1.0 h1:ff3rg1fB+Rp5JN/N8jfxTiZtMKe/9tB9QDc79fPiJKQ=
github.com/gofiber/swagger v1.1.0/go.mod h1:pRZL0Np35sd+lTODTE5The0G+TMHfNY+oC4hM2/i5m8=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hhrutter/lzw v1.0.0 h1:laL89Llp86W3rRs83LvKbwYRx6INE8gDn0XNb1oXtm0=
github.com/hhrutter/lzw v1.0.0/go.mod h1:2HC6DJSn/n6iAZfgM3Pg+cP1KxeWc3ezG8bBqW5+WEo=
github.com/hhrutter/pkcs7 v0.2.0 h1:i4HN2XMbGQpZRnKBLsUwO3dSckzgX142TNqY/KfXg+I=
github.com/hhrutter/pkcs7 v0.2.0/go.mod h1:aEzKz0+ZAlz7YaEMY47jDHL14hVWD6iXt0AgqgAvWgE=
github.com/hhrutter/tiff v1.0.2 h1:7H3FQQpKu/i5WaSChoD1nnJbGx4MxU5TlNqqpxw55z8=
github.com/hhrutter/tiff v1.0.2/go.mod h1:pcOeuK5loFUE7Y/WnzGw20YxUdnqjY1P0Jlcieb/cCw=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/makiuchi-d/gozxing v0.1.1 h1:xxqijhoedi+/lZlhINteGbywIrewVdVv2wl9r5O9S1I=
github.com/makiuchi-d/gozxing v0.1.1/go.mod h1:eRIHbOjX7QWxLIDJoQuMLhuXg9LAuw6znsUtRkNw9DU=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/oxplot/papersizes v0.0.0-20181201065918-90a3a5ae1915 h1:4WzMzgExTgBfuUQ/HegMf+jcHtH+c3fl7eySUQUbfzg=
github.com/oxplot/papersizes v0.0.0-20181201065918-90a3a5ae1915/go.mod h1:LJRTnhoARxQgMyT7T9L+ZzwR4OrmyHTy5LPxZEzE1CM=
github.com/pdfcpu/pdfcpu v0.10.2 h1:DB2dWuoq0eF0QwHjgyLirYKLTCzFOoZdmmIUSu72aL0=
github.com/pdfcpu/pdfcpu v0.10.2/go.mod h1:Q2Z3sqdRqHTdIq1mPAUl8nfAoim8p3c1ASOaQ10mCpE=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/swaggo/files/v2 v2.0.1 h1:XCVJO/i/VosCDsJu1YLpdejGsGnBE9deRMpjN4pJLHk=
github.com/swaggo/files/v2 v2.0.1/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM=
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.57.0 h1:Xw8SjWGEP/+wAAgyy5XTvgrWlOD1+TxbbvNADYCm1Tg=
github.com/valyala/fasthttp v1.57.0/go.mod h1:h6ZBaPRlzpZ6O3H5t2gEk1Qi33+TmLvfwgLLp0t9CpE=
github.com/valyala/fasthttp v1.61.0 h1:VV08V0AfoRaFurP1EWKvQQdPTZHiUzaVoulX1aBDgzU=
github.com/valyala/fasthttp v1.61.0/go.mod h1:wRIV/4cMwUPWnRcDno9hGnYZGh78QzODFfo1LTUhBog=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o=
golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

73
handlers/barcode.go Normal file
View File

@ -0,0 +1,73 @@
package handlers
import (
"strconv"
"github.com/gofiber/fiber/v2"
)
// GenerateBarcode godoc
//
// @Summary Generate barcodes
// @Description Generate barcodes based on the provided data
// @Tags barcodes
// @Param type path string true "Barcode type" Enums(ean13, code128)
// @Param content path string true "Barcode content" MinLength(1)
// @Param width query int false "Barcode width" Minimum(1) Maximum(10000) default(1000)
// @Param height query int false "Barcode height" Minimum(1) Maximum(10000) default(1000)
// @Param padding query int false "Padding around the barcode (included in image size)" Minimum(0) Maximum(100) default(10)
// @Produce image/png
// @Router /v1/barcodes/{type}/{content} [get]
func (h *DefaultHandler) GenerateBarcode(c *fiber.Ctx) error {
logger := h.Logger.Named("GenerateBarcode")
// Get the type and content from the URL
barcodeType := c.Params("type")
barcodeContent := c.Params("content")
// Get the width and height from the query parameters
widthStr := c.Query("width", "1000")
heightStr := c.Query("height", "1000")
paddingStr := c.Query("padding", "10")
// Convert width and height to integers
width, err := strconv.Atoi(widthStr)
if err != nil {
logger.Errorw("Invalid width parameter", "width", widthStr, "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid width parameter",
})
}
height, err := strconv.Atoi(heightStr)
if err != nil {
logger.Errorw("Invalid height parameter", "height", heightStr, "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid height parameter",
})
}
padding, err := strconv.Atoi(paddingStr)
if err != nil {
logger.Errorw("Invalid padding parameter", "padding", paddingStr, "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid padding parameter",
})
}
logger = logger.With("type", barcodeType, "content", barcodeContent, "width", width, "height", height, "padding", padding)
// Generate the barcode
logger.Info("Generating barcode")
barcode, err := h.BarcodeService.GenerateBarcode(barcodeType, barcodeContent, width, height, padding)
if err != nil {
logger.Errorw("Failed to generate barcode", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(),
})
}
logger.Info("Barcode generated")
c.Set(fiber.HeaderContentType, "image/png")
return c.Send(barcode.Bytes())
}

203
handlers/card.go Normal file
View File

@ -0,0 +1,203 @@
package handlers
import (
"fmt"
"os"
"slices"
"git.odit.services/lfk/document-server/models"
"github.com/gofiber/fiber/v2"
"github.com/pdfcpu/pdfcpu/pkg/api"
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model"
)
// GenerateCard godoc
//
// @Summary Generate runner cards
// @Description Generate cards based on the provided data
// @Tags pdfs
// @Accept json
// @Param data body models.CardRequest true "Card data"
// @Produce application/pdf
// @Security ApiKeyAuth
// @Router /v1/pdfs/cards [post]
func (h *DefaultHandler) GenerateCard(c *fiber.Ctx) error {
logger := h.Logger.Named("GenerateCard")
cardRequest := new(models.CardRequest)
if err := c.BodyParser(cardRequest); err != nil {
logger.Errorw("Invalid request", "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": err.Error(),
})
}
if !slices.Contains([]string{"en", "de"}, cardRequest.Locale) {
logger.Errorw("Invalid locale", "locale", cardRequest.Locale)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid locale",
})
}
logger = logger.With("locale", cardRequest.Locale)
templateString, err := h.StaticService.GetTemplate(cardRequest.Locale, "card")
if err != nil {
logger.Errorw("Template not found", "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Template not found",
})
}
template, err := h.Templater.StringToTemplate(templateString)
if err != nil {
logger.Errorw("Error parsing template", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(),
})
}
segmentLength := calculateOptimalSegmentSize(len(cardRequest.Cards))
pdfs := []string{}
for i := 0; i < len(cardRequest.Cards); i += segmentLength {
segment := cardRequest.Cards[i:]
if len(segment) > segmentLength {
segment = cardRequest.Cards[i : i+segmentLength]
}
genConfig := &models.CardTemplateOptions{
CardSegments: splitCardSegments(segment),
EventName: h.Config.EventName,
CardSubtitle: h.Config.CardSubtitle,
BarcodeFormat: h.Config.CardBarcodeFormat,
BarcodePrefix: h.Config.CardBarcodePrefix,
}
logger.Info("Generating card html")
result, err := h.Templater.Execute(template, genConfig)
if err != nil {
logger.Errorw("Error executing template", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(),
})
}
logger.Info("Generated card html")
c.Set(fiber.HeaderContentType, "text/html")
logger.Info("Converting html to pdf")
pdf, err := h.Converter.ToPdf(result, "a4", false)
if err != nil {
logger.Errorw("Error converting html to pdf", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(),
})
}
tempFile, err := os.CreateTemp("", fmt.Sprintf("cards-%d-*.pdf", i/segmentLength))
if err != nil {
logger.Errorw("Error creating temp file", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(),
})
}
defer os.Remove(tempFile.Name()) // Ensure cleanup even on error paths
if _, err := tempFile.Write(pdf); err != nil {
logger.Errorw("Error writing pdf to temp file", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(),
})
}
tempFile.Close()
pdfs = append(pdfs, tempFile.Name())
}
outputFile := "./output.pdf"
conf := model.NewDefaultConfiguration()
err = api.MergeCreateFile(pdfs, outputFile, false, conf)
if err != nil {
logger.Errorw("Failed to merge PDFs", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(),
})
}
// Clean up individual PDF files
for _, file := range pdfs {
if err := os.Remove(file); err != nil {
logger.Warnw("Failed to remove temporary PDF file", "file", file, "error", err)
}
}
// Set headers and return the merged PDF
c.Set(fiber.HeaderContentType, "application/pdf")
c.Set(fiber.HeaderContentDisposition, "attachment; filename=runner-cards.pdf")
pdfBytes, err := os.ReadFile(outputFile)
if err != nil {
logger.Errorw("Failed to read merged PDF", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(),
})
}
os.Remove(outputFile) // Clean up the merged file
logger.Info("Converted html to pdf")
c.Set(fiber.HeaderContentType, "application/pdf")
c.Set(fiber.HeaderContentDisposition, "attachment; filename=runner-cards.pdf")
return c.Send(pdfBytes)
}
func calculateOptimalSegmentSize(totalCards int) int {
if totalCards < 30 {
return 25 // Reduces overhead for really small batches
}
// Base size for small batches
if totalCards < 100 {
return 50
}
// For medium batches
if totalCards < 500 {
return 75
}
// For large batches, be more conservative
return 100
}
func invertCardArrayItemPairs(cards []models.Card) []models.Card {
inverted := make([]models.Card, 0)
for i := 0; i < len(cards); i += 2 {
if i+1 < len(cards) {
inverted = append(inverted, cards[i+1])
}
inverted = append(inverted, cards[i])
}
return inverted
}
func splitCardSegments(cards []models.Card) []models.CardTemplateSegment {
cardSegments := make([]models.CardTemplateSegment, 0)
const currentCards = 0
for i := 0; i < len(cards); i += 10 {
segmentLength := 10
if len(cards)-i < 10 {
segmentLength = len(cards) - i
}
segment := cards[i : i+segmentLength]
if segmentLength%2 != 0 {
segment = append(segment, models.Card{
ID: 0,
Enabled: false,
Runner: models.Runner{},
Code: "",
})
}
cardSegments = append(cardSegments, models.CardTemplateSegment{
Cards: segment,
CardsSwapped: invertCardArrayItemPairs(segment),
})
}
return cardSegments
}

108
handlers/certificate.go Normal file
View File

@ -0,0 +1,108 @@
package handlers
import (
"slices"
"git.odit.services/lfk/document-server/models"
"github.com/gofiber/fiber/v2"
)
// GenerateCertificate godoc
//
// @Summary Generate runner certificates
// @Description Generate certificates based on the provided data
// @Tags pdfs
// @Accept json
// @Param data body models.CertificateRequest true "Certificate data"
// @Produce application/pdf
// @Security ApiKeyAuth
// @Router /v1/pdfs/certificates [post]
func (h *DefaultHandler) GenerateCertificate(c *fiber.Ctx) error {
logger := h.Logger.Named("GenerateCertificate")
certificateRequest := new(models.CertificateRequest)
if err := c.BodyParser(certificateRequest); err != nil {
logger.Errorw("Invalid request", "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": err.Error(),
})
}
if !slices.Contains([]string{"en", "de"}, certificateRequest.Locale) {
logger.Errorw("Invalid locale", "locale", certificateRequest.Locale)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid locale",
})
}
logger = logger.With("locale", certificateRequest.Locale)
templateString, err := h.StaticService.GetTemplate(certificateRequest.Locale, "certificate")
if err != nil {
logger.Errorw("Template not found", "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Template not found",
})
}
template, err := h.Templater.StringToTemplate(templateString)
if err != nil {
logger.Errorw("Error parsing template", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(),
})
}
genConfig := &models.CertificateTemplateOptions{
Runners: addUpRunnerDonations(certificateRequest.Runners),
EventName: h.Config.EventName,
Footer: h.Config.CertificateFooter,
CurrencySymbol: h.Config.CurrencySymbol,
Locale: certificateRequest.Locale,
SepaConfig: &models.SepaConfig{
BIC: h.Config.SepaBic,
HolderName: h.Config.SepaName,
IBAN: h.Config.SepaIban,
CurrencyIdentifier: h.Config.CurrencyIdentifier,
},
}
logger.Info("Generating certificate html")
result, err := h.Templater.Execute(template, genConfig)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(),
})
}
logger.Info("Generated card html")
c.Set(fiber.HeaderContentType, "text/html")
logger.Info("Converting html to pdf")
pdf, err := h.Converter.ToPdf(result, "a4", false)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(),
})
}
logger.Info("Converted html to pdf")
c.Set(fiber.HeaderContentType, "application/pdf")
c.Set(fiber.HeaderContentDisposition, "attachment; filename=certificate.pdf")
return c.Send(pdf)
}
func addUpRunnerDonations(runners []models.RunnerWithDonations) []models.RunnerWithDonations {
for i := 0; i < len(runners); i++ {
for j := 0; j < len(runners[i].DistanceDonations); j++ {
runners[i].TotalDonations += runners[i].DistanceDonations[j].Amount
runners[i].TotalPerDistance += runners[i].DistanceDonations[j].AmountPerDistance
}
if runners[i].Group.ParentGroup.Name != "" {
runners[i].CombinedGroupName = runners[i].Group.ParentGroup.Name + " - " + runners[i].Group.Name
} else {
runners[i].CombinedGroupName = runners[i].Group.Name
}
}
return runners
}

96
handlers/contract.go Normal file
View File

@ -0,0 +1,96 @@
package handlers
import (
"slices"
"git.odit.services/lfk/document-server/models"
"github.com/gofiber/fiber/v2"
)
// GenerateContract godoc
//
// @Summary Generate a contract
// @Description Generate a contract based on the provided data
// @Tags pdfs
// @Accept json
// @Param data body models.ContractRequest true "Contract data"
// @Produce application/pdf
// @Security ApiKeyAuth
// @Router /v1/pdfs/contracts [post]
func (h *DefaultHandler) GenerateContract(c *fiber.Ctx) error {
logger := h.Logger.Named("GenerateContract")
contract := new(models.ContractRequest)
if err := c.BodyParser(contract); err != nil {
logger.Errorw("Invalid request", "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": err.Error(),
})
}
if !slices.Contains([]string{"en", "de"}, contract.Locale) {
logger.Errorw("Invalid locale", "locale", contract.Locale)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid locale",
})
}
logger = logger.With("locale", contract.Locale)
templateString, err := h.StaticService.GetTemplate(contract.Locale, "contract")
if err != nil {
logger.Errorw("Template not found", "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Template not found",
})
}
template, err := h.Templater.StringToTemplate(templateString)
if err != nil {
logger.Errorw("Error parsing template", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(),
})
}
genConfig := &models.ContractTemplateOptions{
Runners: repeatRunnerArrayItems(contract.Runners, 2),
CurrencySymbol: h.Config.CurrencySymbol,
Disclaimer: h.Config.SponosringDisclaimer,
ReceiptMinimumAmount: h.Config.SponsoringReceiptMinimum,
EventName: h.Config.EventName,
BarcodeFormat: h.Config.SponsoringBarcodeFormat,
BarcodePrefix: h.Config.SponsoringBarcodePrefix,
}
logger.Info("Generating contract html")
result, err := h.Templater.Execute(template, genConfig)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(),
})
}
logger.Info("Generated contract html")
logger.Info("Converting html to pdf")
pdf, err := h.Converter.ToPdf(result, "a5", true)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(),
})
}
logger.Info("Converted html to pdf")
c.Set(fiber.HeaderContentType, "application/pdf")
c.Set(fiber.HeaderContentDisposition, "attachment; filename=runner-contracts.pdf")
return c.Send(pdf)
}
func repeatRunnerArrayItems(runners []models.Runner, duplicates int) []models.Runner {
var duplicatedRunners []models.Runner
for _, runner := range runners {
for i := 0; i < duplicates; i++ {
duplicatedRunners = append(duplicatedRunners, runner)
}
}
return duplicatedRunners
}

24
handlers/handlers.go Normal file
View File

@ -0,0 +1,24 @@
package handlers
import (
"git.odit.services/lfk/document-server/models"
"git.odit.services/lfk/document-server/services"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
type Handler interface {
GenerateCard(*fiber.Ctx) error
GenerateContract(*fiber.Ctx) error
GenerateCertificate(*fiber.Ctx) error
GenerateBarcode(*fiber.Ctx) error
}
type DefaultHandler struct {
Config *models.Config
BarcodeService services.BarcodeService
Templater services.Templater
Converter services.Converter
StaticService services.StaticService
Logger *zap.SugaredLogger
}

7
handlers/util.go Normal file
View File

@ -0,0 +1,7 @@
package handlers
import "github.com/gofiber/fiber/v2"
func (h *DefaultHandler) NotFoundHandler(c *fiber.Ctx) error {
return c.Status(404).SendString("Not Found")
}

File diff suppressed because it is too large Load Diff

198
main.go Normal file
View File

@ -0,0 +1,198 @@
package main
import (
"crypto/sha256"
"crypto/subtle"
"os"
"strings"
"git.odit.services/lfk/document-server/docs" // Correct import path for docs
"git.odit.services/lfk/document-server/handlers"
"git.odit.services/lfk/document-server/models"
"git.odit.services/lfk/document-server/services"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/keyauth"
"github.com/gofiber/fiber/v2/middleware/requestid"
"github.com/gofiber/swagger"
"github.com/redis/go-redis/v9"
"github.com/spf13/viper"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var (
config *models.Config
logger *zap.SugaredLogger
)
func validateAPIKey(c *fiber.Ctx, key string) (bool, error) {
hashedAPIKey := sha256.Sum256([]byte(config.APIKey))
hashedKey := sha256.Sum256([]byte(key))
if subtle.ConstantTimeCompare(hashedAPIKey[:], hashedKey[:]) == 1 {
return true, nil
}
return false, keyauth.ErrMissingOrMalformedAPIKey
}
func loadEnv() error {
viper.SetDefault("LOGLEVEL", "INFO")
viper.SetDefault("PRODUCION", false)
viper.SetDefault("PORT", "3000")
viper.SetDefault("APIKEY", "lfk")
viper.SetDefault("EVENTNAME", "Demo Event")
viper.SetDefault("CURRENCYSYMBOL", "€")
viper.SetDefault("CURRENCYIDENTIFIER", "EUR")
viper.SetDefault("CARD_SUBTITLE", "Runner Card")
viper.SetDefault("CARD_BARCODEFORMAT", "ean13")
viper.SetDefault("CARD_BARCODEPREFIX", "")
viper.SetDefault("SPONSORING_RECEIPTMINIMUM", 0)
viper.SetDefault("SPONSORING_DISCLAIMER", "Disclaimer")
viper.SetDefault("SPONSORING_BARCODEFORMAT", "code128")
viper.SetDefault("SPONSORING_BARCODEPREFIX", "")
viper.SetDefault("CERTIFICATE_FOOTER", "Footer")
viper.SetDefault("GOTENBERG_BASEURL", "")
viper.SetDefault("REDIS_ADDR", "")
viper.SetDefault("SEPA_BIC", "")
viper.SetDefault("SEPA_NAME", "")
viper.SetDefault("SEPA_IBAN", "")
// Load .env file
viper.SetConfigFile(".env")
// Load environment variables
viper.AutomaticEnv()
err := viper.ReadInConfig()
if err != nil {
logger.Warn("No .env file found")
}
// Unmarshal the config from file and env into the config struct
err = viper.Unmarshal(&config)
if err != nil {
return err
}
logger.Infow("Loaded config", "config", &config)
return nil
}
func initLogger() error {
logLevel := os.Getenv("LOGLEVEL")
if logLevel == "" {
logLevel = "INFO"
}
var zapLogLevel zapcore.Level
err := zapLogLevel.UnmarshalText([]byte(strings.ToLower(logLevel)))
if err != nil {
zapLogLevel = zapcore.InfoLevel
}
zapConfig := zap.NewProductionConfig()
zapConfig.Level = zap.NewAtomicLevelAt(zapLogLevel)
zapLogger, err := zapConfig.Build()
if err != nil {
return err
}
defer zapLogger.Sync()
logger = zapLogger.Sugar()
logger.Debug("Initialized logger")
return nil
}
// @title LfK Document Server API
// @description This is the API documentation for the LfK Document Server - a tool for pdf generation.
// @license.name CC BY-NC-SA 4.0
// @termsOfService https://lauf-fuer-kaya.de/datenschutz
// @contact.name ODIT.Services UG (haftungsbeschränkt)
// @contact.url https://odit.services
// @contact.email info@odit.services
// @securityDefinitions.apiKey ApiKeyAuth
// @in query
// @name key
func main() {
// Init the logger
err := initLogger()
if err != nil {
return
}
err = loadEnv()
if err != nil {
logger.Error(err)
return
}
var redisClient *redis.Client
if config.RedisAddr != "" {
logger.Infow("Using redis", "redisAddr", config.RedisAddr)
redisClient = redis.NewClient(&redis.Options{
Addr: config.RedisAddr,
})
}
barcodeGenerator := &services.DefaultBarcodeService{
RedisClient: redisClient,
Logger: logger.Named("DefaultBarcodeService"),
}
staticService := &services.DefaultStaticService{
Cache: make(map[string]string),
Logger: logger.Named("DefaultStaticService"),
}
handler := handlers.DefaultHandler{
Config: config,
BarcodeService: barcodeGenerator,
StaticService: staticService,
Templater: &services.DefaultTemplater{
BarcodeService: barcodeGenerator,
StaticService: staticService,
Logger: logger.Named("DefaultTemplater"),
},
Converter: &services.GotenbergConverter{
BaseUrl: config.GotenbergBaseUrl,
Logger: logger.Named("GotenbergConverter"),
},
Logger: logger.Named("DefaultHandler"),
}
logger.Debug("Initialized services")
// Create a new Fiber instance
app := fiber.New(fiber.Config{
Prefork: config.Prod,
})
app.Use(cors.New())
app.Use(requestid.New())
// Swagger documentation route
app.Get("/swagger/*", swagger.HandlerDefault)
v1 := app.Group("/v1")
pdfv1 := v1.Group("/pdfs")
pdfv1.Use(keyauth.New(keyauth.Config{
KeyLookup: "query:key",
Validator: validateAPIKey,
}))
pdfv1.Post("/contracts", handler.GenerateContract)
pdfv1.Post("/cards", handler.GenerateCard)
pdfv1.Post("/certificates", handler.GenerateCertificate)
v1.Get("/barcodes/:type/:content", handler.GenerateBarcode)
logger.Debug("Initialized routes")
app.Use(handler.NotFoundHandler)
docs.SwaggerInfo.BasePath = "/"
logger.Infow("Starting server", "port", config.Port)
logger.Error(app.Listen("0.0.0.0:" + config.Port))
}

26
models/card.go Normal file
View File

@ -0,0 +1,26 @@
package models
type CardRequest struct {
Cards []Card `json:"cards" validate:"required"`
Locale string `json:"locale" enums:"en,de" validate:"required"`
}
type Card struct {
ID int `json:"id" validate:"required"`
Enabled bool `json:"enabled" default:"true"`
Runner Runner `json:"runner" validate:"required"`
Code string `json:"code" validate:"required"`
}
type CardTemplateOptions struct {
CardSegments []CardTemplateSegment `json:"card_segments"`
EventName string `json:"event_name"`
CardSubtitle string `json:"card_subtitle"`
BarcodeFormat string `json:"barcode_format"`
BarcodePrefix string `json:"barcode_prefix"`
}
type CardTemplateSegment struct {
Cards []Card `json:"cards"`
CardsSwapped []Card `json:"cards_swapped"`
}

51
models/certificate.go Normal file
View File

@ -0,0 +1,51 @@
package models
type CertificateRequest struct {
Runners []RunnerWithDonations `json:"runners" validate:"required"`
Locale string `json:"locale" enums:"en,de" validate:"required"`
}
type RunnerWithDonations struct {
ID int `json:"id" validate:"required"`
FirstName string `json:"first_name" validate:"required"`
MiddleName string `json:"middle_name" validate:"optional"`
LastName string `json:"last_name" validate:"required"`
Group Group `json:"group" validate:"required"`
CombinedGroupName string `json:"combined_group_name" validate:"optional"`
Distance int `json:"distance" validate:"required"`
DistanceDonations []DistanceDonation `json:"distance_donations" validate:"optional"`
TotalPerDistance int `json:"total_per_distance" validate:"optional"`
TotalDonations int `json:"total_donations" validate:"optional"`
SelfServiceLink string `json:"self_service_link" validate:"required"`
}
type DistanceDonation struct {
ID int `json:"id" validate:"required"`
Amount int `json:"amount"`
PaidAmount int `json:"paid_amount" validate:"optional"`
AmountPerDistance int `json:"amount_per_distance" validate:"required"`
Donor Donor `json:"donor" validate:"required"`
}
type Donor struct {
ID int `json:"id" validate:"required"`
FirstName string `json:"first_name" validate:"required"`
MiddleName string `json:"middle_name" validate:"optional"`
LastName string `json:"last_name" validate:"required"`
}
type CertificateTemplateOptions struct {
Runners []RunnerWithDonations `json:"runners"`
EventName string `json:"event_name"`
Footer string `json:"footer"`
CurrencySymbol string `json:"currency_symbol"`
Locale string `json:"locale"`
SepaConfig *SepaConfig `json:"sepa_config"`
}
type SepaConfig struct {
IBAN string `json:"iban" validate:"required"`
HolderName string `json:"holder_name" validate:"required"`
BIC string `json:"bic" validate:"required"`
CurrencyIdentifier string `json:"currency_identifier" validate:"required"`
}

24
models/config.go Normal file
View File

@ -0,0 +1,24 @@
package models
type Config struct {
LogLevel string `mapstructure:"LOGLEVEL"`
Prod bool `mapstructure:"PRODUCION"`
Port string `mapstructure:"PORT"`
APIKey string `mapstructure:"APIKEY"`
EventName string `mapstructure:"EVENTNAME"`
CurrencySymbol string `mapstructure:"CURRENCYSYMBOL"`
CurrencyIdentifier string `mapstructure:"CURRENCYIDENTIFIER"`
CardSubtitle string `mapstructure:"CARD_SUBTITLE"`
CardBarcodeFormat string `mapstructure:"CARD_BARCODEFORMAT"`
CardBarcodePrefix string `mapstructure:"CARD_BARCODEPREFIX"`
SponsoringReceiptMinimum string `mapstructure:"SPONSORING_RECEIPTMINIMUM"`
SponosringDisclaimer string `mapstructure:"SPONSORING_DISCLAIMER"`
SponsoringBarcodeFormat string `mapstructure:"SPONSORING_BARCODEFORMAT"`
SponsoringBarcodePrefix string `mapstructure:"SPONSORING_BARCODEPREFIX"`
CertificateFooter string `mapstructure:"CERTIFICATE_FOOTER"`
GotenbergBaseUrl string `mapstructure:"GOTENBERG_BASEURL"`
RedisAddr string `mapstructure:"REDIS_ADDR"`
SepaBic string `mapstructure:"SEPA_BIC"`
SepaName string `mapstructure:"SEPA_NAME"`
SepaIban string `mapstructure:"SEPA_IBAN"`
}

31
models/contract.go Normal file
View File

@ -0,0 +1,31 @@
package models
type ContractRequest struct {
Runners []Runner `json:"runners" validate:"required"`
Locale string `json:"locale" enums:"en,de" validate:"required"`
}
type Runner struct {
ID int `json:"id" validate:"required"`
FirstName string `json:"first_name" validate:"required"`
MiddleName string `json:"middle_name" validate:"optional"`
LastName string `json:"last_name" validate:"required"`
Group Group `json:"group" validate:"required"`
}
type Group struct {
Name string `json:"name" validate:"required"`
ParentGroup struct {
Name string `json:"name" validate:"required"`
} `json:"parent_group" validate:"optional"`
}
type ContractTemplateOptions struct {
Runners []Runner `json:"runners"`
CurrencySymbol string `json:"currency_symbol"`
Disclaimer string `json:"disclaimer"`
ReceiptMinimumAmount string `json:"receipt_minimum_amount"`
EventName string `json:"event_name"`
BarcodeFormat string `json:"barcode_format"`
BarcodePrefix string `json:"barcode_prefix"`
}

View File

@ -1,92 +0,0 @@
{
"name": "@odit/lfk-document-server",
"version": "0.4.2",
"description": "The document generation server for the LfK! runner system. This generates certificates, sponsoring aggreements and more",
"main": "src/app.ts",
"scripts": {
"dev": "nodemon src/app.ts",
"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"
},
"repository": {
"type": "git",
"url": "git@git.odit.services:lfk/document-server.git"
},
"keywords": [
"odit",
"lfk",
"pdf",
"generate"
],
"author": {
"name": "ODIT.Services",
"email": "info@odit.services",
"url": "https://odit.services"
},
"contributors": [
{
"name": "Philipp Dormann",
"email": "philipp@philippdormann.de",
"url": "https://philippdormann.de"
},
{
"name": "Nicolai Ort",
"email": "info@nicolai-ort.com",
"url": "https://nicolai-ort.com"
}
],
"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",
"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",
"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",
"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"
},
"release-it": {
"git": {
"commit": true,
"requireCleanWorkingDir": false,
"commitMessage": "🚀Bumped version to v${version}",
"requireBranch": "dev",
"push": false,
"tag": false
},
"npm": {
"publish": false
}
}
}

129
services/barcode.go Normal file
View File

@ -0,0 +1,129 @@
package services
import (
"bytes"
"context"
"fmt"
"image"
"image/color"
"image/draw"
"image/png"
"slices"
"time"
"github.com/boombuler/barcode"
"github.com/boombuler/barcode/code128"
"github.com/boombuler/barcode/ean"
"github.com/boombuler/barcode/qr"
"github.com/redis/go-redis/v9"
"go.uber.org/zap"
)
type BarcodeService interface {
GenerateBarcode(format string, content string, width int, height int, padding int) (bytes.Buffer, error)
IsTypeSupported(format string) bool
}
type DefaultBarcodeService struct {
RedisClient *redis.Client
Logger *zap.SugaredLogger
}
func (b *DefaultBarcodeService) GenerateBarcode(format string, content string, width int, height int, padding int) (bytes.Buffer, error) {
ctx := context.Background()
logger := b.Logger.Named("GenerateBarcode")
if !b.IsTypeSupported(format) {
logger.Errorw("Unsupported barcode type", "type", format)
return bytes.Buffer{}, fmt.Errorf("unsupported barcode type: %s", format)
}
logger = logger.With("type", format, "content", content, "width", width, "height", height, "padding", padding)
cacheKey := fmt.Sprintf("barcode:%s:%s:%d:%d:%d", format, content, width, height, padding)
if b.RedisClient != nil {
logger.Debugw("Checking cache for barcode", "key", cacheKey)
cachedBarcode, err := b.RedisClient.Get(ctx, cacheKey).Result()
if err == nil {
logger.Infow("Barcode found in cache", "key", cacheKey)
buf := bytes.Buffer{}
buf.Write([]byte(cachedBarcode))
return buf, nil
}
}
var generatedCode barcode.Barcode
var err error
switch format {
case "ean13":
generatedCode, err = ean.Encode(content)
if err != nil {
return bytes.Buffer{}, err
}
break
case "code128":
generatedCode, err = code128.Encode(content)
if err != nil {
return bytes.Buffer{}, err
}
break
case "qr":
// Always use qr.Auto encoding to support all characters in the content
encoding := qr.Auto
// QR code generation with error correction level M and auto encoding
generatedCode, err = qr.Encode(content, qr.M, encoding)
if err != nil {
return bytes.Buffer{}, err
}
break
}
// Create a white background image
bg := image.NewRGBA(image.Rect(0, 0, width, height))
white := color.RGBA{255, 255, 255, 255}
draw.Draw(bg, bg.Bounds(), &image.Uniform{white}, image.Point{}, draw.Src)
logger.Debug("Created white background")
// Calculate the new size for the barcode to fit within the padding
newWidth := width - 2*padding
newHeight := height - 2*padding
// Scale the barcode to the new size
scaledCode, err := barcode.Scale(generatedCode, newWidth, newHeight)
if err != nil {
logger.Errorw("Failed to scale barcode", "error", err)
return bytes.Buffer{}, err
}
logger.Debug("Scaled barcode")
// Draw the barcode on top of the white background with padding
draw.Draw(bg, scaledCode.Bounds().Add(image.Point{padding, padding}), scaledCode, image.Point{}, draw.Over)
logger.Debug("Drew barcode on background")
var buf bytes.Buffer
err = png.Encode(&buf, bg)
if err != nil {
logger.Errorw("Failed to encode barcode to PNG", "error", err)
return bytes.Buffer{}, err
}
logger.Debug("Encoded barcode to PNG")
if b.RedisClient != nil {
err = b.RedisClient.Set(ctx, cacheKey, buf.String(), 10*time.Minute).Err()
logger.Debugw("Cached barcode", "key", cacheKey)
if err != nil {
logger.Errorw("Failed to cache barcode", "error", err)
return bytes.Buffer{}, err
}
}
logger.Info("Generated barcode")
return buf, nil
}
func (b *DefaultBarcodeService) IsTypeSupported(format string) bool {
supportedTypes := []string{"ean13", "code128", "qr"}
return slices.Contains(supportedTypes, format)
}

131
services/converter.go Normal file
View File

@ -0,0 +1,131 @@
package services
import (
"bytes"
"fmt"
"mime/multipart"
"net/http"
"strconv"
"github.com/oxplot/papersizes"
"go.uber.org/zap"
)
type Converter interface {
ToPdf(html string, pageSize string, landscape bool) ([]byte, error)
}
type GotenbergConverter struct {
BaseUrl string
Logger *zap.SugaredLogger
}
func (g *GotenbergConverter) ToPdf(html string, pageSize string, landscape bool) ([]byte, error) {
logger := g.Logger.Named("ToPdf").With("page_size", pageSize, "landscape", landscape, "base_url", g.BaseUrl)
client := &http.Client{}
defer client.CloseIdleConnections()
logger.Debug("Created HTTP client")
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("files", "index.html")
if err != nil {
logger.Errorw("Failed to create form file", "error", err)
return nil, err
}
_, err = part.Write([]byte(html))
if err != nil {
logger.Errorw("Failed to write to form file", "error", err)
return nil, err
}
size := papersizes.FromName(pageSize)
if size == nil {
logger.Errorw("Invalid page size", "size", pageSize)
return nil, fmt.Errorf("invalid page size: %s", pageSize)
}
err = writer.WriteField("paperWidth", strconv.Itoa(size.Width)+"mm")
if err != nil {
logger.Errorw("Failed to write paper width", "error", err)
return nil, err
}
err = writer.WriteField("paperHeight", strconv.Itoa(size.Height)+"mm")
if err != nil {
logger.Errorw("Failed to write paper height", "error", err)
return nil, err
}
err = writer.WriteField("landscape", strconv.FormatBool(landscape))
if err != nil {
logger.Errorw("Failed to write landscape", "error", err)
return nil, err
}
err = writer.WriteField("marginTop", "0")
if err != nil {
logger.Errorw("Failed to write margin top", "error", err)
return nil, err
}
err = writer.WriteField("marginBottom", "0")
if err != nil {
logger.Errorw("Failed to write margin bottom", "error", err)
return nil, err
}
err = writer.WriteField("marginLeft", "0")
if err != nil {
logger.Errorw("Failed to write margin left", "error", err)
return nil, err
}
err = writer.WriteField("marginRight", "0")
if err != nil {
logger.Errorw("Failed to write margin right", "error", err)
return nil, err
}
err = writer.WriteField("preferCssPageSize", "true")
if err != nil {
logger.Errorw("Failed to write prefer css page size", "error", err)
return nil, err
}
err = writer.Close()
if err != nil {
logger.Errorw("Failed to close writer", "error", err)
return nil, err
}
logger.Debug("Created form data")
logger.Debug("Creating HTTP request")
req, err := http.NewRequest("POST", g.BaseUrl+"/forms/chromium/convert/html", body)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", writer.FormDataContentType())
logger.Debug("Sending HTTP request")
resp, err := client.Do(req)
if err != nil {
logger.Errorw("Failed to send request", "error", err)
return nil, err
}
logger.Debug("Received HTTP response")
defer resp.Body.Close()
data := new(bytes.Buffer)
_, err = data.ReadFrom(resp.Body)
if err != nil {
logger.Errorw("Failed to read response body", "error", err)
return nil, err
}
logger.Debug("Returning PDF data")
return data.Bytes(), nil
}

142
services/templater.go Normal file
View File

@ -0,0 +1,142 @@
package services
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"html/template"
"math"
"strings"
"go.uber.org/zap"
)
type Templater interface {
Execute(template *template.Template, data interface{}) (string, error)
StringToTemplate(templateString string) (*template.Template, error)
}
type DefaultTemplater struct {
BarcodeService BarcodeService
StaticService StaticService
Logger *zap.SugaredLogger
}
func idToEan13(id string, prefix string) (string, error) {
if len(id) > 12 {
return "", errors.New("id too long")
}
for len(id) < 11 {
id = "0" + id
}
id = prefix + id
return id, nil
}
func (t *DefaultTemplater) GenerateEPC(iban string, bic string, name string, title string, amount int, currency string) (string, error) {
var err error
code := fmt.Sprintf(`BCD
002
1
INST
%s
%s
%s
%s%.2f
%s
`, bic, name, iban, currency, float64(amount)/100, title,
)
buf, err := t.BarcodeService.GenerateBarcode("qr", code, 500, 500, 0)
return base64.StdEncoding.EncodeToString(buf.Bytes()), err
}
func (t *DefaultTemplater) GenerateBarcode(code string, format string, prefix string) (string, error) {
var err error
if format == "ean13" {
code, err = idToEan13(code, prefix)
if err != nil {
return "", err
}
}
buf, err := t.BarcodeService.GenerateBarcode(format, code, 1000, 500, 0)
return base64.StdEncoding.EncodeToString(buf.Bytes()), err
}
func (t *DefaultTemplater) SelectSponsorImage(id int) (string, error) {
logger := t.Logger.Named("SelectSponsorImage")
sponsors, err := t.StaticService.ListFilesInStaticSubFolder("images/sponsors")
if err != nil {
logger.Errorw("Failed to list sponsors", "error", err)
return "", err
}
logger.Debugw("Selected sponsor", "sponsors", sponsors, "id", id, "selected", sponsors[id%len(sponsors)])
return t.StaticService.GetImage("sponsors/" + strings.TrimSuffix(sponsors[id%len(sponsors)], ".base64")), nil
}
func (t *DefaultTemplater) LoadImage(name string) (string, error) {
return t.StaticService.GetImage(name), nil
}
func (t *DefaultTemplater) FormatUnit(unit string, locale string, amount int) (string, error) {
var formatted string
var seperator string
switch locale {
case "de":
seperator = " "
default:
seperator = ""
}
switch unit {
case "kilometer":
if amount < 1000 {
formatted = fmt.Sprintf("%d%sm", amount, seperator)
} else if (amount % 1000) == 0 {
formatted = fmt.Sprintf("%d%skm", amount/1000, seperator)
} else {
kilometers := math.Floor(float64(amount) / 1000)
meters := amount - int(kilometers)*1000
formatted = fmt.Sprintf("%d%skm %d%sm", int(kilometers), seperator, meters, seperator)
}
case "euro":
formatted = fmt.Sprintf("%.2f", float64(amount)/100)
default:
return "", errors.New("unknown unit")
}
if locale == "de" {
return strings.Replace(formatted, ".", ",", -1), nil
}
return formatted, nil
}
func (t *DefaultTemplater) StringToTemplate(templateString string) (*template.Template, error) {
return template.New("template").Funcs(template.FuncMap{
"barcode": t.GenerateBarcode,
"sponsorLogo": t.SelectSponsorImage,
"formatUnit": t.FormatUnit,
"loadImage": t.LoadImage,
"epcCode": t.GenerateEPC,
}).Parse(templateString)
}
func (t *DefaultTemplater) Execute(baseTemplate *template.Template, data interface{}) (string, error) {
resultBuffer := new(bytes.Buffer)
err := baseTemplate.Execute(resultBuffer, data)
if err != nil {
return "", err
}
return resultBuffer.String(), nil
}

72
services/templates.go Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,16 +0,0 @@
const fs = require('fs');
// get all language files
const files = fs.readdirSync('./src/locales/');
files.forEach((f) => {
// read file as object
const unordered = JSON.parse(fs.readFileSync(`src/locales/${f}`));
// order object by keys alpabetically A-Z
const ordered = Object.keys(unordered).sort().reduce((obj, key) => {
obj[key] = unordered[key];
return obj;
}, {});
// format output as json for commit diff compatibility
const out = JSON.stringify(ordered, 0, 4);
// write output file
fs.writeFileSync(`src/locales/${f}`, out);
});

View File

@ -1,271 +0,0 @@
import axios from 'axios';
import cheerio from "cheerio";
import fs from "fs";
import Handlebars from 'handlebars';
import i18next from "i18next";
import Backend from 'i18next-fs-backend';
import mime from "mime-types";
import path from 'path';
import { PDFDocument } from 'pdf-lib';
import puppeteer from "puppeteer";
import { awaitAsyncHandlebarHelpers, helpers } from './asyncHelpers';
import { config } from './config';
import { Runner } from './models/Runner';
import { RunnerCard } from './models/RunnerCard';
import { RunnerGroup } from './models/RunnerGroup';
/**
* This class is responsible for all things pdf creation.
* This uses the html templates from src/templates.
*/
export class PdfCreator {
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 }
private static contractsPerRunner = config.contracts_per_runner;
/**
* Main constructor.
* Initializes i18n(ext), Handlebars and puppeteer.
*/
constructor() {
this.init();
}
/**
* Main constructor.
* Initializes i18n(ext), Handlebars and puppeteer.
*/
public async init() {
const minimal_args = [
'--autoplay-policy=user-gesture-required',
'--disable-background-networking',
'--disable-background-timer-throttling',
'--disable-backgrounding-occluded-windows',
'--disable-breakpad',
'--disable-client-side-phishing-detection',
'--disable-component-update',
'--disable-default-apps',
'--disable-dev-shm-usage',
'--disable-domain-reliability',
'--disable-extensions',
'--disable-features=AudioServiceOutOfProcess',
'--disable-hang-monitor',
'--disable-ipc-flooding-protection',
'--disable-notifications',
'--disable-offer-store-unmasked-wallet-cards',
'--disable-popup-blocking',
'--disable-print-preview',
'--disable-prompt-on-repost',
'--disable-renderer-backgrounding',
'--disable-speech-api',
'--disable-sync',
'--hide-scrollbars',
'--ignore-gpu-blacklist',
'--metrics-recording-only',
'--mute-audio',
'--no-default-browser-check',
'--no-first-run',
'--no-pings',
'--no-zygote',
'--password-store=basic',
'--use-gl=swiftshader',
'--no-sandbox'
];
await i18next
.use(Backend)
.init({
fallbackLng: 'en',
lng: 'en',
backend: {
loadPath: path.join(__dirname, '/locales/{{lng}}.json')
}
});
await Handlebars.registerHelper(helpers);
await Handlebars.registerHelper('__',
function (str) {
return i18next.t(str, PdfCreator.interpolations).toString();
}
);
await Handlebars.registerHelper('--sponsor',
function (str) {
const index = (parseInt(str) % config.sponor_logos.length);
if (isNaN(index)) {
return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wlseKgAAAABJRU5ErkJggg=="
}
return config.sponor_logos[index];
}
);
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)
*/
public async generateSponsoringContract(runners: Runner[], locale: string = "en", codeformat: string = config.codeformat): Promise<Buffer> {
if (runners.length == 1 && Object.keys(runners[0]).length == 0) {
runners[0] = this.generateEmptyRunner();
}
for (var i = 1; i < PdfCreator.contractsPerRunner; i++) {
runners = runners.reduce(function (res, current, index, array) {
return res.concat([current, current]);
}, []);
}
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.generateSponsoringContract(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}/sponsoring_contract.html`, 'utf8');
const template = Handlebars.compile(template_source);
let result = template({ runners, codeformat, disclaimer: config.disclaimer_text });
result = await awaitAsyncHandlebarHelpers(result);
const pdf = await this.renderPdf(result, { format: "A5", landscape: true });
return pdf
}
/**
* Generate runner card pdfs.
* @param cards The runner cars you want to generate the cards for.
* @param locale The locale used for the cards (default:en)
*/
public async generateRunnerCards(cards: RunnerCard[], locale: string = "en", codeformat: string = config.codeformat): Promise<Buffer> {
if (cards.length > 10) {
let pdf_promises = new Array<Promise<Buffer>>();
let i, j;
for (i = 0, j = cards.length; i < j; i += 10) {
let chunk = cards.slice(i, i + 10);
pdf_promises.push(this.generateRunnerCards(chunk, locale));
}
const pdfs = await Promise.all(pdf_promises);
return await this.mergePdfs(pdfs);
}
const cards_swapped = this.swapArrayPairs(cards);
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" })
result = await awaitAsyncHandlebarHelpers(result);
fs.writeFileSync("lelelelele.tmp", result);
const pdf = await this.renderPdf(result, { format: "A4", landscape: false });
return pdf
}
/**
* Converts all images in html to base64.
* 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> {
const $ = cheerio.load(html)
$('img').each(async (index, element) => {
let imgsrc = $(element).attr("src");
if (imgsrc.startsWith("data:image")) {
return;
}
const img_type = mime.lookup(imgsrc);
if (!(img_type.includes("image"))) {
throw new Error("File is not image mime type");
}
let image;
if (imgsrc.startsWith("http")) {
image = (await axios.get(imgsrc)).data;
image = Buffer.from(image).toString('base64');
}
else {
if (imgsrc.startsWith("./")) {
imgsrc = imgsrc.replace("./", "");
}
image = fs.readFileSync(`${this.templateDir}/${imgsrc}`, { encoding: "base64" });
}
image = `data:${img_type};base64,${image}`
$(element).attr("src", image)
})
return $.html();
}
/**
* This method manages the creation of pdfs via puppeteer.
* @param html The HTML that should get rendered.
* @param options Puppeteer PDF option (eg: {format: "A4"})
*/
public async renderPdf(html: string, options): Promise<any> {
html = await this.imgToBase64(html);
let page = await this.browser.newPage();
await page.setContent(html);
const pdf = await page.pdf(options);
await page.close();
return pdf;
}
/**
* Merges multiple pdfs into one.
* @param pdfs The pdfs you want to merge as an buffer array.
* @returns The merged pdf as a buffer.
*/
private async mergePdfs(pdfs: Buffer[]): Promise<Buffer> {
const mergedPdf = await PDFDocument.create();
for (const pdfBuffer of pdfs) {
const pdf = await PDFDocument.load(pdfBuffer);
const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
copiedPages.forEach((page) => {
mergedPdf.addPage(page);
});
}
return <Buffer>(await mergedPdf.save());
}
/**
* 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.
*/
private generateEmptyRunner(): Runner {
let group = new RunnerGroup();
group.id = 0;
group.name = "";
let runner = new Runner();
runner.id = 0;
runner.firstname = "";
runner.lastname = "";
runner.group = group;
return runner;
}
/**
* Swaps pairs (0/1, 2/3, ...) of elements in an array recursively.
* If the last element has no partner it inserts an empty element at the end and swaps the two
* This is needed to generate pdfs with front- and backside that get printet on one paper.
* @param array The array which's pairs shall get switched.
* @returns Array with swapped pairs,
*/
private swapArrayPairs(array): Array<any> {
if (array.length == 1) {
return [null, array[0]];
}
if (array.length == 0) {
return null;
}
const rest = this.swapArrayPairs(array.slice(2))
if (!rest) {
return [array[1], array[0]]
}
return [array[1], array[0]].concat(rest);
}
}

View File

@ -1,33 +0,0 @@
import { MetadataArgsStorage } from 'routing-controllers';
import { routingControllersToSpec } from 'routing-controllers-openapi';
import { config } from './config';
/**
* This function generates a the openapi spec from route metadata and type schemas.
* @param storage MetadataArgsStorage object generated by routing-controllers.
* @param schemas MetadataArgsStorage object generated by class-validator-jsonschema.
*/
export function generateSpec(storage: MetadataArgsStorage, schemas) {
return routingControllersToSpec(
storage,
{},
{
components: {
schemas,
"securitySchemes": {
"AuthToken": {
"type": "apiKey",
"in": "query",
"name": "key",
description: "A simple api key. See the README's env section for more details."
}
}
},
info: {
description: "The the API for the LfK! document server.",
title: "LfK! document server API",
version: config.version
},
}
);
}

View File

@ -1,31 +0,0 @@
import consola from "consola";
import "reflect-metadata";
import { createExpressServer } from "routing-controllers";
import { config, e as errors } from './config';
import loaders from "./loaders/index";
import AuthChecker from './middlewares/AuthChecker';
import { ErrorHandler } from './middlewares/ErrorHandler';
const CONTROLLERS_FILE_EXTENSION = process.env.NODE_ENV === 'production' ? 'js' : 'ts';
const app = createExpressServer({
middlewares: [ErrorHandler],
authorizationChecker: AuthChecker,
development: config.development,
cors: true,
controllers: [`${__dirname}/controllers/*.${CONTROLLERS_FILE_EXTENSION}`],
});
async function main() {
await loaders(app);
app.listen(config.internal_port, () => {
consola.success(
`⚡️[server]: Server is running at http://localhost:${config.internal_port}`
);
});
}
if (errors === 0) {
main();
} else {
consola.error("error");
// something's wrong
}

File diff suppressed because one or more lines are too long

View File

@ -1,57 +0,0 @@
import consola from "consola";
import { config as configDotenv } from 'dotenv';
configDotenv();
export const config = {
internal_port: parseInt(process.env.APP_PORT) || 4010,
development: process.env.NODE_ENV === "production",
version: process.env.VERSION || require('../package.json').version,
eventname: process.env.EVENT_NAME || "Please set the event name",
currency_symbol: process.env.CURRENCY_SYMBOL || "€",
sponsoring_receipt_minimum_amount: process.env.SPONSORING_RECEIPT_MINIMUM_AMOUNT || "10",
codeformat: process.env.CODEFORMAT || "qrcode",
sponor_logos: getSponsorLogos(),
api_key: getApiKey(),
disclaimer_text: process.env.DISCLAIMER_TEXT || "",
contracts_per_runner: parseInt(process.env.CONTRACTS_PER_RUNNER) || 1,
}
let errors = 0
if (typeof config.internal_port !== "number") {
errors++
}
if (typeof config.contracts_per_runner !== "number") {
errors++
}
if (typeof config.development !== "boolean") {
errors++
}
function getSponsorLogos(): string[] {
try {
const logos = JSON.parse(process.env.SPONOR_LOGOS);
if (!Array.isArray(logos)) { throw new Error("Not an array.") }
return logos;
} catch (error) {
return ["data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wlseKgAAAABJRU5ErkJggg=="];
}
}
function getApiKey(): string {
const key = process.env.API_KEY;
if (!key) {
consola.info("No API key set - generating a random one...");
let result = '';
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const charactersLength = characters.length;
for (var i = 0; i < 64; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
consola.info(`API KEY: ${result}`)
return result;
}
if (key.length < 64) {
consola.error(`API key is too short - minimum: 64, current: ${key.length}`)
throw new Error("API_KEY too short.")
}
return key
}
export let e = errors

View File

@ -1,97 +0,0 @@
import { Authorized, Body, JsonController, Post, QueryParam, Res } from 'routing-controllers';
import { OpenAPI } from 'routing-controllers-openapi';
import { Runner } from '../models/Runner';
import { RunnerCard } from '../models/RunnerCard';
import { PdfCreator } from '../PdfCreator';
/**
* The pdf controller handels all endpoints concerning pdf generation.
* It therefore is the hearth of the document-generation server's endpoints.
* All endpoints have to accept a locale query-param to support i18n.
*/
@JsonController()
@Authorized()
@OpenAPI({ security: [{ "AuthToken": [] }] })
export class PdfController {
private pdf: PdfCreator = new PdfCreator();
private initialized: boolean = false;
@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)." })
async generateContracts(@Body({ validate: true, options: { limit: "500mb" } }) runners: Runner[], @Res() res: any, @QueryParam("locale") locale: string, @QueryParam("codeformat") codeformat: string, @QueryParam("download") download: boolean) {
if (!this.initialized) {
await this.pdf.init();
this.initialized = true;
}
if (!Array.isArray(runners)) {
runners = [runners];
}
runners = this.mapRunnerGroupNames(runners)
const contracts = await this.pdf.generateSponsoringContract(runners, locale, codeformat);
res.setHeader('content-type', 'application/pdf');
if (download) {
res.setHeader('Content-Disposition', 'attachment; filename="contracts.pdf"')
}
return contracts;
}
@Post('/cards')
@OpenAPI({ description: "Generate runner card pdfs from runner card objects.<br>You can choose your prefered locale by passing the 'locale' query-param." })
async generateCards(@Body({ validate: true, options: { limit: "500mb" } }) cards: RunnerCard | RunnerCard[], @Res() res: any, @QueryParam("locale") locale: string, @QueryParam("download") download: boolean) {
if (!this.initialized) {
await this.pdf.init();
this.initialized = true;
}
if (!Array.isArray(cards)) {
cards = [cards];
}
cards = this.mapCardGroupNames(cards);
const contracts = await this.pdf.generateRunnerCards(cards, locale);
res.setHeader('content-type', 'application/pdf');
if (download) {
res.setHeader('Content-Disposition', 'attachment; filename="cards.pdf"')
}
return contracts;
}
private mapRunnerGroupNames(runners: Runner[]): Runner[] {
let response = new Array<Runner>();
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}`;
}
response.push(runner)
}
return response;
}
private mapCardGroupNames(cards: RunnerCard[]): RunnerCard[] {
let response = new Array<RunnerCard>();
for (let card of cards) {
if (!card.runner) {
card.runner = {
id: 0,
firstname: "Blank",
lastname: "Blank",
distance: 0,
group: {
id: 0,
name: "Blank",
fullName: "Blank"
}
}
}
else if (!card.runner.group.parentGroup) {
card.runner.group.fullName = card.runner.group.name;
}
else {
card.runner.group.fullName = `${card.runner.group.parentGroup.name}/${card.runner.group.name}`;
}
response.push(card)
}
return response;
}
}

View File

@ -1,17 +0,0 @@
import { Get, JsonController } from 'routing-controllers';
import { OpenAPI } from 'routing-controllers-openapi';
import { config } from '../config';
/**
* The statuscontroller provides simple endpoints concerning basic information about the server.
*/
@JsonController()
export class StatusController {
@Get('/version')
@OpenAPI({ description: "A very basic endpoint that just returns the curent package version." })
getVersion() {
return {
"version": config.version
}
}
}

View File

@ -1,57 +0,0 @@
import { IsString } from 'class-validator';
import { BadRequestError } from 'routing-controllers';
/**
* Error to throw when an address's postal code fails validation.
*/
export class AddressPostalCodeInvalidError extends BadRequestError {
@IsString()
name = "AddressPostalCodeInvalidError"
@IsString()
message = "The postal code you provided is invalid. \n Please check if your postal code follows the postal code validation guidelines."
}
/**
* Error to throw when an non-empty address's first line isn't set.
*/
export class AddressFirstLineEmptyError extends BadRequestError {
@IsString()
name = "AddressFirstLineEmptyError"
@IsString()
message = "You provided a empty first address line. \n If you want an empty address please set all propertys to null. \n For non-empty addresses the following fields have to be set: address1, postalcode, city, country"
}
/**
* Error to throw when an non-empty address's postal code isn't set.
*/
export class AddressPostalCodeEmptyError extends BadRequestError {
@IsString()
name = "AddressPostalCodeEmptyError"
@IsString()
message = "You provided a empty postal code. \n If you want an empty address please set all propertys to null. \n For non-empty addresses the following fields have to be set: address1, postalcode, city, country"
}
/**
* Error to throw when an non-empty address's city isn't set.
*/
export class AddressCityEmptyError extends BadRequestError {
@IsString()
name = "AddressCityEmptyError"
@IsString()
message = "You provided a empty city. \n If you want an empty address please set all propertys to null. \n For non-empty addresses the following fields have to be set: address1, postalcode, city, country"
}
/**
* Error to throw when an non-empty address's country isn't set.
*/
export class AddressCountryEmptyError extends BadRequestError {
@IsString()
name = "AddressCountryEmptyError"
@IsString()
message = "You provided a empty country. \n If you want an empty address please set all propertys to null. \n For non-empty addresses the following fields have to be set: address1, postalcode, city, country"
}

View File

@ -1,11 +0,0 @@
import { Application } from "express";
/**
* Loader for express related configurations.
* Configures proxy trusts, globally used middlewares and other express features.
*/
export default async (app: Application) => {
app.enable('trust proxy');
app.disable('x-powered-by');
app.disable('x-served-by');
return app;
};

View File

@ -1,13 +0,0 @@
import { Application } from "express";
import expressLoader from "./express";
import openapiLoader from "./openapi";
/**
* Index Loader that executes the other loaders in the right order.
* This basicly exists for abstraction and a overall better dev experience.
*/
export default async (app: Application) => {
await openapiLoader(app);
await expressLoader(app);
return app;
};

View File

@ -1,24 +0,0 @@
import { validationMetadatasToSchemas } from "@odit/class-validator-jsonschema";
import express, { Application } from "express";
import path from 'path';
import { getMetadataArgsStorage } from "routing-controllers";
import { generateSpec } from '../apispec';
/**
* Loader for everything openapi related - from creating the schema to serving it via a static route and swaggerUiExpress.
* All auth schema related stuff also has to be configured here
*/
export default async (app: Application) => {
const storage = getMetadataArgsStorage();
const schemas = validationMetadatasToSchemas({
refPointerPrefix: "#/components/schemas/",
});
//Spec creation based on the previously created schemas
const spec = generateSpec(storage, schemas);
app.get(["/docs/openapi.json", "/docs/swagger.json"], (req, res) => {
res.json(spec);
});
app.use('/docs', express.static(path.join(__dirname, '../static/docs'), { index: "index.html", extensions: ['html'] }));
return app;
};

View File

@ -1,20 +0,0 @@
{
"address": "Adresse",
"city": "Stadt",
"date": "Datum",
"firstname": "Vorname",
"group": "Team/Klasse",
"house_number": "Hausnummer",
"id": "ID",
"lastname": "Nachname",
"location": "Ort",
"please_use_blockletters": "Bitte in DRUCKBUCHSTABEN schreiben",
"postalcode": "Postleitzahl",
"signature": "Unterschrift",
"sponsor": "Sponsor",
"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"
}

View File

@ -1,19 +0,0 @@
{
"address": "Address",
"city": "City",
"date": "date",
"firstname": "First name",
"group": "Team/class",
"house_number": "House number",
"lastname": "Last name",
"location": "Location",
"please_use_blockletters": "Please write in BLOCK LETTERS.",
"postalcode": "Postal code",
"signature": "Signature",
"sponsor": "sponsor",
"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"
}

View File

@ -1,14 +0,0 @@
import { Action } from "routing-controllers";
import { config } from '../config';
/**
* Handles authentication via jwt's (Bearer authorization header) for all api endpoints using the @Authorized decorator.
* @param action Routing-Controllers action object that provides request and response objects among other stuff.
* @param permissions The permissions that the endpoint using @Authorized requires.
*/
const AuthChecker = async (action: Action) => {
const provided_token = action.request.query.key;
return provided_token == config.api_key;
}
export default AuthChecker

View File

@ -1,14 +0,0 @@
import { ExpressErrorMiddlewareInterface, Middleware } from "routing-controllers";
/**
* Our Error handling middlware that returns our custom httperrors to the user.
*/
@Middleware({ type: "after" })
export class ErrorHandler implements ExpressErrorMiddlewareInterface {
public error(error: any, request: any, response: any, next: (err: any) => any) {
if (response.headersSent) {
return;
}
response.json(error);
}
}

View File

@ -1,50 +0,0 @@
import {
IsString
} from "class-validator";
/**
* Defines the Address class.
* Implemented this way to prevent any formatting differences.
*/
export class Address {
/**
* The address's first line.
* Containing the street and house number.
*/
@IsString()
address1?: string;
/**
* The address's second line.
* Containing optional information.
*/
@IsString()
address2?: string;
/**
* The address's postal code.
* This will get checked against the postal code syntax for the configured country.
*/
@IsString()
postalcode: string;
/**
* The address's city.
*/
@IsString()
city: string;
/**
* The address's country.
*/
@IsString()
country: string;
public reset() {
this.address1 = null;
this.address2 = null;
this.city = null;
this.country = null;
this.postalcode = null;
}
}

View File

@ -1,16 +0,0 @@
import {
IsArray
} from "class-validator";
import { DistanceDonation } from './DistanceDonation';
import { Runner } from './Runner';
/**
* Defines the certificate runner class (from which the runner certificates get generated).
*/
export class CertificateRunner extends Runner {
/**
* Array containing all distance donations associated with the runner.
*/
@IsArray()
distanceDonations: DistanceDonation[];
}

View File

@ -1,40 +0,0 @@
import { IsInt, IsNotEmpty, IsObject, IsPositive } from "class-validator";
import { Donation } from "./Donation";
import { Runner } from "./Runner";
/**
* Defines the DistanceDonation class.
* For distanceDonations a donor pledges to donate a certain amount for each kilometer ran by a runner.
*/
export class DistanceDonation extends Donation {
/**
* The donation's associated runner.
* Used as the source of the donation's distance.
*/
@IsObject()
@IsNotEmpty()
runner: Runner;
/**
* The donation's amount donated per distance.
* The amount the donor set to be donated per kilometer that the runner ran.
*/
@IsInt()
@IsPositive()
amountPerDistance: number;
/**
* The donation's amount in cents (or whatever your currency's smallest unit is.).
* Get's calculated from the runner's distance ran and the amount donated per kilometer.
*/
public get amount(): number {
let calculatedAmount = 0;
try {
calculatedAmount = this.amountPerDistance * (this.runner.distance / 1000);
} catch (error) {
throw error;
}
return calculatedAmount;
}
}

View File

@ -1,32 +0,0 @@
import {
IsInt,
IsNotEmpty,
IsObject
} from "class-validator";
import { Donor } from './Donor';
/**
* Defines the Donation base calss.
* A donation just associates a donor with a donation amount.
* The specifics of the amoun's determination has to be implemented in child classes.
*/
export abstract class Donation {
/**
* Autogenerated unique id (primary key).
*/
@IsInt()
id: number;
/**
* The donations's donor.
*/
@IsNotEmpty()
@IsObject()
donor: Donor;
/**
* The donation's amount in cents (or whatever your currency's smallest unit is.).
* The exact implementation may differ for each type of donation.
*/
public abstract get amount(): number;
}

View File

@ -1,37 +0,0 @@
import {
IsInt,
IsString
} from "class-validator";
/**
* Defines the Donor class.
*/
export class Donor {
/**
* The donor's id.
*/
@IsInt()
id: number;
/**
* The donor's first name.
*/
@IsString()
firstname: string;
/**
* The donor's middle name.
*/
@IsString()
middlename?: string;
/**
* The donor's last name.
*/
@IsString()
lastname: string;
}

View File

@ -1,16 +0,0 @@
import { IsInt, IsPositive } from "class-validator";
import { Donation } from "./Donation";
/**
* Defines the FixedDonation entity.
* In the past there was no easy way to track fixed donations (eg. for creating donation receipts).
*/
export class FixedDonation extends Donation {
/**
* The donation's amount in cents (or whatever your currency's smallest unit is.).
*/
@IsInt()
@IsPositive()
amount: number;
}

View File

@ -1,69 +0,0 @@
import {
IsInt,
IsNotEmpty,
IsObject,
IsOptional,
IsPositive,
IsString,
ValidateNested
} from "class-validator";
import { RunnerGroup } from './RunnerGroup';
/**
* Defines the runner class (from which the runner sponsoring contracts get generated).
*/
export class Runner {
/**
* The runner's id.
*/
@IsInt()
@IsPositive()
id: number;
/**
* The runner's first name.
*/
@IsString()
@IsNotEmpty()
firstname: string;
/**
* The runner's middle name.
*/
@IsString()
@IsOptional()
middlename?: string;
/**
* The runner's last name.
*/
@IsString()
@IsNotEmpty()
lastname: string;
/**
* The runner's group.
*/
@IsObject()
@ValidateNested()
group: RunnerGroup;
/**
* The total distance ran by the runner.
*/
@IsInt()
distance: number;
constructor() {
console.log("called")
}
}

View File

@ -1,33 +0,0 @@
import {
IsEAN,
IsInt,
IsNotEmpty,
IsObject,
IsString
} from "class-validator";
import { Runner } from './Runner';
/**
* Defines the runner card class (used to create runner card pdfs).
*/
export class RunnerCard {
/**
* The cards's id.
*/
@IsInt()
id: number;
/**
* The card's associated runner.
*/
@IsObject()
runner: Runner | null;
/**
* The card's code.
*/
@IsEAN()
@IsString()
@IsNotEmpty()
code: string;
}

View File

@ -1,30 +0,0 @@
import { IsInt, IsNotEmpty, IsObject, IsOptional, IsPositive, IsString } from "class-validator";
/**
* Defines the runner group class - a simplified version of the backend's ResponseRunnerTeam/-Organization
*/
export class RunnerGroup {
/**
* The group's id.
*/
@IsInt()
@IsPositive()
id: number;
/**
* The group's name.
*/
@IsString()
@IsNotEmpty()
name: string;
/**
* The group's parent group.
* If it is set this implies that the object is a team.
*/
@IsObject()
@IsOptional()
parentGroup?: RunnerGroup;
fullName: string;
}

View File

@ -1,156 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>API Docs</title>
<style>
:root {
--bg-color: #fff;
--bg-secondary-color: #f3f3f6;
--color-primary: #14854f;
--color-lightGrey: #d2d6dd;
--color-grey: #747681;
--color-darkGrey: #3f4144;
--color-error: #d43939;
--color-success: #28bd14;
--grid-maxWidth: 120rem;
--grid-gutter: 2rem;
--font-size: 1.6rem;
--font-color: #333;
--font-family-sans: -apple-system, BlinkMacSystemFont, Avenir, "Avenir Next", "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
--font-family-mono: monaco, "Consolas", "Lucida Console", monospace
}
html {
-webkit-box-sizing: border-box;
box-sizing: border-box;
font-size: 62.5%;
line-height: 1.15;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%
}
*,
:after,
:before {
-webkit-box-sizing: inherit;
box-sizing: inherit
}
body {
background-color: var(--bg-color);
line-height: 1.6;
font-size: var(--font-size);
color: var(--font-color);
font-family: Segoe UI, Helvetica Neue, sans-serif;
font-family: var(--font-family-sans);
margin: 0;
padding: 0
}
h3 {
font-weight: 500;
margin: .35em 0 .7em
}
h3 {
font-size: 1.5em
}
a {
color: var(--color-primary);
text-decoration: none
}
a:hover:not(.button) {
opacity: .75
}
input:not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=color]):not([type=button]):not([type=reset]):not(:disabled):hover {
border-color: var(--color-grey)
}
::-webkit-input-placeholder {
color: #bdbfc4
}
::-moz-placeholder {
color: #bdbfc4
}
:-ms-input-placeholder {
color: #bdbfc4
}
::-ms-input-placeholder {
color: #bdbfc4
}
.tabs {
display: -webkit-box;
display: -ms-flexbox;
display: flex
}
.tabs a {
text-decoration: none
}
.tabs>a {
padding: 1rem 2rem;
-webkit-box-flex: 0;
-ms-flex: 0 1 auto;
flex: 0 1 auto;
color: var(--color-darkGrey);
border-bottom: 2px solid var(--color-lightGrey);
text-align: center
}
.tabs>a:hover {
opacity: 1;
border-bottom: 2px solid var(--color-darkGrey)
}
.is-vertical-align {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center
}
.is-center {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center
}
.is-center {
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center
}
</style>
</head>
<body>
<div class="hero">
<div class="logo is-center is-vertical-align">
<h3>API Docs</h3>
</div>
<nav class="tabs is-center">
<a href="./redoc">ReDoc</a>
<a href="./swaggerui">SwaggerUI</a>
<a href="./rapidoc">RapiDoc</a>
<a href="./openapi.json">Raw Spec (json)</a>
</nav>
</div>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -1,12 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script type="module" src="./rapidoc-min.js"></script>
</head>
<body>
<rapi-doc
spec-url="/docs/openapi.json"
> </rapi-doc>
</body>
</html>

View File

@ -1,18 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>ReDoc</title>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<redoc spec-url='/docs/openapi.json'></redoc>
<script src="./redoc.standalone.js"> </script>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -1,58 +0,0 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" >
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body
{
margin:0;
background: #fafafa;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
<script>
window.onload = function() {
// Begin Swagger UI call region
const ui = SwaggerUIBundle({
url: "/docs/openapi.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
// End Swagger UI call region
window.ui = ui
}
</script>
</body>
</html>

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 KiB

View File

@ -1,128 +0,0 @@
import axios from "axios"
import faker from "faker"
import { Runner } from '../models/Runner'
import { RunnerCard } from '../models/RunnerCard'
import { RunnerGroup } from '../models/RunnerGroup'
const baseurl = "http://localhost:4010"
axios.interceptors.request.use((config) => {
config.headers['request-startTime'] = process.hrtime()
return config
})
axios.interceptors.response.use((response) => {
const start = response.config.headers['request-startTime']
const end = process.hrtime(start)
const milliseconds = Math.round((end[0] * 1000) + (end[1] / 1000000))
response.headers['request-duration'] = milliseconds
return response
})
function generateRunners(amount: number): Runner[] {
let runners: Runner[] = new Array<Runner>();
let group = new RunnerGroup();
let runner = new Runner();
for (var i = 0; i < amount; i++) {
group.name = faker.company.bsBuzz();
group.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);
runners.push(runner);
}
return runners;
}
function generateCards(amount: number): RunnerCard[] {
let cards: RunnerCard[] = new Array<RunnerCard>();
let card = new RunnerCard();
for (let runner of generateRunners(amount)) {
card.id = runner.id;
card.code = idToEan13(card.id);
card.runner = runner;
cards.push(card);
}
return cards;
}
function idToEan13(id): string {
const multiply = [1, 3];
id = id.toString();
if (id.length > 12) {
throw new Error("id too long");
}
while (id.length < 12) { id = '0' + id; }
let total = 0;
id.split('').forEach((letter, index) => {
total += parseInt(letter, 10) * multiply[index % 2];
});
const checkSum = (Math.ceil(total / 10) * 10) - total;
return id + checkSum.toString();
}
async function postContracts(runners: Runner[]): Promise<Measurement> {
const res = await axios.post(`${baseurl}/contracts`, runners);
return new Measurement("contract", runners.length, parseInt(res.headers['request-duration']))
}
async function postCards(cards: RunnerCard[]): Promise<Measurement> {
const res = await axios.post(`${baseurl}/cards`, cards);
return new Measurement("card", cards.length, parseInt(res.headers['request-duration']))
}
async function testContracts(sizes): Promise<Measurement[]> {
let measurements = new Array<Measurement>();
console.log("#### Testing contracts ####");
for (let size of sizes) {
const m = await postContracts(generateRunners(size));
console.log(m.toString());
measurements.push(m);
}
return measurements;
}
async function testCards(sizes): Promise<Measurement[]> {
let measurements = new Array<Measurement>();
console.log("#### Testing Cards ####");
for (let size of sizes) {
const m = await postCards(generateCards(size));
console.log(m.toString());
measurements.push(m);
}
return measurements;
}
async function main() {
const sizes = [0, 1, 10, 50, 100, 200, 500, 1000]
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);
console.log("####### Results #######");
console.table(contractResults);
console.table(cardResults);
}
main();
class Measurement {
public type: string;
public inputcount: number;
public responsetime: number;
constructor(type: string, input: number, time: number) {
this.type = type;
this.inputcount = input;
this.responsetime = time;
}
public toString(): string {
return `It took ${this.responsetime}ms to generate ${this.inputcount} pdfs for the type ${this.type}.`
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,84 @@
<html>
<head>
<meta charset="utf8">
<title>Läuferkarten</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
}
body.A4 .sheet {
width: 210mm;
height: 296mm
}
.runnercard {
border: 1px solid;
height: 5.5cm;
overflow: hidden;
}
</style>
</head>
<body class="A4 landscape">
{{ range .CardSegments }}
<div class="sheet">
<div class="columns is-multiline">
{{ range .Cards }}
<div class="column is-half runnercard">
{{ if ne .Code "" }}
<p class="title is-5" style="text-align: center; padding-bottom: 0; margin-top: -0.75rem;">{{ $.EventName }}</p>
<p style="text-align: center; margin-top: -1.5rem; font-size: small;">{{ $.CardSubtitle }}</p>
<p style="font-size: small;">Mit Unterstützung von:</p>
<div class="columns" style="height: 6rem; overflow: hidden;">
<div class="column is-half">
<!--SPONSOR LOGO HERE-->
<img style="vertical-align: revert; margin-top: auto; object-fit: cover; max-height: 2cm;"
src="data:image/png;base64,{{ sponsorLogo .ID }}" />
</div>
<div class="column is-half">
<!--BARCODE HERE-->
<img style="vertical-align: revert; margin-top: auto; object-fit: cover; max-height: 5rem;"
src="data:image/png;base64,{{ barcode .Code $.BarcodeFormat $.BarcodePrefix }}" />
<p style="font-size: 0.6rem; text-align: center; margin: 0; padding: 0;">{{ .Code }}</p>
</div>
</div>
{{ if ne .Runner.FirstName "" }}
<p>{{ .Runner.LastName }}, {{ .Runner.FirstName }} {{ .Runner.MiddleName }}</p>
<p>{{ if ne .Runner.Group.ParentGroup.Name "" -}}{{ .Runner.Group.ParentGroup.Name }}/{{end -}}{{ .Runner.Group.Name }}</p>
{{ else }}
<p>Läufer:in</p>
{{ end}}
{{ end}}
</div>
{{ end }}
</div>
</div>
<div class="sheet">
<div class="columns is-multiline">
{{ range .CardsSwapped }}
<div class="column is-half runnercard" style="justify-content: center; align-items: center; text-align: center;">
{{ if ne .Code "" }}
<!--SPONSOR LOGO FIRST-->
<div style="height: 2cm; padding: 0 0 1cm 0">
<img style="object-fit: cover; max-height: 2cm;" src="data:image/png;base64,{{ sponsorLogo .ID }}" />
</div>
<img style="object-fit: cover; max-height: 6rem; position: relative;"
src="data:image/png;base64,{{ barcode .Code $.BarcodeFormat $.BarcodePrefix }}" />
<p style="font-size: 1rem; text-align: center; margin: 0; padding: 0;">{{ .Code }}</p>
{{ end }}
</div>
{{ end }}
</div>
</div>
{{ end}}
</body>
</html>

View File

@ -2,7 +2,7 @@
<head>
<meta charset="utf8">
<title>Sponsoring contract</title>
<title>Runner cards</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
<style>
.sheet {
@ -28,44 +28,52 @@
</head>
<body class="A4 landscape">
{{ range .CardSegments }}
<div class="sheet">
<div class="columns is-multiline">
{{#each cards}}
{{ range .Cards }}
<div class="column is-half runnercard">
<p class="title is-5" style="text-align: center; padding-bottom: 0; margin-top: -0.75rem;">{{../eventname}}</p>
<p style="text-align: center; margin-top: -1.5rem; font-size: small;">lauf-fuer-kaya.de - am 01.01.2021</p>
<p style="font-size: small;">Mit unterstützung von:</p>
<p class="title is-5" style="text-align: center; padding-bottom: 0; margin-top: -0.75rem;">{{ $.EventName }}</p>
<p style="text-align: center; margin-top: -1.5rem; font-size: small;">{{ $.CardSubtitle }}</p>
<p style="font-size: small;">Supported by:</p>
<div class="columns" style="height: 6rem; overflow: hidden;">
<div class="column is-two-thirds">
<div class="column is-half">
<!--SPONSOR LOGO HERE-->
<img style="vertical-align: revert; margin-top: auto; object-fit: cover; max-height: 2cm;"
src="{{--sponsor this.id}}" />
src="data:image/png;base64,{{ sponsorLogo .ID }}" />
</div>
<div class="column is-one-third">
<div class="column is-half">
<!--BARCODE HERE-->
<img style="vertical-align: revert; margin-top: auto; object-fit: cover; max-height: 2cm;"
src="{{--bc this.id ../codeformat}}" />
<img style="vertical-align: revert; margin-top: auto; object-fit: cover; max-height: 5rem;"
src="data:image/png;base64,{{ barcode .Code $.BarcodeFormat $.BarcodePrefix }}" />
<p style="font-size: 0.6rem; text-align: center; margin: 0; padding: 0;">{{ .Code }}</p>
</div>
</div>
<p>{{this.runner.lastname}}, {{this.runner.firstname}} {{this.runner.middlename}}</p>
<p>{{this.runner.group.fullName}}</p>
{{ if ne .Runner.FirstName ""}}
<p>{{ .Runner.LastName }}, {{ .Runner.FirstName }} {{ .Runner.MiddleName }}</p>
<p>{{ if ne .Runner.Group.ParentGroup.Name "" -}}{{ .Runner.Group.ParentGroup.Name }}/{{end -}}{{ .Runner.Group.Name }}</p>
{{ else }}
<p>Runner</p>
{{ end}}
</div>
{{/each}}
{{ end }}
</div>
</div>
<div class="sheet">
<div class="columns is-multiline">
{{#each cards_swapped}}
{{ range .CardsSwapped }}
<div class="column is-half runnercard" style="justify-content: center; align-items: center; text-align: center;">
<!--SPONSOR LOGO FIRST-->
<div style="height: 2cm; padding: 0 0 2.25cm 0">
<img style="object-fit: cover; max-height: 2cm;" src="{{--sponsor this.id}}" />
<div style="height: 2cm; padding: 0 0 1cm 0">
<img style="object-fit: cover; max-height: 2cm;" src="data:image/png;base64,{{ sponsorLogo .ID }}" />
</div>
<img style="object-fit: cover; max-height: 2.5cm; position: relative;" src="{{--bc this.id ../codeformat}}" />
<img style="object-fit: cover; max-height: 6rem; position: relative;" src="data:image/png;base64,{{ barcode .Code $.BarcodeFormat $.BarcodePrefix }}" />
<p style="font-size: 1rem; text-align: center; margin: 0; padding: 0;">{{ .Code }}</p>
</div>
{{/each}}
{{ end }}
</div>
</div>
{{ end}}
</body>
</html>

View File

@ -0,0 +1,119 @@
<html>
<head>
<meta charset="utf8">
<title>Läuferurkunde</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,{{ loadImage "certificate_background" }}");
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">
{{ range .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;">{{ .FirstName }}
{{ .MiddleName }} {{ .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;">{{formatUnit "kilometer" $.Locale .Distance}}</p>
<p style="font-size: 1cm;">für den guten Zweck zurückgelegt.</p>
</main>
<footer class="certificate-footer">
<img src="data:image/png;base64,{{ loadImage "certificate_footer" }}">
</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>
{{ range .DistanceDonations}}
<tr>
<td>{{ .Donor.FirstName }} {{ .Donor.MiddleName }} {{ .Donor.LastName }}</td>
<td>{{ formatUnit "euro" $.Locale .AmountPerDistance }} {{ $.CurrencySymbol }}</td>
<td>{{ formatUnit "euro" $.Locale .Amount }} {{ $.CurrencySymbol }}</td>
</tr>
{{ end }}
</tbody>
<tfoot>
<td>Gesamt</td>
<td>{{ formatUnit "euro" $.Locale .TotalPerDistance }} {{ $.CurrencySymbol }}</td>
<td>{{ formatUnit "euro" $.Locale .TotalDonations }} {{ $.CurrencySymbol }}</td>
</table>
</main>
<footer class="certificate-footer">
<table style="border-collapse: collapse; border: none; width: 17cm;">
<thead>
<tr>
<th style="border: none; width: 50%; text-align: center;">Link zu deinen Rundenzeiten</th>
<th style="border: none; width: 50%; text-align: center;">Spende überweisen</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: none; text-align: center;">
<img src="data:image/png;base64,{{ barcode .SelfServiceLink "qr" "" }}" style="height: 2.5cm; padding: 0.2cm">
</td>
<td style="border: none; text-align: center;">
<img src="data:image/png;base64,{{ epcCode $.SepaConfig.IBAN $.SepaConfig.BIC $.SepaConfig.HolderName (print "Spende LfK " .ID ", " .FirstName " " .LastName ", " .CombinedGroupName) .TotalDonations $.SepaConfig.CurrencyIdentifier}}" style="height: 2.5cm; padding: 0.2cm">
</td>
</tr>
</tbody>
</table>
<p style="width: 17cm; text-align: center;">
Sponsoren überweisen ihre Beträge bitte auf unser Konto: {{ $.SepaConfig.HolderName }} | IBAN: {{ $.SepaConfig.IBAN }} | BIC: {{ $.SepaConfig.BIC }} | Vz: "Spende LfK {{.ID}}, {{ .FirstName }} {{ .LastName }}, {{.CombinedGroupName}}"
</p>
</footer>
</article>
{{ end }}
</body>
</html>

View File

@ -0,0 +1,120 @@
<html>
<head>
<meta charset="utf8">
<title>Runner certificate</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,{{ loadImage "certificate_background" }}");
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">
{{ range .Runners }}
<article class="sheet">
<header class="content has-text-centered">
<p style="font-size: 3cm; font-weight: bold;">Certificate</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;">{{ .FirstName }}
{{ .MiddleName }} {{ .LastName }}
</p>
<p style="font-size: 1cm; margin-bottom: 0;">Ran</p>
<p style="font-size: 2cm; font-weight: bold; margin-bottom: 0;">{{formatUnit "kilometer" $.Locale .Distance}}</p>
<p style="font-size: 1cm;">for our good cause at the {{ $.EventName }}.</p>
</main>
<footer class="certificate-footer">
<img src="data:image/png;base64,{{ loadImage "certificate_footer" }}">
</footer>
</article>
<article class="sheet">
<header class="content has-text-centered">
<p style="font-size: 2.5cm; font-weight: bold;">Donations</p>
</header>
<main>
<table style="border: solid; width: 17cm;">
<thead>
<td class=".td-head">Donor</td>
<td>Amount / km</td>
<td>Total</td>
</thead>
<tbody>
{{ range .DistanceDonations}}
<tr>
<td>{{ .Donor.FirstName }} {{ .Donor.MiddleName }} {{ .Donor.LastName }}</td>
<td>{{ formatUnit "euro" $.Locale .AmountPerDistance }} {{ $.CurrencySymbol }}</td>
<td>{{ formatUnit "euro" $.Locale .Amount }} {{ $.CurrencySymbol }}</td>
</tr>
{{ end }}
</tbody>
<tfoot>
<td>Combined</td>
<td>{{ formatUnit "euro" $.Locale .TotalPerDistance }} {{ $.CurrencySymbol }}</td>
<td>{{ formatUnit "euro" $.Locale .TotalDonations }} {{ $.CurrencySymbol }}</td>
</tfoot>
</table>
</main>
<footer class="certificate-footer">
<table style="border-collapse: collapse; border: none; width: 17cm;">
<thead>
<tr>
<th style="border: none; width: 50%; text-align: center;">Link to your lap times</th>
<th style="border: none; width: 50%; text-align: center;">Transfer donation via SEPA</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: none; text-align: center;">
<img src="data:image/png;base64,{{ barcode .SelfServiceLink "qr" "" }}" style="height: 2.5cm; padding: 0.2cm">
</td>
<td style="border: none; text-align: center;">
<img src="data:image/png;base64,{{ epcCode $.SepaConfig.IBAN $.SepaConfig.BIC $.SepaConfig.HolderName (print "Spende LfK " .ID ", " .FirstName " " .LastName ", " .CombinedGroupName) .TotalDonations $.SepaConfig.CurrencyIdentifier}}" style="height: 2.5cm; padding: 0.2cm">
</td>
</tr>
</tbody>
</table>
<p style="width: 17cm; text-align: center;">
Donors, please transfer your donation to our account: {{ $.SepaConfig.HolderName }} | IBAN: {{ $.SepaConfig.IBAN }} | BIC: {{ $.SepaConfig.BIC }} | Ref: "Spende LfK {{.ID}}, {{ .FirstName }} {{ .LastName }}, {{.CombinedGroupName}}"
</p>
</footer>
</article>
{{ end }}
</body>
</html>

View File

@ -0,0 +1,124 @@
<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;
}
body.A5.landscape .sheet {
width: 210mm;
height: 147mm
}
.column {
margin-bottom: -20;
}
</style>
</head>
<body class="A5 landscape">
{{ range .Runners }}
<div class="sheet">
<img id="header_img" width="100%" src="data:image/png;base64,{{ loadImage "sponsoringheader" }}" />
<div style=" padding: 0 1rem 0 1rem;">
<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;">Sponsoringerklärung</p>
</div>
<div class="column">
<p style="font-size: x-small; vertical-align: revert; margin-top: auto;">Bitte in DRUCKBUCHSTABEN
schreiben
</p>
</div>
</div>
<p>Ich bin/ Wir sind bereit anlässlich des {{ $.EventName }}</p>
<div class="columns">
<div class="column is-9">
<span style="border-bottom: 1px solid; width: 100%; display: block;">{{ .FirstName }}
{{ .MiddleName }}</span>
<p style="font-size: x-small; display: block;">Vorname</p>
</div>
<div class="column is-3">
<span style="border-bottom: 1px solid; width: 100%; display: block;">{{ .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="data:image/png;base64,{{ barcode (printf "%d" .ID) $.BarcodeFormat $.BarcodePrefix }}" />
</div>
</div>
<div class="columns" style="padding-top: 1rem;">
<div class="column is-6">
<span style="border-bottom: 1px solid; width: 100%; display: block;">{{ .LastName }}</span>
<p style="font-size: x-small; display: block;">Nachname</p>
</div>
<div class="column is-6">
<span style="border-bottom: 1px solid; width: 100%; display: block;"><p>{{ if ne .Group.ParentGroup.Name "" -}}{{ .Group.ParentGroup.Name }}/ {{end -}}{{ .Group.Name }}</p></span>
<p style="font-size: x-small; display: block;">Team/Klasse</p>
</div>
</div>
<p style="margin-top: -0.5rem">mit einem Betrag von _____ {{ $.CurrencySymbol }} pro gelaufenem Kilometer zu
unterstützen.</p>
<div class="columns" style="margin-top: -1rem;">
<div class="column is-6">
<span style="border-bottom: 1px solid; width: 100%; display: block;"></span>
<p style="font-size: x-small; display: block;">Nachname</p>
</div>
<div class="column is-6">
<span style="border-bottom: 1px solid; width: 100%; display: block;"></span>
<p style="font-size: x-small; display: block;">Vorname</p>
</div>
</div>
<p style="font-size: medium; margin-top: -0.5rem;">Adresse (Sponsor)</p>
<p style="font-size: x-small;">(Muss ausgefüllt werden, wenn Sie eine Spendenquittung benötigen -
Spendenquittungen können erst ab einem Gesamtbetrag von {{ $.ReceiptMinimumAmount }}{{ $.CurrencySymbol }}
ausgestellt werden)</p>
<div class="columns" style="margin-top: -1rem;">
<div class="column is-8">
<span style="border-bottom: 1px solid; width: 100%; display: block;"></span>
<p style="font-size: x-small; display: block;">Straße</p>
</div>
<div class="column is-4">
<span style="border-bottom: 1px solid; width: 100%; display: block;"></span>
<p style="font-size: x-small; display: block;">Hausnummer</p>
</div>
</div>
<div class="columns" style="margin-top: -1rem;">
<div class="column is-4">
<span style="border-bottom: 1px solid; width: 100%; display: block;"></span>
<p style="font-size: x-small; display: block;">Postleitzahl</p>
</div>
<div class="column is-8">
<span style="border-bottom: 1px solid; width: 100%; display: block;"></span>
<p style="font-size: x-small; display: block;">Stadt</p>
</div>
</div>
<div class="columns" style="margin-top: -1rem;">
<div class="column is-7">
<span style="border-bottom: 1px solid; width: 100%; display: block;"></span>
<p style="font-size: x-small; display: block;">Ort, Datum</p>
</div>
<div class="column is-5">
<span style="border-bottom: 1px solid; width: 100%; display: block;"></span>
<p style="font-size: x-small; display: block;">Unterschrift</p>
</div>
</div>
<p style="font-size: xx-small; overflow: hidden; height: 4rem; text-align: center;"> {{ $.Disclaimer }}</p>
</div>
</div>
{{ end }}
</body>
</html>

View File

@ -25,96 +25,96 @@
</head>
<body class="A5 landscape">
{{#each runners}}
{{ range .Runners }}
<div class="sheet">
<img id="header_img" width="100%" src="sponsoringheader.png" />
<img id="header_img" width="100%" src="data:image/png;base64,{{ loadImage "sponsoringheader" }}" />
<div style=" padding: 0 1rem 0 1rem;">
<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>
<p style="font-size: large; font-weight: bold;">Sponsoring contract</p>
</div>
<div class="column">
<p style="font-size: x-small; vertical-align: revert; margin-top: auto;">{{__ "please_use_blockletters"}}
<p style="font-size: x-small; vertical-align: revert; margin-top: auto;">Please write in BLOCK LETTERS.
</p>
</div>
</div>
<p> {{__ "sponsoring_subtitle"}} </p>
<p> On the occasion of the {{ $.EventName }} I/We want to support </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>
<span style="border-bottom: 1px solid; width: 100%; display: block;">{{ .FirstName }}
{{ .MiddleName }}</span>
<p style="font-size: x-small; display: block;">First name</p>
</div>
<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;">{{ .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}}" />
src="data:image/png;base64,{{ barcode (printf "%d" .ID) $.BarcodeFormat $.BarcodePrefix }}"/>
</div>
</div>
<div class="columns" style="padding-top: 1rem;">
<div class="column is-6">
<span style="border-bottom: 1px solid; width: 100%; display: block;">{{this.lastname}}</span>
<p style="font-size: x-small; display: block;">{{__ "lastname"}}</p>
<span style="border-bottom: 1px solid; width: 100%; display: block;">{{ .LastName }}</span>
<p style="font-size: x-small; display: block;">Last Name</p>
</div>
<div class="column is-6">
<span style="border-bottom: 1px solid; width: 100%; display: block;">{{this.group.fullName}}</span>
<p style="font-size: x-small; display: block;">{{__ "group"}}</p>
<span style="border-bottom: 1px solid; width: 100%; display: block;"><p>{{ if ne .Group.ParentGroup.Name "" -}}{{ .Group.ParentGroup.Name }}/ {{end -}}{{ .Group.Name }}</p></span>
<p style="font-size: x-small; display: block;">Team/class</p>
</div>
</div>
<p style="margin-top: -0.5rem">{{__ "sponsoring_amount_per_distance"}}</p>
<p style="margin-top: -0.5rem">with the amount of _____{{ $.CurrencySymbol }} per kilometer run.</p>
<div class="columns" style="margin-top: -1rem;">
<div class="column is-6">
<span style="border-bottom: 1px solid; width: 100%; display: block;"></span>
<p style="font-size: x-small; display: block;">{{__ "lastname"}}</p>
<p style="font-size: x-small; display: block;">Last name</p>
</div>
<div class="column is-6">
<span style="border-bottom: 1px solid; width: 100%; display: block;"></span>
<p style="font-size: x-small; display: block;">{{__ "firstname"}}</p>
<p style="font-size: x-small; display: block;">First name</p>
</div>
</div>
<p style="font-size: medium; margin-top: -0.5rem;">{{__ "address"}} ({{__ "sponsor"}})</p>
<p style="font-size: x-small;">({{__ "sponsoring_address_condition"}})</p>
<p style="font-size: medium; margin-top: -0.5rem;">Address (Sponsor)</p>
<p style="font-size: x-small;">(You have to provide an address if you want a donation receipt - Donation receipts can't be issued for total donation amounts under {{ $.ReceiptMinimumAmount }}{{ $.CurrencySymbol }})</p>
<div class="columns" style="margin-top: -1rem;">
<div class="column is-8">
<span style="border-bottom: 1px solid; width: 100%; display: block;"></span>
<p style="font-size: x-small; display: block;">{{__ "street"}}</p>
<p style="font-size: x-small; display: block;">Street</p>
</div>
<div class="column is-4">
<span style="border-bottom: 1px solid; width: 100%; display: block;"></span>
<p style="font-size: x-small; display: block;">{{__ "house_number"}}</p>
<p style="font-size: x-small; display: block;">House number</p>
</div>
</div>
<div class="columns" style="margin-top: -1rem;">
<div class="column is-4">
<span style="border-bottom: 1px solid; width: 100%; display: block;"></span>
<p style="font-size: x-small; display: block;">{{__ "postalcode"}}</p>
<p style="font-size: x-small; display: block;">Postal code</p>
</div>
<div class="column is-8">
<span style="border-bottom: 1px solid; width: 100%; display: block;"></span>
<p style="font-size: x-small; display: block;">{{__ "city"}}</p>
<p style="font-size: x-small; display: block;">City</p>
</div>
</div>
<div class="columns" style="margin-top: -1rem;">
<div class="column is-7">
<span style="border-bottom: 1px solid; width: 100%; display: block;"></span>
<p style="font-size: x-small; display: block;">{{__ "location"}}, {{__ "date"}}</p>
<p style="font-size: x-small; display: block;">Location, Date</p>
</div>
<div class="column is-5">
<span style="border-bottom: 1px solid; width: 100%; display: block;"></span>
<p style="font-size: x-small; display: block;">{{__ "signature"}}</p>
<p style="font-size: x-small; display: block;">Signature</p>
</div>
</div>
<p style="font-size: xx-small; overflow: hidden; height: 4rem; text-align: center;">{{../disclaimer}}</p>
<p style="font-size: xx-small; overflow: hidden; height: 4rem; text-align: center;">{{ $.Disclaimer }}</p>
</div>
</div>
{{/each}}
{{ end }}
</body>
</html>

View File

@ -1,19 +0,0 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"rootDir": "./src",
"outDir": "./dist",
"esModuleInterop": true,
"strict": false,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"sourceMap": false
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
]
}