Compare commits
525 Commits
Author | SHA1 | Date | |
---|---|---|---|
ac471b28a6 | |||
76d982fa04 | |||
14795e1831 | |||
c48a1f855f | |||
92380802e9 | |||
a38a0149b7 | |||
af587b0ac1 | |||
50e3eff294 | |||
bc17f7256b | |||
d2f3eea8a5 | |||
f902c61490 | |||
11e8cc5b1d | |||
84155b7404 | |||
45b37197ec | |||
f65848924c | |||
98d584867e | |||
376e8de1a4 | |||
2911391fb9 | |||
6d2e0241c9 | |||
afc5b1f0c6 | |||
4a76ee469b | |||
b58bf700df | |||
efd3a35802 | |||
0f7e44a42a | |||
f90e5d75fa | |||
31d4ec5f27 | |||
d61d4d6e7e | |||
606ce6b940 | |||
750fa70332 | |||
7d503edbc9 | |||
5c9235df8d | |||
11ea0858bb | |||
4d57cf827d | |||
df9f7fdc13 | |||
cdd2b5e250 | |||
94b766f106 | |||
a2e94f715b | |||
f64daaf817 | |||
b4bb732303 | |||
3dee3e72af | |||
f5914e5c38 | |||
5a5a7179e9 | |||
6c57d63891 | |||
b502e2fbd5 | |||
c9475d0093 | |||
1cc19e0085 | |||
b792806481 | |||
ea6a4a7080 | |||
de6fe4991c | |||
1d068b2655 | |||
ef25adf5ed | |||
c09c00ec68 | |||
1f4981b0a9 | |||
c9f28612be | |||
c2d192d8a3 | |||
31734596f6 | |||
850fa0a760 | |||
1c0a9860fa | |||
7c32f1aad2 | |||
b9e550d6f5 | |||
2a4f126377 | |||
5c932158e9 | |||
1296c9e399 | |||
0f2452eca0 | |||
b6cc98a165 | |||
c5da33f10f | |||
69f4de6739 | |||
649ac2a3c2 | |||
5587fdaaa8 | |||
8f676f08a9 | |||
28de60d375 | |||
d19029b5ad | |||
1dfd96869d | |||
a1ba28cacb | |||
d2bace87af | |||
9d507b9572 | |||
e89c17806f | |||
924f76a100 | |||
99ec0933ea | |||
cceca7f5e1 | |||
54d294a8b4 | |||
41291b9200 | |||
4faf76a073 | |||
715eb8e1cb | |||
2686bee1d1 | |||
57a3777891 | |||
f6dc33edb4 | |||
e2cd445aeb | |||
f5debf58fc | |||
11a9b51197 | |||
c5dc4f7e79 | |||
f9f30e96c7 | |||
eff3354867 | |||
2bfff006ed | |||
53eab5db94 | |||
af73b35b18 | |||
692f378eab | |||
12f61e373f | |||
79a0062f60 | |||
561e7151c6 | |||
2108c88001 | |||
2537235ce6 | |||
4deda8adf1 | |||
d910a722df | |||
e10448f1e3 | |||
f880e9f10c | |||
b179541532 | |||
5098d27ae9 | |||
d8fb9ea2fd | |||
5dbe7816cd | |||
1657a10dec | |||
7d22a32cb4 | |||
ed4941b403 | |||
c227c291c9 | |||
389922f22d | |||
670290b60b | |||
3345571bd8 | |||
d51e78a442 | |||
bd70ac4542 | |||
62f04f7d1c | |||
8812bf2410 | |||
323c0b0ff9 | |||
e9c28efd47 | |||
e5f9eff54f | |||
382a799038 | |||
f4f5c8b63a | |||
c0dd30f08c | |||
3eb914d640 | |||
ba7e02fa30 | |||
56e09dafb9 | |||
145ebd8346 | |||
13e9c88a8e | |||
7d571ad46b | |||
491d31e2e9 | |||
3f35d35016 | |||
aeeb7b3448 | |||
f9b471b59e | |||
8f3faee573 | |||
6ff2ba702b | |||
4ecefe8534 | |||
ab9295ee23 | |||
29d4e21656 | |||
490f24422b | |||
67eb761a99 | |||
22978e5bbc | |||
0dbd7311ab | |||
cbda10e2a1 | |||
ee3c443216 | |||
bbe01cd231 | |||
10c68cfb37 | |||
c21076a049 | |||
fc774d5af3 | |||
dd0b60b2ba | |||
6add09c7df | |||
d064b51e1b | |||
744f567b7c | |||
7dbce9e870 | |||
72ed2495f6 | |||
05e0c63931 | |||
fcbcec85c5 | |||
b47b2f804f | |||
5f4fc74cd9 | |||
300b8fd01a | |||
8b951dfb3f | |||
517e8a0819 | |||
3af4b521d3 | |||
dca2829323 | |||
57a84c256a | |||
b78534e1b0 | |||
9b85b54da5 | |||
4b5a86282d | |||
c9cb03ea95 | |||
c7f57548f3 | |||
8d00307170 | |||
5e92b9a48f | |||
01e1323555 | |||
f8465721cd | |||
4cea7cb32f | |||
72303b1105 | |||
451b7fbe05 | |||
2a3322612d | |||
4b4d66ae78 | |||
c935950eb0 | |||
573b921197 | |||
274c13e358 | |||
ff0421da2f | |||
915baa6efa | |||
bac004d74e | |||
b7b7f6a0ae | |||
11efdebacf | |||
0f2d6f58d6 | |||
df8bd1133b | |||
22fb3edd78 | |||
ded610f114 | |||
a4c8dade23 | |||
b6fc069042 | |||
60cc343adf | |||
010f2046ad | |||
c18cb7f135 | |||
2e7c3e8a5b | |||
ac9be793bd | |||
c18fc4ec93 | |||
981bae4786 | |||
754d0ca58c | |||
fa26ed6012 | |||
cc4a2b4ab4 | |||
e97e209746 | |||
8f30d8933f | |||
f78037c0f1 | |||
3c02e13997 | |||
d8f3a6ed06 | |||
2ee4c06055 | |||
76418f65e1 | |||
a57e0909b9 | |||
a81db03ba3 | |||
7ae4750307 | |||
f623c0a7cd | |||
3fc612488d | |||
f220e70743 | |||
d3f7d1a6c9 | |||
82159bed53 | |||
479e28c46c | |||
e75f15142e | |||
cec893032d | |||
2278e4ad06 | |||
5a98688d60 | |||
63c7beb8b9 | |||
2a4cfdb2f8 | |||
a580841973 | |||
6b23dea477 | |||
e0add846bb | |||
1d12de7045 | |||
b43aeec0cf | |||
405bb20601 | |||
ac572f1ea3 | |||
7fea1ca78f | |||
64fce5bd01 | |||
5ba26c4cbf | |||
b82a32ae3e | |||
0af9b81b38 | |||
955e11846b | |||
d1577cd08d | |||
6767c3b2d1 | |||
2b2195727b | |||
3ca2237953 | |||
8d6ea4dbf9 | |||
8b71608792 | |||
f1084b59a7 | |||
c8dc998ecd | |||
3df3d26708 | |||
922e762aa2 | |||
c3beb3e103 | |||
457ea26cf8 | |||
c2d2b66f2f | |||
289a0d8671 | |||
ce3053c0ba | |||
6608456c68 | |||
1cbe5a1614 | |||
9584bfed8b | |||
0839ff6359 | |||
4739193709 | |||
0ade57536e | |||
d17108f4b9 | |||
3c42ca3042 | |||
073433f308 | |||
8bac1fadd6 | |||
a478081727 | |||
b1978e796f | |||
c51ec74d30 | |||
b8f0d1fa60 | |||
96886c74bc | |||
8d3cc34395 | |||
85519bc2e4 | |||
ed02306738 | |||
31a59500fa | |||
d01b4a0b99 | |||
d9919404b5 | |||
b180e04045 | |||
7f28525ec2 | |||
677bd86133 | |||
b612562d34 | |||
e9d3574599 | |||
9dd62cefa9 | |||
0797c678c8 | |||
5e4d6f44da | |||
885765ac71 | |||
03ed6d5bc1 | |||
4e1e124d0d | |||
19fbf50f6f | |||
21b5e048ed | |||
c012b4943d | |||
7c23dba493 | |||
ba566bcc33 | |||
a386c5bef8 | |||
1ca5d3ea07 | |||
fd8b7e56da | |||
55877de2aa | |||
d1a29c1cbb | |||
e6f7dd2be8 | |||
49590b897e | |||
a9019e4c67 | |||
cc6a53b258 | |||
e0db6f6a78 | |||
0fcfb30d5c | |||
c2909082a2 | |||
92c52401b3 | |||
7ca7266ea4 | |||
adf11ab1c3 | |||
db91661556 | |||
9d7d044384 | |||
95099c5fbd | |||
782861bd93 | |||
dcde424b77 | |||
b7c6c6e157 | |||
2d031dae03 | |||
729f2d7240 | |||
454309278e | |||
7be211f8b7 | |||
bdeadd274b | |||
e306cdb2c8 | |||
406add3d51 | |||
e74b9a4c9d | |||
449a96b302 | |||
703eaa0e9d | |||
b4dc1d2be7 | |||
cf0f5839ee | |||
29376a7782 | |||
08e858726c | |||
68a1b8f3e0 | |||
9697d53a15 | |||
d38923c4ad | |||
03c7d590d0 | |||
8a90f63b09 | |||
491cdb8d71 | |||
68572b194e | |||
1eb634fe11 | |||
dbccbf68f4 | |||
96204d809e | |||
7ac8edb5cf | |||
05561db5b8 | |||
149bf1849d | |||
b952ac4d72 | |||
5c075bce8b | |||
b77945a9e4 | |||
aefe5493b0 | |||
75b8b281b8 | |||
44f139aa01 | |||
a1b0a1918d | |||
467d8df6db | |||
fea0b1dc05 | |||
7122fe7dbe | |||
ff36b4871f | |||
03f63e3777 | |||
ecd02a1af7 | |||
b0d42939a6 | |||
502345782f | |||
9a7c1d64fd | |||
4b79b29ee6 | |||
edc846ab05 | |||
0d27916188 | |||
0894446085 | |||
4187a8e820 | |||
e1ec193a4f | |||
ad9a8a4fe0 | |||
6a14232889 | |||
b6296b8d97 | |||
bc4d16e6f8 | |||
e3a45a61ac | |||
7f58dd694b | |||
68f46a45b5 | |||
016f746c7c | |||
b77bb3ad9d | |||
b4ebae283b | |||
3bb322ede5 | |||
b92a6f7b2b | |||
d3a213ce33 | |||
8fc6c7176e | |||
929ac81515 | |||
75d2ac3c5f | |||
3e2b011d28 | |||
1c06689800 | |||
a35f8cfd3a | |||
27d1d69360 | |||
8072d0b194 | |||
a30600943d | |||
123cf8ad48 | |||
22b1e0097e | |||
7e507d4cc4 | |||
f7dfd6d0c3 | |||
cbff3074d1 | |||
9755e437c2 | |||
fe0f45ea92 | |||
b07c5a96f2 | |||
dba765cb01 | |||
8ca015ecf8 | |||
6ab3946c31 | |||
603a814ad4 | |||
7736ccdc0c | |||
d1b07f39fd | |||
8d4e7a16d1 | |||
4fca5afdfa | |||
ea8028dfd5 | |||
bc2b6fadd9 | |||
ee19efa0e6 | |||
f3de80af78 | |||
6a42fe37d0 | |||
84259d37d4 | |||
140fda11cc | |||
e92820e12f | |||
4773a5f18c | |||
06dedc0797 | |||
785544ca16 | |||
4f191dcb52 | |||
1ced0e3175 | |||
5e1252545b | |||
2d0b7ce79e | |||
1962499d18 | |||
1e67672ef0 | |||
e401d0ec72 | |||
42443af734 | |||
c07319b250 | |||
388d8a2dc6 | |||
ee8ba99cc7 | |||
47a05facb3 | |||
f755f4f9fb | |||
784d7c656f | |||
e345c36dd0 | |||
c617c40e9d | |||
a9e3360ee2 | |||
13776d1ecb | |||
f69e7779e4 | |||
8ec5de4877 | |||
7c3813b94d | |||
b4232e51e0 | |||
cd51e78282 | |||
f833ae222e | |||
4cd437b6af | |||
9be3e789a5 | |||
119102aef8 | |||
cf9cd298b6 | |||
05e471878f | |||
6f81566cb8 | |||
a596188c00 | |||
75eb925267 | |||
19697692bc | |||
4acf3e39ce | |||
3af76a53e3 | |||
d58453faf9 | |||
bd6ec6215d | |||
96d863bfc8 | |||
ba7cedd187 | |||
78205ee8c7 | |||
041c0ed6bb | |||
6121b1e3bf | |||
5afd26ea22 | |||
4585a83838 | |||
c07c6aeeab | |||
9a115e75b1 | |||
f4a34dd36b | |||
42d6c40d0c | |||
de432c4481 | |||
73915da203 | |||
15cd5f6579 | |||
95b882aced | |||
376087fe66 | |||
68e34a9054 | |||
465efe0300 | |||
782b41756d | |||
15f16415c0 | |||
82b5f9e86e | |||
b5e79e51ef | |||
8141269dd9 | |||
56b72275ac | |||
df94b1b750 | |||
15ff412d75 | |||
36396af50a | |||
4d45e0f80d | |||
6435c8a88e | |||
3fb8be22b7 | |||
7d5b5750ad | |||
64bd1ffc3a | |||
3ca38abe93 | |||
2c7b0254ec | |||
f726a6e699 | |||
a7d4001ad9 | |||
4617f2c5bd | |||
557cc26f28 | |||
8a30265fc6 | |||
9f3758de39 | |||
cfa65e83ca | |||
1d73e4ed9c | |||
1d1fa50327 | |||
d08bdfd961 | |||
aae4f507ea | |||
cb7325bbf9 | |||
e29c17a29a | |||
63f9523766 | |||
d291cf0d1b | |||
d493e74bf3 | |||
83c4bd62cb | |||
8f250f747a | |||
0c4e5a182c | |||
48d7cad9f3 | |||
185e66fb5b | |||
cbc421cd83 | |||
b9d4cc3619 | |||
1d62e7f14c | |||
4efb62921b | |||
c2d714116e | |||
cff70110fd | |||
814b564752 | |||
915ff92418 | |||
94e5e51f8d | |||
57afbd4b6c | |||
6539fd7855 | |||
9d62225478 | |||
b1747f6374 | |||
ae466b4d7e | |||
e1f03788db | |||
278c56386b | |||
66134c0e1e | |||
61c7dbff0b | |||
43bb728d0d | |||
d450ceac74 | |||
b0427a6d81 |
46
.air.linux.toml
Normal file
46
.air.linux.toml
Normal 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
46
.air.windows.toml
Normal 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
3
.dockerignore
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
tmp
|
||||||
|
docker-compose.yaml
|
||||||
|
.air*.toml
|
23
.env
Normal file
23
.env
Normal 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
27
.gitea/workflows/dev.yaml
Normal 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
|
27
.gitea/workflows/release.yaml
Normal file
27
.gitea/workflows/release.yaml
Normal 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
|
130
.gitignore
vendored
130
.gitignore
vendored
@ -1,129 +1 @@
|
|||||||
# ---> VisualStudioCode
|
tmp
|
||||||
.vscode/*
|
|
||||||
!.vscode/settings.json
|
|
||||||
!.vscode/tasks.json
|
|
||||||
!.vscode/launch.json
|
|
||||||
!.vscode/extensions.json
|
|
||||||
*.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.*
|
|
||||||
|
|
16
Dockerfile
Normal file
16
Dockerfile
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
FROM golang:1.23-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -o server
|
||||||
|
|
||||||
|
FROM alpine:3.18
|
||||||
|
RUN mkdir -p /tmp && chmod 1777 /tmp
|
||||||
|
|
||||||
|
COPY --from=builder /app/server /server
|
||||||
|
COPY static /static
|
||||||
|
ENTRYPOINT [ "/server" ]
|
362
LICENSE
362
LICENSE
@ -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
|
|
||||||
reason–for example, because of any applicable exception or limitation to copyright–then
|
|
||||||
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.
|
|
97
README.md
97
README.md
@ -1,3 +1,96 @@
|
|||||||
# document-server
|
# ✒️ Document generation microservices
|
||||||
|
|
||||||
The document generation server responsible for creating pdfs for sponsoring contracts, certificates and more.
|
## 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
9
docker-compose.dev.yaml
Normal 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
15
docker-compose.yaml
Normal 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"
|
18
docker.env
Normal file
18
docker.env
Normal 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
437
docs/docs.go
Normal 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
410
docs/swagger.json
Normal 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
278
docs/swagger.yaml
Normal 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
69
go.mod
Normal 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
162
go.sum
Normal 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
73
handlers/barcode.go
Normal 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
203
handlers/card.go
Normal 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
108
handlers/certificate.go
Normal 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
96
handlers/contract.go
Normal 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
24
handlers/handlers.go
Normal 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
7
handlers/util.go
Normal 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")
|
||||||
|
}
|
198
main.go
Normal file
198
main.go
Normal 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
26
models/card.go
Normal 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
51
models/certificate.go
Normal 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
24
models/config.go
Normal 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
31
models/contract.go
Normal 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"`
|
||||||
|
}
|
129
services/barcode.go
Normal file
129
services/barcode.go
Normal 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
131
services/converter.go
Normal 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
142
services/templater.go
Normal 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
72
services/templates.go
Normal file
File diff suppressed because one or more lines are too long
1
static/images/certificate_background.base64
Normal file
1
static/images/certificate_background.base64
Normal file
File diff suppressed because one or more lines are too long
1
static/images/certificate_footer.base64
Normal file
1
static/images/certificate_footer.base64
Normal file
File diff suppressed because one or more lines are too long
1
static/images/error.base64
Normal file
1
static/images/error.base64
Normal file
File diff suppressed because one or more lines are too long
1
static/images/sponsoringheader.base64
Normal file
1
static/images/sponsoringheader.base64
Normal file
File diff suppressed because one or more lines are too long
1
static/images/sponsors/atlantis.base64
Normal file
1
static/images/sponsors/atlantis.base64
Normal file
File diff suppressed because one or more lines are too long
1
static/images/sponsors/herzogspark.base64
Normal file
1
static/images/sponsors/herzogspark.base64
Normal file
File diff suppressed because one or more lines are too long
1
static/images/sponsors/odit.base64
Normal file
1
static/images/sponsors/odit.base64
Normal file
File diff suppressed because one or more lines are too long
1
static/images/sponsors/sparkasse.base64
Normal file
1
static/images/sponsors/sparkasse.base64
Normal file
File diff suppressed because one or more lines are too long
1
static/images/sponsors/vrbank.base64
Normal file
1
static/images/sponsors/vrbank.base64
Normal file
File diff suppressed because one or more lines are too long
84
static/templates/card/de.html
Normal file
84
static/templates/card/de.html
Normal 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>
|
79
static/templates/card/en.html
Normal file
79
static/templates/card/en.html
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf8">
|
||||||
|
<title>Runner cards</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">
|
||||||
|
<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-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>Runner</p>
|
||||||
|
{{ 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;">
|
||||||
|
<!--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>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end}}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
119
static/templates/certificate/de.html
Normal file
119
static/templates/certificate/de.html
Normal 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>
|
120
static/templates/certificate/en.html
Normal file
120
static/templates/certificate/en.html
Normal 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>
|
124
static/templates/contract/de.html
Normal file
124
static/templates/contract/de.html
Normal 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>
|
120
static/templates/contract/en.html
Normal file
120
static/templates/contract/en.html
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
<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;">Sponsoring contract</p>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<p style="font-size: x-small; vertical-align: revert; margin-top: auto;">Please write in BLOCK LETTERS.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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;">{{ .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;">{{ .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;">Last Name</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/class</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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;">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;">First name</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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>
|
||||||
|
</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>
|
||||||
|
</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;">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>
|
||||||
|
</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>
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p style="font-size: xx-small; overflow: hidden; height: 4rem; text-align: center;">{{ $.Disclaimer }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
Loading…
x
Reference in New Issue
Block a user