Compare commits
47 Commits
1dfd96869d
...
1.4.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
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
|
2
.env
2
.env
@@ -1,9 +1,11 @@
|
|||||||
|
LOGLEVEL=debug
|
||||||
PORT=3000
|
PORT=3000
|
||||||
PRODUCION=false
|
PRODUCION=false
|
||||||
APIKEY=lfk
|
APIKEY=lfk
|
||||||
EVENTNAME=Lauf für Kaya! 2025
|
EVENTNAME=Lauf für Kaya! 2025
|
||||||
CURRENCYSYMBOL=€
|
CURRENCYSYMBOL=€
|
||||||
GOTENBERG_BASEURL=http://localhost:3001
|
GOTENBERG_BASEURL=http://localhost:3001
|
||||||
|
REDIS_ADDR=localhost:6379
|
||||||
|
|
||||||
CARD_SUBTITLE=Kaya ist cool
|
CARD_SUBTITLE=Kaya ist cool
|
||||||
CARD_BARCODEFORMAT=ean13
|
CARD_BARCODEFORMAT=ean13
|
||||||
|
|||||||
27
.gitea/workflows/dev.yaml
Normal file
27
.gitea/workflows/dev.yaml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
name: Build latest image
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-container:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Login to registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: registry.odit.services
|
||||||
|
username: ${{ vars.REGISTRY_USERNAME }}
|
||||||
|
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ vars.REGISTRY }}/lfk/document-server:latest
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
27
.gitea/workflows/release.yaml
Normal file
27
.gitea/workflows/release.yaml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
name: Build release images
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "*.*.*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-container:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Login to registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: registry.odit.services
|
||||||
|
username: ${{ vars.REGISTRY_USERNAME }}
|
||||||
|
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ vars.REGISTRY }}/lfk/document-server:${{ github.ref_name }}
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
steps:
|
|
||||||
- name: build latest
|
|
||||||
image: woodpeckerci/plugin-docker-buildx
|
|
||||||
settings:
|
|
||||||
repo: registry.odit.services/lfk/document-server
|
|
||||||
tags:
|
|
||||||
- latest
|
|
||||||
registry: registry.odit.services
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
cache_from: registry.odit.services/lfk/document-server:latest
|
|
||||||
username:
|
|
||||||
from_secret: odit-registry-builder-username
|
|
||||||
password:
|
|
||||||
from_secret: odit-registry-builder-password
|
|
||||||
when:
|
|
||||||
branch: main
|
|
||||||
when:
|
|
||||||
event: push
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
steps:
|
|
||||||
- name: build tag
|
|
||||||
image: woodpeckerci/plugin-docker-buildx
|
|
||||||
settings:
|
|
||||||
repo: registry.odit.services/lfk/document-server
|
|
||||||
tags:
|
|
||||||
- "${CI_COMMIT_TAG}"
|
|
||||||
registry: registry.odit.services
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
cache_from: registry.odit.services/lfk/document-server:latest
|
|
||||||
username:
|
|
||||||
from_secret: odit-registry-builder-username
|
|
||||||
password:
|
|
||||||
from_secret: odit-registry-builder-password
|
|
||||||
when:
|
|
||||||
event:
|
|
||||||
- tag
|
|
||||||
26
README.md
26
README.md
@@ -68,3 +68,29 @@ docker build -t registry.odit.services/lfk/document-server:latest .
|
|||||||
# multiarch
|
# multiarch
|
||||||
docker buildx build --platform=linux/amd64,linux/arm64 -t registry.odit.services/lfk/document-server:latest --push .
|
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 |
|
||||||
@@ -3,3 +3,7 @@ services:
|
|||||||
image: gotenberg/gotenberg:8
|
image: gotenberg/gotenberg:8
|
||||||
ports:
|
ports:
|
||||||
- "3001:3000"
|
- "3001:3000"
|
||||||
|
redis:
|
||||||
|
image: docker.dragonflydb.io/dragonflydb/dragonfly
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
@@ -9,3 +9,7 @@ services:
|
|||||||
image: gotenberg/gotenberg:8
|
image: gotenberg/gotenberg:8
|
||||||
ports:
|
ports:
|
||||||
- "3001:3000"
|
- "3001:3000"
|
||||||
|
redis:
|
||||||
|
image: docker.dragonflydb.io/dragonflydb/dragonfly
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
@@ -4,6 +4,7 @@ APIKEY=lfk
|
|||||||
EVENTNAME=Lauf für Kaya! 2025
|
EVENTNAME=Lauf für Kaya! 2025
|
||||||
CURRENCYSYMBOL=€
|
CURRENCYSYMBOL=€
|
||||||
GOTENBERG_BASEURL=http://gotenberg:3000
|
GOTENBERG_BASEURL=http://gotenberg:3000
|
||||||
|
REDIS_ADDR=redis:6379
|
||||||
|
|
||||||
CARD_SUBTITLE=Kaya ist cool
|
CARD_SUBTITLE=Kaya ist cool
|
||||||
CARD_BARCODEFORMAT=ean13
|
CARD_BARCODEFORMAT=ean13
|
||||||
|
|||||||
117
docs/docs.go
117
docs/docs.go
@@ -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,13 @@ const docTemplate = `{
|
|||||||
"responses": {}
|
"responses": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/certificates": {
|
"/v1/pdfs/certificates": {
|
||||||
"post": {
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"description": "Generate certificates based on the provided data",
|
"description": "Generate certificates based on the provided data",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
@@ -52,7 +131,7 @@ const docTemplate = `{
|
|||||||
"application/pdf"
|
"application/pdf"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"certificates"
|
"pdfs"
|
||||||
],
|
],
|
||||||
"summary": "Generate runner certificates",
|
"summary": "Generate runner certificates",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@@ -69,8 +148,13 @@ const docTemplate = `{
|
|||||||
"responses": {}
|
"responses": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/contracts": {
|
"/v1/pdfs/contracts": {
|
||||||
"post": {
|
"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"
|
||||||
@@ -79,7 +163,7 @@ const docTemplate = `{
|
|||||||
"application/pdf"
|
"application/pdf"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"contracts"
|
"pdfs"
|
||||||
],
|
],
|
||||||
"summary": "Generate a contract",
|
"summary": "Generate a contract",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@@ -237,18 +321,22 @@ const docTemplate = `{
|
|||||||
"models.Group": {
|
"models.Group": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"id",
|
|
||||||
"name"
|
"name"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"parent_group": {
|
"parent_group": {
|
||||||
"$ref": "#/definitions/models.Group"
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -327,12 +415,7 @@ const docTemplate = `{
|
|||||||
"name": "key",
|
"name": "key",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"ApiKeyAuth": []
|
|
||||||
}
|
}
|
||||||
]
|
|
||||||
}`
|
}`
|
||||||
|
|
||||||
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||||
|
|||||||
@@ -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,13 @@
|
|||||||
"responses": {}
|
"responses": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/certificates": {
|
"/v1/pdfs/certificates": {
|
||||||
"post": {
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
"description": "Generate certificates based on the provided data",
|
"description": "Generate certificates based on the provided data",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
@@ -43,7 +122,7 @@
|
|||||||
"application/pdf"
|
"application/pdf"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"certificates"
|
"pdfs"
|
||||||
],
|
],
|
||||||
"summary": "Generate runner certificates",
|
"summary": "Generate runner certificates",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@@ -60,8 +139,13 @@
|
|||||||
"responses": {}
|
"responses": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/contracts": {
|
"/v1/pdfs/contracts": {
|
||||||
"post": {
|
"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"
|
||||||
@@ -70,7 +154,7 @@
|
|||||||
"application/pdf"
|
"application/pdf"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"contracts"
|
"pdfs"
|
||||||
],
|
],
|
||||||
"summary": "Generate a contract",
|
"summary": "Generate a contract",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@@ -228,18 +312,22 @@
|
|||||||
"models.Group": {
|
"models.Group": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"id",
|
|
||||||
"name"
|
"name"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"parent_group": {
|
"parent_group": {
|
||||||
"$ref": "#/definitions/models.Group"
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -318,10 +406,5 @@
|
|||||||
"name": "key",
|
"name": "key",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"ApiKeyAuth": []
|
|
||||||
}
|
}
|
||||||
]
|
|
||||||
}
|
}
|
||||||
@@ -94,14 +94,16 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
models.Group:
|
models.Group:
|
||||||
properties:
|
properties:
|
||||||
id:
|
|
||||||
type: integer
|
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
parent_group:
|
parent_group:
|
||||||
$ref: '#/definitions/models.Group'
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
type: object
|
||||||
required:
|
required:
|
||||||
- id
|
|
||||||
- name
|
- name
|
||||||
type: object
|
type: object
|
||||||
models.Runner:
|
models.Runner:
|
||||||
@@ -152,12 +154,63 @@ definitions:
|
|||||||
- last_name
|
- 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
|
||||||
@@ -172,10 +225,12 @@ paths:
|
|||||||
produces:
|
produces:
|
||||||
- application/pdf
|
- application/pdf
|
||||||
responses: {}
|
responses: {}
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
summary: Generate runner cards
|
summary: Generate runner cards
|
||||||
tags:
|
tags:
|
||||||
- cards
|
- pdfs
|
||||||
/certificates:
|
/v1/pdfs/certificates:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
@@ -190,10 +245,12 @@ paths:
|
|||||||
produces:
|
produces:
|
||||||
- application/pdf
|
- application/pdf
|
||||||
responses: {}
|
responses: {}
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
summary: Generate runner certificates
|
summary: Generate runner certificates
|
||||||
tags:
|
tags:
|
||||||
- certificates
|
- pdfs
|
||||||
/contracts:
|
/v1/pdfs/contracts:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
@@ -208,11 +265,11 @@ paths:
|
|||||||
produces:
|
produces:
|
||||||
- application/pdf
|
- application/pdf
|
||||||
responses: {}
|
responses: {}
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
summary: Generate a contract
|
summary: Generate a contract
|
||||||
tags:
|
tags:
|
||||||
- contracts
|
- pdfs
|
||||||
security:
|
|
||||||
- ApiKeyAuth: []
|
|
||||||
securityDefinitions:
|
securityDefinitions:
|
||||||
ApiKeyAuth:
|
ApiKeyAuth:
|
||||||
in: query
|
in: query
|
||||||
|
|||||||
6
go.mod
6
go.mod
@@ -14,6 +14,8 @@ 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/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
|
||||||
@@ -32,6 +34,7 @@ require (
|
|||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/oxplot/papersizes v0.0.0-20181201065918-90a3a5ae1915 // indirect
|
github.com/oxplot/papersizes v0.0.0-20181201065918-90a3a5ae1915 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 // 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/locafero v0.4.0 // indirect
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||||
@@ -46,7 +49,8 @@ require (
|
|||||||
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/atomic v1.9.0 // indirect
|
||||||
go.uber.org/multierr v1.9.0 // indirect
|
go.uber.org/multierr v1.10.0 // indirect
|
||||||
|
go.uber.org/zap v1.27.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||||
golang.org/x/sys v0.27.0 // indirect
|
golang.org/x/sys v0.27.0 // indirect
|
||||||
golang.org/x/text v0.19.0 // indirect
|
golang.org/x/text v0.19.0 // indirect
|
||||||
|
|||||||
10
go.sum
10
go.sum
@@ -4,9 +4,13 @@ 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.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 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
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=
|
||||||
@@ -54,6 +58,8 @@ github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6
|
|||||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||||
github.com/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=
|
||||||
@@ -101,6 +107,10 @@ go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
|||||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||||
|
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||||
|
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||||
|
|||||||
73
handlers/barcode.go
Normal file
73
handlers/barcode.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GenerateBarcode godoc
|
||||||
|
//
|
||||||
|
// @Summary Generate barcodes
|
||||||
|
// @Description Generate barcodes based on the provided data
|
||||||
|
// @Tags barcodes
|
||||||
|
// @Param type path string true "Barcode type" Enums(ean13, code128)
|
||||||
|
// @Param content path string true "Barcode content" MinLength(1)
|
||||||
|
// @Param width query int false "Barcode width" Minimum(1) Maximum(10000) default(1000)
|
||||||
|
// @Param height query int false "Barcode height" Minimum(1) Maximum(10000) default(1000)
|
||||||
|
// @Param padding query int false "Padding around the barcode (included in image size)" Minimum(0) Maximum(100) default(10)
|
||||||
|
// @Produce image/png
|
||||||
|
// @Router /v1/barcodes/{type}/{content} [get]
|
||||||
|
func (h *DefaultHandler) GenerateBarcode(c *fiber.Ctx) error {
|
||||||
|
|
||||||
|
logger := h.Logger.Named("GenerateBarcode")
|
||||||
|
|
||||||
|
// Get the type and content from the URL
|
||||||
|
barcodeType := c.Params("type")
|
||||||
|
barcodeContent := c.Params("content")
|
||||||
|
|
||||||
|
// Get the width and height from the query parameters
|
||||||
|
widthStr := c.Query("width", "1000")
|
||||||
|
heightStr := c.Query("height", "1000")
|
||||||
|
paddingStr := c.Query("padding", "10")
|
||||||
|
|
||||||
|
// Convert width and height to integers
|
||||||
|
width, err := strconv.Atoi(widthStr)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorw("Invalid width parameter", "width", widthStr, "error", err)
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"error": "Invalid width parameter",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
height, err := strconv.Atoi(heightStr)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorw("Invalid height parameter", "height", heightStr, "error", err)
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"error": "Invalid height parameter",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
padding, err := strconv.Atoi(paddingStr)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorw("Invalid padding parameter", "padding", paddingStr, "error", err)
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"error": "Invalid padding parameter",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
logger = logger.With("type", barcodeType, "content", barcodeContent, "width", width, "height", height, "padding", padding)
|
||||||
|
|
||||||
|
// Generate the barcode
|
||||||
|
logger.Info("Generating barcode")
|
||||||
|
barcode, err := h.BarcodeService.GenerateBarcode(barcodeType, barcodeContent, width, height, padding)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorw("Failed to generate barcode", "error", err)
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
logger.Info("Barcode generated")
|
||||||
|
|
||||||
|
c.Set(fiber.HeaderContentType, "image/png")
|
||||||
|
return c.Send(barcode.Bytes())
|
||||||
|
}
|
||||||
@@ -1,45 +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"
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GenerateCard godoc
|
// GenerateCard godoc
|
||||||
|
//
|
||||||
// @Summary Generate runner cards
|
// @Summary Generate runner cards
|
||||||
// @Description Generate cards based on the provided data
|
// @Description Generate cards based on the provided data
|
||||||
// @Tags cards
|
// @Tags pdfs
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param data body models.CardRequest true "Card data"
|
// @Param data body models.CardRequest true "Card data"
|
||||||
// @Produce application/pdf
|
// @Produce application/pdf
|
||||||
// @Router /cards [post]
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /v1/pdfs/cards [post]
|
||||||
func (h *DefaultHandler) GenerateCard(c *fiber.Ctx) error {
|
func (h *DefaultHandler) GenerateCard(c *fiber.Ctx) error {
|
||||||
|
|
||||||
|
logger := h.Logger.Named("GenerateCard")
|
||||||
|
|
||||||
cardRequest := new(models.CardRequest)
|
cardRequest := new(models.CardRequest)
|
||||||
if err := c.BodyParser(cardRequest); err != nil {
|
if err := c.BodyParser(cardRequest); err != nil {
|
||||||
|
logger.Errorw("Invalid request", "error", err)
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if !slices.Contains([]string{"en", "de"}, cardRequest.Locale) {
|
if !slices.Contains([]string{"en", "de"}, cardRequest.Locale) {
|
||||||
|
logger.Errorw("Invalid locale", "locale", cardRequest.Locale)
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
"error": "Invalid locale",
|
"error": "Invalid locale",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
generator := services.DefaultTemplater{}
|
logger = logger.With("locale", cardRequest.Locale)
|
||||||
templateString, err := services.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(),
|
||||||
})
|
})
|
||||||
@@ -53,22 +60,29 @@ func (h *DefaultHandler) GenerateCard(c *fiber.Ctx) error {
|
|||||||
BarcodePrefix: h.Config.CardBarcodePrefix,
|
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: h.Config.GotenbergBaseUrl}
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,45 +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"
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GenerateCertificate godoc
|
// GenerateCertificate godoc
|
||||||
|
//
|
||||||
// @Summary Generate runner certificates
|
// @Summary Generate runner certificates
|
||||||
// @Description Generate certificates based on the provided data
|
// @Description Generate certificates based on the provided data
|
||||||
// @Tags certificates
|
// @Tags pdfs
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param data body models.CertificateRequest true "Certificate data"
|
// @Param data body models.CertificateRequest true "Certificate data"
|
||||||
// @Produce application/pdf
|
// @Produce application/pdf
|
||||||
// @Router /certificates [post]
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /v1/pdfs/certificates [post]
|
||||||
func (h *DefaultHandler) GenerateCertificate(c *fiber.Ctx) error {
|
func (h *DefaultHandler) GenerateCertificate(c *fiber.Ctx) error {
|
||||||
|
|
||||||
|
logger := h.Logger.Named("GenerateCertificate")
|
||||||
|
|
||||||
certificateRequest := new(models.CertificateRequest)
|
certificateRequest := new(models.CertificateRequest)
|
||||||
if err := c.BodyParser(certificateRequest); err != nil {
|
if err := c.BodyParser(certificateRequest); err != nil {
|
||||||
|
logger.Errorw("Invalid request", "error", err)
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if !slices.Contains([]string{"en", "de"}, certificateRequest.Locale) {
|
if !slices.Contains([]string{"en", "de"}, certificateRequest.Locale) {
|
||||||
|
logger.Errorw("Invalid locale", "locale", certificateRequest.Locale)
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
"error": "Invalid locale",
|
"error": "Invalid locale",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
generator := services.DefaultTemplater{}
|
logger = logger.With("locale", certificateRequest.Locale)
|
||||||
templateString, err := services.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(),
|
||||||
})
|
})
|
||||||
@@ -53,22 +60,27 @@ func (h *DefaultHandler) GenerateCertificate(c *fiber.Ctx) error {
|
|||||||
Locale: certificateRequest.Locale,
|
Locale: certificateRequest.Locale,
|
||||||
}
|
}
|
||||||
|
|
||||||
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: h.Config.GotenbergBaseUrl}
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,54 +1,59 @@
|
|||||||
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"
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GenerateContract godoc
|
// GenerateContract godoc
|
||||||
|
//
|
||||||
// @Summary Generate a contract
|
// @Summary Generate a contract
|
||||||
// @Description Generate a contract based on the provided data
|
// @Description Generate a contract based on the provided data
|
||||||
// @Tags contracts
|
// @Tags pdfs
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param data body models.ContractRequest true "Contract data"
|
// @Param data body models.ContractRequest true "Contract data"
|
||||||
// @Produce application/pdf
|
// @Produce application/pdf
|
||||||
// @Router /contracts [post]
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /v1/pdfs/contracts [post]
|
||||||
func (h *DefaultHandler) GenerateContract(c *fiber.Ctx) error {
|
func (h *DefaultHandler) GenerateContract(c *fiber.Ctx) error {
|
||||||
|
|
||||||
|
logger := h.Logger.Named("GenerateContract")
|
||||||
|
|
||||||
contract := new(models.ContractRequest)
|
contract := new(models.ContractRequest)
|
||||||
if err := c.BodyParser(contract); err != nil {
|
if err := c.BodyParser(contract); err != nil {
|
||||||
|
logger.Errorw("Invalid request", "error", err)
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if !slices.Contains([]string{"en", "de"}, contract.Locale) {
|
if !slices.Contains([]string{"en", "de"}, contract.Locale) {
|
||||||
|
logger.Errorw("Invalid locale", "locale", contract.Locale)
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
"error": "Invalid locale",
|
"error": "Invalid locale",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
contract.Runners = repeatRunnerArrayItems(contract.Runners, 2)
|
logger = logger.With("locale", contract.Locale)
|
||||||
|
|
||||||
generator := services.DefaultTemplater{}
|
templateString, err := h.StaticService.GetTemplate(contract.Locale, "contract")
|
||||||
templateString, err := services.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: h.Config.CurrencySymbol,
|
CurrencySymbol: h.Config.CurrencySymbol,
|
||||||
Disclaimer: h.Config.SponosringDisclaimer,
|
Disclaimer: h.Config.SponosringDisclaimer,
|
||||||
ReceiptMinimumAmount: h.Config.SponsoringReceiptMinimum,
|
ReceiptMinimumAmount: h.Config.SponsoringReceiptMinimum,
|
||||||
@@ -57,22 +62,26 @@ func (h *DefaultHandler) GenerateContract(c *fiber.Ctx) error {
|
|||||||
BarcodePrefix: h.Config.SponsoringBarcodePrefix,
|
BarcodePrefix: h.Config.SponsoringBarcodePrefix,
|
||||||
}
|
}
|
||||||
|
|
||||||
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: h.Config.GotenbergBaseUrl}
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,23 @@ package handlers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"git.odit.services/lfk/document-server/models"
|
"git.odit.services/lfk/document-server/models"
|
||||||
|
"git.odit.services/lfk/document-server/services"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handler interface {
|
type Handler interface {
|
||||||
GenerateCard(*fiber.Ctx) error
|
GenerateCard(*fiber.Ctx) error
|
||||||
GenerateContract(*fiber.Ctx) error
|
GenerateContract(*fiber.Ctx) error
|
||||||
GenerateCertificate(*fiber.Ctx) error
|
GenerateCertificate(*fiber.Ctx) error
|
||||||
|
GenerateBarcode(*fiber.Ctx) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type DefaultHandler struct {
|
type DefaultHandler struct {
|
||||||
Config *models.Config
|
Config *models.Config
|
||||||
|
BarcodeService services.BarcodeService
|
||||||
|
Templater services.Templater
|
||||||
|
Converter services.Converter
|
||||||
|
StaticService services.StaticService
|
||||||
|
Logger *zap.SugaredLogger
|
||||||
}
|
}
|
||||||
|
|||||||
120
main.go
120
main.go
@@ -3,19 +3,27 @@ package main
|
|||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"log"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.odit.services/lfk/document-server/docs" // Correct import path for docs
|
"git.odit.services/lfk/document-server/docs" // Correct import path for docs
|
||||||
"git.odit.services/lfk/document-server/handlers"
|
"git.odit.services/lfk/document-server/handlers"
|
||||||
"git.odit.services/lfk/document-server/models"
|
"git.odit.services/lfk/document-server/models"
|
||||||
|
"git.odit.services/lfk/document-server/services"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||||
"github.com/gofiber/fiber/v2/middleware/keyauth"
|
"github.com/gofiber/fiber/v2/middleware/keyauth"
|
||||||
|
"github.com/gofiber/fiber/v2/middleware/requestid"
|
||||||
"github.com/gofiber/swagger"
|
"github.com/gofiber/swagger"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
config *models.Config
|
config *models.Config
|
||||||
|
logger *zap.SugaredLogger
|
||||||
)
|
)
|
||||||
|
|
||||||
func validateAPIKey(c *fiber.Ctx, key string) (bool, error) {
|
func validateAPIKey(c *fiber.Ctx, key string) (bool, error) {
|
||||||
@@ -30,11 +38,22 @@ func validateAPIKey(c *fiber.Ctx, key string) (bool, error) {
|
|||||||
|
|
||||||
func loadEnv() error {
|
func loadEnv() error {
|
||||||
|
|
||||||
|
viper.SetDefault("LOGLEVEL", "INFO")
|
||||||
|
viper.SetDefault("PRODUCION", false)
|
||||||
viper.SetDefault("PORT", "3000")
|
viper.SetDefault("PORT", "3000")
|
||||||
|
viper.SetDefault("APIKEY", "lfk")
|
||||||
|
viper.SetDefault("EVENTNAME", "Demo Event")
|
||||||
|
viper.SetDefault("CURRENCYSYMBOL", "€")
|
||||||
|
viper.SetDefault("CARD_SUBTITLE", "Runner Card")
|
||||||
viper.SetDefault("CARD_BARCODEFORMAT", "ean13")
|
viper.SetDefault("CARD_BARCODEFORMAT", "ean13")
|
||||||
viper.SetDefault("CARD_BARCODEPREFIX", "")
|
viper.SetDefault("CARD_BARCODEPREFIX", "")
|
||||||
|
viper.SetDefault("SPONSORING_RECEIPTMINIMUM", 0)
|
||||||
|
viper.SetDefault("SPONSORING_DISCLAIMER", "Disclaimer")
|
||||||
viper.SetDefault("SPONSORING_BARCODEFORMAT", "code128")
|
viper.SetDefault("SPONSORING_BARCODEFORMAT", "code128")
|
||||||
viper.SetDefault("SPONSORING_BARCODEPREFIX", "")
|
viper.SetDefault("SPONSORING_BARCODEPREFIX", "")
|
||||||
|
viper.SetDefault("CERTIFICATE_FOOTER", "Footer")
|
||||||
|
viper.SetDefault("GOTENBERG_BASEURL", "")
|
||||||
|
viper.SetDefault("REDIS_ADDR", "")
|
||||||
|
|
||||||
// Load .env file
|
// Load .env file
|
||||||
viper.SetConfigFile(".env")
|
viper.SetConfigFile(".env")
|
||||||
@@ -43,7 +62,7 @@ func loadEnv() error {
|
|||||||
viper.AutomaticEnv()
|
viper.AutomaticEnv()
|
||||||
err := viper.ReadInConfig()
|
err := viper.ReadInConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
logger.Warn("No .env file found")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshal the config from file and env into the config struct
|
// Unmarshal the config from file and env into the config struct
|
||||||
@@ -52,49 +71,124 @@ func loadEnv() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.Infow("Loaded config", "config", &config)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initLogger() error {
|
||||||
|
logLevel := os.Getenv("LOGLEVEL")
|
||||||
|
if logLevel == "" {
|
||||||
|
logLevel = "INFO"
|
||||||
|
}
|
||||||
|
|
||||||
|
var zapLogLevel zapcore.Level
|
||||||
|
err := zapLogLevel.UnmarshalText([]byte(strings.ToLower(logLevel)))
|
||||||
|
if err != nil {
|
||||||
|
zapLogLevel = zapcore.InfoLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
zapConfig := zap.NewProductionConfig()
|
||||||
|
zapConfig.Level = zap.NewAtomicLevelAt(zapLogLevel)
|
||||||
|
zapLogger, err := zapConfig.Build()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer zapLogger.Sync()
|
||||||
|
logger = zapLogger.Sugar()
|
||||||
|
|
||||||
|
logger.Debug("Initialized logger")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// @title LfK Document Server API
|
// @title LfK Document Server API
|
||||||
// @description This is the API documentation for the LfK Document Server - a tool for pdf generation.
|
// @description This is the API documentation for the LfK Document Server - a tool for pdf generation.
|
||||||
|
// @license.name CC BY-NC-SA 4.0
|
||||||
|
// @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
|
// @securityDefinitions.apiKey ApiKeyAuth
|
||||||
// @in query
|
// @in query
|
||||||
// @name key
|
// @name key
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
err := loadEnv()
|
// Init the logger
|
||||||
|
err := initLogger()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = loadEnv()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var redisClient *redis.Client
|
||||||
|
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{
|
handler := handlers.DefaultHandler{
|
||||||
Config: config,
|
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: config.Prod,
|
Prefork: config.Prod,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.Use(cors.New())
|
||||||
|
app.Use(requestid.New())
|
||||||
|
|
||||||
// Swagger documentation route
|
// Swagger documentation route
|
||||||
app.Get("/swagger/*", swagger.HandlerDefault)
|
app.Get("/swagger/*", swagger.HandlerDefault)
|
||||||
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
v1 := app.Group("/v1")
|
v1 := app.Group("/v1")
|
||||||
v1.Use(keyauth.New(keyauth.Config{
|
|
||||||
|
pdfv1 := v1.Group("/pdfs")
|
||||||
|
pdfv1.Use(keyauth.New(keyauth.Config{
|
||||||
KeyLookup: "query:key",
|
KeyLookup: "query:key",
|
||||||
Validator: validateAPIKey,
|
Validator: validateAPIKey,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
v1.Get("/", func(c *fiber.Ctx) error {
|
pdfv1.Post("/contracts", handler.GenerateContract)
|
||||||
return c.SendString("Hello, World!")
|
pdfv1.Post("/cards", handler.GenerateCard)
|
||||||
})
|
pdfv1.Post("/certificates", handler.GenerateCertificate)
|
||||||
v1.Post("/contracts", handler.GenerateContract)
|
|
||||||
v1.Post("/cards", handler.GenerateCard)
|
v1.Get("/barcodes/:type/:content", handler.GenerateBarcode)
|
||||||
v1.Post("/certificates", handler.GenerateCertificate)
|
logger.Debug("Initialized routes")
|
||||||
|
|
||||||
app.Use(handler.NotFoundHandler)
|
app.Use(handler.NotFoundHandler)
|
||||||
docs.SwaggerInfo.BasePath = "/"
|
docs.SwaggerInfo.BasePath = "/"
|
||||||
|
|
||||||
log.Fatal(app.Listen("0.0.0.0:" + config.Port))
|
logger.Infow("Starting server", "port", config.Port)
|
||||||
|
logger.Error(app.Listen("0.0.0.0:" + config.Port))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
LogLevel string `mapstructure:"LOGLEVEL"`
|
||||||
Prod bool `mapstructure:"PRODUCION"`
|
Prod bool `mapstructure:"PRODUCION"`
|
||||||
Port string `mapstructure:"PORT"`
|
Port string `mapstructure:"PORT"`
|
||||||
APIKey string `mapstructure:"APIKEY"`
|
APIKey string `mapstructure:"APIKEY"`
|
||||||
@@ -15,4 +16,5 @@ type Config struct {
|
|||||||
SponsoringBarcodePrefix string `mapstructure:"SPONSORING_BARCODEPREFIX"`
|
SponsoringBarcodePrefix string `mapstructure:"SPONSORING_BARCODEPREFIX"`
|
||||||
CertificateFooter string `mapstructure:"CERTIFICATE_FOOTER"`
|
CertificateFooter string `mapstructure:"CERTIFICATE_FOOTER"`
|
||||||
GotenbergBaseUrl string `mapstructure:"GOTENBERG_BASEURL"`
|
GotenbergBaseUrl string `mapstructure:"GOTENBERG_BASEURL"`
|
||||||
|
RedisAddr string `mapstructure:"REDIS_ADDR"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,10 @@ type Runner struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Group struct {
|
type Group struct {
|
||||||
ID int `json:"id" validate:"required"`
|
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
ParentGroup *Group `json:"parent_group" validate:"optional"`
|
ParentGroup struct {
|
||||||
|
Name string `json:"name" validate:"required"`
|
||||||
|
} `json:"parent_group" validate:"optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContractTemplateOptions struct {
|
type ContractTemplateOptions struct {
|
||||||
|
|||||||
125
services/barcode.go
Normal file
125
services/barcode.go
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
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":
|
||||||
|
generatedCode, err = qr.Encode(content, qr.M, qr.AlphaNumeric)
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,9 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"image/png"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/boombuler/barcode"
|
"go.uber.org/zap"
|
||||||
"github.com/boombuler/barcode/code128"
|
|
||||||
"github.com/boombuler/barcode/ean"
|
|
||||||
"github.com/boombuler/barcode/qr"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Templater interface {
|
type Templater interface {
|
||||||
@@ -21,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) {
|
||||||
@@ -37,60 +36,33 @@ func idToEan13(id string, prefix string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *DefaultTemplater) GenerateBarcode(code string, format string, prefix string) (string, error) {
|
func (t *DefaultTemplater) GenerateBarcode(code string, format string, prefix string) (string, error) {
|
||||||
var generatedCode barcode.Barcode
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
switch format {
|
if format == "ean13" {
|
||||||
case "ean13":
|
code, err = idToEan13(code, prefix)
|
||||||
encodedEan, 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, err := ListFilesInStaticSubFolder("images/sponsors")
|
logger := t.Logger.Named("SelectSponsorImage")
|
||||||
|
sponsors, err := t.StaticService.ListFilesInStaticSubFolder("images/sponsors")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
logger.Errorw("Failed to list sponsors", "error", err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return GetImage("sponsors/" + strings.TrimSuffix(sponsors[id%len(sponsors)], ".base64")), 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) {
|
func (t *DefaultTemplater) LoadImage(name string) (string, error) {
|
||||||
return GetImage(name), nil
|
return t.StaticService.GetImage(name), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *DefaultTemplater) FormatUnit(unit string, locale string, amount int) (string, error) {
|
func (t *DefaultTemplater) FormatUnit(unit string, locale string, amount int) (string, error) {
|
||||||
|
|||||||
@@ -3,39 +3,67 @@ package services
|
|||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetTemplate(locale, templateName string) (string, error) {
|
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))
|
content, err := os.ReadFile(fmt.Sprintf("static/templates/%s/%s.html", templateName, locale))
|
||||||
if content == nil || err != nil {
|
if content == nil || err != nil {
|
||||||
log.Printf("error reading template %s with locale %s: %v", templateName, locale, err)
|
logger.Errorw("Failed to read template", "error", err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.Cache[locale+templateName] = string(content)
|
||||||
|
logger.Debugw("Saved template to cache", "key", locale+templateName)
|
||||||
|
|
||||||
return string(content), nil
|
return string(content), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListFilesInStaticSubFolder(folderName string) ([]string, error) {
|
func (s *DefaultStaticService) ListFilesInStaticSubFolder(folderName string) ([]string, error) {
|
||||||
|
logger := s.Logger.Named("ListFilesInStaticSubFolder").With("folder_name", folderName)
|
||||||
|
|
||||||
files, err := os.ReadDir(fmt.Sprintf("static/%s", folderName))
|
files, err := os.ReadDir(fmt.Sprintf("static/%s", folderName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error reading files from folder %s: %v", folderName, err)
|
logger.Errorw("Failed to list files", "error", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var images []string
|
var images []string
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
if file.IsDir() {
|
if file.IsDir() {
|
||||||
continue
|
logger.Debugw("Skipping directory", "file", file.Name())
|
||||||
}
|
}
|
||||||
images = append(images, file.Name())
|
images = append(images, file.Name())
|
||||||
}
|
}
|
||||||
|
logger.Debugw("Listed files", "files", images)
|
||||||
return images, nil
|
return images, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetImage(imageName string) string {
|
func (s *DefaultStaticService) GetImage(imageName string) string {
|
||||||
|
logger := s.Logger.Named("GetImage").With("image_name", imageName)
|
||||||
|
|
||||||
content, err := os.ReadFile("static/images/" + imageName + ".base64")
|
content, err := os.ReadFile("static/images/" + imageName + ".base64")
|
||||||
if content == nil || err != nil {
|
if content == nil || err != nil {
|
||||||
log.Printf("error reading image %s: %v", imageName, err)
|
logger.Errorw("Failed to read image", "error", err)
|
||||||
return ImageErrorBase64
|
return ImageErrorBase64
|
||||||
}
|
}
|
||||||
return string(content)
|
return string(content)
|
||||||
|
|||||||
@@ -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 }}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -65,8 +65,8 @@
|
|||||||
<p style="font-size: x-small; display: block;">Nachname</p>
|
<p style="font-size: x-small; display: block;">Nachname</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-6">
|
<div class="column is-6">
|
||||||
<span style="border-bottom: 1px solid; width: 100%; display: block;">{{ .Group.Name }}</span>
|
<span style="border-bottom: 1px solid; width: 100%; display: block;"><p>{{ if ne .Group.ParentGroup.Name "" -}}{{ .Group.ParentGroup.Name }}/ {{end -}}{{ .Group.Name }}</p></span>
|
||||||
<p style="font-size: x-small; display: block;">Team/ Klasse</p>
|
<p style="font-size: x-small; display: block;">Team/Klasse</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p style="margin-top: -0.5rem">mit einem Betrag von _____ {{ $.CurrencySymbol }} pro gelaufenem Kilometer zu
|
<p style="margin-top: -0.5rem">mit einem Betrag von _____ {{ $.CurrencySymbol }} pro gelaufenem Kilometer zu
|
||||||
|
|||||||
@@ -64,7 +64,7 @@
|
|||||||
<p style="font-size: x-small; display: block;">Last Name</p>
|
<p style="font-size: x-small; display: block;">Last Name</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-6">
|
<div class="column is-6">
|
||||||
<span style="border-bottom: 1px solid; width: 100%; display: block;">{{ .Group.Name}}</span>
|
<span style="border-bottom: 1px solid; width: 100%; display: block;"><p>{{ if ne .Group.ParentGroup.Name "" -}}{{ .Group.ParentGroup.Name }}/ {{end -}}{{ .Group.Name }}</p></span>
|
||||||
<p style="font-size: x-small; display: block;">Team/class</p>
|
<p style="font-size: x-small; display: block;">Team/class</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user