Compare commits
91
Commits
12f61e373f
..
1.5.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af587b0ac1
|
||
|
|
50e3eff294
|
||
|
|
bc17f7256b
|
||
|
|
d2f3eea8a5
|
||
|
|
f902c61490
|
||
|
|
11e8cc5b1d
|
||
|
|
84155b7404
|
||
|
|
45b37197ec
|
||
|
|
f65848924c
|
||
|
|
98d584867e
|
||
|
|
376e8de1a4
|
||
|
|
2911391fb9
|
||
|
|
6d2e0241c9
|
||
|
|
afc5b1f0c6
|
||
|
|
4a76ee469b
|
||
|
|
b58bf700df
|
||
|
|
efd3a35802
|
||
|
|
0f7e44a42a
|
||
|
|
f90e5d75fa
|
||
|
|
31d4ec5f27
|
||
|
|
d61d4d6e7e
|
||
|
|
606ce6b940
|
||
|
|
750fa70332
|
||
|
|
7d503edbc9
|
||
|
|
5c9235df8d
|
||
|
|
11ea0858bb
|
||
|
|
4d57cf827d
|
||
|
|
df9f7fdc13
|
||
|
|
cdd2b5e250
|
||
|
|
94b766f106
|
||
|
|
a2e94f715b
|
||
|
|
f64daaf817
|
||
|
|
b4bb732303
|
||
|
|
3dee3e72af
|
||
|
|
f5914e5c38
|
||
|
|
5a5a7179e9
|
||
|
|
6c57d63891
|
||
|
|
b502e2fbd5
|
||
|
|
c9475d0093
|
||
|
|
1cc19e0085
|
||
|
|
b792806481
|
||
|
|
ea6a4a7080
|
||
|
|
de6fe4991c
|
||
|
|
1d068b2655
|
||
|
|
ef25adf5ed
|
||
|
|
c09c00ec68
|
||
|
|
1f4981b0a9
|
||
|
|
c9f28612be
|
||
|
|
c2d192d8a3
|
||
|
|
31734596f6
|
||
|
|
850fa0a760
|
||
|
|
1c0a9860fa
|
||
|
|
7c32f1aad2
|
||
|
|
b9e550d6f5
|
||
|
|
2a4f126377
|
||
|
|
5c932158e9
|
||
|
|
1296c9e399
|
||
|
|
0f2452eca0
|
||
|
|
b6cc98a165
|
||
|
|
c5da33f10f
|
||
|
|
69f4de6739
|
||
|
|
649ac2a3c2
|
||
|
|
5587fdaaa8
|
||
|
|
8f676f08a9
|
||
|
|
28de60d375 | ||
|
|
d19029b5ad
|
||
|
|
1dfd96869d
|
||
|
|
a1ba28cacb
|
||
|
|
d2bace87af
|
||
|
|
9d507b9572
|
||
|
|
e89c17806f
|
||
|
|
924f76a100
|
||
|
|
99ec0933ea
|
||
|
|
cceca7f5e1
|
||
|
|
54d294a8b4
|
||
|
|
41291b9200
|
||
|
|
4faf76a073
|
||
|
|
715eb8e1cb
|
||
|
|
2686bee1d1
|
||
|
|
57a3777891
|
||
|
|
f6dc33edb4
|
||
|
|
e2cd445aeb
|
||
|
|
f5debf58fc
|
||
|
|
11a9b51197
|
||
|
|
c5dc4f7e79
|
||
|
|
f9f30e96c7
|
||
|
|
eff3354867
|
||
|
|
2bfff006ed
|
||
|
|
53eab5db94
|
||
|
|
af73b35b18
|
||
|
|
692f378eab
|
No files matched your search
@@ -0,0 +1,46 @@
|
|||||||
|
root = "."
|
||||||
|
testdata_dir = "testdata"
|
||||||
|
tmp_dir = "tmp"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
args_bin = []
|
||||||
|
bin = "tmp\\main"
|
||||||
|
cmd = "go build -o ./tmp/main ."
|
||||||
|
delay = 1000
|
||||||
|
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
|
||||||
|
exclude_file = []
|
||||||
|
exclude_regex = ["_test.go"]
|
||||||
|
exclude_unchanged = false
|
||||||
|
follow_symlink = false
|
||||||
|
full_bin = ""
|
||||||
|
include_dir = []
|
||||||
|
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||||
|
include_file = []
|
||||||
|
kill_delay = "0s"
|
||||||
|
log = "build-errors.log"
|
||||||
|
poll = false
|
||||||
|
poll_interval = 0
|
||||||
|
post_cmd = []
|
||||||
|
pre_cmd = []
|
||||||
|
rerun = false
|
||||||
|
rerun_delay = 500
|
||||||
|
send_interrupt = false
|
||||||
|
stop_on_error = false
|
||||||
|
|
||||||
|
[color]
|
||||||
|
app = ""
|
||||||
|
build = "yellow"
|
||||||
|
main = "magenta"
|
||||||
|
runner = "green"
|
||||||
|
watcher = "cyan"
|
||||||
|
|
||||||
|
[log]
|
||||||
|
main_only = false
|
||||||
|
time = false
|
||||||
|
|
||||||
|
[misc]
|
||||||
|
clean_on_exit = false
|
||||||
|
|
||||||
|
[screen]
|
||||||
|
clear_on_rebuild = false
|
||||||
|
keep_scroll = true
|
||||||
File renamed without changes.
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
tmp
|
tmp
|
||||||
docker-compose.yaml
|
docker-compose.yaml
|
||||||
.air.toml
|
.air*.toml
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
LOGLEVEL=debug
|
||||||
|
PORT=3000
|
||||||
|
PRODUCION=false
|
||||||
|
APIKEY=lfk
|
||||||
|
EVENTNAME=Lauf für Kaya! 2025
|
||||||
|
CURRENCYSYMBOL=€
|
||||||
|
GOTENBERG_BASEURL=http://localhost:3001
|
||||||
|
REDIS_ADDR=localhost:6379
|
||||||
|
|
||||||
|
CARD_SUBTITLE=Kaya ist cool
|
||||||
|
CARD_BARCODEFORMAT=ean13
|
||||||
|
# CARD_BARCODEPREFIX=
|
||||||
|
|
||||||
|
SPONSORING_RECEIPTMINIMUM=40
|
||||||
|
SPONSORING_DISCLAIMER=Kaya ist cool, aber pass auf, dass du nicht zu viel Geld sammelst!
|
||||||
|
SPONSORING_BARCODEFORMAT=code128
|
||||||
|
# SPONSORING_BARCODEPREFIX=
|
||||||
|
|
||||||
|
CERTIFICATE_FOOTER=Kaya ist cool, danke für deine Unterstützung!
|
||||||
|
|
||||||
|
SEPA_BIC=FNOMDEB2
|
||||||
|
SEPA_NAME=ODIT.Services
|
||||||
|
SEPA_IBAN=DE25100180000690238989
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -10,5 +10,6 @@ RUN CGO_ENABLED=0 GOOS=linux go build -o server
|
|||||||
|
|
||||||
FROM scratch
|
FROM scratch
|
||||||
COPY --from=builder /app/server /server
|
COPY --from=builder /app/server /server
|
||||||
|
COPY static /static
|
||||||
ADD https://curl.haxx.se/ca/cacert.pem /etc/ssl/certs/ca-certificates.crt
|
ADD https://curl.haxx.se/ca/cacert.pem /etc/ssl/certs/ca-certificates.crt
|
||||||
ENTRYPOINT [ "/server" ]
|
ENTRYPOINT [ "/server" ]
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- 📝 HTML pdf templates
|
- 📝 HTML templates for pdf generation
|
||||||
- 📚 OpenAPI/Swagger documentation
|
- 📚 OpenAPI/Swagger documentation
|
||||||
- ⚡ High-performance with go and gotenberg
|
- ⚡ High-performance with go and gotenberg
|
||||||
|
|
||||||
@@ -12,9 +12,6 @@
|
|||||||
# Install dependencies
|
# Install dependencies
|
||||||
go mod download
|
go mod download
|
||||||
|
|
||||||
# Generate the swagger docs
|
|
||||||
swag init
|
|
||||||
|
|
||||||
# Run the server
|
# Run the server
|
||||||
air
|
air
|
||||||
```
|
```
|
||||||
@@ -29,3 +26,71 @@ The project uses:
|
|||||||
|
|
||||||
- 🏃♂️ go as the language and build tool
|
- 🏃♂️ go as the language and build tool
|
||||||
- 🌐 gofiber for the web framework
|
- 🌐 gofiber for the web framework
|
||||||
|
- 📦 air for live reload
|
||||||
|
- 📝 swaggo for API documentation
|
||||||
|
- 📄 gotenberg for HTML to PDF conversion
|
||||||
|
|
||||||
|
### 📦 Use docker compose for external dependencies
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker compose -f docker-compose.dev.yaml up
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🏃 Run via air
|
||||||
|
|
||||||
|
> Install air via `go install github.com/air-verse/air@latest`
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# With the default air config
|
||||||
|
air
|
||||||
|
|
||||||
|
# With the config for linux/macOS
|
||||||
|
air -c .air.linux.toml
|
||||||
|
|
||||||
|
# With the config for windows
|
||||||
|
air -c .air.windows.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
### ✒️ Update the swagger docs
|
||||||
|
|
||||||
|
> Install swag via `go install github.com/swaggo/swag/cmd/swag@latest`
|
||||||
|
|
||||||
|
```shell
|
||||||
|
swag init
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🐋 Build container
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# single arch
|
||||||
|
docker build -t registry.odit.services/lfk/document-server:latest .
|
||||||
|
|
||||||
|
# multiarch
|
||||||
|
docker buildx build --platform=linux/amd64,linux/arm64 -t registry.odit.services/lfk/document-server:latest --push .
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⏱️ Benchmarks
|
||||||
|
|
||||||
|
### Barcode Generation
|
||||||
|
|
||||||
|
- Requests: 10000
|
||||||
|
- Concurrency: Unlimited
|
||||||
|
- Data: `123456789123`
|
||||||
|
- Width: 1000
|
||||||
|
- Height: 500
|
||||||
|
|
||||||
|
#### No cache (cold start)
|
||||||
|
|
||||||
|
| Format | Data | Requests/sec | Slowest | Fastest | Average |
|
||||||
|
| ------- | -------------- | ------------ | ------- | ------- | ------- |
|
||||||
|
| Code128 | `123456789123` | 763.3996 | 0.7995 | 0.0172 | 0.0654 |
|
||||||
|
| EAN13 | `123456789123` | 767.1170 | 0.7607 | 0.0171 | 0.0651 |
|
||||||
|
| QR | `123456789123` | 683.8472 | 0.6528 | 0.0178 | 0.0730 |
|
||||||
|
|
||||||
|
#### Redis cache (warm start)
|
||||||
|
|
||||||
|
| Format | Data | Requests/sec | Slowest | Fastest | Average |
|
||||||
|
| ------- | -------------- | ------------ | ------- | ------- | ------- |
|
||||||
|
| Code128 | `123456789123` | 15611.5521 | 0.0965 | 0.0006 | 0.0032 |
|
||||||
|
| EAN13 | `123456789123` | 14985.4401 | 0.0925 | 0.0006 | 0.0033 |
|
||||||
|
| QR | `123456789123` | 14306.2540 | 0.1143 | 0.0005 | 0.0035 |
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
services:
|
||||||
|
gotenberg:
|
||||||
|
image: gotenberg/gotenberg:8
|
||||||
|
ports:
|
||||||
|
- "3001:3000"
|
||||||
|
redis:
|
||||||
|
image: docker.dragonflydb.io/dragonflydb/dragonfly
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
@@ -1,5 +1,15 @@
|
|||||||
services:
|
services:
|
||||||
|
document-server:
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
volumes:
|
||||||
|
- ./docker.env:/.env
|
||||||
gotenberg:
|
gotenberg:
|
||||||
image: gotenberg/gotenberg:8
|
image: gotenberg/gotenberg:8
|
||||||
ports:
|
ports:
|
||||||
- "3001:3000"
|
- "3001:3000"
|
||||||
|
redis:
|
||||||
|
image: docker.dragonflydb.io/dragonflydb/dragonfly
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
PORT=3000
|
||||||
|
PRODUCION=false
|
||||||
|
APIKEY=lfk
|
||||||
|
EVENTNAME=Lauf für Kaya! 2025
|
||||||
|
CURRENCYSYMBOL=€
|
||||||
|
GOTENBERG_BASEURL=http://gotenberg:3000
|
||||||
|
REDIS_ADDR=redis:6379
|
||||||
|
|
||||||
|
CARD_SUBTITLE=Kaya ist cool
|
||||||
|
CARD_BARCODEFORMAT=ean13
|
||||||
|
# CARD_BARCODEPREFIX=
|
||||||
|
|
||||||
|
SPONSOING_RECEIPTMINIMUM=10
|
||||||
|
SPONSORING_DISCLAIMER=Kaya ist cool, aber pass auf, dass du nicht zu viel Geld sammelst!
|
||||||
|
SPONSORING_BARCODEFORMAT=code128
|
||||||
|
# SPONSORING_BARCODEPREFIX=
|
||||||
|
|
||||||
|
CERTIFICATE_FOOTER=Kaya ist cool, danke für deine Unterstützung!
|
||||||
+265
-8
@@ -9,14 +9,88 @@ const docTemplate = `{
|
|||||||
"info": {
|
"info": {
|
||||||
"description": "{{escape .Description}}",
|
"description": "{{escape .Description}}",
|
||||||
"title": "{{.Title}}",
|
"title": "{{.Title}}",
|
||||||
"contact": {},
|
"termsOfService": "https://lauf-fuer-kaya.de/datenschutz",
|
||||||
|
"contact": {
|
||||||
|
"name": "ODIT.Services UG (haftungsbeschränkt)",
|
||||||
|
"url": "https://odit.services",
|
||||||
|
"email": "info@odit.services"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"name": "CC BY-NC-SA 4.0"
|
||||||
|
},
|
||||||
"version": "{{.Version}}"
|
"version": "{{.Version}}"
|
||||||
},
|
},
|
||||||
"host": "{{.Host}}",
|
"host": "{{.Host}}",
|
||||||
"basePath": "{{.BasePath}}",
|
"basePath": "{{.BasePath}}",
|
||||||
"paths": {
|
"paths": {
|
||||||
"/cards": {
|
"/v1/barcodes/{type}/{content}": {
|
||||||
|
"get": {
|
||||||
|
"description": "Generate barcodes based on the provided data",
|
||||||
|
"produces": [
|
||||||
|
"image/png"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"barcodes"
|
||||||
|
],
|
||||||
|
"summary": "Generate barcodes",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"ean13",
|
||||||
|
"code128"
|
||||||
|
],
|
||||||
|
"type": "string",
|
||||||
|
"description": "Barcode type",
|
||||||
|
"name": "type",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minLength": 1,
|
||||||
|
"type": "string",
|
||||||
|
"description": "Barcode content",
|
||||||
|
"name": "content",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maximum": 10000,
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "integer",
|
||||||
|
"default": 1000,
|
||||||
|
"description": "Barcode width",
|
||||||
|
"name": "width",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maximum": 10000,
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "integer",
|
||||||
|
"default": 1000,
|
||||||
|
"description": "Barcode height",
|
||||||
|
"name": "height",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maximum": 100,
|
||||||
|
"minimum": 0,
|
||||||
|
"type": "integer",
|
||||||
|
"default": 10,
|
||||||
|
"description": "Padding around the barcode (included in image size)",
|
||||||
|
"name": "padding",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v1/pdfs/cards": {
|
||||||
"post": {
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"description": "Generate cards based on the provided data",
|
"description": "Generate cards based on the provided data",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
@@ -25,7 +99,7 @@ const docTemplate = `{
|
|||||||
"application/pdf"
|
"application/pdf"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"cards"
|
"pdfs"
|
||||||
],
|
],
|
||||||
"summary": "Generate runner cards",
|
"summary": "Generate runner cards",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@@ -42,8 +116,45 @@ const docTemplate = `{
|
|||||||
"responses": {}
|
"responses": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/contracts": {
|
"/v1/pdfs/certificates": {
|
||||||
"post": {
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Generate certificates based on the provided data",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/pdf"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"pdfs"
|
||||||
|
],
|
||||||
|
"summary": "Generate runner certificates",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Certificate data",
|
||||||
|
"name": "data",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/models.CertificateRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v1/pdfs/contracts": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"description": "Generate a contract based on the provided data",
|
"description": "Generate a contract based on the provided data",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
@@ -52,7 +163,7 @@ const docTemplate = `{
|
|||||||
"application/pdf"
|
"application/pdf"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"contracts"
|
"pdfs"
|
||||||
],
|
],
|
||||||
"summary": "Generate a contract",
|
"summary": "Generate a contract",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@@ -73,12 +184,18 @@ const docTemplate = `{
|
|||||||
"definitions": {
|
"definitions": {
|
||||||
"models.Card": {
|
"models.Card": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"code",
|
||||||
|
"id",
|
||||||
|
"runner"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"code": {
|
"code": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"type": "boolean"
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
},
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
@@ -90,6 +207,10 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"models.CardRequest": {
|
"models.CardRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"cards",
|
||||||
|
"locale"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"cards": {
|
"cards": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
@@ -106,8 +227,34 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"models.CertificateRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"locale",
|
||||||
|
"runners"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"locale": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"en",
|
||||||
|
"de"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"runners": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/models.RunnerWithDonations"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"models.ContractRequest": {
|
"models.ContractRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"locale",
|
||||||
|
"runners"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"locale": {
|
"locale": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -124,22 +271,83 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"models.Group": {
|
"models.DistanceDonation": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"amount_per_distance",
|
||||||
|
"donor",
|
||||||
|
"id"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"amount": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"amount_per_distance": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"donor": {
|
||||||
|
"$ref": "#/definitions/models.Donor"
|
||||||
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
|
"paid_amount": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"models.Donor": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"first_name",
|
||||||
|
"id",
|
||||||
|
"last_name"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"first_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"last_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"middle_name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"models.Group": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"parent_group": {
|
"parent_group": {
|
||||||
"$ref": "#/definitions/models.Group"
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"models.Runner": {
|
"models.Runner": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"first_name",
|
||||||
|
"group",
|
||||||
|
"id",
|
||||||
|
"last_name"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"first_name": {
|
"first_name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -157,6 +365,55 @@ const docTemplate = `{
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"models.RunnerWithDonations": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"distance",
|
||||||
|
"first_name",
|
||||||
|
"group",
|
||||||
|
"id",
|
||||||
|
"last_name"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"distance": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"distance_donations": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/models.DistanceDonation"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"first_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"group": {
|
||||||
|
"$ref": "#/definitions/models.Group"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"last_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"middle_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"total_donations": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total_per_distance": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securityDefinitions": {
|
||||||
|
"ApiKeyAuth": {
|
||||||
|
"type": "apiKey",
|
||||||
|
"name": "key",
|
||||||
|
"in": "query"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|||||||
+265
-8
@@ -3,11 +3,85 @@
|
|||||||
"info": {
|
"info": {
|
||||||
"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.",
|
||||||
"title": "LfK Document Server API",
|
"title": "LfK Document Server API",
|
||||||
"contact": {}
|
"termsOfService": "https://lauf-fuer-kaya.de/datenschutz",
|
||||||
|
"contact": {
|
||||||
|
"name": "ODIT.Services UG (haftungsbeschränkt)",
|
||||||
|
"url": "https://odit.services",
|
||||||
|
"email": "info@odit.services"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"name": "CC BY-NC-SA 4.0"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
"/cards": {
|
"/v1/barcodes/{type}/{content}": {
|
||||||
|
"get": {
|
||||||
|
"description": "Generate barcodes based on the provided data",
|
||||||
|
"produces": [
|
||||||
|
"image/png"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"barcodes"
|
||||||
|
],
|
||||||
|
"summary": "Generate barcodes",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"enum": [
|
||||||
|
"ean13",
|
||||||
|
"code128"
|
||||||
|
],
|
||||||
|
"type": "string",
|
||||||
|
"description": "Barcode type",
|
||||||
|
"name": "type",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minLength": 1,
|
||||||
|
"type": "string",
|
||||||
|
"description": "Barcode content",
|
||||||
|
"name": "content",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maximum": 10000,
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "integer",
|
||||||
|
"default": 1000,
|
||||||
|
"description": "Barcode width",
|
||||||
|
"name": "width",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maximum": 10000,
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "integer",
|
||||||
|
"default": 1000,
|
||||||
|
"description": "Barcode height",
|
||||||
|
"name": "height",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maximum": 100,
|
||||||
|
"minimum": 0,
|
||||||
|
"type": "integer",
|
||||||
|
"default": 10,
|
||||||
|
"description": "Padding around the barcode (included in image size)",
|
||||||
|
"name": "padding",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v1/pdfs/cards": {
|
||||||
"post": {
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"description": "Generate cards based on the provided data",
|
"description": "Generate cards based on the provided data",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
@@ -16,7 +90,7 @@
|
|||||||
"application/pdf"
|
"application/pdf"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"cards"
|
"pdfs"
|
||||||
],
|
],
|
||||||
"summary": "Generate runner cards",
|
"summary": "Generate runner cards",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@@ -33,8 +107,45 @@
|
|||||||
"responses": {}
|
"responses": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/contracts": {
|
"/v1/pdfs/certificates": {
|
||||||
"post": {
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Generate certificates based on the provided data",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/pdf"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"pdfs"
|
||||||
|
],
|
||||||
|
"summary": "Generate runner certificates",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Certificate data",
|
||||||
|
"name": "data",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/models.CertificateRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v1/pdfs/contracts": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"description": "Generate a contract based on the provided data",
|
"description": "Generate a contract based on the provided data",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
@@ -43,7 +154,7 @@
|
|||||||
"application/pdf"
|
"application/pdf"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"contracts"
|
"pdfs"
|
||||||
],
|
],
|
||||||
"summary": "Generate a contract",
|
"summary": "Generate a contract",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@@ -64,12 +175,18 @@
|
|||||||
"definitions": {
|
"definitions": {
|
||||||
"models.Card": {
|
"models.Card": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"code",
|
||||||
|
"id",
|
||||||
|
"runner"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"code": {
|
"code": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"type": "boolean"
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
},
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
@@ -81,6 +198,10 @@
|
|||||||
},
|
},
|
||||||
"models.CardRequest": {
|
"models.CardRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"cards",
|
||||||
|
"locale"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"cards": {
|
"cards": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
@@ -97,8 +218,34 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"models.CertificateRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"locale",
|
||||||
|
"runners"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"locale": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"en",
|
||||||
|
"de"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"runners": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/models.RunnerWithDonations"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"models.ContractRequest": {
|
"models.ContractRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"locale",
|
||||||
|
"runners"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"locale": {
|
"locale": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -115,22 +262,83 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"models.Group": {
|
"models.DistanceDonation": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"amount_per_distance",
|
||||||
|
"donor",
|
||||||
|
"id"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"amount": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"amount_per_distance": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"donor": {
|
||||||
|
"$ref": "#/definitions/models.Donor"
|
||||||
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
|
"paid_amount": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"models.Donor": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"first_name",
|
||||||
|
"id",
|
||||||
|
"last_name"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"first_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"last_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"middle_name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"models.Group": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"parent_group": {
|
"parent_group": {
|
||||||
"$ref": "#/definitions/models.Group"
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"models.Runner": {
|
"models.Runner": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"first_name",
|
||||||
|
"group",
|
||||||
|
"id",
|
||||||
|
"last_name"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"first_name": {
|
"first_name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -148,6 +356,55 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"models.RunnerWithDonations": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"distance",
|
||||||
|
"first_name",
|
||||||
|
"group",
|
||||||
|
"id",
|
||||||
|
"last_name"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"distance": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"distance_donations": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/models.DistanceDonation"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"first_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"group": {
|
||||||
|
"$ref": "#/definitions/models.Group"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"last_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"middle_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"total_donations": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total_per_distance": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securityDefinitions": {
|
||||||
|
"ApiKeyAuth": {
|
||||||
|
"type": "apiKey",
|
||||||
|
"name": "key",
|
||||||
|
"in": "query"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+185
-8
@@ -4,11 +4,16 @@ definitions:
|
|||||||
code:
|
code:
|
||||||
type: string
|
type: string
|
||||||
enabled:
|
enabled:
|
||||||
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
id:
|
id:
|
||||||
type: integer
|
type: integer
|
||||||
runner:
|
runner:
|
||||||
$ref: '#/definitions/models.Runner'
|
$ref: '#/definitions/models.Runner'
|
||||||
|
required:
|
||||||
|
- code
|
||||||
|
- id
|
||||||
|
- runner
|
||||||
type: object
|
type: object
|
||||||
models.CardRequest:
|
models.CardRequest:
|
||||||
properties:
|
properties:
|
||||||
@@ -21,6 +26,24 @@ definitions:
|
|||||||
- en
|
- en
|
||||||
- de
|
- de
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- cards
|
||||||
|
- locale
|
||||||
|
type: object
|
||||||
|
models.CertificateRequest:
|
||||||
|
properties:
|
||||||
|
locale:
|
||||||
|
enum:
|
||||||
|
- en
|
||||||
|
- de
|
||||||
|
type: string
|
||||||
|
runners:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/models.RunnerWithDonations'
|
||||||
|
type: array
|
||||||
|
required:
|
||||||
|
- locale
|
||||||
|
- runners
|
||||||
type: object
|
type: object
|
||||||
models.ContractRequest:
|
models.ContractRequest:
|
||||||
properties:
|
properties:
|
||||||
@@ -33,15 +56,55 @@ definitions:
|
|||||||
items:
|
items:
|
||||||
$ref: '#/definitions/models.Runner'
|
$ref: '#/definitions/models.Runner'
|
||||||
type: array
|
type: array
|
||||||
|
required:
|
||||||
|
- locale
|
||||||
|
- runners
|
||||||
|
type: object
|
||||||
|
models.DistanceDonation:
|
||||||
|
properties:
|
||||||
|
amount:
|
||||||
|
type: integer
|
||||||
|
amount_per_distance:
|
||||||
|
type: integer
|
||||||
|
donor:
|
||||||
|
$ref: '#/definitions/models.Donor'
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
paid_amount:
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- amount_per_distance
|
||||||
|
- donor
|
||||||
|
- id
|
||||||
|
type: object
|
||||||
|
models.Donor:
|
||||||
|
properties:
|
||||||
|
first_name:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
last_name:
|
||||||
|
type: string
|
||||||
|
middle_name:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- first_name
|
||||||
|
- id
|
||||||
|
- last_name
|
||||||
type: object
|
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:
|
||||||
|
- name
|
||||||
type: object
|
type: object
|
||||||
models.Runner:
|
models.Runner:
|
||||||
properties:
|
properties:
|
||||||
@@ -55,14 +118,99 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
middle_name:
|
middle_name:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- first_name
|
||||||
|
- group
|
||||||
|
- id
|
||||||
|
- last_name
|
||||||
|
type: object
|
||||||
|
models.RunnerWithDonations:
|
||||||
|
properties:
|
||||||
|
distance:
|
||||||
|
type: integer
|
||||||
|
distance_donations:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/models.DistanceDonation'
|
||||||
|
type: array
|
||||||
|
first_name:
|
||||||
|
type: string
|
||||||
|
group:
|
||||||
|
$ref: '#/definitions/models.Group'
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
last_name:
|
||||||
|
type: string
|
||||||
|
middle_name:
|
||||||
|
type: string
|
||||||
|
total_donations:
|
||||||
|
type: integer
|
||||||
|
total_per_distance:
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- distance
|
||||||
|
- first_name
|
||||||
|
- group
|
||||||
|
- id
|
||||||
|
- last_name
|
||||||
type: object
|
type: object
|
||||||
info:
|
info:
|
||||||
contact: {}
|
contact:
|
||||||
|
email: info@odit.services
|
||||||
|
name: ODIT.Services UG (haftungsbeschränkt)
|
||||||
|
url: https://odit.services
|
||||||
description: This is the API documentation for the LfK Document Server - a tool
|
description: This is the API documentation for the LfK Document Server - a tool
|
||||||
for pdf generation.
|
for pdf generation.
|
||||||
|
license:
|
||||||
|
name: CC BY-NC-SA 4.0
|
||||||
|
termsOfService: https://lauf-fuer-kaya.de/datenschutz
|
||||||
title: LfK Document Server API
|
title: LfK Document Server API
|
||||||
paths:
|
paths:
|
||||||
/cards:
|
/v1/barcodes/{type}/{content}:
|
||||||
|
get:
|
||||||
|
description: Generate barcodes based on the provided data
|
||||||
|
parameters:
|
||||||
|
- description: Barcode type
|
||||||
|
enum:
|
||||||
|
- ean13
|
||||||
|
- code128
|
||||||
|
in: path
|
||||||
|
name: type
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: Barcode content
|
||||||
|
in: path
|
||||||
|
minLength: 1
|
||||||
|
name: content
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- default: 1000
|
||||||
|
description: Barcode width
|
||||||
|
in: query
|
||||||
|
maximum: 10000
|
||||||
|
minimum: 1
|
||||||
|
name: width
|
||||||
|
type: integer
|
||||||
|
- default: 1000
|
||||||
|
description: Barcode height
|
||||||
|
in: query
|
||||||
|
maximum: 10000
|
||||||
|
minimum: 1
|
||||||
|
name: height
|
||||||
|
type: integer
|
||||||
|
- default: 10
|
||||||
|
description: Padding around the barcode (included in image size)
|
||||||
|
in: query
|
||||||
|
maximum: 100
|
||||||
|
minimum: 0
|
||||||
|
name: padding
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- image/png
|
||||||
|
responses: {}
|
||||||
|
summary: Generate barcodes
|
||||||
|
tags:
|
||||||
|
- barcodes
|
||||||
|
/v1/pdfs/cards:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
@@ -77,10 +225,32 @@ paths:
|
|||||||
produces:
|
produces:
|
||||||
- application/pdf
|
- application/pdf
|
||||||
responses: {}
|
responses: {}
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
summary: Generate runner cards
|
summary: Generate runner cards
|
||||||
tags:
|
tags:
|
||||||
- cards
|
- pdfs
|
||||||
/contracts:
|
/v1/pdfs/certificates:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Generate certificates based on the provided data
|
||||||
|
parameters:
|
||||||
|
- description: Certificate data
|
||||||
|
in: body
|
||||||
|
name: data
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/models.CertificateRequest'
|
||||||
|
produces:
|
||||||
|
- application/pdf
|
||||||
|
responses: {}
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: Generate runner certificates
|
||||||
|
tags:
|
||||||
|
- pdfs
|
||||||
|
/v1/pdfs/contracts:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
@@ -95,7 +265,14 @@ paths:
|
|||||||
produces:
|
produces:
|
||||||
- application/pdf
|
- application/pdf
|
||||||
responses: {}
|
responses: {}
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
summary: Generate a contract
|
summary: Generate a contract
|
||||||
tags:
|
tags:
|
||||||
- contracts
|
- pdfs
|
||||||
|
securityDefinitions:
|
||||||
|
ApiKeyAuth:
|
||||||
|
in: query
|
||||||
|
name: key
|
||||||
|
type: apiKey
|
||||||
swagger: "2.0"
|
swagger: "2.0"
|
||||||
@@ -14,26 +14,48 @@ require (
|
|||||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||||
github.com/boombuler/barcode v1.0.2 // indirect
|
github.com/boombuler/barcode v1.0.2 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||||
github.com/go-openapi/spec v0.21.0 // indirect
|
github.com/go-openapi/spec v0.21.0 // indirect
|
||||||
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/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.17.11 // 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
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
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/oxplot/papersizes v0.0.0-20181201065918-90a3a5ae1915 // indirect
|
github.com/oxplot/papersizes v0.0.0-20181201065918-90a3a5ae1915 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.2 // 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/slog-shim v0.1.0 // indirect
|
||||||
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
|
github.com/spf13/afero v1.11.0 // indirect
|
||||||
|
github.com/spf13/cast v1.6.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/spf13/viper v1.19.0 // indirect
|
||||||
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/swaggo/files/v2 v2.0.1 // indirect
|
github.com/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.57.0 // indirect
|
||||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||||
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
|
go.uber.org/multierr v1.10.0 // indirect
|
||||||
|
go.uber.org/zap v1.27.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||||
golang.org/x/sys v0.27.0 // indirect
|
golang.org/x/sys v0.27.0 // indirect
|
||||||
|
golang.org/x/text v0.19.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-20200804184101-5ec99f83aff1 // indirect
|
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
@@ -4,8 +4,15 @@ github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7X
|
|||||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||||
github.com/boombuler/barcode v1.0.2 h1:79yrbttoZrLGkL/oOI8hBrUKucwOL0oOjUgEguGMcJ4=
|
github.com/boombuler/barcode v1.0.2 h1:79yrbttoZrLGkL/oOI8hBrUKucwOL0oOjUgEguGMcJ4=
|
||||||
github.com/boombuler/barcode v1.0.2/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
github.com/boombuler/barcode v1.0.2/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||||
@@ -20,6 +27,8 @@ github.com/gofiber/swagger v1.1.0 h1:ff3rg1fB+Rp5JN/N8jfxTiZtMKe/9tB9QDc79fPiJKQ
|
|||||||
github.com/gofiber/swagger v1.1.0/go.mod h1:pRZL0Np35sd+lTODTE5The0G+TMHfNY+oC4hM2/i5m8=
|
github.com/gofiber/swagger v1.1.0/go.mod h1:pRZL0Np35sd+lTODTE5The0G+TMHfNY+oC4hM2/i5m8=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 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/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
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=
|
||||||
@@ -28,6 +37,8 @@ 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=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||||
|
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
github.com/makiuchi-d/gozxing v0.1.1 h1:xxqijhoedi+/lZlhINteGbywIrewVdVv2wl9r5O9S1I=
|
github.com/makiuchi-d/gozxing v0.1.1 h1:xxqijhoedi+/lZlhINteGbywIrewVdVv2wl9r5O9S1I=
|
||||||
@@ -39,17 +50,47 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
|||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/oxplot/papersizes v0.0.0-20181201065918-90a3a5ae1915 h1:4WzMzgExTgBfuUQ/HegMf+jcHtH+c3fl7eySUQUbfzg=
|
github.com/oxplot/papersizes v0.0.0-20181201065918-90a3a5ae1915 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/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/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/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
|
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||||
|
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||||
|
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||||
|
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||||
|
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||||
|
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||||
|
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||||
|
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||||
|
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||||
|
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||||
|
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
github.com/swaggo/files/v2 v2.0.1 h1:XCVJO/i/VosCDsJu1YLpdejGsGnBE9deRMpjN4pJLHk=
|
github.com/swaggo/files/v2 v2.0.1 h1:XCVJO/i/VosCDsJu1YLpdejGsGnBE9deRMpjN4pJLHk=
|
||||||
github.com/swaggo/files/v2 v2.0.1/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM=
|
github.com/swaggo/files/v2 v2.0.1/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM=
|
||||||
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
|
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
|
||||||
@@ -62,6 +103,16 @@ github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVS
|
|||||||
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=
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
|
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||||
|
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
|
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||||
|
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||||
|
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||||
|
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
|
golang.org/x/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/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=
|
||||||
@@ -70,12 +121,18 @@ 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/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||||
|
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
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=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
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/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
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=
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GenerateBarcode godoc
|
||||||
|
//
|
||||||
|
// @Summary Generate barcodes
|
||||||
|
// @Description Generate barcodes based on the provided data
|
||||||
|
// @Tags barcodes
|
||||||
|
// @Param type path string true "Barcode type" Enums(ean13, code128)
|
||||||
|
// @Param content path string true "Barcode content" MinLength(1)
|
||||||
|
// @Param width query int false "Barcode width" Minimum(1) Maximum(10000) default(1000)
|
||||||
|
// @Param height query int false "Barcode height" Minimum(1) Maximum(10000) default(1000)
|
||||||
|
// @Param padding query int false "Padding around the barcode (included in image size)" Minimum(0) Maximum(100) default(10)
|
||||||
|
// @Produce image/png
|
||||||
|
// @Router /v1/barcodes/{type}/{content} [get]
|
||||||
|
func (h *DefaultHandler) GenerateBarcode(c *fiber.Ctx) error {
|
||||||
|
|
||||||
|
logger := h.Logger.Named("GenerateBarcode")
|
||||||
|
|
||||||
|
// Get the type and content from the URL
|
||||||
|
barcodeType := c.Params("type")
|
||||||
|
barcodeContent := c.Params("content")
|
||||||
|
|
||||||
|
// Get the width and height from the query parameters
|
||||||
|
widthStr := c.Query("width", "1000")
|
||||||
|
heightStr := c.Query("height", "1000")
|
||||||
|
paddingStr := c.Query("padding", "10")
|
||||||
|
|
||||||
|
// Convert width and height to integers
|
||||||
|
width, err := strconv.Atoi(widthStr)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorw("Invalid width parameter", "width", widthStr, "error", err)
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"error": "Invalid width parameter",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
height, err := strconv.Atoi(heightStr)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorw("Invalid height parameter", "height", heightStr, "error", err)
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"error": "Invalid height parameter",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
padding, err := strconv.Atoi(paddingStr)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorw("Invalid padding parameter", "padding", paddingStr, "error", err)
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"error": "Invalid padding parameter",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
logger = logger.With("type", barcodeType, "content", barcodeContent, "width", width, "height", height, "padding", padding)
|
||||||
|
|
||||||
|
// Generate the barcode
|
||||||
|
logger.Info("Generating barcode")
|
||||||
|
barcode, err := h.BarcodeService.GenerateBarcode(barcodeType, barcodeContent, width, height, padding)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorw("Failed to generate barcode", "error", err)
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
logger.Info("Barcode generated")
|
||||||
|
|
||||||
|
c.Set(fiber.HeaderContentType, "image/png")
|
||||||
|
return c.Send(barcode.Bytes())
|
||||||
|
}
|
||||||
+35
-22
@@ -1,46 +1,52 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"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/templates"
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GenerateCard godoc
|
// GenerateCard godoc
|
||||||
// @Summary Generate runner cards
|
//
|
||||||
// @Description Generate cards based on the provided data
|
// @Summary Generate runner cards
|
||||||
// @Tags cards
|
// @Description Generate cards based on the provided data
|
||||||
// @Accept json
|
// @Tags pdfs
|
||||||
// @Param data body models.CardRequest true "Card data"
|
// @Accept json
|
||||||
// @Produce application/pdf
|
// @Param data body models.CardRequest true "Card data"
|
||||||
// @Router /cards [post]
|
// @Produce application/pdf
|
||||||
func GenerateCard(c *fiber.Ctx) error {
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /v1/pdfs/cards [post]
|
||||||
|
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",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
generator := services.DefaultTemplater{}
|
logger = logger.With("locale", cardRequest.Locale)
|
||||||
templateString, err := templates.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 := generator.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(),
|
||||||
})
|
})
|
||||||
@@ -48,28 +54,35 @@ func GenerateCard(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
genConfig := &models.CardTemplateOptions{
|
genConfig := &models.CardTemplateOptions{
|
||||||
CardSegments: splitCardSegments(cardRequest.Cards),
|
CardSegments: splitCardSegments(cardRequest.Cards),
|
||||||
EventName: "Event name",
|
EventName: h.Config.EventName,
|
||||||
CardSubtitle: "Card subtitle",
|
CardSubtitle: h.Config.CardSubtitle,
|
||||||
BarcodeFormat: "ean13",
|
BarcodeFormat: h.Config.CardBarcodeFormat,
|
||||||
BarcodePrefix: "",
|
BarcodePrefix: h.Config.CardBarcodePrefix,
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := generator.Execute(template, genConfig)
|
logger.Info("Generating card html")
|
||||||
|
result, err := h.Templater.Execute(template, genConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
logger.Errorw("Error executing 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(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
logger.Info("Generated card html")
|
||||||
c.Set(fiber.HeaderContentType, "text/html")
|
c.Set(fiber.HeaderContentType, "text/html")
|
||||||
converter := services.GotenbergConverter{BaseUrl: "http://localhost:3001"}
|
|
||||||
pdf, err := converter.ToPdf(result, "a4", false)
|
logger.Info("Converting html to pdf")
|
||||||
|
pdf, err := h.Converter.ToPdf(result, "a4", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
logger.Errorw("Error converting html to 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(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
logger.Info("Converted html to pdf")
|
||||||
|
|
||||||
c.Set(fiber.HeaderContentType, "application/pdf")
|
c.Set(fiber.HeaderContentType, "application/pdf")
|
||||||
|
c.Set(fiber.HeaderContentDisposition, "attachment; filename=runner-cards.pdf")
|
||||||
return c.Send(pdf)
|
return c.Send(pdf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+57
-22
@@ -1,73 +1,108 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"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/templates"
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GenerateCertificate godoc
|
// GenerateCertificate godoc
|
||||||
// @Summary Generate runner certificates
|
//
|
||||||
// @Description Generate certificates based on the provided data
|
// @Summary Generate runner certificates
|
||||||
// @Tags certificates
|
// @Description Generate certificates based on the provided data
|
||||||
// @Accept json
|
// @Tags pdfs
|
||||||
// @Param data body models.CertificateRequest true "Certificate data"
|
// @Accept json
|
||||||
// @Produce application/pdf
|
// @Param data body models.CertificateRequest true "Certificate data"
|
||||||
// @Router /certificates [post]
|
// @Produce application/pdf
|
||||||
func GenerateCertificate(c *fiber.Ctx) error {
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /v1/pdfs/certificates [post]
|
||||||
|
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",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
generator := services.DefaultTemplater{}
|
logger = logger.With("locale", certificateRequest.Locale)
|
||||||
templateString, err := templates.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 := generator.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.CertificateTemplateOptions{
|
genConfig := &models.CertificateTemplateOptions{
|
||||||
Runners: certificateRequest.Runners,
|
Runners: addUpRunnerDonations(certificateRequest.Runners),
|
||||||
EventName: "Event name",
|
EventName: h.Config.EventName,
|
||||||
Footer: "Footer",
|
Footer: h.Config.CertificateFooter,
|
||||||
CurrencySymbol: "€",
|
CurrencySymbol: h.Config.CurrencySymbol,
|
||||||
|
Locale: certificateRequest.Locale,
|
||||||
|
SepaConfig: &models.SepaConfig{
|
||||||
|
BIC: h.Config.SepaBic,
|
||||||
|
HolderName: h.Config.SepaName,
|
||||||
|
IBAN: h.Config.SepaIban,
|
||||||
|
CurrencyIdentifier: h.Config.CurrencyIdentifier,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := generator.Execute(template, genConfig)
|
logger.Info("Generating certificate html")
|
||||||
|
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")
|
||||||
converter := services.GotenbergConverter{BaseUrl: "http://localhost:3001"}
|
|
||||||
pdf, err := converter.ToPdf(result, "a4", false)
|
logger.Info("Converting html to pdf")
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addUpRunnerDonations(runners []models.RunnerWithDonations) []models.RunnerWithDonations {
|
||||||
|
for i := 0; i < len(runners); i++ {
|
||||||
|
for j := 0; j < len(runners[i].DistanceDonations); j++ {
|
||||||
|
runners[i].TotalDonations += runners[i].DistanceDonations[j].Amount
|
||||||
|
runners[i].TotalPerDistance += runners[i].DistanceDonations[j].AmountPerDistance
|
||||||
|
}
|
||||||
|
if runners[i].Group.ParentGroup.Name != "" {
|
||||||
|
runners[i].CombinedGroupName = runners[i].Group.ParentGroup.Name + " - " + runners[i].Group.Name
|
||||||
|
} else {
|
||||||
|
runners[i].CombinedGroupName = runners[i].Group.Name
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return runners
|
||||||
|
}
|
||||||
+34
-27
@@ -1,80 +1,87 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"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/templates"
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GenerateContract godoc
|
// GenerateContract godoc
|
||||||
// @Summary Generate a contract
|
//
|
||||||
// @Description Generate a contract based on the provided data
|
// @Summary Generate a contract
|
||||||
// @Tags contracts
|
// @Description Generate a contract based on the provided data
|
||||||
// @Accept json
|
// @Tags pdfs
|
||||||
// @Param data body models.ContractRequest true "Contract data"
|
// @Accept json
|
||||||
// @Produce application/pdf
|
// @Param data body models.ContractRequest true "Contract data"
|
||||||
// @Router /contracts [post]
|
// @Produce application/pdf
|
||||||
func GenerateContract(c *fiber.Ctx) error {
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /v1/pdfs/contracts [post]
|
||||||
|
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)
|
||||||
|
|
||||||
generator := services.DefaultTemplater{}
|
templateString, err := h.StaticService.GetTemplate(contract.Locale, "contract")
|
||||||
templateString, err := templates.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 := generator.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: "€",
|
CurrencySymbol: h.Config.CurrencySymbol,
|
||||||
Disclaimer: "This is a disclaimer",
|
Disclaimer: h.Config.SponosringDisclaimer,
|
||||||
ReceiptMinimumAmount: 10,
|
ReceiptMinimumAmount: h.Config.SponsoringReceiptMinimum,
|
||||||
EventName: "Event name",
|
EventName: h.Config.EventName,
|
||||||
SponsoringHeader: templates.ImageSponsoringHeaderBase64,
|
BarcodeFormat: h.Config.SponsoringBarcodeFormat,
|
||||||
BarcodeFormat: "ean13",
|
BarcodePrefix: h.Config.SponsoringBarcodePrefix,
|
||||||
BarcodePrefix: "1",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := generator.Execute(template, genConfig)
|
logger.Info("Generating contract html")
|
||||||
|
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")
|
||||||
|
|
||||||
converter := services.GotenbergConverter{BaseUrl: "http://localhost:3001"}
|
logger.Info("Converting html to pdf")
|
||||||
pdf, err := 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.odit.services/lfk/document-server/models"
|
||||||
|
"git.odit.services/lfk/document-server/services"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Handler interface {
|
||||||
|
GenerateCard(*fiber.Ctx) error
|
||||||
|
GenerateContract(*fiber.Ctx) error
|
||||||
|
GenerateCertificate(*fiber.Ctx) error
|
||||||
|
GenerateBarcode(*fiber.Ctx) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultHandler struct {
|
||||||
|
Config *models.Config
|
||||||
|
BarcodeService services.BarcodeService
|
||||||
|
Templater services.Templater
|
||||||
|
Converter services.Converter
|
||||||
|
StaticService services.StaticService
|
||||||
|
Logger *zap.SugaredLogger
|
||||||
|
}
|
||||||
+1
-1
@@ -2,6 +2,6 @@ package handlers
|
|||||||
|
|
||||||
import "github.com/gofiber/fiber/v2"
|
import "github.com/gofiber/fiber/v2"
|
||||||
|
|
||||||
func NotFoundHandler(c *fiber.Ctx) error {
|
func (h *DefaultHandler) NotFoundHandler(c *fiber.Ctx) error {
|
||||||
return c.Status(404).SendString("Not Found")
|
return c.Status(404).SendString("Not Found")
|
||||||
}
|
}
|
||||||
@@ -1,45 +1,198 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"crypto/sha256"
|
||||||
"log"
|
"crypto/subtle"
|
||||||
|
"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/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/requestid"
|
||||||
"github.com/gofiber/swagger"
|
"github.com/gofiber/swagger"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
port = flag.String("port", ":3000", "Port to listen on")
|
config *models.Config
|
||||||
prod = flag.Bool("prod", false, "Enable prefork in Production")
|
logger *zap.SugaredLogger
|
||||||
)
|
)
|
||||||
|
|
||||||
// @title LfK Document Server API
|
func validateAPIKey(c *fiber.Ctx, key string) (bool, error) {
|
||||||
// @description This is the API documentation for the LfK Document Server - a tool for pdf generation.
|
hashedAPIKey := sha256.Sum256([]byte(config.APIKey))
|
||||||
|
hashedKey := sha256.Sum256([]byte(key))
|
||||||
|
|
||||||
|
if subtle.ConstantTimeCompare(hashedAPIKey[:], hashedKey[:]) == 1 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, keyauth.ErrMissingOrMalformedAPIKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadEnv() error {
|
||||||
|
|
||||||
|
viper.SetDefault("LOGLEVEL", "INFO")
|
||||||
|
viper.SetDefault("PRODUCION", false)
|
||||||
|
viper.SetDefault("PORT", "3000")
|
||||||
|
viper.SetDefault("APIKEY", "lfk")
|
||||||
|
viper.SetDefault("EVENTNAME", "Demo Event")
|
||||||
|
viper.SetDefault("CURRENCYSYMBOL", "€")
|
||||||
|
viper.SetDefault("CURRENCYIDENTIFIER", "EUR")
|
||||||
|
viper.SetDefault("CARD_SUBTITLE", "Runner Card")
|
||||||
|
viper.SetDefault("CARD_BARCODEFORMAT", "ean13")
|
||||||
|
viper.SetDefault("CARD_BARCODEPREFIX", "")
|
||||||
|
viper.SetDefault("SPONSORING_RECEIPTMINIMUM", 0)
|
||||||
|
viper.SetDefault("SPONSORING_DISCLAIMER", "Disclaimer")
|
||||||
|
viper.SetDefault("SPONSORING_BARCODEFORMAT", "code128")
|
||||||
|
viper.SetDefault("SPONSORING_BARCODEPREFIX", "")
|
||||||
|
viper.SetDefault("CERTIFICATE_FOOTER", "Footer")
|
||||||
|
viper.SetDefault("GOTENBERG_BASEURL", "")
|
||||||
|
viper.SetDefault("REDIS_ADDR", "")
|
||||||
|
viper.SetDefault("SEPA_BIC", "")
|
||||||
|
viper.SetDefault("SEPA_NAME", "")
|
||||||
|
viper.SetDefault("SEPA_IBAN", "")
|
||||||
|
|
||||||
|
// Load .env file
|
||||||
|
viper.SetConfigFile(".env")
|
||||||
|
|
||||||
|
// Load environment variables
|
||||||
|
viper.AutomaticEnv()
|
||||||
|
err := viper.ReadInConfig()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("No .env file found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal the config from file and env into the config struct
|
||||||
|
err = viper.Unmarshal(&config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Infow("Loaded config", "config", &config)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initLogger() error {
|
||||||
|
logLevel := os.Getenv("LOGLEVEL")
|
||||||
|
if logLevel == "" {
|
||||||
|
logLevel = "INFO"
|
||||||
|
}
|
||||||
|
|
||||||
|
var zapLogLevel zapcore.Level
|
||||||
|
err := zapLogLevel.UnmarshalText([]byte(strings.ToLower(logLevel)))
|
||||||
|
if err != nil {
|
||||||
|
zapLogLevel = zapcore.InfoLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
zapConfig := zap.NewProductionConfig()
|
||||||
|
zapConfig.Level = zap.NewAtomicLevelAt(zapLogLevel)
|
||||||
|
zapLogger, err := zapConfig.Build()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer zapLogger.Sync()
|
||||||
|
logger = zapLogger.Sugar()
|
||||||
|
|
||||||
|
logger.Debug("Initialized logger")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// @title LfK Document Server API
|
||||||
|
// @description This is the API documentation for the LfK Document Server - a tool for pdf generation.
|
||||||
|
// @license.name CC BY-NC-SA 4.0
|
||||||
|
// @termsOfService https://lauf-fuer-kaya.de/datenschutz
|
||||||
|
// @contact.name ODIT.Services UG (haftungsbeschränkt)
|
||||||
|
// @contact.url https://odit.services
|
||||||
|
// @contact.email info@odit.services
|
||||||
|
// @securityDefinitions.apiKey ApiKeyAuth
|
||||||
|
// @in query
|
||||||
|
// @name key
|
||||||
func main() {
|
func main() {
|
||||||
// Parse command-line flags
|
|
||||||
flag.Parse()
|
// Init the logger
|
||||||
|
err := initLogger()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = loadEnv()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var redisClient *redis.Client
|
||||||
|
if config.RedisAddr != "" {
|
||||||
|
logger.Infow("Using redis", "redisAddr", config.RedisAddr)
|
||||||
|
redisClient = redis.NewClient(&redis.Options{
|
||||||
|
Addr: config.RedisAddr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
barcodeGenerator := &services.DefaultBarcodeService{
|
||||||
|
RedisClient: redisClient,
|
||||||
|
Logger: logger.Named("DefaultBarcodeService"),
|
||||||
|
}
|
||||||
|
staticService := &services.DefaultStaticService{
|
||||||
|
Cache: make(map[string]string),
|
||||||
|
Logger: logger.Named("DefaultStaticService"),
|
||||||
|
}
|
||||||
|
handler := handlers.DefaultHandler{
|
||||||
|
Config: config,
|
||||||
|
BarcodeService: barcodeGenerator,
|
||||||
|
StaticService: staticService,
|
||||||
|
Templater: &services.DefaultTemplater{
|
||||||
|
BarcodeService: barcodeGenerator,
|
||||||
|
StaticService: staticService,
|
||||||
|
Logger: logger.Named("DefaultTemplater"),
|
||||||
|
},
|
||||||
|
Converter: &services.GotenbergConverter{
|
||||||
|
BaseUrl: config.GotenbergBaseUrl,
|
||||||
|
Logger: logger.Named("GotenbergConverter"),
|
||||||
|
},
|
||||||
|
Logger: logger.Named("DefaultHandler"),
|
||||||
|
}
|
||||||
|
logger.Debug("Initialized services")
|
||||||
|
|
||||||
// Create a new Fiber instance
|
// Create a new Fiber instance
|
||||||
app := fiber.New(fiber.Config{
|
app := fiber.New(fiber.Config{
|
||||||
Prefork: *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)
|
||||||
|
|
||||||
v1 := app.Group("/v1")
|
v1 := app.Group("/v1")
|
||||||
|
|
||||||
v1.Get("/", func(c *fiber.Ctx) error {
|
pdfv1 := v1.Group("/pdfs")
|
||||||
return c.SendString("Hello, World!")
|
pdfv1.Use(keyauth.New(keyauth.Config{
|
||||||
})
|
KeyLookup: "query:key",
|
||||||
v1.Post("/contracts", handlers.GenerateContract)
|
Validator: validateAPIKey,
|
||||||
v1.Post("/cards", handlers.GenerateCard)
|
}))
|
||||||
v1.Post("/certificates", handlers.GenerateCertificate)
|
|
||||||
|
|
||||||
app.Use(handlers.NotFoundHandler)
|
pdfv1.Post("/contracts", handler.GenerateContract)
|
||||||
|
pdfv1.Post("/cards", handler.GenerateCard)
|
||||||
|
pdfv1.Post("/certificates", handler.GenerateCertificate)
|
||||||
|
|
||||||
|
v1.Get("/barcodes/:type/:content", handler.GenerateBarcode)
|
||||||
|
logger.Debug("Initialized routes")
|
||||||
|
|
||||||
|
app.Use(handler.NotFoundHandler)
|
||||||
docs.SwaggerInfo.BasePath = "/"
|
docs.SwaggerInfo.BasePath = "/"
|
||||||
|
|
||||||
log.Fatal(app.Listen(*port))
|
logger.Infow("Starting server", "port", config.Port)
|
||||||
|
logger.Error(app.Listen("0.0.0.0:" + config.Port))
|
||||||
}
|
}
|
||||||
+6
-6
@@ -1,15 +1,15 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
type CardRequest struct {
|
type CardRequest struct {
|
||||||
Cards []Card `json:"cards"`
|
Cards []Card `json:"cards" validate:"required"`
|
||||||
Locale string `json:"locale" enums:"en,de"`
|
Locale string `json:"locale" enums:"en,de" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Card struct {
|
type Card struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id" validate:"required"`
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled" default:"true"`
|
||||||
Runner Runner `json:"runner"`
|
Runner Runner `json:"runner" validate:"required"`
|
||||||
Code string `json:"code"`
|
Code string `json:"code" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CardTemplateOptions struct {
|
type CardTemplateOptions struct {
|
||||||
|
|||||||
+30
-19
@@ -1,35 +1,37 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
type CertificateRequest struct {
|
type CertificateRequest struct {
|
||||||
Runners []RunnerWithDonations `json:"runners"`
|
Runners []RunnerWithDonations `json:"runners" validate:"required"`
|
||||||
Locale string `json:"locale" enums:"en,de"`
|
Locale string `json:"locale" enums:"en,de" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RunnerWithDonations struct {
|
type RunnerWithDonations struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id" validate:"required"`
|
||||||
FirstName string `json:"first_name"`
|
FirstName string `json:"first_name" validate:"required"`
|
||||||
MiddleName string `json:"middle_name"`
|
MiddleName string `json:"middle_name" validate:"optional"`
|
||||||
LastName string `json:"last_name"`
|
LastName string `json:"last_name" validate:"required"`
|
||||||
Group Group `json:"group"`
|
Group Group `json:"group" validate:"required"`
|
||||||
Distance int `json:"distance"`
|
CombinedGroupName string `json:"combined_group_name" validate:"optional"`
|
||||||
DistanceDonations []DistanceDonation `json:"distance_donations"`
|
Distance int `json:"distance" validate:"required"`
|
||||||
TotalPerDistance int `json:"total_per_distance"`
|
DistanceDonations []DistanceDonation `json:"distance_donations" validate:"optional"`
|
||||||
TotalDonations int `json:"total_donations"`
|
TotalPerDistance int `json:"total_per_distance" validate:"optional"`
|
||||||
|
TotalDonations int `json:"total_donations" validate:"optional"`
|
||||||
|
SelfServiceLink string `json:"self_service_link" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DistanceDonation struct {
|
type DistanceDonation struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id" validate:"required"`
|
||||||
Amount int `json:"amount"`
|
Amount int `json:"amount"`
|
||||||
PaidAmount int `json:"paid_amount"`
|
PaidAmount int `json:"paid_amount" validate:"optional"`
|
||||||
AmountPerDistance int `json:"amount_per_distance"`
|
AmountPerDistance int `json:"amount_per_distance" validate:"required"`
|
||||||
Donor Donor `json:"donor"`
|
Donor Donor `json:"donor" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Donor struct {
|
type Donor struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id" validate:"required"`
|
||||||
FirstName string `json:"first_name"`
|
FirstName string `json:"first_name" validate:"required"`
|
||||||
MiddleName string `json:"middle_name"`
|
MiddleName string `json:"middle_name" validate:"optional"`
|
||||||
LastName string `json:"last_name"`
|
LastName string `json:"last_name" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CertificateTemplateOptions struct {
|
type CertificateTemplateOptions struct {
|
||||||
@@ -37,4 +39,13 @@ type CertificateTemplateOptions struct {
|
|||||||
EventName string `json:"event_name"`
|
EventName string `json:"event_name"`
|
||||||
Footer string `json:"footer"`
|
Footer string `json:"footer"`
|
||||||
CurrencySymbol string `json:"currency_symbol"`
|
CurrencySymbol string `json:"currency_symbol"`
|
||||||
|
Locale string `json:"locale"`
|
||||||
|
SepaConfig *SepaConfig `json:"sepa_config"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SepaConfig struct {
|
||||||
|
IBAN string `json:"iban" validate:"required"`
|
||||||
|
HolderName string `json:"holder_name" validate:"required"`
|
||||||
|
BIC string `json:"bic" validate:"required"`
|
||||||
|
CurrencyIdentifier string `json:"currency_identifier" validate:"required"`
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
LogLevel string `mapstructure:"LOGLEVEL"`
|
||||||
|
Prod bool `mapstructure:"PRODUCION"`
|
||||||
|
Port string `mapstructure:"PORT"`
|
||||||
|
APIKey string `mapstructure:"APIKEY"`
|
||||||
|
EventName string `mapstructure:"EVENTNAME"`
|
||||||
|
CurrencySymbol string `mapstructure:"CURRENCYSYMBOL"`
|
||||||
|
CurrencyIdentifier string `mapstructure:"CURRENCYIDENTIFIER"`
|
||||||
|
CardSubtitle string `mapstructure:"CARD_SUBTITLE"`
|
||||||
|
CardBarcodeFormat string `mapstructure:"CARD_BARCODEFORMAT"`
|
||||||
|
CardBarcodePrefix string `mapstructure:"CARD_BARCODEPREFIX"`
|
||||||
|
SponsoringReceiptMinimum string `mapstructure:"SPONSORING_RECEIPTMINIMUM"`
|
||||||
|
SponosringDisclaimer string `mapstructure:"SPONSORING_DISCLAIMER"`
|
||||||
|
SponsoringBarcodeFormat string `mapstructure:"SPONSORING_BARCODEFORMAT"`
|
||||||
|
SponsoringBarcodePrefix string `mapstructure:"SPONSORING_BARCODEPREFIX"`
|
||||||
|
CertificateFooter string `mapstructure:"CERTIFICATE_FOOTER"`
|
||||||
|
GotenbergBaseUrl string `mapstructure:"GOTENBERG_BASEURL"`
|
||||||
|
RedisAddr string `mapstructure:"REDIS_ADDR"`
|
||||||
|
SepaBic string `mapstructure:"SEPA_BIC"`
|
||||||
|
SepaName string `mapstructure:"SEPA_NAME"`
|
||||||
|
SepaIban string `mapstructure:"SEPA_IBAN"`
|
||||||
|
}
|
||||||
+12
-12
@@ -1,31 +1,31 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
type ContractRequest struct {
|
type ContractRequest struct {
|
||||||
Runners []Runner `json:"runners"`
|
Runners []Runner `json:"runners" validate:"required"`
|
||||||
Locale string `json:"locale" enums:"en,de"`
|
Locale string `json:"locale" enums:"en,de" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Runner struct {
|
type Runner struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id" validate:"required"`
|
||||||
FirstName string `json:"first_name"`
|
FirstName string `json:"first_name" validate:"required"`
|
||||||
MiddleName string `json:"middle_name"`
|
MiddleName string `json:"middle_name" validate:"optional"`
|
||||||
LastName string `json:"last_name"`
|
LastName string `json:"last_name" validate:"required"`
|
||||||
Group Group `json:"group"`
|
Group Group `json:"group" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Group struct {
|
type Group struct {
|
||||||
ID int `json:"id"`
|
Name string `json:"name" validate:"required"`
|
||||||
Name string `json:"name"`
|
ParentGroup struct {
|
||||||
ParentGroup *Group `json:"parent_group"`
|
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"`
|
||||||
SponsoringHeader string `json:"base_url"`
|
|
||||||
BarcodeFormat string `json:"barcode_format"`
|
BarcodeFormat string `json:"barcode_format"`
|
||||||
BarcodePrefix string `json:"barcode_prefix"`
|
BarcodePrefix string `json:"barcode_prefix"`
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/draw"
|
||||||
|
"image/png"
|
||||||
|
"slices"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/boombuler/barcode"
|
||||||
|
"github.com/boombuler/barcode/code128"
|
||||||
|
"github.com/boombuler/barcode/ean"
|
||||||
|
"github.com/boombuler/barcode/qr"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BarcodeService interface {
|
||||||
|
GenerateBarcode(format string, content string, width int, height int, padding int) (bytes.Buffer, error)
|
||||||
|
IsTypeSupported(format string) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultBarcodeService struct {
|
||||||
|
RedisClient *redis.Client
|
||||||
|
Logger *zap.SugaredLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *DefaultBarcodeService) GenerateBarcode(format string, content string, width int, height int, padding int) (bytes.Buffer, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
logger := b.Logger.Named("GenerateBarcode")
|
||||||
|
|
||||||
|
if !b.IsTypeSupported(format) {
|
||||||
|
logger.Errorw("Unsupported barcode type", "type", format)
|
||||||
|
return bytes.Buffer{}, fmt.Errorf("unsupported barcode type: %s", format)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger = logger.With("type", format, "content", content, "width", width, "height", height, "padding", padding)
|
||||||
|
cacheKey := fmt.Sprintf("barcode:%s:%s:%d:%d:%d", format, content, width, height, padding)
|
||||||
|
|
||||||
|
if b.RedisClient != nil {
|
||||||
|
logger.Debugw("Checking cache for barcode", "key", cacheKey)
|
||||||
|
cachedBarcode, err := b.RedisClient.Get(ctx, cacheKey).Result()
|
||||||
|
if err == nil {
|
||||||
|
logger.Infow("Barcode found in cache", "key", cacheKey)
|
||||||
|
buf := bytes.Buffer{}
|
||||||
|
buf.Write([]byte(cachedBarcode))
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var generatedCode barcode.Barcode
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch format {
|
||||||
|
case "ean13":
|
||||||
|
generatedCode, err = ean.Encode(content)
|
||||||
|
if err != nil {
|
||||||
|
return bytes.Buffer{}, err
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case "code128":
|
||||||
|
generatedCode, err = code128.Encode(content)
|
||||||
|
if err != nil {
|
||||||
|
return bytes.Buffer{}, err
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case "qr":
|
||||||
|
// Always use qr.Auto encoding to support all characters in the content
|
||||||
|
encoding := qr.Auto
|
||||||
|
|
||||||
|
// QR code generation with error correction level M and auto encoding
|
||||||
|
generatedCode, err = qr.Encode(content, qr.M, encoding)
|
||||||
|
if err != nil {
|
||||||
|
return bytes.Buffer{}, err
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a white background image
|
||||||
|
bg := image.NewRGBA(image.Rect(0, 0, width, height))
|
||||||
|
white := color.RGBA{255, 255, 255, 255}
|
||||||
|
draw.Draw(bg, bg.Bounds(), &image.Uniform{white}, image.Point{}, draw.Src)
|
||||||
|
logger.Debug("Created white background")
|
||||||
|
|
||||||
|
// Calculate the new size for the barcode to fit within the padding
|
||||||
|
newWidth := width - 2*padding
|
||||||
|
newHeight := height - 2*padding
|
||||||
|
|
||||||
|
// Scale the barcode to the new size
|
||||||
|
scaledCode, err := barcode.Scale(generatedCode, newWidth, newHeight)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorw("Failed to scale barcode", "error", err)
|
||||||
|
return bytes.Buffer{}, err
|
||||||
|
}
|
||||||
|
logger.Debug("Scaled barcode")
|
||||||
|
|
||||||
|
// Draw the barcode on top of the white background with padding
|
||||||
|
draw.Draw(bg, scaledCode.Bounds().Add(image.Point{padding, padding}), scaledCode, image.Point{}, draw.Over)
|
||||||
|
logger.Debug("Drew barcode on background")
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = png.Encode(&buf, bg)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorw("Failed to encode barcode to PNG", "error", err)
|
||||||
|
return bytes.Buffer{}, err
|
||||||
|
}
|
||||||
|
logger.Debug("Encoded barcode to PNG")
|
||||||
|
|
||||||
|
if b.RedisClient != nil {
|
||||||
|
err = b.RedisClient.Set(ctx, cacheKey, buf.String(), 10*time.Minute).Err()
|
||||||
|
logger.Debugw("Cached barcode", "key", cacheKey)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorw("Failed to cache barcode", "error", err)
|
||||||
|
return bytes.Buffer{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.Info("Generated barcode")
|
||||||
|
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *DefaultBarcodeService) IsTypeSupported(format string) bool {
|
||||||
|
supportedTypes := []string{"ean13", "code128", "qr"}
|
||||||
|
return slices.Contains(supportedTypes, format)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
+64
-46
@@ -4,14 +4,11 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"image/png"
|
"strings"
|
||||||
|
|
||||||
"git.odit.services/lfk/document-server/templates"
|
"go.uber.org/zap"
|
||||||
"github.com/boombuler/barcode"
|
|
||||||
"github.com/boombuler/barcode/code128"
|
|
||||||
"github.com/boombuler/barcode/ean"
|
|
||||||
"github.com/boombuler/barcode/qr"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Templater interface {
|
type Templater interface {
|
||||||
@@ -20,6 +17,9 @@ type Templater interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DefaultTemplater struct {
|
type DefaultTemplater struct {
|
||||||
|
BarcodeService BarcodeService
|
||||||
|
StaticService StaticService
|
||||||
|
Logger *zap.SugaredLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
func idToEan13(id string, prefix string) (string, error) {
|
func idToEan13(id string, prefix string) (string, error) {
|
||||||
@@ -35,64 +35,82 @@ func idToEan13(id string, prefix string) (string, error) {
|
|||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *DefaultTemplater) GenerateBarcode(code string, format string, prefix string) (string, error) {
|
func (t *DefaultTemplater) GenerateEPC(iban string, bic string, name string, title string, amount int, currency string) (string, error) {
|
||||||
var generatedCode barcode.Barcode
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
switch format {
|
code := fmt.Sprintf(`BCD
|
||||||
case "ean13":
|
002
|
||||||
encodedEan, err := idToEan13(code, prefix)
|
1
|
||||||
|
INST
|
||||||
|
%s
|
||||||
|
%s
|
||||||
|
%s
|
||||||
|
%s%.2f
|
||||||
|
|
||||||
|
%s
|
||||||
|
|
||||||
|
`, bic, name, iban, currency, float64(amount)/100, title,
|
||||||
|
)
|
||||||
|
|
||||||
|
buf, err := t.BarcodeService.GenerateBarcode("qr", code, 500, 500, 0)
|
||||||
|
|
||||||
|
return base64.StdEncoding.EncodeToString(buf.Bytes()), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DefaultTemplater) GenerateBarcode(code string, format string, prefix string) (string, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if format == "ean13" {
|
||||||
|
code, err = idToEan13(code, prefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
generatedCode, err = ean.Encode(encodedEan)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case "code128":
|
|
||||||
generatedCode, err = code128.Encode(prefix + code)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case "qr":
|
|
||||||
generatedCode, err = qr.Encode(prefix+code, qr.M, qr.AlphaNumeric)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
return "", errors.New("unknown barcode format")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scaledCode, err := barcode.Scale(generatedCode, 1000, 500)
|
buf, err := t.BarcodeService.GenerateBarcode(format, code, 1000, 500, 0)
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
return base64.StdEncoding.EncodeToString(buf.Bytes()), err
|
||||||
err = png.Encode(&buf, scaledCode)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return base64.StdEncoding.EncodeToString(buf.Bytes()), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *DefaultTemplater) SelectSponsorImage(id int) (string, error) {
|
func (t *DefaultTemplater) SelectSponsorImage(id int) (string, error) {
|
||||||
sponsors := []string{
|
logger := t.Logger.Named("SelectSponsorImage")
|
||||||
"vrbank",
|
sponsors, err := t.StaticService.ListFilesInStaticSubFolder("images/sponsors")
|
||||||
"odit",
|
if err != nil {
|
||||||
"sparkasse",
|
logger.Errorw("Failed to list sponsors", "error", err)
|
||||||
|
return "", err
|
||||||
}
|
}
|
||||||
return templates.GetImage(sponsors[id%len(sponsors)]), nil
|
logger.Debugw("Selected sponsor", "sponsors", sponsors, "id", id, "selected", sponsors[id%len(sponsors)])
|
||||||
|
return t.StaticService.GetImage("sponsors/" + strings.TrimSuffix(sponsors[id%len(sponsors)], ".base64")), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DefaultTemplater) LoadImage(name string) (string, error) {
|
||||||
|
return t.StaticService.GetImage(name), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DefaultTemplater) FormatUnit(unit string, locale string, amount int) (string, error) {
|
||||||
|
var formatted string
|
||||||
|
switch unit {
|
||||||
|
case "kilometer":
|
||||||
|
formatted = fmt.Sprintf("%.3f", float64(amount)/1000)
|
||||||
|
case "euro":
|
||||||
|
formatted = fmt.Sprintf("%.2f", float64(amount)/100)
|
||||||
|
default:
|
||||||
|
return "", errors.New("unknown unit")
|
||||||
|
}
|
||||||
|
|
||||||
|
if locale == "de" {
|
||||||
|
return strings.Replace(formatted, ".", ",", -1), nil
|
||||||
|
}
|
||||||
|
return formatted, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *DefaultTemplater) StringToTemplate(templateString string) (*template.Template, error) {
|
func (t *DefaultTemplater) StringToTemplate(templateString string) (*template.Template, error) {
|
||||||
return template.New("template").Funcs(template.FuncMap{
|
return template.New("template").Funcs(template.FuncMap{
|
||||||
"barcode": t.GenerateBarcode,
|
"barcode": t.GenerateBarcode,
|
||||||
"sponsorLogo": t.SelectSponsorImage,
|
"sponsorLogo": t.SelectSponsorImage,
|
||||||
|
"formatUnit": t.FormatUnit,
|
||||||
|
"loadImage": t.LoadImage,
|
||||||
|
"epcCode": t.GenerateEPC,
|
||||||
}).Parse(templateString)
|
}).Parse(templateString)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StaticService interface {
|
||||||
|
GetTemplate(locale, templateName string) (string, error)
|
||||||
|
ListFilesInStaticSubFolder(folderName string) ([]string, error)
|
||||||
|
GetImage(imageName string) string
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultStaticService struct {
|
||||||
|
Cache map[string]string
|
||||||
|
Logger *zap.SugaredLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
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] != "" {
|
||||||
|
logger.Debugw("Template found in cache", "key", locale+templateName)
|
||||||
|
return s.Cache[locale+templateName], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := os.ReadFile(fmt.Sprintf("static/templates/%s/%s.html", templateName, locale))
|
||||||
|
if content == nil || err != nil {
|
||||||
|
logger.Errorw("Failed to read template", "error", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Cache[locale+templateName] = string(content)
|
||||||
|
logger.Debugw("Saved template to cache", "key", locale+templateName)
|
||||||
|
|
||||||
|
return string(content), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorw("Failed to list files", "error", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var images []string
|
||||||
|
for _, file := range files {
|
||||||
|
if file.IsDir() {
|
||||||
|
logger.Debugw("Skipping directory", "file", file.Name())
|
||||||
|
}
|
||||||
|
images = append(images, file.Name())
|
||||||
|
}
|
||||||
|
logger.Debugw("Listed files", "files", images)
|
||||||
|
return images, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultStaticService) GetImage(imageName string) string {
|
||||||
|
logger := s.Logger.Named("GetImage").With("image_name", imageName)
|
||||||
|
|
||||||
|
content, err := os.ReadFile("static/images/" + imageName + ".base64")
|
||||||
|
if content == nil || err != nil {
|
||||||
|
logger.Errorw("Failed to read image", "error", err)
|
||||||
|
return ImageErrorBase64
|
||||||
|
}
|
||||||
|
return string(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImageErrorBase64 string = "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 truncated
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
iVBORw0KGgoAAAANSUhEUgAAAqsAAASrCAYAAAC1y/HLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDIxLTAzLTMxVDEzOjUzOjUyKzAyOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyMS0wMy0zMVQxNzowMzo1OCswMjowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyMS0wMy0zMVQxNzowMzo1OCswMjowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2MjMzNzM0My1jYjBj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 truncated
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
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 truncated
|
||||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1 @@
|
|||||||
|
iVBORw0KGgoAAAANSUhEUgAAA+gAAAJvCAYAAADoYfswAAAACXBIWXMAAC4jAAAuIwF4pT92AAAQ32lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgOS4xLWMwMDIgNzkuYTZhNjM5NjhhLCAyMDI0LzAzLzA2LTExOjUyOjA1ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnBkZj0iaHR0cDovL25zLmFkb2JlLmNvbS9wZGYvMS4zLyIgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIiB4bWxuczp4bXBUUGc9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC90L3BnLyIgeG1sbnM6c3REaW09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9EaW1lbnNpb25zIyIgeG1sbnM6eG1wRz0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL2cvIiB4bWxuczppbGx1c3RyYXRvcj0iaHR0cDovL25zLmFkb2JlLmNvbS9pbGx1c3RyYXRvci8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1wOkNyZWF0ZURhdGU9IjIwMjQtMDctMTVUMTI6NTA6MDgrMDI6MDAiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgSWxsdXN0cmF0b3IgMjguNSAoTWFjaW50b3NoKSIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyNC0wNy0xN1QxMjo1NDo1NCswMjowMCIgeG1wOk1vZGlmeURhdGU9IjIwMjQtMDctMTdUMTI6NTQ6NTQrMDI6MDAiIHBkZjpQcm9kdWNlcj0iQWRvYmUgUERGIGxpYnJhcnkgMTcuMDAiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiB4bXBUUGc6TlBhZ2VzPSIxIiB4bXBUUGc6SGFzVmlzaWJsZVRyYW5zcGFyZW5jeT0iRmFsc2UiIHhtcFRQZzpIYXNWaXNpYmxlT3ZlcnByaW50PSJUcnVlIiBpbGx1c3RyYXRvcjpDcmVhdG9yU3ViVG9vbD0iQWRvYmUgSWxsdXN0cmF0b3IiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDo5ZjNmZDFmMi0zNWVmLTkxNDUtOTA1Zi03ZmMzNGExMmEwNDEiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NzMxNjE2YTctZWJhNy00MjRiLTlhZjgtZjY5MTRlNDBjMDRkIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MjAwMTFiN2QtZTdiYy00OGQ4LWE2MDQtOWE5ZDlkMGJjOTU5IiB4bXBNTTpSZW5kaXRpb25DbGFzcz0iZGVmYXVsdCIgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyI+IDxkYzp0aXRsZT4gPHJkZjpBbHQ+IDxyZGY6bGkgeG1sOmxhbmc9IngtZGVmYXVsdCI+QXRsYW50aXNfTG9nb180Y19SZWxhdW5jaDIwMjRfY2xhaW1fdHVlcmtpczwvcmRmOmxpPiA8L3JkZjpBbHQ+IDwvZGM6dGl0bGU+IDx4bXBUUGc6TWF4UGFnZVNpemUgc3REaW06dz0iMjk3LjAwMDAwMiIgc3REaW06aD0iMTg2LjM1OTUzMiIgc3REaW06dW5pdD0iTWlsbGltZXRlcnMiLz4gPHhtcFRQZzpQbGF0ZU5hbWVzPiA8cmRmOlNlcT4gPHJkZjpsaT5DeWFuPC9yZGY6bGk+IDxyZGY6bGk+TWFnZW50YTwvcmRmOmxpPiA8cmRmOmxpPlllbGxvdzwvcmRmOmxpPiA8cmRmOmxpPkJsYWNrPC9yZGY6bGk+IDwvcmRmOlNlcT4gPC94bXBUUGc6UGxhdGVOYW1lcz4gPHhtcFRQZzpTd2F0Y2hHcm91cHM+IDxyZGY6U2VxPiA8cmRmOmxpPiA8cmRmOkRlc2NyaXB0aW9uIHhtcEc6Z3JvdXBOYW1lPSJTdGFuZGFyZC1GYXJiZmVsZGdydXBwZSIgeG1wRzpncm91cFR5cGU9IjAiPiA8eG1wRzpDb2xvcmFudHM+IDxyZGY6U2VxPiA8cmRmOmxpIHhtcEc6c3dhdGNoTmFtZT0iQmxhdSBBVExBTlRJUyIgeG1wRzp0eXBlPSJQUk9DRVNTIiB4bXBHOnRpbnQ9IjEwMC4wMDAwMDAiIHhtcEc6bW9kZT0iQ01ZSyIgeG1wRzpjeWFuPSIxMDAuMDAwMDAwIiB4bXBHOm1hZ2VudGE9IjY5Ljk5OTk5OSIgeG1wRzp5ZWxsb3c9IjEwLjAwMDAwMCIgeG1wRzpibGFjaz0iMC4wMDAwMDAiLz4gPHJkZjpsaSB4bXBHOnN3YXRjaE5hbWU9IkM9MCBNPTAgWT0wIEs9MCAxIiB4bXBHOm1vZGU9IkNNWUsiIHhtcEc6dHlwZT0iUFJPQ0VTUyIgeG1wRzpjeWFuPSIwLjAwMDAwMCIgeG1wRzptYWdlbnRhPSIwLjAwMDAwMCIgeG1wRzp5ZWxsb3c9IjAuMDAwMDAwIiB4bXBHOmJsYWNrPSIwLjAwMDAwMCIvPiA8cmRmOmxpIHhtcEc6c3dhdGNoTmFtZT0iVHVlcmtpcyIgeG1wRzp0eXBlPSJQUk9DRVNTIiB4bXBHOnRpbnQ9IjEwMC4wMDAwMDAiIHhtcEc6bW9kZT0iQ01ZSyIgeG1wRzpjeWFuPSI4OS45OTk5OTgiIHhtcEc6bWFnZW50YT0iNS4wMDAwMDAiIHhtcEc6eWVsbG93PSI0MC4wMDAwMDEiIHhtcEc6YmxhY2s9IjAuMDAwMDAwIi8+IDxyZGY6bGkgeG1wRzpzd2F0Y2hOYW1lPSJDPTAgTT0wIFk9MCBLPTEwMCIgeG1wRzptb2RlPSJDTVlLIiB4bXBHOnR5cGU9IlBST0NFU1MiIHhtcEc6Y3lhbj0iMC4wMDAwMDAiIHhtcEc6bWFnZW50YT0iMC4wMDAwMDAiIHhtcEc6eWVsbG93PSIwLjAwMDAwMCIgeG1wRzpibGFjaz0iMTAwLjAwMDAwMCIvPiA8L3JkZjpTZXE+IDwveG1wRzpDb2xvcmFudHM+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpsaT4gPC9yZGY6U2VxPiA8L3htcFRQZzpTd2F0Y2hHcm91cHM+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjExYmMwNDFlLTVhYjEtNDEwMC04ZDM3LTYyMGVjOWJkM2MyYyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoxMWJjMDQxZS01YWIxLTQxMDAtOGQzNy02MjBlYzliZDNjMmMiIHN0UmVmOm9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDoyMDAxMWI3ZC1lN2JjLTQ4ZDgtYTYwNC05YTlkOWQwYmM5NTkiIHN0UmVmOnJlbmRpdGlvbkNsYXNzPSJkZWZhdWx0Ii8+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjIwMDExYjdkLWU3YmMtNDhkOC1hNjA0LTlhOWQ5ZDBiYzk1OSIgc3RFdnQ6d2hlbj0iMjAyMy0wOS0yNVQxMjoyNTozNyswMjowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgSWxsdXN0cmF0b3IgMjcuOSAoTWFjaW50b3NoKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6ZjQwNWEyNjQtNTFkMS00OWY0LTg0NjQtM2U5ZTMyMmExOGRjIiBzdEV2dDp3aGVuPSIyMDI0LTA3LTE1VDEyOjUwOjA4KzAyOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBJbGx1c3RyYXRvciAyOC41IChNYWNpbnRvc2gpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJjb252ZXJ0ZWQiIHN0RXZ0OnBhcmFtZXRlcnM9ImZyb20gYXBwbGljYXRpb24vcG9zdHNjcmlwdCB0byBpbWFnZS9lcHNmIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoxMWJLine truncated
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
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 truncated
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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>Kein Läufer zugewiesen</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>Blank card</p>
|
||||||
|
{{ end}}
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf8">
|
||||||
|
<title>Läuferurkunde</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
|
||||||
|
<style>
|
||||||
|
.sheet {
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
page-break-after: always;
|
||||||
|
padding: 1.2cm 2cm 1.2cm 2cm;
|
||||||
|
background-image: url("data:image/png;base64,{{ loadImage "certificate_background" }}");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 11cm;
|
||||||
|
background-position: 5cm 5cm;
|
||||||
|
color: #000;
|
||||||
|
/* border-style: solid; */
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
p {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.A4 .sheet {
|
||||||
|
width: 210mm;
|
||||||
|
height: 296mm
|
||||||
|
}
|
||||||
|
|
||||||
|
.certificate-footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0cm;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
border: 1px solid black;
|
||||||
|
font-size: large;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="A4 landscape">
|
||||||
|
{{ range .Runners }}
|
||||||
|
<article class="sheet">
|
||||||
|
<header class="content has-text-centered">
|
||||||
|
<p style="font-size: 3cm; font-weight: bold;">Urkunde</p>
|
||||||
|
</header>
|
||||||
|
<main class="content has-text-centered" style="padding-top: 3cm;">
|
||||||
|
<p style="width: 50%; font-size: 4vw; font-weight: bold; margin-bottom: 0; display: inline;">{{ .FirstName }}
|
||||||
|
{{ .MiddleName }} {{ .LastName }}
|
||||||
|
</p>
|
||||||
|
<p style="font-size: 1cm; margin-bottom: 0;">hat beim {{ $.EventName }}</p>
|
||||||
|
<p style="font-size: 2cm; font-weight: bold; margin-bottom: 0;">{{formatUnit "kilometer" $.Locale .Distance}}km</p>
|
||||||
|
<p style="font-size: 1cm;">für den guten Zweck zurückgelegt</p>
|
||||||
|
</main>
|
||||||
|
<footer class="certificate-footer">
|
||||||
|
<img src="data:image/png;base64,{{ loadImage "certificate_footer" }}">
|
||||||
|
</footer>
|
||||||
|
</article>
|
||||||
|
<article class="sheet">
|
||||||
|
<header class="content has-text-centered">
|
||||||
|
<p style="font-size: 2.5cm; font-weight: bold;">Sponsorings</p>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<table style="border: solid; width: 17cm;">
|
||||||
|
<thead>
|
||||||
|
<td class=".td-head">Sponsor:in</td>
|
||||||
|
<td>Betrag/km</td>
|
||||||
|
<td>Gesamtbetrag</td>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{ range .DistanceDonations}}
|
||||||
|
<tr>
|
||||||
|
<td>{{ .Donor.FirstName }} {{ .Donor.MiddleName }} {{ .Donor.LastName }}</td>
|
||||||
|
<td>{{ formatUnit "euro" $.Locale .AmountPerDistance }} {{ $.CurrencySymbol }}</td>
|
||||||
|
<td>{{ formatUnit "euro" $.Locale .Amount }} {{ $.CurrencySymbol }}</td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<td>Gesamt</td>
|
||||||
|
<td>{{ formatUnit "euro" $.Locale .TotalPerDistance }} {{ $.CurrencySymbol }}</td>
|
||||||
|
<td>{{ formatUnit "euro" $.Locale .TotalDonations }} {{ $.CurrencySymbol }}</td>
|
||||||
|
</table>
|
||||||
|
</main>
|
||||||
|
<footer class="certificate-footer">
|
||||||
|
<table style="border-collapse: collapse; border: none; width: 17cm;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="border: none; width: 50%; text-align: center;">Link zu deinen Rundenzeiten</th>
|
||||||
|
<th style="border: none; width: 50%; text-align: center;">Spende überweisen</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="border: none; text-align: center;">
|
||||||
|
<img src="data:image/png;base64,{{ barcode .SelfServiceLink "qr" "" }}" style="height: 2.5cm; padding: 0.2cm">
|
||||||
|
</td>
|
||||||
|
<td style="border: none; text-align: center;">
|
||||||
|
<img src="data:image/png;base64,{{ epcCode $.SepaConfig.IBAN $.SepaConfig.BIC $.SepaConfig.HolderName (print "Spende LfK " .ID ", " .FirstName " " .LastName ", " .CombinedGroupName) .TotalDonations $.SepaConfig.CurrencyIdentifier}}" style="height: 2.5cm; padding: 0.2cm">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p style="width: 17cm; text-align: center;">
|
||||||
|
Sponsoren überweisen ihre Beträge bitte auf unser Konto: {{ $.SepaConfig.HolderName }} | IBAN: {{ $.SepaConfig.IBAN }} | BIC: {{ $.SepaConfig.BIC }} | Vz: "Spende LfK {{.ID}}, {{ .FirstName }} {{ .LastName }}, {{.CombinedGroupName}}"
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
</article>
|
||||||
|
{{ end }}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf8">
|
||||||
|
<title>Runner certificate</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
|
||||||
|
<style>
|
||||||
|
.sheet {
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
page-break-after: always;
|
||||||
|
padding: 1.2cm 2cm 1.2cm 2cm;
|
||||||
|
background-image: url("data:image/png;base64,{{ loadImage "certificate_background" }}");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 11cm;
|
||||||
|
background-position: 5cm 5cm;
|
||||||
|
color: #000;
|
||||||
|
/* border-style: solid; */
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
p {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.A4 .sheet {
|
||||||
|
width: 210mm;
|
||||||
|
height: 296mm
|
||||||
|
}
|
||||||
|
|
||||||
|
.certificate-footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0cm;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
border: 1px solid black;
|
||||||
|
font-size: large;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="A4 landscape">
|
||||||
|
{{ range .Runners }}
|
||||||
|
<article class="sheet">
|
||||||
|
<header class="content has-text-centered">
|
||||||
|
<p style="font-size: 3cm; font-weight: bold;">Certificate</p>
|
||||||
|
</header>
|
||||||
|
<main class="content has-text-centered" style="padding-top: 3cm;">
|
||||||
|
<p style="width: 50%; font-size: 4vw; font-weight: bold; margin-bottom: 0; display: inline;">{{ .FirstName }}
|
||||||
|
{{ .MiddleName }} {{ .LastName }}
|
||||||
|
</p>
|
||||||
|
<p style="font-size: 1cm; margin-bottom: 0;">Ran</p>
|
||||||
|
<p style="font-size: 2cm; font-weight: bold; margin-bottom: 0;">{{formatUnit "kilometer" $.Locale .Distance}}km</p>
|
||||||
|
<p style="font-size: 1cm;">for our good cause at the {{ $.EventName }}</p>
|
||||||
|
</main>
|
||||||
|
<footer class="certificate-footer">
|
||||||
|
<img src="data:image/png;base64,{{ loadImage "certificate_footer" }}">
|
||||||
|
</footer>
|
||||||
|
</article>
|
||||||
|
<article class="sheet">
|
||||||
|
<header class="content has-text-centered">
|
||||||
|
<p style="font-size: 2.5cm; font-weight: bold;">Donations</p>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<table style="border: solid; width: 17cm;">
|
||||||
|
<thead>
|
||||||
|
<td class=".td-head">Donor</td>
|
||||||
|
<td>Amount / km</td>
|
||||||
|
<td>Total</td>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{ range .DistanceDonations}}
|
||||||
|
<tr>
|
||||||
|
<td>{{ .Donor.FirstName }} {{ .Donor.MiddleName }} {{ .Donor.LastName }}</td>
|
||||||
|
<td>{{ formatUnit "euro" $.Locale .AmountPerDistance }} {{ $.CurrencySymbol }}</td>
|
||||||
|
<td>{{ formatUnit "euro" $.Locale .Amount }} {{ $.CurrencySymbol }}</td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<td>Combined</td>
|
||||||
|
<td>{{ formatUnit "euro" $.Locale .TotalPerDistance }} {{ $.CurrencySymbol }}</td>
|
||||||
|
<td>{{ formatUnit "euro" $.Locale .TotalDonations }} {{ $.CurrencySymbol }}</td>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</main>
|
||||||
|
<footer class="certificate-footer">
|
||||||
|
<table style="border-collapse: collapse; border: none; width: 17cm;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="border: none; width: 50%; text-align: center;">Link to your lap times</th>
|
||||||
|
<th style="border: none; width: 50%; text-align: center;">Transfer donation via SEPA</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="border: none; text-align: center;">
|
||||||
|
<img src="data:image/png;base64,{{ barcode .SelfServiceLink "qr" "" }}" style="height: 2.5cm; padding: 0.2cm">
|
||||||
|
</td>
|
||||||
|
<td style="border: none; text-align: center;">
|
||||||
|
<img src="data:image/png;base64,{{ epcCode $.SepaConfig.IBAN $.SepaConfig.BIC $.SepaConfig.HolderName (print "Spende LfK " .ID ", " .FirstName " " .LastName ", " .CombinedGroupName) .TotalDonations $.SepaConfig.CurrencyIdentifier}}" style="height: 2.5cm; padding: 0.2cm">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p style="width: 17cm; text-align: center;">
|
||||||
|
Donors, please transfer your donation to our account: {{ $.SepaConfig.HolderName }} | IBAN: {{ $.SepaConfig.IBAN }} | BIC: {{ $.SepaConfig.BIC }} | Ref: "Spende LfK {{.ID}}, {{ .FirstName }} {{ .LastName }}, {{.CombinedGroupName}}"
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
</article>
|
||||||
|
{{ end }}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
<body class="A5 landscape">
|
<body class="A5 landscape">
|
||||||
{{ range .Runners }}
|
{{ range .Runners }}
|
||||||
<div class="sheet">
|
<div class="sheet">
|
||||||
<img id="header_img" width="100%" src="data:image/png;base64,{{ $.SponsoringHeader}}" />
|
<img id="header_img" width="100%" src="data:image/png;base64,{{ loadImage "sponsoringheader" }}" />
|
||||||
<div style=" padding: 0 1rem 0 1rem;">
|
<div style=" padding: 0 1rem 0 1rem;">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-10">
|
<div class="column is-10">
|
||||||
@@ -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
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
<body class="A5 landscape">
|
<body class="A5 landscape">
|
||||||
{{ range .Runners }}
|
{{ range .Runners }}
|
||||||
<div class="sheet">
|
<div class="sheet">
|
||||||
<img id="header_img" width="100%" src="data:image/png;base64,{{ $.SponsoringHeader}}" />
|
<img id="header_img" width="100%" src="data:image/png;base64,{{ loadImage "sponsoringheader" }}" />
|
||||||
<div style=" padding: 0 1rem 0 1rem;">
|
<div style=" padding: 0 1rem 0 1rem;">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-10">
|
<div class="column is-10">
|
||||||
@@ -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>
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf8">
|
|
||||||
<title>Sponsoring contract</title>
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
|
|
||||||
<style>
|
|
||||||
.sheet {
|
|
||||||
margin: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
box-sizing: border-box;
|
|
||||||
page-break-after: always;
|
|
||||||
padding: 1.2cm 2cm 1.2cm 2cm;
|
|
||||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAqsAAASrCAYAAAC1y/HLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDIxLTAzLTMxVDEzOjUzOjUyKzAyOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyMS0wMy0zMVQxNzowMzo1OCswMjowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyMS0wMy0zMVQxNzowMzo1OCswMjowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2MjMzNzM0My1jYjBj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 truncated
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 11cm;
|
|
||||||
background-position: 5cm 5cm;
|
|
||||||
color: #000;
|
|
||||||
/* border-style: solid; */
|
|
||||||
}
|
|
||||||
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
p {
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.A4 .sheet {
|
|
||||||
width: 210mm;
|
|
||||||
height: 296mm
|
|
||||||
}
|
|
||||||
|
|
||||||
.certificate-footer {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0cm;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
border: 1px solid black;
|
|
||||||
font-size: large;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body class="A4 landscape">
|
|
||||||
{{ range .Runners }}
|
|
||||||
<article class="sheet">
|
|
||||||
<header class="content has-text-centered">
|
|
||||||
<p style="font-size: 3cm; font-weight: bold;">Urkunde</p>
|
|
||||||
</header>
|
|
||||||
<main class="content has-text-centered" style="padding-top: 3cm;">
|
|
||||||
<p style="width: 50%; font-size: 4vw; font-weight: bold; margin-bottom: 0; display: inline;">{{ .FirstName }}
|
|
||||||
{{ .MiddleName }} {{ .LastName }}
|
|
||||||
</p>
|
|
||||||
<p style="font-size: 1cm; margin-bottom: 0;">hat beim {{ $.EventName }}</p>
|
|
||||||
<p style="font-size: 2cm; font-weight: bold; margin-bottom: 0;">{{unitFormat "kilometer" .Distance}}km</p>
|
|
||||||
<p style="font-size: 1cm;">für den guten Zweck zurückgelegt</p>
|
|
||||||
</main>
|
|
||||||
<footer class="certificate-footer">
|
|
||||||
<img src="./certificate_footer.png">
|
|
||||||
</footer>
|
|
||||||
</article>
|
|
||||||
<article class="sheet">
|
|
||||||
<header class="content has-text-centered">
|
|
||||||
<p style="font-size: 2.5cm; font-weight: bold;">Sponsorings</p>
|
|
||||||
</header>
|
|
||||||
<main>
|
|
||||||
<table style="border: solid; width: 17cm;">
|
|
||||||
<thead>
|
|
||||||
<td class=".td-head">Sponsor:in</td>
|
|
||||||
<td>Betrag/km</td>
|
|
||||||
<td>Gesamtbetrag</td>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{{ range .DistanceDonations}}
|
|
||||||
<tr>
|
|
||||||
<td>{{ .Donor.FirstName }} {{ .Donor.MiddleName }} {{ .Donor.LastName }}</td>
|
|
||||||
<td>{{ unitFormat "euro" .AmountPerDistance }} {{ $.CurrencySymbol }}</td>
|
|
||||||
<td>{{ unitFormat "euro" .Amount }} {{ $.CurrencySymbol }}</td>
|
|
||||||
</tr>
|
|
||||||
{{ end }}
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<td>Gesamt</td>
|
|
||||||
<td>{{ unitFormat .TotalPerDistance }} {{ $.CurrencySymbol }}</td>
|
|
||||||
<td>{{ unitFormat .TotalDonations }} {{ $.CurrencySymbol }}</td>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</main>
|
|
||||||
<footer class="certificate-footer">
|
|
||||||
<p>
|
|
||||||
{{ $.Footer }}
|
|
||||||
</p>
|
|
||||||
</footer>
|
|
||||||
</article>
|
|
||||||
{{ end }}
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 225 KiB |
@@ -1,65 +0,0 @@
|
|||||||
package templates
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "embed"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed card/en.html
|
|
||||||
var CardTemplateEn string
|
|
||||||
|
|
||||||
//go:embed card/de.html
|
|
||||||
var CardTemplateDe string
|
|
||||||
|
|
||||||
//go:embed contract/en.html
|
|
||||||
var ContractTemplateEn string
|
|
||||||
|
|
||||||
//go:embed contract/de.html
|
|
||||||
var ContractTemplateDe string
|
|
||||||
|
|
||||||
//go:embed certificate/de.html
|
|
||||||
var CertificateTemplateDe string
|
|
||||||
|
|
||||||
//go:embed images/sponsoringheader.base64
|
|
||||||
var ImageSponsoringHeaderBase64 string
|
|
||||||
|
|
||||||
//go:embed images/error.base64
|
|
||||||
var ImageErrorBase64 string
|
|
||||||
|
|
||||||
func GetTemplate(locale, templateName string) (string, error) {
|
|
||||||
switch templateName {
|
|
||||||
case "card":
|
|
||||||
switch locale {
|
|
||||||
case "en":
|
|
||||||
return CardTemplateEn, nil
|
|
||||||
case "de":
|
|
||||||
return CardTemplateDe, nil
|
|
||||||
}
|
|
||||||
case "contract":
|
|
||||||
switch locale {
|
|
||||||
case "en":
|
|
||||||
return ContractTemplateEn, nil
|
|
||||||
case "de":
|
|
||||||
return ContractTemplateDe, nil
|
|
||||||
}
|
|
||||||
case "certificate":
|
|
||||||
switch locale {
|
|
||||||
case "en":
|
|
||||||
return ContractTemplateEn, nil
|
|
||||||
case "de":
|
|
||||||
return CertificateTemplateDe, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("template not found with name %s and locale %s", templateName, locale)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetImage(imageName string) string {
|
|
||||||
content, err := os.ReadFile("static/images/" + imageName + ".base64")
|
|
||||||
if content == nil || err != nil {
|
|
||||||
log.Printf("error reading image %s: %v", imageName, err)
|
|
||||||
return ImageErrorBase64
|
|
||||||
}
|
|
||||||
return string(content)
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user