Compare commits

..

No commits in common. "main" and "1.2.4" have entirely different histories.
main ... 1.2.4

33 changed files with 115 additions and 556 deletions

9
.env
View File

@ -1,4 +1,3 @@
LOGLEVEL=debug
PORT=3000 PORT=3000
PRODUCION=false PRODUCION=false
APIKEY=lfk APIKEY=lfk
@ -11,13 +10,9 @@ CARD_SUBTITLE=Kaya ist cool
CARD_BARCODEFORMAT=ean13 CARD_BARCODEFORMAT=ean13
# CARD_BARCODEPREFIX= # CARD_BARCODEPREFIX=
SPONSORING_RECEIPTMINIMUM=40 SPONSOING_RECEIPTMINIMUM=10
SPONSORING_DISCLAIMER=Kaya ist cool, aber pass auf, dass du nicht zu viel Geld sammelst! SPONSORING_DISCLAIMER=Kaya ist cool, aber pass auf, dass du nicht zu viel Geld sammelst!
SPONSORING_BARCODEFORMAT=code128 SPONSORING_BARCODEFORMAT=code128
# SPONSORING_BARCODEPREFIX= # SPONSORING_BARCODEPREFIX=
CERTIFICATE_FOOTER=Kaya ist cool, danke für deine Unterstützung! CERTIFICATE_FOOTER=Kaya ist cool, danke für deine Unterstützung!
SEPA_BIC=FNOMDEB2
SEPA_NAME=ODIT.Services
SEPA_IBAN=DE25100180000690238989

View File

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

View File

@ -1,27 +0,0 @@
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

18
.woodpecker/build.yml Normal file
View File

@ -0,0 +1,18 @@
steps:
- name: build latest
image: woodpeckerci/plugin-docker-buildx
settings:
repo: registry.odit.services/lfk/document-server
tags:
- latest
registry: registry.odit.services
platforms: linux/amd64,linux/arm64
cache_from: registry.odit.services/lfk/document-server:latest
username:
from_secret: odit-registry-builder-username
password:
from_secret: odit-registry-builder-password
when:
branch: main
when:
event: push

17
.woodpecker/release.yml Normal file
View File

@ -0,0 +1,17 @@
steps:
- name: build tag
image: woodpeckerci/plugin-docker-buildx
settings:
repo: registry.odit.services/lfk/document-server
tags:
- "${CI_COMMIT_TAG}"
registry: registry.odit.services
platforms: linux/amd64,linux/arm64
cache_from: registry.odit.services/lfk/document-server:latest
username:
from_secret: odit-registry-builder-username
password:
from_secret: odit-registry-builder-password
when:
event:
- tag

View File

@ -8,9 +8,8 @@ RUN go mod download
COPY . . COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o server RUN CGO_ENABLED=0 GOOS=linux go build -o server
FROM alpine:3.18 FROM scratch
RUN mkdir -p /tmp && chmod 1777 /tmp
COPY --from=builder /app/server /server COPY --from=builder /app/server /server
COPY static /static COPY static /static
ADD https://curl.haxx.se/ca/cacert.pem /etc/ssl/certs/ca-certificates.crt
ENTRYPOINT [ "/server" ] ENTRYPOINT [ "/server" ]

View File

@ -326,17 +326,6 @@ const docTemplate = `{
"properties": { "properties": {
"name": { "name": {
"type": "string" "type": "string"
},
"parent_group": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
}
}
} }
} }
}, },

View File

@ -317,17 +317,6 @@
"properties": { "properties": {
"name": { "name": {
"type": "string" "type": "string"
},
"parent_group": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
}
}
} }
} }
}, },

View File

@ -96,13 +96,6 @@ definitions:
properties: properties:
name: name:
type: string type: string
parent_group:
properties:
name:
type: string
required:
- name
type: object
required: required:
- name - name
type: object type: object

19
go.mod
View File

@ -23,11 +23,8 @@ require (
github.com/go-openapi/swag v0.23.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/hcl v1.0.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/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/compress v1.17.11 // indirect
github.com/magiconair/properties v1.8.7 // indirect github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/makiuchi-d/gozxing v0.1.1 // indirect github.com/makiuchi-d/gozxing v0.1.1 // indirect
@ -36,9 +33,7 @@ require (
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/oxplot/papersizes v0.0.0-20181201065918-90a3a5ae1915 // 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/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/redis/go-redis/v9 v9.7.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect
@ -51,19 +46,15 @@ require (
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
github.com/swaggo/files/v2 v2.0.1 // indirect github.com/swaggo/files/v2 v2.0.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.61.0 // indirect github.com/valyala/fasthttp v1.57.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect
go.uber.org/atomic v1.9.0 // indirect go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.10.0 // indirect go.uber.org/multierr v1.9.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/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/image v0.26.0 // indirect golang.org/x/sys v0.27.0 // indirect
golang.org/x/sys v0.32.0 // indirect golang.org/x/text v0.19.0 // indirect
golang.org/x/text v0.24.0 // indirect
golang.org/x/tools v0.27.0 // indirect golang.org/x/tools v0.27.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
gopkg.in/ini.v1 v1.67.0 // 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 gopkg.in/yaml.v3 v3.0.1 // indirect
) )

28
go.sum
View File

@ -29,18 +29,10 @@ 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/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 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 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 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 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 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= 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 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@ -62,12 +54,8 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 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 h1:4WzMzgExTgBfuUQ/HegMf+jcHtH+c3fl7eySUQUbfzg=
github.com/oxplot/papersizes v0.0.0-20181201065918-90a3a5ae1915/go.mod h1:LJRTnhoARxQgMyT7T9L+ZzwR4OrmyHTy5LPxZEzE1CM= 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 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
@ -111,8 +99,6 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 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 h1:Xw8SjWGEP/+wAAgyy5XTvgrWlOD1+TxbbvNADYCm1Tg=
github.com/valyala/fasthttp v1.57.0/go.mod h1:h6ZBaPRlzpZ6O3H5t2gEk1Qi33+TmLvfwgLLp0t9CpE= 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 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= 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 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
@ -121,16 +107,8 @@ 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/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 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= 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 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= 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 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 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 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
@ -139,12 +117,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.6.0/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 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 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 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o=
golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= 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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
@ -155,8 +129,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 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 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -20,8 +20,6 @@ import (
// @Router /v1/barcodes/{type}/{content} [get] // @Router /v1/barcodes/{type}/{content} [get]
func (h *DefaultHandler) GenerateBarcode(c *fiber.Ctx) error { func (h *DefaultHandler) GenerateBarcode(c *fiber.Ctx) error {
logger := h.Logger.Named("GenerateBarcode")
// Get the type and content from the URL // Get the type and content from the URL
barcodeType := c.Params("type") barcodeType := c.Params("type")
barcodeContent := c.Params("content") barcodeContent := c.Params("content")
@ -34,7 +32,6 @@ func (h *DefaultHandler) GenerateBarcode(c *fiber.Ctx) error {
// Convert width and height to integers // Convert width and height to integers
width, err := strconv.Atoi(widthStr) width, err := strconv.Atoi(widthStr)
if err != nil { if err != nil {
logger.Errorw("Invalid width parameter", "width", widthStr, "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid width parameter", "error": "Invalid width parameter",
}) })
@ -42,7 +39,6 @@ func (h *DefaultHandler) GenerateBarcode(c *fiber.Ctx) error {
height, err := strconv.Atoi(heightStr) height, err := strconv.Atoi(heightStr)
if err != nil { if err != nil {
logger.Errorw("Invalid height parameter", "height", heightStr, "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid height parameter", "error": "Invalid height parameter",
}) })
@ -50,23 +46,18 @@ func (h *DefaultHandler) GenerateBarcode(c *fiber.Ctx) error {
padding, err := strconv.Atoi(paddingStr) padding, err := strconv.Atoi(paddingStr)
if err != nil { if err != nil {
logger.Errorw("Invalid padding parameter", "padding", paddingStr, "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid padding parameter", "error": "Invalid padding parameter",
}) })
} }
logger = logger.With("type", barcodeType, "content", barcodeContent, "width", width, "height", height, "padding", padding)
// Generate the barcode // Generate the barcode
logger.Info("Generating barcode")
barcode, err := h.BarcodeService.GenerateBarcode(barcodeType, barcodeContent, width, height, padding) barcode, err := h.BarcodeService.GenerateBarcode(barcodeType, barcodeContent, width, height, padding)
if err != nil { if err != nil {
logger.Errorw("Failed to generate barcode", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(), "error": err.Error(),
}) })
} }
logger.Info("Barcode generated")
c.Set(fiber.HeaderContentType, "image/png") c.Set(fiber.HeaderContentType, "image/png")
return c.Send(barcode.Bytes()) return c.Send(barcode.Bytes())

View File

@ -1,14 +1,11 @@
package handlers package handlers
import ( import (
"fmt" "log"
"os"
"slices" "slices"
"git.odit.services/lfk/document-server/models" "git.odit.services/lfk/document-server/models"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/pdfcpu/pdfcpu/pkg/api"
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model"
) )
// GenerateCard godoc // GenerateCard godoc
@ -22,148 +19,58 @@ import (
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /v1/pdfs/cards [post] // @Router /v1/pdfs/cards [post]
func (h *DefaultHandler) GenerateCard(c *fiber.Ctx) error { func (h *DefaultHandler) GenerateCard(c *fiber.Ctx) error {
logger := h.Logger.Named("GenerateCard")
cardRequest := new(models.CardRequest) cardRequest := new(models.CardRequest)
if err := c.BodyParser(cardRequest); err != nil { if err := c.BodyParser(cardRequest); err != nil {
logger.Errorw("Invalid request", "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": err.Error(), "error": err.Error(),
}) })
} }
if !slices.Contains([]string{"en", "de"}, cardRequest.Locale) { if !slices.Contains([]string{"en", "de"}, cardRequest.Locale) {
logger.Errorw("Invalid locale", "locale", cardRequest.Locale)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid locale", "error": "Invalid locale",
}) })
} }
logger = logger.With("locale", cardRequest.Locale)
templateString, err := h.StaticService.GetTemplate(cardRequest.Locale, "card") templateString, err := h.StaticService.GetTemplate(cardRequest.Locale, "card")
if err != nil { if err != nil {
logger.Errorw("Template not found", "error", err) log.Println(err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Template not found", "error": "Template not found",
}) })
} }
template, err := h.Templater.StringToTemplate(templateString) template, err := h.Templater.StringToTemplate(templateString)
if err != nil { if err != nil {
logger.Errorw("Error parsing template", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(), "error": err.Error(),
}) })
} }
segmentLength := calculateOptimalSegmentSize(len(cardRequest.Cards)) genConfig := &models.CardTemplateOptions{
pdfs := []string{} CardSegments: splitCardSegments(cardRequest.Cards),
for i := 0; i < len(cardRequest.Cards); i += segmentLength { EventName: h.Config.EventName,
CardSubtitle: h.Config.CardSubtitle,
segment := cardRequest.Cards[i:] BarcodeFormat: h.Config.CardBarcodeFormat,
if len(segment) > segmentLength { BarcodePrefix: h.Config.CardBarcodePrefix,
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" result, err := h.Templater.Execute(template, genConfig)
conf := model.NewDefaultConfiguration()
err = api.MergeCreateFile(pdfs, outputFile, false, conf)
if err != nil { if err != nil {
logger.Errorw("Failed to merge PDFs", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(), "error": err.Error(),
}) })
} }
c.Set(fiber.HeaderContentType, "text/html")
// Clean up individual PDF files pdf, err := h.Converter.ToPdf(result, "a4", false)
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 { if err != nil {
logger.Errorw("Failed to read merged PDF", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(), "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.HeaderContentType, "application/pdf")
c.Set(fiber.HeaderContentDisposition, "attachment; filename=runner-cards.pdf") c.Set(fiber.HeaderContentDisposition, "attachment; filename=runner-cards.pdf")
return c.Send(pdfBytes) return c.Send(pdf)
}
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 { func invertCardArrayItemPairs(cards []models.Card) []models.Card {

View File

@ -1,6 +1,7 @@
package handlers package handlers
import ( import (
"log"
"slices" "slices"
"git.odit.services/lfk/document-server/models" "git.odit.services/lfk/document-server/models"
@ -18,35 +19,27 @@ import (
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /v1/pdfs/certificates [post] // @Router /v1/pdfs/certificates [post]
func (h *DefaultHandler) GenerateCertificate(c *fiber.Ctx) error { func (h *DefaultHandler) GenerateCertificate(c *fiber.Ctx) error {
logger := h.Logger.Named("GenerateCertificate")
certificateRequest := new(models.CertificateRequest) certificateRequest := new(models.CertificateRequest)
if err := c.BodyParser(certificateRequest); err != nil { if err := c.BodyParser(certificateRequest); err != nil {
logger.Errorw("Invalid request", "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": err.Error(), "error": err.Error(),
}) })
} }
if !slices.Contains([]string{"en", "de"}, certificateRequest.Locale) { if !slices.Contains([]string{"en", "de"}, certificateRequest.Locale) {
logger.Errorw("Invalid locale", "locale", certificateRequest.Locale)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid locale", "error": "Invalid locale",
}) })
} }
logger = logger.With("locale", certificateRequest.Locale)
templateString, err := h.StaticService.GetTemplate(certificateRequest.Locale, "certificate") templateString, err := h.StaticService.GetTemplate(certificateRequest.Locale, "certificate")
if err != nil { if err != nil {
logger.Errorw("Template not found", "error", err) log.Println(err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Template not found", "error": "Template not found",
}) })
} }
template, err := h.Templater.StringToTemplate(templateString) template, err := h.Templater.StringToTemplate(templateString)
if err != nil { if err != nil {
logger.Errorw("Error parsing template", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(), "error": err.Error(),
}) })
@ -58,32 +51,22 @@ func (h *DefaultHandler) GenerateCertificate(c *fiber.Ctx) error {
Footer: h.Config.CertificateFooter, Footer: h.Config.CertificateFooter,
CurrencySymbol: h.Config.CurrencySymbol, CurrencySymbol: h.Config.CurrencySymbol,
Locale: certificateRequest.Locale, 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) result, err := h.Templater.Execute(template, genConfig)
if err != nil { if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(), "error": err.Error(),
}) })
} }
logger.Info("Generated card html")
c.Set(fiber.HeaderContentType, "text/html") c.Set(fiber.HeaderContentType, "text/html")
logger.Info("Converting html to pdf")
pdf, err := h.Converter.ToPdf(result, "a4", false) pdf, err := h.Converter.ToPdf(result, "a4", false)
if err != nil { if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(), "error": err.Error(),
}) })
} }
logger.Info("Converted html to pdf")
c.Set(fiber.HeaderContentType, "application/pdf") c.Set(fiber.HeaderContentType, "application/pdf")
c.Set(fiber.HeaderContentDisposition, "attachment; filename=certificate.pdf") c.Set(fiber.HeaderContentDisposition, "attachment; filename=certificate.pdf")
@ -96,13 +79,6 @@ func addUpRunnerDonations(runners []models.RunnerWithDonations) []models.RunnerW
runners[i].TotalDonations += runners[i].DistanceDonations[j].Amount runners[i].TotalDonations += runners[i].DistanceDonations[j].Amount
runners[i].TotalPerDistance += runners[i].DistanceDonations[j].AmountPerDistance 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 return runners
} }

View File

@ -1,6 +1,7 @@
package handlers package handlers
import ( import (
"log"
"slices" "slices"
"git.odit.services/lfk/document-server/models" "git.odit.services/lfk/document-server/models"
@ -18,42 +19,36 @@ import (
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /v1/pdfs/contracts [post] // @Router /v1/pdfs/contracts [post]
func (h *DefaultHandler) GenerateContract(c *fiber.Ctx) error { func (h *DefaultHandler) GenerateContract(c *fiber.Ctx) error {
logger := h.Logger.Named("GenerateContract")
contract := new(models.ContractRequest) contract := new(models.ContractRequest)
if err := c.BodyParser(contract); err != nil { if err := c.BodyParser(contract); err != nil {
logger.Errorw("Invalid request", "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": err.Error(), "error": err.Error(),
}) })
} }
if !slices.Contains([]string{"en", "de"}, contract.Locale) { if !slices.Contains([]string{"en", "de"}, contract.Locale) {
logger.Errorw("Invalid locale", "locale", contract.Locale)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid locale", "error": "Invalid locale",
}) })
} }
logger = logger.With("locale", contract.Locale) contract.Runners = repeatRunnerArrayItems(contract.Runners, 2)
templateString, err := h.StaticService.GetTemplate(contract.Locale, "contract") templateString, err := h.StaticService.GetTemplate(contract.Locale, "contract")
if err != nil { if err != nil {
logger.Errorw("Template not found", "error", err) log.Println(err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Template not found", "error": "Template not found",
}) })
} }
template, err := h.Templater.StringToTemplate(templateString) template, err := h.Templater.StringToTemplate(templateString)
if err != nil { if err != nil {
logger.Errorw("Error parsing template", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(), "error": err.Error(),
}) })
} }
genConfig := &models.ContractTemplateOptions{ genConfig := &models.ContractTemplateOptions{
Runners: repeatRunnerArrayItems(contract.Runners, 2), Runners: contract.Runners,
CurrencySymbol: h.Config.CurrencySymbol, CurrencySymbol: h.Config.CurrencySymbol,
Disclaimer: h.Config.SponosringDisclaimer, Disclaimer: h.Config.SponosringDisclaimer,
ReceiptMinimumAmount: h.Config.SponsoringReceiptMinimum, ReceiptMinimumAmount: h.Config.SponsoringReceiptMinimum,
@ -62,23 +57,19 @@ func (h *DefaultHandler) GenerateContract(c *fiber.Ctx) error {
BarcodePrefix: h.Config.SponsoringBarcodePrefix, BarcodePrefix: h.Config.SponsoringBarcodePrefix,
} }
logger.Info("Generating contract html")
result, err := h.Templater.Execute(template, genConfig) result, err := h.Templater.Execute(template, genConfig)
if err != nil { if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(), "error": err.Error(),
}) })
} }
logger.Info("Generated contract html")
logger.Info("Converting html to pdf")
pdf, err := h.Converter.ToPdf(result, "a5", true) pdf, err := h.Converter.ToPdf(result, "a5", true)
if err != nil { if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(), "error": err.Error(),
}) })
} }
logger.Info("Converted html to pdf")
c.Set(fiber.HeaderContentType, "application/pdf") c.Set(fiber.HeaderContentType, "application/pdf")
c.Set(fiber.HeaderContentDisposition, "attachment; filename=runner-contracts.pdf") c.Set(fiber.HeaderContentDisposition, "attachment; filename=runner-contracts.pdf")

View File

@ -4,7 +4,6 @@ import (
"git.odit.services/lfk/document-server/models" "git.odit.services/lfk/document-server/models"
"git.odit.services/lfk/document-server/services" "git.odit.services/lfk/document-server/services"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"go.uber.org/zap"
) )
type Handler interface { type Handler interface {
@ -20,5 +19,4 @@ type DefaultHandler struct {
Templater services.Templater Templater services.Templater
Converter services.Converter Converter services.Converter
StaticService services.StaticService StaticService services.StaticService
Logger *zap.SugaredLogger
} }

80
main.go
View File

@ -3,8 +3,7 @@ package main
import ( import (
"crypto/sha256" "crypto/sha256"
"crypto/subtle" "crypto/subtle"
"os" "log"
"strings"
"git.odit.services/lfk/document-server/docs" // Correct import path for docs "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/handlers"
@ -13,17 +12,14 @@ import (
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors" "github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/keyauth" "github.com/gofiber/fiber/v2/middleware/keyauth"
"github.com/gofiber/fiber/v2/middleware/requestid"
"github.com/gofiber/swagger" "github.com/gofiber/swagger"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
"github.com/rs/cors"
"github.com/spf13/viper" "github.com/spf13/viper"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
) )
var ( var (
config *models.Config config *models.Config
logger *zap.SugaredLogger
) )
func validateAPIKey(c *fiber.Ctx, key string) (bool, error) { func validateAPIKey(c *fiber.Ctx, key string) (bool, error) {
@ -38,13 +34,11 @@ func validateAPIKey(c *fiber.Ctx, key string) (bool, error) {
func loadEnv() error { func loadEnv() error {
viper.SetDefault("LOGLEVEL", "INFO") viper.SetDefault("PRODUCION", true)
viper.SetDefault("PRODUCION", false)
viper.SetDefault("PORT", "3000") viper.SetDefault("PORT", "3000")
viper.SetDefault("APIKEY", "lfk") viper.SetDefault("APIKEY", "lfk")
viper.SetDefault("EVENTNAME", "Demo Event") viper.SetDefault("EVENT_NAME", "Demo Event")
viper.SetDefault("CURRENCYSYMBOL", "€") viper.SetDefault("CURRENCY_SYMBOL", "€")
viper.SetDefault("CURRENCYIDENTIFIER", "EUR")
viper.SetDefault("CARD_SUBTITLE", "Runner Card") viper.SetDefault("CARD_SUBTITLE", "Runner Card")
viper.SetDefault("CARD_BARCODEFORMAT", "ean13") viper.SetDefault("CARD_BARCODEFORMAT", "ean13")
viper.SetDefault("CARD_BARCODEPREFIX", "") viper.SetDefault("CARD_BARCODEPREFIX", "")
@ -55,9 +49,6 @@ func loadEnv() error {
viper.SetDefault("CERTIFICATE_FOOTER", "Footer") viper.SetDefault("CERTIFICATE_FOOTER", "Footer")
viper.SetDefault("GOTENBERG_BASEURL", "") viper.SetDefault("GOTENBERG_BASEURL", "")
viper.SetDefault("REDIS_ADDR", "") viper.SetDefault("REDIS_ADDR", "")
viper.SetDefault("SEPA_BIC", "")
viper.SetDefault("SEPA_NAME", "")
viper.SetDefault("SEPA_IBAN", "")
// Load .env file // Load .env file
viper.SetConfigFile(".env") viper.SetConfigFile(".env")
@ -66,7 +57,7 @@ func loadEnv() error {
viper.AutomaticEnv() viper.AutomaticEnv()
err := viper.ReadInConfig() err := viper.ReadInConfig()
if err != nil { if err != nil {
logger.Warn("No .env file found") log.Println("No .env file found")
} }
// Unmarshal the config from file and env into the config struct // Unmarshal the config from file and env into the config struct
@ -75,65 +66,31 @@ func loadEnv() error {
return err return err
} }
logger.Infow("Loaded config", "config", &config) log.Printf("Loaded config: %+v\n", 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 return nil
} }
// @title LfK Document Server API // @title LfK Document Server API
// @description This is the API documentation for the LfK Document Server - a tool for pdf generation. // @description This is the API documentation for the LfK Document Server - a tool for pdf generation.
// @license.name CC BY-NC-SA 4.0 // @license.name CC BY-NC-SA 4.0
// @termsOfService https://lauf-fuer-kaya.de/datenschutz // @termsOfService https://lauf-fuer-kaya.de/datenschutz
// @contact.name ODIT.Services UG (haftungsbeschränkt) // @contact.name ODIT.Services UG (haftungsbeschränkt)
// @contact.url https://odit.services // @contact.url https://odit.services
// @contact.email info@odit.services // @contact.email info@odit.services
// @securityDefinitions.apiKey ApiKeyAuth // @securityDefinitions.apiKey ApiKeyAuth
// @in query // @in query
// @name key // @name key
func main() { func main() {
// Init the logger err := loadEnv()
err := initLogger()
if err != nil { if err != nil {
return log.Fatal(err)
}
err = loadEnv()
if err != nil {
logger.Error(err)
return
} }
var redisClient *redis.Client var redisClient *redis.Client
if config.RedisAddr != "" { if config.RedisAddr != "" {
logger.Infow("Using redis", "redisAddr", config.RedisAddr) log.Println("Using redis at", config.RedisAddr)
redisClient = redis.NewClient(&redis.Options{ redisClient = redis.NewClient(&redis.Options{
Addr: config.RedisAddr, Addr: config.RedisAddr,
}) })
@ -141,11 +98,9 @@ func main() {
barcodeGenerator := &services.DefaultBarcodeService{ barcodeGenerator := &services.DefaultBarcodeService{
RedisClient: redisClient, RedisClient: redisClient,
Logger: logger.Named("DefaultBarcodeService"),
} }
staticService := &services.DefaultStaticService{ staticService := &services.DefaultStaticService{
Cache: make(map[string]string), Cache: make(map[string]string),
Logger: logger.Named("DefaultStaticService"),
} }
handler := handlers.DefaultHandler{ handler := handlers.DefaultHandler{
Config: config, Config: config,
@ -154,15 +109,11 @@ func main() {
Templater: &services.DefaultTemplater{ Templater: &services.DefaultTemplater{
BarcodeService: barcodeGenerator, BarcodeService: barcodeGenerator,
StaticService: staticService, StaticService: staticService,
Logger: logger.Named("DefaultTemplater"),
}, },
Converter: &services.GotenbergConverter{ Converter: &services.GotenbergConverter{
BaseUrl: config.GotenbergBaseUrl, BaseUrl: config.GotenbergBaseUrl,
Logger: logger.Named("GotenbergConverter"),
}, },
Logger: logger.Named("DefaultHandler"),
} }
logger.Debug("Initialized services")
// Create a new Fiber instance // Create a new Fiber instance
app := fiber.New(fiber.Config{ app := fiber.New(fiber.Config{
@ -170,7 +121,6 @@ func main() {
}) })
app.Use(cors.New()) app.Use(cors.New())
app.Use(requestid.New())
// Swagger documentation route // Swagger documentation route
app.Get("/swagger/*", swagger.HandlerDefault) app.Get("/swagger/*", swagger.HandlerDefault)
@ -188,11 +138,9 @@ func main() {
pdfv1.Post("/certificates", handler.GenerateCertificate) pdfv1.Post("/certificates", handler.GenerateCertificate)
v1.Get("/barcodes/:type/:content", handler.GenerateBarcode) v1.Get("/barcodes/:type/:content", handler.GenerateBarcode)
logger.Debug("Initialized routes")
app.Use(handler.NotFoundHandler) app.Use(handler.NotFoundHandler)
docs.SwaggerInfo.BasePath = "/" docs.SwaggerInfo.BasePath = "/"
logger.Infow("Starting server", "port", config.Port) log.Fatal(app.Listen("0.0.0.0:" + config.Port))
logger.Error(app.Listen("0.0.0.0:" + config.Port))
} }

View File

@ -11,12 +11,10 @@ type RunnerWithDonations struct {
MiddleName string `json:"middle_name" validate:"optional"` MiddleName string `json:"middle_name" validate:"optional"`
LastName string `json:"last_name" validate:"required"` LastName string `json:"last_name" validate:"required"`
Group Group `json:"group" validate:"required"` Group Group `json:"group" validate:"required"`
CombinedGroupName string `json:"combined_group_name" validate:"optional"`
Distance int `json:"distance" validate:"required"` Distance int `json:"distance" validate:"required"`
DistanceDonations []DistanceDonation `json:"distance_donations" validate:"optional"` DistanceDonations []DistanceDonation `json:"distance_donations" validate:"optional"`
TotalPerDistance int `json:"total_per_distance" validate:"optional"` TotalPerDistance int `json:"total_per_distance" validate:"optional"`
TotalDonations int `json:"total_donations" validate:"optional"` TotalDonations int `json:"total_donations" validate:"optional"`
SelfServiceLink string `json:"self_service_link" validate:"required"`
} }
type DistanceDonation struct { type DistanceDonation struct {
@ -40,12 +38,4 @@ type CertificateTemplateOptions struct {
Footer string `json:"footer"` Footer string `json:"footer"`
CurrencySymbol string `json:"currency_symbol"` CurrencySymbol string `json:"currency_symbol"`
Locale string `json:"locale"` 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"`
} }

View File

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

View File

@ -14,17 +14,14 @@ type Runner struct {
} }
type Group struct { type Group struct {
Name string `json:"name" validate:"required"` Name string `json:"name" validate:"required"`
ParentGroup struct {
Name string `json:"name" validate:"required"`
} `json:"parent_group" validate:"optional"`
} }
type ContractTemplateOptions struct { type ContractTemplateOptions struct {
Runners []Runner `json:"runners"` Runners []Runner `json:"runners"`
CurrencySymbol string `json:"currency_symbol"` CurrencySymbol string `json:"currency_symbol"`
Disclaimer string `json:"disclaimer"` Disclaimer string `json:"disclaimer"`
ReceiptMinimumAmount string `json:"receipt_minimum_amount"` ReceiptMinimumAmount int `json:"receipt_minimum_amount"`
EventName string `json:"event_name"` EventName string `json:"event_name"`
BarcodeFormat string `json:"barcode_format"` BarcodeFormat string `json:"barcode_format"`
BarcodePrefix string `json:"barcode_prefix"` BarcodePrefix string `json:"barcode_prefix"`

View File

@ -8,6 +8,7 @@ import (
"image/color" "image/color"
"image/draw" "image/draw"
"image/png" "image/png"
"log"
"slices" "slices"
"time" "time"
@ -16,7 +17,6 @@ import (
"github.com/boombuler/barcode/ean" "github.com/boombuler/barcode/ean"
"github.com/boombuler/barcode/qr" "github.com/boombuler/barcode/qr"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
"go.uber.org/zap"
) )
type BarcodeService interface { type BarcodeService interface {
@ -26,26 +26,19 @@ type BarcodeService interface {
type DefaultBarcodeService struct { type DefaultBarcodeService struct {
RedisClient *redis.Client RedisClient *redis.Client
Logger *zap.SugaredLogger
} }
func (b *DefaultBarcodeService) GenerateBarcode(format string, content string, width int, height int, padding int) (bytes.Buffer, error) { func (b *DefaultBarcodeService) GenerateBarcode(format string, content string, width int, height int, padding int) (bytes.Buffer, error) {
ctx := context.Background() ctx := context.Background()
logger := b.Logger.Named("GenerateBarcode")
if !b.IsTypeSupported(format) { if !b.IsTypeSupported(format) {
logger.Errorw("Unsupported barcode type", "type", format)
return bytes.Buffer{}, fmt.Errorf("unsupported barcode type: %s", 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 { if b.RedisClient != nil {
logger.Debugw("Checking cache for barcode", "key", cacheKey) cachedBarcode, err := b.RedisClient.Get(ctx, fmt.Sprintf("barcode:%s:%s:%d:%d:%d", format, content, width, height, padding)).Result()
cachedBarcode, err := b.RedisClient.Get(ctx, cacheKey).Result()
if err == nil { if err == nil {
logger.Infow("Barcode found in cache", "key", cacheKey) log.Printf("Cache hit for barcode:%s:%s:%d:%d", format, content, width, height)
buf := bytes.Buffer{} buf := bytes.Buffer{}
buf.Write([]byte(cachedBarcode)) buf.Write([]byte(cachedBarcode))
return buf, nil return buf, nil
@ -69,11 +62,7 @@ func (b *DefaultBarcodeService) GenerateBarcode(format string, content string, w
} }
break break
case "qr": case "qr":
// Always use qr.Auto encoding to support all characters in the content generatedCode, err = qr.Encode(content, qr.M, qr.AlphaNumeric)
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 { if err != nil {
return bytes.Buffer{}, err return bytes.Buffer{}, err
} }
@ -84,7 +73,6 @@ func (b *DefaultBarcodeService) GenerateBarcode(format string, content string, w
bg := image.NewRGBA(image.Rect(0, 0, width, height)) bg := image.NewRGBA(image.Rect(0, 0, width, height))
white := color.RGBA{255, 255, 255, 255} white := color.RGBA{255, 255, 255, 255}
draw.Draw(bg, bg.Bounds(), &image.Uniform{white}, image.Point{}, draw.Src) 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 // Calculate the new size for the barcode to fit within the padding
newWidth := width - 2*padding newWidth := width - 2*padding
@ -93,32 +81,24 @@ func (b *DefaultBarcodeService) GenerateBarcode(format string, content string, w
// Scale the barcode to the new size // Scale the barcode to the new size
scaledCode, err := barcode.Scale(generatedCode, newWidth, newHeight) scaledCode, err := barcode.Scale(generatedCode, newWidth, newHeight)
if err != nil { if err != nil {
logger.Errorw("Failed to scale barcode", "error", err)
return bytes.Buffer{}, err return bytes.Buffer{}, err
} }
logger.Debug("Scaled barcode")
// Draw the barcode on top of the white background with padding // 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) 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 var buf bytes.Buffer
err = png.Encode(&buf, bg) err = png.Encode(&buf, bg)
if err != nil { if err != nil {
logger.Errorw("Failed to encode barcode to PNG", "error", err)
return bytes.Buffer{}, err return bytes.Buffer{}, err
} }
logger.Debug("Encoded barcode to PNG")
if b.RedisClient != nil { if b.RedisClient != nil {
err = b.RedisClient.Set(ctx, cacheKey, buf.String(), 10*time.Minute).Err() err = b.RedisClient.Set(ctx, fmt.Sprintf("barcode:%s:%s:%d:%d", format, content, width, height), buf.String(), 10*time.Minute).Err()
logger.Debugw("Cached barcode", "key", cacheKey)
if err != nil { if err != nil {
logger.Errorw("Failed to cache barcode", "error", err)
return bytes.Buffer{}, err return bytes.Buffer{}, err
} }
} }
logger.Info("Generated barcode")
return buf, nil return buf, nil
} }

View File

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

View File

@ -6,10 +6,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"html/template" "html/template"
"math"
"strings" "strings"
"go.uber.org/zap"
) )
type Templater interface { type Templater interface {
@ -20,7 +17,6 @@ type Templater interface {
type DefaultTemplater struct { type DefaultTemplater struct {
BarcodeService BarcodeService BarcodeService BarcodeService
StaticService StaticService StaticService StaticService
Logger *zap.SugaredLogger
} }
func idToEan13(id string, prefix string) (string, error) { func idToEan13(id string, prefix string) (string, error) {
@ -36,28 +32,6 @@ func idToEan13(id string, prefix string) (string, error) {
return id, nil 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) { func (t *DefaultTemplater) GenerateBarcode(code string, format string, prefix string) (string, error) {
var err error var err error
@ -74,13 +48,10 @@ func (t *DefaultTemplater) GenerateBarcode(code string, format string, prefix st
} }
func (t *DefaultTemplater) SelectSponsorImage(id int) (string, error) { func (t *DefaultTemplater) SelectSponsorImage(id int) (string, error) {
logger := t.Logger.Named("SelectSponsorImage")
sponsors, err := t.StaticService.ListFilesInStaticSubFolder("images/sponsors") sponsors, err := t.StaticService.ListFilesInStaticSubFolder("images/sponsors")
if err != nil { if err != nil {
logger.Errorw("Failed to list sponsors", "error", err)
return "", 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 return t.StaticService.GetImage("sponsors/" + strings.TrimSuffix(sponsors[id%len(sponsors)], ".base64")), nil
} }
@ -90,24 +61,9 @@ func (t *DefaultTemplater) LoadImage(name string) (string, error) {
func (t *DefaultTemplater) FormatUnit(unit string, locale string, amount int) (string, error) { func (t *DefaultTemplater) FormatUnit(unit string, locale string, amount int) (string, error) {
var formatted string var formatted string
var seperator string
switch locale {
case "de":
seperator = " "
default:
seperator = ""
}
switch unit { switch unit {
case "kilometer": case "kilometer":
if amount < 1000 { formatted = fmt.Sprintf("%.3f", float64(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": case "euro":
formatted = fmt.Sprintf("%.2f", float64(amount)/100) formatted = fmt.Sprintf("%.2f", float64(amount)/100)
default: default:
@ -126,7 +82,6 @@ func (t *DefaultTemplater) StringToTemplate(templateString string) (*template.Te
"sponsorLogo": t.SelectSponsorImage, "sponsorLogo": t.SelectSponsorImage,
"formatUnit": t.FormatUnit, "formatUnit": t.FormatUnit,
"loadImage": t.LoadImage, "loadImage": t.LoadImage,
"epcCode": t.GenerateEPC,
}).Parse(templateString) }).Parse(templateString)
} }

View File

@ -3,9 +3,8 @@ package services
import ( import (
_ "embed" _ "embed"
"fmt" "fmt"
"log"
"os" "os"
"go.uber.org/zap"
) )
type StaticService interface { type StaticService interface {
@ -15,55 +14,47 @@ type StaticService interface {
} }
type DefaultStaticService struct { type DefaultStaticService struct {
Cache map[string]string Cache map[string]string
Logger *zap.SugaredLogger
} }
func (s *DefaultStaticService) GetTemplate(locale, templateName string) (string, error) { func (s *DefaultStaticService) GetTemplate(locale, templateName string) (string, error) {
logger := s.Logger.Named("GetTemplate").With("locale", locale, "template_name", templateName)
if s.Cache[locale+templateName] != "" { if s.Cache[locale+templateName] != "" {
logger.Debugw("Template found in cache", "key", locale+templateName) log.Printf("returning cached template %s with locale %s", templateName, locale)
return s.Cache[locale+templateName], nil return s.Cache[locale+templateName], nil
} }
content, err := os.ReadFile(fmt.Sprintf("static/templates/%s/%s.html", templateName, locale)) content, err := os.ReadFile(fmt.Sprintf("static/templates/%s/%s.html", templateName, locale))
if content == nil || err != nil { if content == nil || err != nil {
logger.Errorw("Failed to read template", "error", err) log.Printf("error reading template %s with locale %s: %v", templateName, locale, err)
return "", err return "", err
} }
s.Cache[locale+templateName] = string(content) s.Cache[locale+templateName] = string(content)
logger.Debugw("Saved template to cache", "key", locale+templateName)
return string(content), nil return string(content), nil
} }
func (s *DefaultStaticService) ListFilesInStaticSubFolder(folderName string) ([]string, error) { func (s *DefaultStaticService) ListFilesInStaticSubFolder(folderName string) ([]string, error) {
logger := s.Logger.Named("ListFilesInStaticSubFolder").With("folder_name", folderName)
files, err := os.ReadDir(fmt.Sprintf("static/%s", folderName)) files, err := os.ReadDir(fmt.Sprintf("static/%s", folderName))
if err != nil { if err != nil {
logger.Errorw("Failed to list files", "error", err) log.Printf("error reading files from folder %s: %v", folderName, err)
return nil, err return nil, err
} }
var images []string var images []string
for _, file := range files { for _, file := range files {
if file.IsDir() { if file.IsDir() {
logger.Debugw("Skipping directory", "file", file.Name()) continue
} }
images = append(images, file.Name()) images = append(images, file.Name())
} }
logger.Debugw("Listed files", "files", images)
return images, nil return images, nil
} }
func (s *DefaultStaticService) GetImage(imageName string) string { func (s *DefaultStaticService) GetImage(imageName string) string {
logger := s.Logger.Named("GetImage").With("image_name", imageName)
content, err := os.ReadFile("static/images/" + imageName + ".base64") content, err := os.ReadFile("static/images/" + imageName + ".base64")
if content == nil || err != nil { if content == nil || err != nil {
logger.Errorw("Failed to read image", "error", err) log.Printf("error reading image %s: %v", imageName, err)
return ImageErrorBase64 return ImageErrorBase64
} }
return string(content) return string(content)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,7 @@
<head> <head>
<meta charset="utf8"> <meta charset="utf8">
<title>Läuferkarten</title> <title>Sponsoring contract</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
<style> <style>
.sheet { .sheet {
@ -50,12 +50,8 @@
<p style="font-size: 0.6rem; text-align: center; margin: 0; padding: 0;">{{ .Code }}</p> <p style="font-size: 0.6rem; text-align: center; margin: 0; padding: 0;">{{ .Code }}</p>
</div> </div>
</div> </div>
{{ if ne .Runner.FirstName "" }}
<p>{{ .Runner.LastName }}, {{ .Runner.FirstName }} {{ .Runner.MiddleName }}</p> <p>{{ .Runner.LastName }}, {{ .Runner.FirstName }} {{ .Runner.MiddleName }}</p>
<p>{{ if ne .Runner.Group.ParentGroup.Name "" -}}{{ .Runner.Group.ParentGroup.Name }}/{{end -}}{{ .Runner.Group.Name }}</p> <p>{{ .Runner.Group.Name }}</p>
{{ else }}
<p>Läufer:in</p>
{{ end}}
{{ end}} {{ end}}
</div> </div>
{{ end }} {{ end }}

View File

@ -2,7 +2,7 @@
<head> <head>
<meta charset="utf8"> <meta charset="utf8">
<title>Runner cards</title> <title>Sponsoring contract</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
<style> <style>
.sheet { .sheet {
@ -49,12 +49,8 @@
<p style="font-size: 0.6rem; text-align: center; margin: 0; padding: 0;">{{ .Code }}</p> <p style="font-size: 0.6rem; text-align: center; margin: 0; padding: 0;">{{ .Code }}</p>
</div> </div>
</div> </div>
{{ if ne .Runner.FirstName ""}}
<p>{{ .Runner.LastName }}, {{ .Runner.FirstName }} {{ .Runner.MiddleName }}</p> <p>{{ .Runner.LastName }}, {{ .Runner.FirstName }} {{ .Runner.MiddleName }}</p>
<p>{{ if ne .Runner.Group.ParentGroup.Name "" -}}{{ .Runner.Group.ParentGroup.Name }}/{{end -}}{{ .Runner.Group.Name }}</p> <p>{{ .Runner.Group.Name }}</p>
{{ else }}
<p>Runner</p>
{{ end}}
</div> </div>
{{ end }} {{ end }}
</div> </div>

View File

@ -2,7 +2,7 @@
<head> <head>
<meta charset="utf8"> <meta charset="utf8">
<title>Läuferurkunde</title> <title>Sponsoring contract</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
<style> <style>
.sheet { .sheet {
@ -56,8 +56,8 @@
{{ .MiddleName }} {{ .LastName }} {{ .MiddleName }} {{ .LastName }}
</p> </p>
<p style="font-size: 1cm; margin-bottom: 0;">hat beim {{ $.EventName }}</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: 2cm; font-weight: bold; margin-bottom: 0;">{{formatUnit "kilometer" $.Locale .Distance}}km</p>
<p style="font-size: 1cm;">für den guten Zweck zurückgelegt.</p> <p style="font-size: 1cm;">für den guten Zweck zurückgelegt</p>
</main> </main>
<footer class="certificate-footer"> <footer class="certificate-footer">
<img src="data:image/png;base64,{{ loadImage "certificate_footer" }}"> <img src="data:image/png;base64,{{ loadImage "certificate_footer" }}">
@ -87,30 +87,13 @@
<td>Gesamt</td> <td>Gesamt</td>
<td>{{ formatUnit "euro" $.Locale .TotalPerDistance }} {{ $.CurrencySymbol }}</td> <td>{{ formatUnit "euro" $.Locale .TotalPerDistance }} {{ $.CurrencySymbol }}</td>
<td>{{ formatUnit "euro" $.Locale .TotalDonations }} {{ $.CurrencySymbol }}</td> <td>{{ formatUnit "euro" $.Locale .TotalDonations }} {{ $.CurrencySymbol }}</td>
</table> </tfoot>
</main> </table>
<footer class="certificate-footer"> </main>
<table style="border-collapse: collapse; border: none; width: 17cm;"> <footer class="certificate-footer">
<thead> <p>
<tr> {{ $.Footer }}
<th style="border: none; width: 50%; text-align: center;">Link zu deinen Rundenzeiten</th> </p>
<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> </footer>
</article> </article>
{{ end }} {{ end }}

View File

@ -2,7 +2,7 @@
<head> <head>
<meta charset="utf8"> <meta charset="utf8">
<title>Runner certificate</title> <title>Sponsoring contract</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
<style> <style>
.sheet { .sheet {
@ -56,8 +56,8 @@
{{ .MiddleName }} {{ .LastName }} {{ .MiddleName }} {{ .LastName }}
</p> </p>
<p style="font-size: 1cm; margin-bottom: 0;">Ran</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: 2cm; font-weight: bold; margin-bottom: 0;">{{formatUnit "kilometer" $.Locale .Distance}}km</p>
<p style="font-size: 1cm;">for our good cause at the {{ $.EventName }}.</p> <p style="font-size: 1cm;">for our good cause at the {{ $.EventName }}</p>
</main> </main>
<footer class="certificate-footer"> <footer class="certificate-footer">
<img src="data:image/png;base64,{{ loadImage "certificate_footer" }}"> <img src="data:image/png;base64,{{ loadImage "certificate_footer" }}">
@ -91,26 +91,8 @@
</table> </table>
</main> </main>
<footer class="certificate-footer"> <footer class="certificate-footer">
<table style="border-collapse: collapse; border: none; width: 17cm;"> <p>
<thead> {{ $.Footer }}
<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> </p>
</footer> </footer>
</article> </article>

View File

@ -65,8 +65,8 @@
<p style="font-size: x-small; display: block;">Nachname</p> <p style="font-size: x-small; display: block;">Nachname</p>
</div> </div>
<div class="column is-6"> <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> <span style="border-bottom: 1px solid; width: 100%; display: block;">{{ .Group.Name }}</span>
<p style="font-size: x-small; display: block;">Team/Klasse</p> <p style="font-size: x-small; display: block;">Team/ Klasse</p>
</div> </div>
</div> </div>
<p style="margin-top: -0.5rem">mit einem Betrag von _____ {{ $.CurrencySymbol }} pro gelaufenem Kilometer zu <p style="margin-top: -0.5rem">mit einem Betrag von _____ {{ $.CurrencySymbol }} pro gelaufenem Kilometer zu

View File

@ -64,7 +64,7 @@
<p style="font-size: x-small; display: block;">Last Name</p> <p style="font-size: x-small; display: block;">Last Name</p>
</div> </div>
<div class="column is-6"> <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> <span style="border-bottom: 1px solid; width: 100%; display: block;">{{ .Group.Name}}</span>
<p style="font-size: x-small; display: block;">Team/class</p> <p style="font-size: x-small; display: block;">Team/class</p>
</div> </div>
</div> </div>