Compare commits
52 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 |
9
.env
9
.env
@ -1,3 +1,4 @@
|
|||||||
|
LOGLEVEL=debug
|
||||||
PORT=3000
|
PORT=3000
|
||||||
PRODUCION=false
|
PRODUCION=false
|
||||||
APIKEY=lfk
|
APIKEY=lfk
|
||||||
@ -10,9 +11,13 @@ CARD_SUBTITLE=Kaya ist cool
|
|||||||
CARD_BARCODEFORMAT=ean13
|
CARD_BARCODEFORMAT=ean13
|
||||||
# CARD_BARCODEPREFIX=
|
# CARD_BARCODEPREFIX=
|
||||||
|
|
||||||
SPONSOING_RECEIPTMINIMUM=10
|
SPONSORING_RECEIPTMINIMUM=40
|
||||||
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
|
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
|
@ -1,18 +0,0 @@
|
|||||||
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
|
|
@ -1,17 +0,0 @@
|
|||||||
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
|
|
@ -8,8 +8,9 @@ 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 scratch
|
FROM alpine:3.18
|
||||||
|
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" ]
|
23
docs/docs.go
23
docs/docs.go
@ -70,6 +70,15 @@ const docTemplate = `{
|
|||||||
"description": "Barcode height",
|
"description": "Barcode height",
|
||||||
"name": "height",
|
"name": "height",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maximum": 100,
|
||||||
|
"minimum": 0,
|
||||||
|
"type": "integer",
|
||||||
|
"default": 10,
|
||||||
|
"description": "Padding around the barcode (included in image size)",
|
||||||
|
"name": "padding",
|
||||||
|
"in": "query"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {}
|
"responses": {}
|
||||||
@ -312,18 +321,22 @@ const docTemplate = `{
|
|||||||
"models.Group": {
|
"models.Group": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"id",
|
|
||||||
"name"
|
"name"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"parent_group": {
|
"parent_group": {
|
||||||
"$ref": "#/definitions/models.Group"
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -61,6 +61,15 @@
|
|||||||
"description": "Barcode height",
|
"description": "Barcode height",
|
||||||
"name": "height",
|
"name": "height",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maximum": 100,
|
||||||
|
"minimum": 0,
|
||||||
|
"type": "integer",
|
||||||
|
"default": 10,
|
||||||
|
"description": "Padding around the barcode (included in image size)",
|
||||||
|
"name": "padding",
|
||||||
|
"in": "query"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {}
|
"responses": {}
|
||||||
@ -303,18 +312,22 @@
|
|||||||
"models.Group": {
|
"models.Group": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"id",
|
|
||||||
"name"
|
"name"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"parent_group": {
|
"parent_group": {
|
||||||
"$ref": "#/definitions/models.Group"
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -94,14 +94,16 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
models.Group:
|
models.Group:
|
||||||
properties:
|
properties:
|
||||||
id:
|
|
||||||
type: integer
|
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
parent_group:
|
parent_group:
|
||||||
$ref: '#/definitions/models.Group'
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
type: object
|
||||||
required:
|
required:
|
||||||
- id
|
|
||||||
- name
|
- name
|
||||||
type: object
|
type: object
|
||||||
models.Runner:
|
models.Runner:
|
||||||
@ -195,6 +197,13 @@ paths:
|
|||||||
minimum: 1
|
minimum: 1
|
||||||
name: height
|
name: height
|
||||||
type: integer
|
type: integer
|
||||||
|
- default: 10
|
||||||
|
description: Padding around the barcode (included in image size)
|
||||||
|
in: query
|
||||||
|
maximum: 100
|
||||||
|
minimum: 0
|
||||||
|
name: padding
|
||||||
|
type: integer
|
||||||
produces:
|
produces:
|
||||||
- image/png
|
- image/png
|
||||||
responses: {}
|
responses: {}
|
||||||
|
19
go.mod
19
go.mod
@ -23,8 +23,11 @@ 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.17.11 // indirect
|
github.com/klauspost/compress v1.18.0 // 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
|
||||||
@ -33,7 +36,9 @@ 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
|
||||||
@ -46,15 +51,19 @@ 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.57.0 // indirect
|
github.com/valyala/fasthttp v1.61.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.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/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||||
golang.org/x/sys v0.27.0 // indirect
|
golang.org/x/image v0.26.0 // indirect
|
||||||
golang.org/x/text v0.19.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/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
28
go.sum
@ -29,10 +29,18 @@ 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=
|
||||||
@ -54,8 +62,12 @@ 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=
|
||||||
@ -99,6 +111,8 @@ 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=
|
||||||
@ -107,8 +121,16 @@ 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=
|
||||||
@ -117,8 +139,12 @@ 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=
|
||||||
@ -129,6 +155,8 @@ 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=
|
||||||
|
@ -11,14 +11,17 @@ import (
|
|||||||
// @Summary Generate barcodes
|
// @Summary Generate barcodes
|
||||||
// @Description Generate barcodes based on the provided data
|
// @Description Generate barcodes based on the provided data
|
||||||
// @Tags barcodes
|
// @Tags barcodes
|
||||||
// @Param type path string true "Barcode type" Enums(ean13, code128)
|
// @Param type path string true "Barcode type" Enums(ean13, code128)
|
||||||
// @Param content path string true "Barcode content" MinLength(1)
|
// @Param content path string true "Barcode content" MinLength(1)
|
||||||
// @Param width query int false "Barcode width" Minimum(1) Maximum(10000) default(1000)
|
// @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 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
|
// @Produce image/png
|
||||||
// @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")
|
||||||
@ -26,10 +29,12 @@ func (h *DefaultHandler) GenerateBarcode(c *fiber.Ctx) error {
|
|||||||
// Get the width and height from the query parameters
|
// Get the width and height from the query parameters
|
||||||
widthStr := c.Query("width", "1000")
|
widthStr := c.Query("width", "1000")
|
||||||
heightStr := c.Query("height", "1000")
|
heightStr := c.Query("height", "1000")
|
||||||
|
paddingStr := c.Query("padding", "10")
|
||||||
|
|
||||||
// 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",
|
||||||
})
|
})
|
||||||
@ -37,18 +42,31 @@ 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",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate the barcode
|
padding, err := strconv.Atoi(paddingStr)
|
||||||
barcode, err := h.BarcodeService.GenerateBarcode(barcodeType, barcodeContent, width, height)
|
|
||||||
if err != nil {
|
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{
|
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())
|
||||||
|
118
handlers/card.go
118
handlers/card.go
@ -1,11 +1,14 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"fmt"
|
||||||
|
"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
|
||||||
@ -19,57 +22,148 @@ 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 {
|
||||||
log.Println(err)
|
logger.Errorw("Template not found", "error", 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.CardTemplateOptions{
|
segmentLength := calculateOptimalSegmentSize(len(cardRequest.Cards))
|
||||||
CardSegments: splitCardSegments(cardRequest.Cards),
|
pdfs := []string{}
|
||||||
EventName: h.Config.EventName,
|
for i := 0; i < len(cardRequest.Cards); i += segmentLength {
|
||||||
CardSubtitle: h.Config.CardSubtitle,
|
|
||||||
BarcodeFormat: h.Config.CardBarcodeFormat,
|
segment := cardRequest.Cards[i:]
|
||||||
BarcodePrefix: h.Config.CardBarcodePrefix,
|
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())
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := h.Templater.Execute(template, genConfig)
|
outputFile := "./output.pdf"
|
||||||
|
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")
|
|
||||||
|
|
||||||
pdf, err := h.Converter.ToPdf(result, "a4", false)
|
// 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 {
|
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")
|
||||||
return c.Send(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 {
|
func invertCardArrayItemPairs(cards []models.Card) []models.Card {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"git.odit.services/lfk/document-server/models"
|
"git.odit.services/lfk/document-server/models"
|
||||||
@ -19,27 +18,35 @@ 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 {
|
||||||
log.Println(err)
|
logger.Errorw("Template not found", "error", 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(),
|
||||||
})
|
})
|
||||||
@ -51,24 +58,35 @@ 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")
|
||||||
return c.Send(pdf)
|
return c.Send(pdf)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,6 +96,13 @@ 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
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"git.odit.services/lfk/document-server/models"
|
"git.odit.services/lfk/document-server/models"
|
||||||
@ -19,36 +18,42 @@ 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",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
contract.Runners = repeatRunnerArrayItems(contract.Runners, 2)
|
logger = logger.With("locale", contract.Locale)
|
||||||
|
|
||||||
templateString, err := h.StaticService.GetTemplate(contract.Locale, "contract")
|
templateString, err := h.StaticService.GetTemplate(contract.Locale, "contract")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
logger.Errorw("Template not found", "error", 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: contract.Runners,
|
Runners: repeatRunnerArrayItems(contract.Runners, 2),
|
||||||
CurrencySymbol: h.Config.CurrencySymbol,
|
CurrencySymbol: h.Config.CurrencySymbol,
|
||||||
Disclaimer: h.Config.SponosringDisclaimer,
|
Disclaimer: h.Config.SponosringDisclaimer,
|
||||||
ReceiptMinimumAmount: h.Config.SponsoringReceiptMinimum,
|
ReceiptMinimumAmount: h.Config.SponsoringReceiptMinimum,
|
||||||
@ -57,21 +62,26 @@ 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")
|
||||||
return c.Send(pdf)
|
return c.Send(pdf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ 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 {
|
||||||
@ -19,4 +20,5 @@ type DefaultHandler struct {
|
|||||||
Templater services.Templater
|
Templater services.Templater
|
||||||
Converter services.Converter
|
Converter services.Converter
|
||||||
StaticService services.StaticService
|
StaticService services.StaticService
|
||||||
|
Logger *zap.SugaredLogger
|
||||||
}
|
}
|
||||||
|
86
main.go
86
main.go
@ -3,21 +3,27 @@ package main
|
|||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"log"
|
"os"
|
||||||
|
"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"
|
||||||
"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"
|
||||||
|
"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/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) {
|
||||||
@ -32,11 +38,26 @@ func validateAPIKey(c *fiber.Ctx, key string) (bool, error) {
|
|||||||
|
|
||||||
func loadEnv() error {
|
func loadEnv() error {
|
||||||
|
|
||||||
|
viper.SetDefault("LOGLEVEL", "INFO")
|
||||||
|
viper.SetDefault("PRODUCION", false)
|
||||||
viper.SetDefault("PORT", "3000")
|
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_BARCODEFORMAT", "ean13")
|
||||||
viper.SetDefault("CARD_BARCODEPREFIX", "")
|
viper.SetDefault("CARD_BARCODEPREFIX", "")
|
||||||
|
viper.SetDefault("SPONSORING_RECEIPTMINIMUM", 0)
|
||||||
|
viper.SetDefault("SPONSORING_DISCLAIMER", "Disclaimer")
|
||||||
viper.SetDefault("SPONSORING_BARCODEFORMAT", "code128")
|
viper.SetDefault("SPONSORING_BARCODEFORMAT", "code128")
|
||||||
viper.SetDefault("SPONSORING_BARCODEPREFIX", "")
|
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
|
// Load .env file
|
||||||
viper.SetConfigFile(".env")
|
viper.SetConfigFile(".env")
|
||||||
@ -45,7 +66,7 @@ func loadEnv() error {
|
|||||||
viper.AutomaticEnv()
|
viper.AutomaticEnv()
|
||||||
err := viper.ReadInConfig()
|
err := viper.ReadInConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("No .env file found")
|
logger.Warn("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
|
||||||
@ -54,29 +75,65 @@ func loadEnv() error {
|
|||||||
return err
|
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
|
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() {
|
||||||
|
|
||||||
err := loadEnv()
|
// Init the logger
|
||||||
|
err := initLogger()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = loadEnv()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var redisClient *redis.Client
|
var redisClient *redis.Client
|
||||||
if config.RedisAddr != "" {
|
if config.RedisAddr != "" {
|
||||||
log.Println("Using redis at", config.RedisAddr)
|
logger.Infow("Using redis", "redisAddr", config.RedisAddr)
|
||||||
redisClient = redis.NewClient(&redis.Options{
|
redisClient = redis.NewClient(&redis.Options{
|
||||||
Addr: config.RedisAddr,
|
Addr: config.RedisAddr,
|
||||||
})
|
})
|
||||||
@ -84,9 +141,11 @@ 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,
|
||||||
@ -95,17 +154,24 @@ 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{
|
||||||
Prefork: config.Prod,
|
Prefork: config.Prod,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.Use(cors.New())
|
||||||
|
app.Use(requestid.New())
|
||||||
|
|
||||||
// Swagger documentation route
|
// Swagger documentation route
|
||||||
app.Get("/swagger/*", swagger.HandlerDefault)
|
app.Get("/swagger/*", swagger.HandlerDefault)
|
||||||
|
|
||||||
@ -122,9 +188,11 @@ 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 = "/"
|
||||||
|
|
||||||
log.Fatal(app.Listen("0.0.0.0:" + config.Port))
|
logger.Infow("Starting server", "port", config.Port)
|
||||||
|
logger.Error(app.Listen("0.0.0.0:" + config.Port))
|
||||||
}
|
}
|
||||||
|
@ -11,10 +11,12 @@ 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 {
|
||||||
@ -38,4 +40,12 @@ 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"`
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,24 @@
|
|||||||
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 int `mapstructure:"SPONSOING_RECEIPTMINIMUM"`
|
SponsoringReceiptMinimum string `mapstructure:"SPONSORING_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"`
|
||||||
}
|
}
|
||||||
|
@ -14,16 +14,17 @@ type Runner struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Group struct {
|
type Group struct {
|
||||||
ID int `json:"id" validate:"required"`
|
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
ParentGroup *Group `json:"parent_group" validate:"optional"`
|
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 int `json:"receipt_minimum_amount"`
|
ReceiptMinimumAmount string `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"`
|
||||||
|
@ -4,8 +4,10 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/draw"
|
||||||
"image/png"
|
"image/png"
|
||||||
"log"
|
|
||||||
"slices"
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -14,28 +16,36 @@ 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 {
|
||||||
GenerateBarcode(format string, content string, width int, height int) (bytes.Buffer, error)
|
GenerateBarcode(format string, content string, width int, height int, padding int) (bytes.Buffer, error)
|
||||||
IsTypeSupported(format string) bool
|
IsTypeSupported(format string) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
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) (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 {
|
||||||
cachedBarcode, err := b.RedisClient.Get(ctx, fmt.Sprintf("barcode:%s:%s:%d:%d", format, content, width, height)).Result()
|
logger.Debugw("Checking cache for barcode", "key", cacheKey)
|
||||||
|
cachedBarcode, err := b.RedisClient.Get(ctx, cacheKey).Result()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
log.Printf("Cache hit for barcode:%s:%s:%d:%d", format, content, width, height)
|
logger.Infow("Barcode found in cache", "key", cacheKey)
|
||||||
buf := bytes.Buffer{}
|
buf := bytes.Buffer{}
|
||||||
buf.Write([]byte(cachedBarcode))
|
buf.Write([]byte(cachedBarcode))
|
||||||
return buf, nil
|
return buf, nil
|
||||||
@ -59,30 +69,56 @@ func (b *DefaultBarcodeService) GenerateBarcode(format string, content string, w
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
case "qr":
|
case "qr":
|
||||||
generatedCode, err = qr.Encode(content, qr.M, qr.AlphaNumeric)
|
// 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 {
|
if err != nil {
|
||||||
return bytes.Buffer{}, err
|
return bytes.Buffer{}, err
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
scaledCode, err := barcode.Scale(generatedCode, width, height)
|
// 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 {
|
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.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, scaledCode)
|
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, fmt.Sprintf("barcode:%s:%s:%d:%d", format, content, width, height), buf.String(), 10*time.Minute).Err()
|
err = b.RedisClient.Set(ctx, cacheKey, 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
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/oxplot/papersizes"
|
"github.com/oxplot/papersizes"
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Converter interface {
|
type Converter interface {
|
||||||
@ -16,92 +17,115 @@ 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
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,10 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Templater interface {
|
type Templater interface {
|
||||||
@ -17,6 +20,7 @@ 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) {
|
||||||
@ -32,6 +36,28 @@ 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
|
||||||
|
|
||||||
@ -42,16 +68,19 @@ func (t *DefaultTemplater) GenerateBarcode(code string, format string, prefix st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, err := t.BarcodeService.GenerateBarcode(format, code, 1000, 500)
|
buf, err := t.BarcodeService.GenerateBarcode(format, code, 1000, 500, 0)
|
||||||
|
|
||||||
return base64.StdEncoding.EncodeToString(buf.Bytes()), err
|
return base64.StdEncoding.EncodeToString(buf.Bytes()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,9 +90,24 @@ 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":
|
||||||
formatted = fmt.Sprintf("%.3f", float64(amount)/1000)
|
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":
|
case "euro":
|
||||||
formatted = fmt.Sprintf("%.2f", float64(amount)/100)
|
formatted = fmt.Sprintf("%.2f", float64(amount)/100)
|
||||||
default:
|
default:
|
||||||
@ -82,6 +126,7 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,8 +3,9 @@ package services
|
|||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StaticService interface {
|
type StaticService interface {
|
||||||
@ -14,47 +15,55 @@ 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] != "" {
|
||||||
log.Printf("returning cached template %s with locale %s", templateName, locale)
|
logger.Debugw("Template found in cache", "key", locale+templateName)
|
||||||
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 {
|
||||||
log.Printf("error reading template %s with locale %s: %v", templateName, locale, err)
|
logger.Errorw("Failed to read template", "error", 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 {
|
||||||
log.Printf("error reading files from folder %s: %v", folderName, err)
|
logger.Errorw("Failed to list files", "error", 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() {
|
||||||
continue
|
logger.Debugw("Skipping directory", "file", file.Name())
|
||||||
}
|
}
|
||||||
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 {
|
||||||
log.Printf("error reading image %s: %v", imageName, err)
|
logger.Errorw("Failed to read image", "error", err)
|
||||||
return ImageErrorBase64
|
return ImageErrorBase64
|
||||||
}
|
}
|
||||||
return string(content)
|
return string(content)
|
||||||
|
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
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf8">
|
<meta charset="utf8">
|
||||||
<title>Sponsoring contract</title>
|
<title>Läuferkarten</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,8 +50,12 @@
|
|||||||
<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>{{ .Runner.Group.Name }}</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}}
|
{{ end}}
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf8">
|
<meta charset="utf8">
|
||||||
<title>Sponsoring contract</title>
|
<title>Runner cards</title>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
|
||||||
<style>
|
<style>
|
||||||
.sheet {
|
.sheet {
|
||||||
@ -49,8 +49,12 @@
|
|||||||
<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>{{ .Runner.Group.Name }}</p>
|
<p>{{ if ne .Runner.Group.ParentGroup.Name "" -}}{{ .Runner.Group.ParentGroup.Name }}/{{end -}}{{ .Runner.Group.Name }}</p>
|
||||||
|
{{ else }}
|
||||||
|
<p>Runner</p>
|
||||||
|
{{ end}}
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf8">
|
<meta charset="utf8">
|
||||||
<title>Sponsoring contract</title>
|
<title>Läuferurkunde</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}}km</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>
|
<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,13 +87,30 @@
|
|||||||
<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>
|
||||||
</tfoot>
|
</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>
|
||||||
</p>
|
<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>
|
</footer>
|
||||||
</article>
|
</article>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf8">
|
<meta charset="utf8">
|
||||||
<title>Sponsoring contract</title>
|
<title>Runner certificate</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}}km</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>
|
<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,8 +91,26 @@
|
|||||||
</table>
|
</table>
|
||||||
</main>
|
</main>
|
||||||
<footer class="certificate-footer">
|
<footer class="certificate-footer">
|
||||||
<p>
|
<table style="border-collapse: collapse; border: none; width: 17cm;">
|
||||||
{{ $.Footer }}
|
<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>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
</article>
|
</article>
|
||||||
|
@ -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;">{{ .Group.Name }}</span>
|
<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>
|
<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
|
||||||
|
@ -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;">{{ .Group.Name}}</span>
|
<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>
|
<p style="font-size: x-small; display: block;">Team/class</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user