65 Commits
UMBE ... main

Author SHA1 Message Date
Jens Cornelsen
f56c0f83c2 Merge pull request #576 from IBCornelsen/staging
Fernwärme Pforzheim hinzugefuegt
2025-10-14 17:37:07 +02:00
Jens Cornelsen
35a849913e Fernwärme Pforzheim hinzugefuegt 2025-10-14 17:34:59 +02:00
Jens Cornelsen
e01b9b5445 Merge pull request #575 from IBCornelsen/staging
Update main
2025-10-01 16:24:41 +02:00
Jens Cornelsen
e3484718a0 Merge pull request #574 from IBCornelsen/dev
Update staging
2025-10-01 16:21:18 +02:00
Jens Cornelsen
762c0aeb97 Primaerenergiefasktor Strom angepasst 2025-10-01 16:11:44 +02:00
Moritz Utcke
c010bbaff0 Fläche darf nur noch Integer sein 2025-09-25 11:41:28 -04:00
Moritz Utcke
6e3f749b07 Merge branch 'main' into dev 2025-09-25 10:55:30 -04:00
Moritz Utcke
e4ef1230dd Registrierung Email Lowercase
Bei der Registrierung wird die Email nun immer zu Kleinbuchstaben umgewandelt
2025-09-25 10:54:56 -04:00
Moritz Utcke
a7bdd02652 HEIC und HEIF zu JPEG Konvertierung
Damit wird nun auch das bescheuerte Bildformat von Apple unterstützt
2025-09-25 10:43:49 -04:00
Moritz Utcke
aa60e63d1b Merge branch 'staging' 2025-09-25 10:20:38 -04:00
Moritz Utcke
6b5ae04e36 Merge branch 'dev' into staging 2025-09-25 10:20:31 -04:00
Moritz Utcke
634bef971d Float zu Int und Bildupload 2025-09-25 10:20:18 -04:00
Moritz Utcke
2b76953e68 Merge branch 'staging' [skip ci] 2025-09-25 08:09:22 -04:00
Moritz Utcke
192396933f Merge branch 'dev' into staging 2025-09-25 08:04:54 -04:00
Moritz Utcke
82f8cfc0ff Int zu float umgestellt 2025-09-24 14:26:08 -04:00
Moritz Utcke
c2e96aad74 Merge pull request #573 from IBCornelsen/staging
Resller kann sich nicht einloggen
2025-09-17 10:27:52 -04:00
Moritz Utcke
2d7ea57f57 Merge branch 'dev' into staging 2025-09-17 10:25:28 -04:00
Moritz Utcke
9e6e893c28 Reseller kann sich nicht einloggen 2025-09-17 10:25:15 -04:00
Moritz Utcke
f71c4b9ef9 Merge pull request #572 from IBCornelsen/staging
Klimafaktoren Bug Gefixt
2025-09-14 15:04:19 -03:00
Moritz Utcke
ce8d4bde18 Merge branch 'staging' into dev 2025-09-14 14:03:42 -04:00
Moritz Utcke
4e54511fe0 Klimafaktoren Bug Gefixt
In dem Client Skript was sich die Klimfaktoren abholen sollte war noch eine alte Definition der getKlimafaktoren Funktion genutzt worden.
2025-09-14 14:03:16 -04:00
Moritz Utcke
a1a286b67f Merge pull request #571 from IBCornelsen/staging
Abrechnung Fix
2025-09-12 13:12:35 -03:00
Moritz Utcke
fe4b4fb53a Merge branch 'dev' into staging 2025-09-12 12:11:25 -04:00
Moritz Utcke
b4f84d7ecd Abrechnung Fix 2025-09-12 12:11:05 -04:00
Jens Cornelsen
9157df7a1c Merge pull request #570 from IBCornelsen/staging
Update main
2025-09-12 17:58:20 +02:00
Jens Cornelsen
ef7086a08a Merge pull request #569 from IBCornelsen/dev
Partner Code aufgeräumt
2025-09-12 17:55:34 +02:00
Moritz Utcke
15d57f0a2c Partner Code aufgeräumt 2025-09-12 12:27:57 -03:00
Jens Cornelsen
82993ce74d Merge pull request #568 from IBCornelsen/staging
Update main
2025-09-12 17:14:50 +02:00
Jens Cornelsen
76fa34bf64 Merge pull request #567 from IBCornelsen/dev
Update staging
2025-09-12 17:13:25 +02:00
Jens Cornelsen
d31c8c2fb2 Fernwärme Neumünster 2025-09-08 14:07:41 +02:00
Jens Cornelsen
4f5b94cbd7 Fernwärme Neumünster 2025-09-08 14:05:27 +02:00
Jens Cornelsen
db4325ad2c Fernwärme Erfurt hinzugefügt 2025-09-07 14:00:25 +02:00
Jens Cornelsen
1d2f0a7ce8 Fernwärme Erfurt hinzugefügt 2025-09-07 13:46:11 +02:00
Moritz Utcke
3fc7196652 Docker Datenbank Log Limit 2025-08-30 18:27:00 -04:00
Carl Mahnke
a7bd398f39 Support Popup: Email vorbelegen wenn eingeloggt 2025-08-19 17:07:14 +02:00
Moritz Utcke
cd4e021332 Email von Nutzern klein machen 2025-08-06 10:53:15 -04:00
Moritz Utcke
9c897bd1e3 Merge branch 'main' into dev 2025-08-05 19:49:58 -04:00
Moritz Utcke
addb647618 Layout 2025-08-05 12:13:18 -04:00
Moritz Utcke
8d07486bed Logs entfernt 2025-08-05 11:57:30 -04:00
Moritz Utcke
208ade8219 Fix 2025-08-05 11:48:19 -04:00
Moritz Utcke
2cc44d2c09 Gesamtpreis 2025-08-05 11:36:31 -04:00
Moritz Utcke
366e813f29 Adresse 2025-08-05 11:16:14 -04:00
Moritz Utcke
5683596e09 Zeit Fix 2025-08-05 11:06:27 -04:00
Moritz Utcke
811d574576 Filter 2025-08-05 10:56:55 -04:00
Moritz Utcke
4df30004de Ausweistyp 2025-08-05 10:35:15 -04:00
Moritz Utcke
55a0248de4 Netto Fix 2025-08-05 09:53:45 -04:00
Moritz Utcke
b3c84e1cb6 Sortierung 2025-08-05 09:45:03 -04:00
Moritz Utcke
7b7a1f77a0 Zeitzonen 2025-08-05 09:41:00 -04:00
Moritz Utcke
24da37aa87 Merge pull request #565 from IBCornelsen/dev
Abrechnung PDF
2025-08-05 09:26:47 -04:00
Moritz Utcke
2fdd71f6d6 Merge remote-tracking branch 'origin/dev' into dev 2025-08-05 09:25:43 -04:00
Moritz Utcke
648ecc5901 PDF Abrechnung 2025-08-05 09:25:37 -04:00
Jens Cornelsen
f0e2bd8096 Merge pull request #564 from IBCornelsen/staging
Update main
2025-08-05 14:41:02 +02:00
Jens Cornelsen
10591444a8 Merge pull request #563 from IBCornelsen/dev
Update staging
2025-08-05 14:38:24 +02:00
Jens Cornelsen
6c706573b0 Merge branch 'staging' into dev 2025-08-05 14:33:53 +02:00
Jens Cornelsen
0106d380fc Merge pull request #562 from IBCornelsen/main
Update staging
2025-08-05 14:24:43 +02:00
Moritz Utcke
a05c32167e Benutzer Rolle Fix 2025-08-04 20:16:04 -04:00
Moritz Utcke
cbfa49b7bc Fix? 2025-08-04 19:33:17 -04:00
Moritz Utcke
f56684a6e8 Abrechnung Generierung 2025-08-04 17:23:04 -04:00
Moritz Utcke
2bee3f55c6 Ticket Telefon Hotfix 2025-08-04 11:09:36 -04:00
Moritz Utcke
eb04f5b0b5 Trello ID Hotfix 2025-08-04 11:04:41 -04:00
UMBENOMENA
0866a6e4a1 Merge pull request #561 from IBCornelsen/staging
Landingpage Menu Titel
2025-08-04 00:08:31 +02:00
Robert Jagtiani
0dc4289232 Titel PY- 2025-08-04 00:06:48 +02:00
Robert Jagtiani
6d8d8cb39e landingpage partner header menu und Titel fix 2025-08-04 00:06:24 +02:00
UMBENOMENA
3096f05f67 Merge pull request #560 from IBCornelsen/UMBE
Titel PY-
2025-08-03 22:47:44 +02:00
UMBENOMENA
ea695e3234 Merge pull request #559 from IBCornelsen/UMBE
Landing Page Menu Titel Fix
2025-08-03 22:36:30 +02:00
72 changed files with 796 additions and 500 deletions

View File

@@ -1,76 +0,0 @@
name: Auto Merge Staging into Main
on:
schedule:
- cron: '0 2 * * *' # 2:00 UTC = 4:00 Europäische Zeit
workflow_dispatch:
jobs:
merge:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set Git user
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Fetch all branches
run: |
git fetch origin main
git fetch origin staging
- name: Check if main has commits not in staging
id: check
run: |
git fetch origin
if [ $(git rev-list --count origin/staging..origin/main) -gt 0 ]; then
echo "❌ Staging is behind main and requires manual merging."
exit 1
fi
- name: Create PR from staging to main
id: create_pr
run: |
PR_URL=$(gh pr create --base main --head staging --title "Auto-merge staging into main" --body "This PR was created automatically by GitHub Actions. It merges the latest \`staging\` into \`main\`.")
echo "PR_URL=$PR_URL" >> $GITHUB_OUTPUT
PR_NUMBER=$(echo $PR_URL | awk -F'/' '{print $NF}')
echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_OUTPUT
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Enable auto-merge on PR
if: steps.create_pr.outputs.PR_NUMBER != ''
run: |
gh pr merge ${{ steps.create_pr.outputs.PR_NUMBER }} --merge --auto
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
notify_failure:
needs: merge
if: failure()
runs-on: ubuntu-latest
steps:
- name: Send Discord notification on failure
run: |
curl -H "Content-Type: application/json" \
-X POST \
-d "{\"content\": \"🚨 Auto-Merge fehlgeschlagen! Bitte manuell prüfen: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}\"}" \
${{ secrets.DISCORD_WEBHOOK_URL }}
notify_success:
needs: merge
if: success()
runs-on: ubuntu-latest
steps:
- name: Send Discord notification on success
run: |
curl -H "Content-Type: application/json" \
-X POST \
-d "{\"content\": \"✅ Auto-Merge ausgeführt! Ergebnis jetzt auf [GitHub](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) und [online-energieausweis.org](https://online-energieausweis.org) einsehen.\"}" \
${{ secrets.DISCORD_WEBHOOK_URL }}

View File

@@ -30,6 +30,9 @@ run-database: stop-database
docker volume create $(DB_VOLUME) docker volume create $(DB_VOLUME)
docker build -t $(DB_CONTAINER_NAME) . docker build -t $(DB_CONTAINER_NAME) .
docker run -d --name $(DB_CONTAINER_NAME) \ docker run -d --name $(DB_CONTAINER_NAME) \
--log-driver=json-file \
--log-opt max-size=50m \
--log-opt max-file=3 \
--restart=always \ --restart=always \
-e POSTGRES_USER=$(DB_USER) \ -e POSTGRES_USER=$(DB_USER) \
-e POSTGRES_PASSWORD=$(DB_PASSWORD) \ -e POSTGRES_PASSWORD=$(DB_PASSWORD) \

View File

@@ -2,6 +2,7 @@
FILE_NAME=data-dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql.br FILE_NAME=data-dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql.br
FILE_NAME_COMPLETE=full-dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql.br FILE_NAME_COMPLETE=full-dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql.br
DATABASE_NAME=database
# Das wird benötigt für AWS Ionos Kompatibilität. # Das wird benötigt für AWS Ionos Kompatibilität.
export AWS_REQUEST_CHECKSUM_CALCULATION=when_required export AWS_REQUEST_CHECKSUM_CALCULATION=when_required
@@ -11,19 +12,15 @@ export AWS_RESPONSE_CHECKSUM_VALIDATION=when_required
# IMPORTANT: Dieser Befehl benötigt das `ionos` Profil, sonst wird er nicht funktionieren. # IMPORTANT: Dieser Befehl benötigt das `ionos` Profil, sonst wird er nicht funktionieren.
# Das Profil kann mit `aws configure --profile ionos` erstellt werden. # Das Profil kann mit `aws configure --profile ionos` erstellt werden.
# Den Key dafür findet man auf https://dcd.ionos.com/latest/?lang=en#/key-management # Den Key dafür findet man auf https://dcd.ionos.com/latest/?lang=en#/key-management
docker exec -t online-energieausweis-database-1 pg_dump --data-only -U main main | brotli --best > $FILE_NAME docker exec -t $DATABASE_NAME pg_dump --data-only -U main main | brotli --quality=3 > $FILE_NAME
aws s3 cp $FILE_NAME s3://ibc-db-backup/ --profile ionos --endpoint-url https://s3.eu-central-3.ionoscloud.com --storage-class STANDARD aws s3 cp $FILE_NAME s3://ibc-db-backup/ --profile ionos --endpoint-url https://s3.eu-central-3.ionoscloud.com --storage-class STANDARD
echo "Uploaded $FILE_NAME" echo "Uploaded $FILE_NAME"
docker exec -t online-energieausweis-database-1 pg_dumpall -c -U main | brotli --best > $FILE_NAME_COMPLETE docker exec -t $DATABASE_NAME pg_dumpall -c -U main | brotli --quality=3 > $FILE_NAME_COMPLETE
<<<<<<< HEAD
aws s3 cp $FILE_NAME_COMPLETE s3://ibc-db-backup/ --profile ionos --endpoint-url https://s3-eu-central-3.ionoscloud.com --storage-class STANDARD
=======
aws s3 cp $FILE_NAME_COMPLETE s3://ibc-db-backup/ --profile ionos --endpoint-url https://s3.eu-central-3.ionoscloud.com --storage-class STANDARD aws s3 cp $FILE_NAME_COMPLETE s3://ibc-db-backup/ --profile ionos --endpoint-url https://s3.eu-central-3.ionoscloud.com --storage-class STANDARD
>>>>>>> dev
echo "Uploaded $FILE_NAME_COMPLETE" echo "Uploaded $FILE_NAME_COMPLETE"

View File

@@ -27,6 +27,8 @@
"express": "^4.21.2", "express": "^4.21.2",
"flag-icons": "^6.15.0", "flag-icons": "^6.15.0",
"fontkit": "^2.0.4", "fontkit": "^2.0.4",
"handlebars": "^4.7.8",
"heic2any": "^0.0.4",
"highlight.run": "^9.14.0", "highlight.run": "^9.14.0",
"is-base64": "^1.1.0", "is-base64": "^1.1.0",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
@@ -35,11 +37,11 @@
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"mime": "^4.0.6", "mime": "^4.0.6",
"moment": "^2.30.1", "moment": "^2.30.1",
"moment-timezone": "^0.5.46", "moment-timezone": "^0.6.0",
"nodemailer": "^6.10.0", "nodemailer": "^6.10.0",
"pdf-lib": "^1.17.1", "pdf-lib": "^1.17.1",
"postcss-nested": "^7.0.2", "postcss-nested": "^7.0.2",
"puppeteer": "^24.7.2", "puppeteer": "^24.15.0",
"radix-svelte-icons": "^1.0.0", "radix-svelte-icons": "^1.0.0",
"sass": "^1.83.4", "sass": "^1.83.4",
"sharp": "^0.33.5", "sharp": "^0.33.5",
@@ -524,7 +526,7 @@
"@proload/core": ["@proload/core@0.3.3", "", { "dependencies": { "deepmerge": "^4.2.2", "escalade": "^3.1.1" } }, "sha512-7dAFWsIK84C90AMl24+N/ProHKm4iw0akcnoKjRvbfHifJZBLhaDsDus1QJmhG12lXj4e/uB/8mB/0aduCW+NQ=="], "@proload/core": ["@proload/core@0.3.3", "", { "dependencies": { "deepmerge": "^4.2.2", "escalade": "^3.1.1" } }, "sha512-7dAFWsIK84C90AMl24+N/ProHKm4iw0akcnoKjRvbfHifJZBLhaDsDus1QJmhG12lXj4e/uB/8mB/0aduCW+NQ=="],
"@puppeteer/browsers": ["@puppeteer/browsers@2.10.2", "", { "dependencies": { "debug": "^4.4.0", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.5.0", "semver": "^7.7.1", "tar-fs": "^3.0.8", "yargs": "^17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" } }, "sha512-i4Ez+s9oRWQbNjtI/3+jxr7OH508mjAKvza0ekPJem0ZtmsYHP3B5dq62+IaBHKaGCOuqJxXzvFLUhJvQ6jtsQ=="], "@puppeteer/browsers": ["@puppeteer/browsers@2.10.6", "", { "dependencies": { "debug": "^4.4.1", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.5.0", "semver": "^7.7.2", "tar-fs": "^3.1.0", "yargs": "^17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" } }, "sha512-pHUn6ZRt39bP3698HFQlu2ZHCkS/lPcpv7fVQcGBSzNNygw171UXAKrCUhy+TEMw4lEttOKDgNpb04hwUAJeiQ=="],
"@rc-component/async-validator": ["@rc-component/async-validator@5.0.4", "", { "dependencies": { "@babel/runtime": "^7.24.4" } }, "sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg=="], "@rc-component/async-validator": ["@rc-component/async-validator@5.0.4", "", { "dependencies": { "@babel/runtime": "^7.24.4" } }, "sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg=="],
@@ -1060,7 +1062,7 @@
"chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="], "chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="],
"chromium-bidi": ["chromium-bidi@4.1.1", "", { "dependencies": { "mitt": "^3.0.1", "zod": "^3.24.1" }, "peerDependencies": { "devtools-protocol": "*" } }, "sha512-biR7t4vF3YluE6RlMSk9IWk+b9U+WWyzHp+N2pL9vRTk+UXHYRTVp7jTK58ZNzMLBgoLMHY4QyJMbeuw3eKxqg=="], "chromium-bidi": ["chromium-bidi@7.2.0", "", { "dependencies": { "mitt": "^3.0.1", "zod": "^3.24.1" }, "peerDependencies": { "devtools-protocol": "*" } }, "sha512-gREyhyBstermK+0RbcJLbFhcQctg92AGgDe/h/taMJEOLRdtSswBAO9KmvltFSQWgM2LrwWu5SIuEUbdm3JsyQ=="],
"ci-info": ["ci-info@4.1.0", "", {}, "sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A=="], "ci-info": ["ci-info@4.1.0", "", {}, "sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A=="],
@@ -1244,7 +1246,7 @@
"devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
"devtools-protocol": ["devtools-protocol@0.0.1425554", "", {}, "sha512-uRfxR6Nlzdzt0ihVIkV+sLztKgs7rgquY/Mhcv1YNCWDh5IZgl5mnn2aeEnW5stYTE0wwiF4RYVz8eMEpV1SEw=="], "devtools-protocol": ["devtools-protocol@0.0.1464554", "", {}, "sha512-CAoP3lYfwAGQTaAXYvA6JZR0fjGUb7qec1qf4mToyoH2TZgUFeIqYcjh6f9jNuhHfuZiEdH+PONHYrLhRQX6aw=="],
"dezalgo": ["dezalgo@1.0.4", "", { "dependencies": { "asap": "^2.0.0", "wrappy": "1" } }, "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig=="], "dezalgo": ["dezalgo@1.0.4", "", { "dependencies": { "asap": "^2.0.0", "wrappy": "1" } }, "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig=="],
@@ -1518,6 +1520,8 @@
"h3": ["h3@1.14.0", "", { "dependencies": { "cookie-es": "^1.2.2", "crossws": "^0.3.2", "defu": "^6.1.4", "destr": "^2.0.3", "iron-webcrypto": "^1.2.1", "ohash": "^1.1.4", "radix3": "^1.1.2", "ufo": "^1.5.4", "uncrypto": "^0.1.3", "unenv": "^1.10.0" } }, "sha512-ao22eiONdgelqcnknw0iD645qW0s9NnrJHr5OBz4WOMdBdycfSas1EQf1wXRsm+PcB2Yoj43pjBPwqIpJQTeWg=="], "h3": ["h3@1.14.0", "", { "dependencies": { "cookie-es": "^1.2.2", "crossws": "^0.3.2", "defu": "^6.1.4", "destr": "^2.0.3", "iron-webcrypto": "^1.2.1", "ohash": "^1.1.4", "radix3": "^1.1.2", "ufo": "^1.5.4", "uncrypto": "^0.1.3", "unenv": "^1.10.0" } }, "sha512-ao22eiONdgelqcnknw0iD645qW0s9NnrJHr5OBz4WOMdBdycfSas1EQf1wXRsm+PcB2Yoj43pjBPwqIpJQTeWg=="],
"handlebars": ["handlebars@4.7.8", "", { "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, "optionalDependencies": { "uglify-js": "^3.1.4" }, "bin": { "handlebars": "bin/handlebars" } }, "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ=="],
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
@@ -1552,6 +1556,8 @@
"hastscript": ["hastscript@9.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-jzaLBGavEDKHrc5EfFImKN7nZKKBdSLIdGvCwDZ9TfzbF2ffXiov8CKE445L2Z1Ek2t/m4SKQ2j6Ipv7NyUolw=="], "hastscript": ["hastscript@9.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-jzaLBGavEDKHrc5EfFImKN7nZKKBdSLIdGvCwDZ9TfzbF2ffXiov8CKE445L2Z1Ek2t/m4SKQ2j6Ipv7NyUolw=="],
"heic2any": ["heic2any@0.0.4", "", {}, "sha512-3lLnZiDELfabVH87htnRolZ2iehX9zwpRyGNz22GKXIu0fznlblf0/ftppXKNqS26dqFSeqfIBhAmAj/uSp0cA=="],
"hexoid": ["hexoid@2.0.0", "", {}, "sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw=="], "hexoid": ["hexoid@2.0.0", "", {}, "sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw=="],
"highlight.run": ["highlight.run@9.14.0", "", {}, "sha512-ZR+ZLHlVU8lXqsuto0ZEMAOuvptaTBBf1jradnKDIn9OfAXupcYFbkASDlbsZtyBh2SYJSK50xwrucXujhksRg=="], "highlight.run": ["highlight.run@9.14.0", "", {}, "sha512-ZR+ZLHlVU8lXqsuto0ZEMAOuvptaTBBf1jradnKDIn9OfAXupcYFbkASDlbsZtyBh2SYJSK50xwrucXujhksRg=="],
@@ -1962,7 +1968,7 @@
"moment": ["moment@2.30.1", "", {}, "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how=="], "moment": ["moment@2.30.1", "", {}, "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how=="],
"moment-timezone": ["moment-timezone@0.5.47", "", { "dependencies": { "moment": "^2.29.4" } }, "sha512-UbNt/JAWS0m/NJOebR0QMRHBk0hu03r5dx9GK8Cs0AS3I81yDcOc9k+DytPItgVvBP7J6Mf6U2n3BPAacAV9oA=="], "moment-timezone": ["moment-timezone@0.6.0", "", { "dependencies": { "moment": "^2.29.4" } }, "sha512-ldA5lRNm3iJCWZcBCab4pnNL3HSZYXVb/3TYr75/1WCTWYuTqYUb5f/S384pncYjJ88lbO8Z4uPDvmoluHJc8Q=="],
"mrmime": ["mrmime@2.0.0", "", {}, "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw=="], "mrmime": ["mrmime@2.0.0", "", {}, "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw=="],
@@ -1980,6 +1986,8 @@
"negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
"neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="],
"neotraverse": ["neotraverse@0.6.18", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="], "neotraverse": ["neotraverse@0.6.18", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="],
"netmask": ["netmask@2.0.2", "", {}, "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg=="], "netmask": ["netmask@2.0.2", "", {}, "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg=="],
@@ -2190,9 +2198,9 @@
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"puppeteer": ["puppeteer@24.7.2", "", { "dependencies": { "@puppeteer/browsers": "2.10.2", "chromium-bidi": "4.1.1", "cosmiconfig": "^9.0.0", "devtools-protocol": "0.0.1425554", "puppeteer-core": "24.7.2", "typed-query-selector": "^2.12.0" }, "bin": { "puppeteer": "lib/cjs/puppeteer/node/cli.js" } }, "sha512-ifYqoY6wGs0yZeFuFPn8BE9FhuveXkarF+eO18I2e/axdoCh4Qh1AE+qXdJBhdaeoPt6eRNTY4Dih29Jbq8wow=="], "puppeteer": ["puppeteer@24.15.0", "", { "dependencies": { "@puppeteer/browsers": "2.10.6", "chromium-bidi": "7.2.0", "cosmiconfig": "^9.0.0", "devtools-protocol": "0.0.1464554", "puppeteer-core": "24.15.0", "typed-query-selector": "^2.12.0" }, "bin": { "puppeteer": "lib/cjs/puppeteer/node/cli.js" } }, "sha512-HPSOTw+DFsU/5s2TUUWEum9WjFbyjmvFDuGHtj2X4YUz2AzOzvKMkT3+A3FR+E+ZefiX/h3kyLyXzWJWx/eMLQ=="],
"puppeteer-core": ["puppeteer-core@24.7.2", "", { "dependencies": { "@puppeteer/browsers": "2.10.2", "chromium-bidi": "4.1.1", "debug": "^4.4.0", "devtools-protocol": "0.0.1425554", "typed-query-selector": "^2.12.0", "ws": "^8.18.1" } }, "sha512-P9pZyTmJqKODFCnkZgemCpoFA4LbAa8+NumHVQKyP5X9IgdNS1ZnAnIh1sMAwhF8/xEUGf7jt+qmNLlKieFw1Q=="], "puppeteer-core": ["puppeteer-core@24.15.0", "", { "dependencies": { "@puppeteer/browsers": "2.10.6", "chromium-bidi": "7.2.0", "debug": "^4.4.1", "devtools-protocol": "0.0.1464554", "typed-query-selector": "^2.12.0", "ws": "^8.18.3" } }, "sha512-2iy0iBeWbNyhgiCGd/wvGrDSo73emNFjSxYOcyAqYiagkYt5q4cPfVXaVDKBsukgc2fIIfLAalBZlaxldxdDYg=="],
"qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="], "qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="],
@@ -2566,7 +2574,7 @@
"tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="], "tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="],
"tar-fs": ["tar-fs@3.0.8", "", { "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" }, "optionalDependencies": { "bare-fs": "^4.0.1", "bare-path": "^3.0.0" } }, "sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg=="], "tar-fs": ["tar-fs@3.1.0", "", { "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" }, "optionalDependencies": { "bare-fs": "^4.0.1", "bare-path": "^3.0.0" } }, "sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w=="],
"tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="],
@@ -2666,6 +2674,8 @@
"ufo": ["ufo@1.5.4", "", {}, "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ=="], "ufo": ["ufo@1.5.4", "", {}, "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ=="],
"uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="],
"uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="], "uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="],
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
@@ -2764,13 +2774,15 @@
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
"wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="],
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
"wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"ws": ["ws@8.18.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w=="], "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
"xml-crypto": ["xml-crypto@6.0.0", "", { "dependencies": { "@xmldom/is-dom-node": "^1.0.1", "@xmldom/xmldom": "^0.8.10", "xpath": "^0.0.33" } }, "sha512-L3RgnkaDrHaYcCnoENv4Idzt1ZRj5U1z1BDH98QdDTQfssScx8adgxhd9qwyYo+E3fXbQZjEQH7aiXHLVgxGvw=="], "xml-crypto": ["xml-crypto@6.0.0", "", { "dependencies": { "@xmldom/is-dom-node": "^1.0.1", "@xmldom/xmldom": "^0.8.10", "xpath": "^0.0.33" } }, "sha512-L3RgnkaDrHaYcCnoENv4Idzt1ZRj5U1z1BDH98QdDTQfssScx8adgxhd9qwyYo+E3fXbQZjEQH7aiXHLVgxGvw=="],
@@ -2870,7 +2882,9 @@
"@prisma/schema-files-loader/fs-extra": ["fs-extra@11.1.1", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ=="], "@prisma/schema-files-loader/fs-extra": ["fs-extra@11.1.1", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ=="],
"@puppeteer/browsers/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], "@puppeteer/browsers/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
"@puppeteer/browsers/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
@@ -2968,6 +2982,8 @@
"gray-matter/js-yaml": ["js-yaml@3.14.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g=="], "gray-matter/js-yaml": ["js-yaml@3.14.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g=="],
"handlebars/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"hasha/type-fest": ["type-fest@0.8.1", "", {}, "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA=="], "hasha/type-fest": ["type-fest@0.8.1", "", {}, "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA=="],
"ignore-walk/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], "ignore-walk/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
@@ -3066,6 +3082,8 @@
"proxy-agent/proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], "proxy-agent/proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
"puppeteer-core/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
"rc-align/rc-util": ["rc-util@4.21.1", "", { "dependencies": { "add-dom-event-listener": "^1.1.0", "prop-types": "^15.5.10", "react-is": "^16.12.0", "react-lifecycles-compat": "^3.0.4", "shallowequal": "^1.1.0" } }, "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg=="], "rc-align/rc-util": ["rc-util@4.21.1", "", { "dependencies": { "add-dom-event-listener": "^1.1.0", "prop-types": "^15.5.10", "react-is": "^16.12.0", "react-lifecycles-compat": "^3.0.4", "shallowequal": "^1.1.0" } }, "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg=="],
"rc-animate/rc-util": ["rc-util@4.21.1", "", { "dependencies": { "add-dom-event-listener": "^1.1.0", "prop-types": "^15.5.10", "react-is": "^16.12.0", "react-lifecycles-compat": "^3.0.4", "shallowequal": "^1.1.0" } }, "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg=="], "rc-animate/rc-util": ["rc-util@4.21.1", "", { "dependencies": { "add-dom-event-listener": "^1.1.0", "prop-types": "^15.5.10", "react-is": "^16.12.0", "react-lifecycles-compat": "^3.0.4", "shallowequal": "^1.1.0" } }, "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg=="],

View File

@@ -1,6 +1,9 @@
version: '3' version: '3'
services: services:
database: database:
container_name: database
image: postgres:17.5
build: ./ build: ./
restart: always restart: always
env_file: env_file:

View File

@@ -41,6 +41,8 @@
"express": "^4.21.2", "express": "^4.21.2",
"flag-icons": "^6.15.0", "flag-icons": "^6.15.0",
"fontkit": "^2.0.4", "fontkit": "^2.0.4",
"handlebars": "^4.7.8",
"heic2any": "^0.0.4",
"highlight.run": "^9.14.0", "highlight.run": "^9.14.0",
"is-base64": "^1.1.0", "is-base64": "^1.1.0",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
@@ -49,11 +51,11 @@
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"mime": "^4.0.6", "mime": "^4.0.6",
"moment": "^2.30.1", "moment": "^2.30.1",
"moment-timezone": "^0.5.46", "moment-timezone": "^0.6.0",
"nodemailer": "^6.10.0", "nodemailer": "^6.10.0",
"pdf-lib": "^1.17.1", "pdf-lib": "^1.17.1",
"postcss-nested": "^7.0.2", "postcss-nested": "^7.0.2",
"puppeteer": "^24.7.2", "puppeteer": "^24.15.0",
"radix-svelte-icons": "^1.0.0", "radix-svelte-icons": "^1.0.0",
"sass": "^1.83.4", "sass": "^1.83.4",
"sharp": "^0.33.5", "sharp": "^0.33.5",

View File

@@ -0,0 +1,49 @@
/*
Warnings:
- The `fenster_art_1` column on the `BedarfsausweisWohnen` table would be dropped and recreated. This will lead to data loss if there is data in the column.
- The `fenster_art_2` column on the `BedarfsausweisWohnen` table would be dropped and recreated. This will lead to data loss if there is data in the column.
- The `dachfenster_art` column on the `BedarfsausweisWohnen` table would be dropped and recreated. This will lead to data loss if there is data in the column.
- The `haustuer_art` column on the `BedarfsausweisWohnen` table would be dropped and recreated. This will lead to data loss if there is data in the column.
- The `dach_daemmung` column on the `BedarfsausweisWohnen` table would be dropped and recreated. This will lead to data loss if there is data in the column.
- The `decke_daemmung` column on the `BedarfsausweisWohnen` table would be dropped and recreated. This will lead to data loss if there is data in the column.
- The `aussenwand_daemmung` column on the `BedarfsausweisWohnen` table would be dropped and recreated. This will lead to data loss if there is data in the column.
- The `boden_daemmung` column on the `BedarfsausweisWohnen` table would be dropped and recreated. This will lead to data loss if there is data in the column.
*/
-- AlterEnum
ALTER TYPE "BenutzerRolle" ADD VALUE 'RESELLER';
-- AlterTable
ALTER TABLE "BedarfsausweisWohnen" DROP COLUMN "fenster_art_1",
ADD COLUMN "fenster_art_1" DOUBLE PRECISION,
DROP COLUMN "fenster_art_2",
ADD COLUMN "fenster_art_2" DOUBLE PRECISION,
DROP COLUMN "dachfenster_art",
ADD COLUMN "dachfenster_art" DOUBLE PRECISION,
DROP COLUMN "haustuer_art",
ADD COLUMN "haustuer_art" DOUBLE PRECISION,
DROP COLUMN "dach_daemmung",
ADD COLUMN "dach_daemmung" DOUBLE PRECISION,
DROP COLUMN "decke_daemmung",
ADD COLUMN "decke_daemmung" DOUBLE PRECISION,
DROP COLUMN "aussenwand_daemmung",
ADD COLUMN "aussenwand_daemmung" DOUBLE PRECISION,
DROP COLUMN "boden_daemmung",
ADD COLUMN "boden_daemmung" DOUBLE PRECISION;
-- CreateTable
CREATE TABLE "Provisionen" (
"id" TEXT NOT NULL,
"ausweisart" TEXT NOT NULL,
"provision_prozent" DOUBLE PRECISION NOT NULL,
"provision_betrag" DOUBLE PRECISION NOT NULL,
"benutzer_id" VARCHAR(11),
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Provisionen_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "Provisionen" ADD CONSTRAINT "Provisionen_benutzer_id_fkey" FOREIGN KEY ("benutzer_id") REFERENCES "benutzer"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@@ -0,0 +1,15 @@
/*
Warnings:
- The primary key for the `Provisionen` table will be changed. If it partially fails, the table could be left without primary key constraint.
- The `id` column on the `Provisionen` table would be dropped and recreated. This will lead to data loss if there is data in the column.
- Changed the type of `ausweisart` on the `Provisionen` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required.
*/
-- AlterTable
ALTER TABLE "Provisionen" DROP CONSTRAINT "Provisionen_pkey",
DROP COLUMN "id",
ADD COLUMN "id" SERIAL NOT NULL,
DROP COLUMN "ausweisart",
ADD COLUMN "ausweisart" "Ausweisart" NOT NULL,
ADD CONSTRAINT "Provisionen_pkey" PRIMARY KEY ("id");

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "benutzer" ADD COLUMN "partner_code" TEXT;

View File

@@ -0,0 +1,12 @@
/*
Warnings:
- Added the required column `ausweistyp` to the `Provisionen` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "Aufnahme" ALTER COLUMN "flaeche" SET DATA TYPE DOUBLE PRECISION,
ALTER COLUMN "nutzflaeche" SET DATA TYPE DOUBLE PRECISION;
-- AlterTable
ALTER TABLE "Provisionen" ADD COLUMN "ausweistyp" "AusweisTyp" NOT NULL;

View File

@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "Bild" ADD COLUMN "benutzer_id" TEXT;
-- AddForeignKey
ALTER TABLE "Bild" ADD CONSTRAINT "Bild_benutzer_id_fkey" FOREIGN KEY ("benutzer_id") REFERENCES "benutzer"("id") ON DELETE NO ACTION ON UPDATE NO ACTION;

View File

@@ -0,0 +1,10 @@
/*
Warnings:
- You are about to alter the column `flaeche` on the `Aufnahme` table. The data in that column could be lost. The data in that column will be cast from `DoublePrecision` to `Integer`.
- You are about to alter the column `nutzflaeche` on the `Aufnahme` table. The data in that column could be lost. The data in that column will be cast from `DoublePrecision` to `Integer`.
*/
-- AlterTable
ALTER TABLE "Aufnahme" ALTER COLUMN "flaeche" SET DATA TYPE INTEGER,
ALTER COLUMN "nutzflaeche" SET DATA TYPE INTEGER;

View File

@@ -2,6 +2,7 @@
enum BenutzerRolle { enum BenutzerRolle {
USER USER
ADMIN ADMIN
RESELLER
} }
model Benutzer { model Benutzer {
@@ -20,6 +21,7 @@ model Benutzer {
rolle BenutzerRolle @default(USER) rolle BenutzerRolle @default(USER)
firma String? firma String?
lex_office_id String? lex_office_id String?
partner_code String?
verified Boolean @default(false) verified Boolean @default(false)
@@ -50,6 +52,8 @@ model Benutzer {
events Event[] events Event[]
@@map("benutzer") @@map("benutzer")
Provisionen Provisionen[]
bilder Bild[]
} }

View File

@@ -15,6 +15,8 @@ model Bild {
created_at DateTime @default(now()) created_at DateTime @default(now())
updated_at DateTime @updatedAt @default(now()) updated_at DateTime @updatedAt @default(now())
benutzer_id String?
benutzer Benutzer? @relation(fields: [benutzer_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
aufnahme_id String? aufnahme_id String?
aufnahme Aufnahme? @relation(fields: [aufnahme_id], references: [id], onDelete: NoAction, onUpdate: NoAction) aufnahme Aufnahme? @relation(fields: [aufnahme_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
} }

View File

@@ -0,0 +1,11 @@
model Provisionen {
id Int @id @default(autoincrement())
ausweisart Ausweisart
ausweistyp AusweisTyp
provision_prozent Float
provision_betrag Float
benutzer_id String? @db.VarChar(11)
benutzer Benutzer? @relation(fields: [benutzer_id], references: [id])
created_at DateTime @default(now())
updated_at DateTime @updatedAt
}

View File

@@ -4,19 +4,35 @@
BUCKET_NAME="ibc-db-backup" BUCKET_NAME="ibc-db-backup"
ENDPOINT_URL="https://s3.eu-central-3.ionoscloud.com" ENDPOINT_URL="https://s3.eu-central-3.ionoscloud.com"
LOCAL_DOWNLOAD_DIR="./" # Where to save the file LOCAL_DOWNLOAD_DIR="./" # Where to save the file
DATABASE_NAME=database
# === Check if a custom file is given as a command line argument === # === Check if a custom file is given as a command line argument ===
if [ $# -eq 1 ]; then if [ $# -eq 1 ]; then
CUSTOM_FILE="$1" CUSTOM_FILE="$1"
echo "🔍 Using custom file: $CUSTOM_FILE" echo "🔍 Using custom file: $CUSTOM_FILE"
# Check if the file exists # Check if file exists locally
if [ ! -f "$CUSTOM_FILE" ]; then if [ ! -f "$CUSTOM_FILE" ]; then
echo "❌ Custom file does not exist: $CUSTOM_FILE" # Check if the file exists on the remote
exit 1 if ! aws s3api head-object --bucket "$BUCKET_NAME" --key "$CUSTOM_FILE" --endpoint-url "$ENDPOINT_URL" > /dev/null 2>&1; then
echo "❌ Custom file does not exist in S3 bucket or locally."
exit 1
else
echo "📥 Downloading $CUSTOM_FILE from S3"
aws s3 cp "s3://$BUCKET_NAME/$CUSTOM_FILE" "$LOCAL_DOWNLOAD_DIR" \
--endpoint-url "$ENDPOINT_URL"
fi
fi fi
LATEST_FILE="$CUSTOM_FILE" LATEST_FILE="$CUSTOM_FILE"
FILENAME=$(basename "$LATEST_FILE") FILENAME=$(basename "$LATEST_FILE")
SQL_FILE="${FILENAME%.br}" # Remove .br suffix if [[ "$FILENAME" == *.br ]]; then
echo "🗜️ Detected compressed file: $FILENAME"
# Remove the .br suffix for the SQL file
SQL_FILE="${FILENAME%.br}" # Remove .br suffix
brotli -d "$FILENAME"
else
SQL_FILE=$FILENAME
fi
else else
echo "🔍 No custom file provided, searching for latest .sql.br file in S3" echo "🔍 No custom file provided, searching for latest .sql.br file in S3"
@@ -36,22 +52,27 @@ else
echo "🔍 Latest file found: $LATEST_FILE" echo "🔍 Latest file found: $LATEST_FILE"
FILENAME=$(basename "$LATEST_FILE") FILENAME=$(basename "$LATEST_FILE")
SQL_FILE="${FILENAME%.br}" # Remove .br suffix SQL_FILE="${FILENAME%.br}" # Remove .br suffix
echo "📥 Downloading $LATEST_FILE" echo "📥 Downloading $LATEST_FILE"
aws s3 cp "s3://$BUCKET_NAME/$LATEST_FILE" "$LOCAL_DOWNLOAD_DIR" \ aws s3 cp "s3://$BUCKET_NAME/$LATEST_FILE" "$LOCAL_DOWNLOAD_DIR" \
--endpoint-url "$ENDPOINT_URL" --endpoint-url "$ENDPOINT_URL"
brotli -d "$FILENAME"
echo "🗜️ Decompressed to $SQL_FILE"
fi fi
# === Decompress with Brotli ===
echo "🗜️ Decompressing $FILENAME -> $SQL_FILE"
brotli -d "$FILENAME"
# === Import into Postgres inside Docker === # === Import into Postgres inside Docker ===
echo "🐘 Importing into PostgreSQL (online-energieausweis-database-1:main)" echo "🐘 Importing into PostgreSQL ($DATABASE_NAME:main)"
docker exec -i "online-energieausweis-database-1" env PGPASSWORD="hHMP8cd^N3SnzGRR" \ docker exec -i "$DATABASE_NAME" env PGPASSWORD="hHMP8cd^N3SnzGRR" \
psql -U "main" -d "main" < "$SQL_FILE" psql -v ON_ERROR_STOP=0 -U main -d main < "$SQL_FILE"
echo "✅ Import complete." echo "✅ Import complete."
# === Optional: Clean up # === Optional: Clean up
rm "$FILENAME" "$SQL_FILE" # If custom file was provided, do not delete it
if [ -z "$CUSTOM_FILE" ]; then
echo "🧹 Cleaning up downloaded files..."
rm "$FILENAME" "$SQL_FILE"
fi

View File

@@ -5,6 +5,12 @@ export const createCaller = createCallerFactory({
"klimafaktoren": await import("../src/pages/api/klimafaktoren.ts"), "klimafaktoren": await import("../src/pages/api/klimafaktoren.ts"),
"postleitzahlen": await import("../src/pages/api/postleitzahlen.ts"), "postleitzahlen": await import("../src/pages/api/postleitzahlen.ts"),
"unterlage": await import("../src/pages/api/unterlage.ts"), "unterlage": await import("../src/pages/api/unterlage.ts"),
"ausweise": await import("../src/pages/api/ausweise/index.ts"),
"bedarfsausweis-gewerbe/[id]": await import("../src/pages/api/bedarfsausweis-gewerbe/[id].ts"),
"bedarfsausweis-gewerbe": await import("../src/pages/api/bedarfsausweis-gewerbe/index.ts"),
"aufnahme": await import("../src/pages/api/aufnahme/index.ts"),
"bedarfsausweis-wohnen/[id]": await import("../src/pages/api/bedarfsausweis-wohnen/[id].ts"),
"bedarfsausweis-wohnen": await import("../src/pages/api/bedarfsausweis-wohnen/index.ts"),
"admin/ausstellen": await import("../src/pages/api/admin/ausstellen.ts"), "admin/ausstellen": await import("../src/pages/api/admin/ausstellen.ts"),
"admin/bedarfsausweis-ausstellen": await import("../src/pages/api/admin/bedarfsausweis-ausstellen.ts"), "admin/bedarfsausweis-ausstellen": await import("../src/pages/api/admin/bedarfsausweis-ausstellen.ts"),
"admin/bestellbestaetigung": await import("../src/pages/api/admin/bestellbestaetigung.ts"), "admin/bestellbestaetigung": await import("../src/pages/api/admin/bestellbestaetigung.ts"),
@@ -12,29 +18,24 @@ export const createCaller = createCallerFactory({
"admin/nicht-ausstellen": await import("../src/pages/api/admin/nicht-ausstellen.ts"), "admin/nicht-ausstellen": await import("../src/pages/api/admin/nicht-ausstellen.ts"),
"admin/registriernummer": await import("../src/pages/api/admin/registriernummer.ts"), "admin/registriernummer": await import("../src/pages/api/admin/registriernummer.ts"),
"admin/stornieren": await import("../src/pages/api/admin/stornieren.ts"), "admin/stornieren": await import("../src/pages/api/admin/stornieren.ts"),
"aufnahme": await import("../src/pages/api/aufnahme/index.ts"), "bilder/[id]": await import("../src/pages/api/bilder/[id].ts"),
"ausweise": await import("../src/pages/api/ausweise/index.ts"), "geg-nachweis-wohnen/[id]": await import("../src/pages/api/geg-nachweis-wohnen/[id].ts"),
"geg-nachweis-wohnen": await import("../src/pages/api/geg-nachweis-wohnen/index.ts"),
"objekt": await import("../src/pages/api/objekt/index.ts"),
"auth/access-token": await import("../src/pages/api/auth/access-token.ts"), "auth/access-token": await import("../src/pages/api/auth/access-token.ts"),
"auth/passwort-vergessen": await import("../src/pages/api/auth/passwort-vergessen.ts"), "auth/passwort-vergessen": await import("../src/pages/api/auth/passwort-vergessen.ts"),
"auth/refresh-token": await import("../src/pages/api/auth/refresh-token.ts"), "auth/refresh-token": await import("../src/pages/api/auth/refresh-token.ts"),
"bedarfsausweis-gewerbe/[id]": await import("../src/pages/api/bedarfsausweis-gewerbe/[id].ts"),
"bedarfsausweis-gewerbe": await import("../src/pages/api/bedarfsausweis-gewerbe/index.ts"),
"bedarfsausweis-wohnen/[id]": await import("../src/pages/api/bedarfsausweis-wohnen/[id].ts"),
"bedarfsausweis-wohnen": await import("../src/pages/api/bedarfsausweis-wohnen/index.ts"),
"bilder/[id]": await import("../src/pages/api/bilder/[id].ts"),
"geg-nachweis-gewerbe/[id]": await import("../src/pages/api/geg-nachweis-gewerbe/[id].ts"),
"geg-nachweis-gewerbe": await import("../src/pages/api/geg-nachweis-gewerbe/index.ts"),
"geg-nachweis-wohnen/[id]": await import("../src/pages/api/geg-nachweis-wohnen/[id].ts"),
"geg-nachweis-wohnen": await import("../src/pages/api/geg-nachweis-wohnen/index.ts"),
"rechnung/[id]": await import("../src/pages/api/rechnung/[id].ts"), "rechnung/[id]": await import("../src/pages/api/rechnung/[id].ts"),
"rechnung/anfordern": await import("../src/pages/api/rechnung/anfordern.ts"), "rechnung/anfordern": await import("../src/pages/api/rechnung/anfordern.ts"),
"rechnung": await import("../src/pages/api/rechnung/index.ts"), "rechnung": await import("../src/pages/api/rechnung/index.ts"),
"objekt": await import("../src/pages/api/objekt/index.ts"),
"ticket": await import("../src/pages/api/ticket/index.ts"),
"user": await import("../src/pages/api/user/index.ts"), "user": await import("../src/pages/api/user/index.ts"),
"user/self": await import("../src/pages/api/user/self.ts"), "user/self": await import("../src/pages/api/user/self.ts"),
"ticket": await import("../src/pages/api/ticket/index.ts"),
"verbrauchsausweis-wohnen/[id]": await import("../src/pages/api/verbrauchsausweis-wohnen/[id].ts"),
"verbrauchsausweis-wohnen": await import("../src/pages/api/verbrauchsausweis-wohnen/index.ts"),
"verbrauchsausweis-gewerbe/[id]": await import("../src/pages/api/verbrauchsausweis-gewerbe/[id].ts"), "verbrauchsausweis-gewerbe/[id]": await import("../src/pages/api/verbrauchsausweis-gewerbe/[id].ts"),
"verbrauchsausweis-gewerbe": await import("../src/pages/api/verbrauchsausweis-gewerbe/index.ts"), "verbrauchsausweis-gewerbe": await import("../src/pages/api/verbrauchsausweis-gewerbe/index.ts"),
"ticket": await import("../src/pages/api/ticket/index.ts"),
"verbrauchsausweis-wohnen/[id]": await import("../src/pages/api/verbrauchsausweis-wohnen/[id].ts"), "verbrauchsausweis-wohnen/[id]": await import("../src/pages/api/verbrauchsausweis-wohnen/[id].ts"),
"verbrauchsausweis-wohnen": await import("../src/pages/api/verbrauchsausweis-wohnen/index.ts"), "verbrauchsausweis-wohnen": await import("../src/pages/api/verbrauchsausweis-wohnen/index.ts"),
"webhooks/mollie": await import("../src/pages/api/webhooks/mollie.ts"), "webhooks/mollie": await import("../src/pages/api/webhooks/mollie.ts"),

View File

@@ -1,49 +0,0 @@
import { dialogs } from "../../../svelte-dialogs.config";
import { loginClient } from "#lib/login";
import { addNotification } from "#components/Notifications/shared";
export async function spawnLoginPrompt() {
const result = await dialogs.prompt(
[
{
label: "Email",
type: "email",
required: true,
placeholder: "Email",
name: "email"
},
{
label: "Passwort",
type: "password",
name: "passwort",
required: true,
placeholder: "********",
},
],
{
title: "Login",
submitButtonText: "Einloggen",
cancelButtonText: "Abbrechen",
}
);
if (!result) return false;
const [email, passwort] = result;
const loginResult = await loginClient(email, passwort);
if (loginResult === null) {
addNotification({
type: "error",
message: "Einloggen fehlgeschlagen",
dismissable: true,
subtext: "Bitte überprüfen Sie ihre Eingaben und versuchen es erneut.",
timeout: 5000,
})
return false
}
return true
}

View File

@@ -1,67 +0,0 @@
import { dialogs } from "../../../svelte-dialogs.config.js";
import { addNotification } from "#components/Notifications/shared.js";
import { api } from "astro-typesafe-api/client";
export async function spawnSignupPrompt() {
const result = await dialogs.prompt(
[
{
label: "Vorname",
type: "text",
required: true,
placeholder: "Vorname",
name: "vorname"
},
{
label: "Name",
type: "text",
required: true,
placeholder: "Name",
name: "name"
},
{
label: "Email",
type: "email",
required: true,
placeholder: "Email",
name: "email"
},
{
label: "Passwort",
type: "password",
name: "passwort",
required: true,
placeholder: "********",
},
],
{
title: "Registrieren",
submitButtonText: "Registrieren",
cancelButtonText: "Abbrechen",
}
);
if (!result) return false;
const [vorname, name, email, passwort] = result;
try {
const response = await api.user.PUT.fetch({
email,
passwort,
vorname,
name,
});
return true;
} catch(e) {
addNotification({
type: "error",
message: "Registrieren fehlgeschlagen",
dismissable: true,
subtext: "Ein Fehler ist aufgetreten, vielleicht wird die angegebene Email bereits verwendet.",
timeout: 5000,
})
return false;
}
}

View File

@@ -1,13 +1,17 @@
<script lang="ts"> <script lang="ts">
import { Aufnahme, BedarfsausweisWohnen, Enums, Objekt, Rechnung, VerbrauchsausweisGewerbe, VerbrauchsausweisWohnen } from "#lib/server/prisma.js"; import { Aufnahme, BedarfsausweisWohnen, Enums, Objekt, Provisionen, Rechnung, VerbrauchsausweisGewerbe, VerbrauchsausweisWohnen } from "#lib/server/prisma.js";
import moment from "moment"; import moment from "moment-timezone"
import { DatePicker } from "@svelte-plugins/datepicker" import { DatePicker } from "@svelte-plugins/datepicker"
import { getProvision } from "#lib/provision.js";
export let bestellungen: (Rechnung & { export let bestellungen: (Rechnung & {
ausweis: (VerbrauchsausweisWohnen | BedarfsausweisWohnen | VerbrauchsausweisGewerbe) & { aufnahme: Aufnahme & { objekt: Objekt }} ausweis: (VerbrauchsausweisWohnen | BedarfsausweisWohnen | VerbrauchsausweisGewerbe) & { aufnahme: Aufnahme & { objekt: Objekt }}
})[]; })[];
export let provisionen: Record<Enums.Ausweisart, number>; export let provisionen: Provisionen[];
export let partnerCodeErstesMal: Date;
export let email: string; export let email: string;
export let startdatum: Date;
export let enddatum: Date;
moment.locale("de");
moment.tz.setDefault("Europe/Berlin");
const bestellungenNachMonat: Record<string, (typeof bestellungen)> = {}; const bestellungenNachMonat: Record<string, (typeof bestellungen)> = {};
for (const bestellung of bestellungen) { for (const bestellung of bestellungen) {
@@ -20,17 +24,9 @@
} }
// Wir brauchen alle Monate zwischen dem ersten Mal, dass der partner_code benutzt wurde bis zum heutigen Zeitpunkt. // Wir brauchen alle Monate zwischen dem ersten Mal, dass der partner_code benutzt wurde bis zum heutigen Zeitpunkt.
const months: Record<string, string> = { function getMonthlyPeriods(from: Date, to: Date): moment.Moment[] {
"01": "Januar", "02": "Februar", "03": "März", "04": "April", const start = moment(from).startOf('month');
"05": "Mai", "06": "Juni", "07": "Juli", "08": "August", const end = moment(to).endOf('month');
"09": "September", "10": "Oktober", "11": "November", "12": "Dezember"
};
function getMonthlyPeriods(minTime?: Date): moment.Moment[] {
const min = minTime ? moment(minTime) : moment();
const start = min.clone().startOf('month');
const end = moment().add(1, 'month').startOf('month');
const monthsArray: moment.Moment[] = []; const monthsArray: moment.Moment[] = [];
const current = start.clone(); const current = start.clone();
@@ -43,14 +39,12 @@
return monthsArray.reverse(); // Most recent month first return monthsArray.reverse(); // Most recent month first
} }
const periods = getMonthlyPeriods(partnerCodeErstesMal) const periods = getMonthlyPeriods(startdatum, enddatum)
let isOpen = false; let isOpen = false;
export let startDate = moment(partnerCodeErstesMal).startOf('month').toDate(); $: formatiertesStartDatum = moment(startdatum).format("DD.MM.YYYY");
export let endDate = moment().endOf('month').toDate(); $: formatiertesEndDatum = moment(enddatum).format("DD.MM.YYYY");
$: formattedStartDate = moment(startDate).format("DD.MM.YYYY");
$: formattedEndDate = moment(endDate).format("DD.MM.YYYY");
function toggleDatePicker() { function toggleDatePicker() {
isOpen = !isOpen; isOpen = !isOpen;
} }
@@ -62,14 +56,14 @@
<div class="fixed top-0 left-0 right-0 bg-white p-4 shadow z-10"> <div class="fixed top-0 left-0 right-0 bg-white p-4 shadow z-10">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<DatePicker bind:isOpen bind:startDate bind:endDate isRange={true} onDateChange={onChange} isMultipane={true}> <DatePicker bind:isOpen bind:startDate={startdatum} bind:endDate={enddatum} enableFutureDates={false} isRange={true} onDateChange={onChange} isMultipane={true}>
<input type="text" class="w-min" readonly value={`${formattedStartDate} - ${formattedEndDate}`} on:click={toggleDatePicker} /> <input type="text" class="w-min" readonly value={`${formatiertesStartDatum} - ${formatiertesEndDatum}`} on:click={toggleDatePicker} />
</DatePicker> </DatePicker>
<p>Abrechnungsübersicht für <strong>{email}</strong></p> <p>Abrechnungsübersicht für <strong>{email}</strong></p>
</div> </div>
</div> </div>
<main class="my-24 flex justify-center max-w-6xl mx-auto px-4"> <main class="my-24 flex flex-col justify-center max-w-6xl mx-auto px-4">
{#if !bestellungen || bestellungen.length === 0} {#if !bestellungen || bestellungen.length === 0}
<p class="text-center text-gray-500">Keine Bestellungen gefunden.</p> <p class="text-center text-gray-500">Keine Bestellungen gefunden.</p>
{/if} {/if}
@@ -78,44 +72,62 @@
{#if jahrMonat in bestellungenNachMonat && bestellungenNachMonat[jahrMonat].length > 0} {#if jahrMonat in bestellungenNachMonat && bestellungenNachMonat[jahrMonat].length > 0}
<!-- Echo dropdown foreach month. --> <!-- Echo dropdown foreach month. -->
{@const provisionMonat = bestellungenNachMonat[jahrMonat].reduce((acc, bestellung) => { {@const provisionMonat = bestellungenNachMonat[jahrMonat].reduce((acc, bestellung) => {
return acc + provisionen[bestellung.ausweis.ausweisart] || 0; const { provision_prozent, provision_betrag } = getProvision(bestellung.ausweis.ausweisart, bestellung.ausweis.ausweistyp, provisionen);
}, 0) * 1.19} return acc + provision_betrag;
}, 0)}
<!-- <div onclick="$(this).nextUntil('.dropdown_month').filter('table').toggle(); $('#betrag_gesamt').html('Abrechnungsbetrag $month_name: <b>$provision_month €</b>')" class='dropdown_month'> <details class="group" open>
<p>$month_name $year_name - Klicke, um Tabelle anzuzeigen</p> <summary class="flex justify-between items-center cursor-pointer p-4 bg-gray-100 hover:bg-gray-200">
<a target='_blank' rel='noreferrer noopener' href='/user/abrechnung/pdf.php?month={dt.format("m")}&year={dt.format("Y")}'>PDF Ansehen</a> <span class="font-semibold">{moment(dt).format("MMMM YYYY")}</span>
</div> --> <div class="flex flex-row gap-4 items-center">
<table class="w-full mb-4 border-collapse border border-gray-300"> <a href={`/dashboard/abrechnung/monatlich.pdf?d=${moment(dt).format("YYYY-MM")}`} target="_blank" rel="noreferrer noopener">PDF generieren</a>
<thead> <span class="text-gray-500">{provisionMonat.toFixed(2)}</span>
<tr class="bg-primary text-white"> <svg class="w-4 h-4 transition-transform duration-300 group-open:rotate-180" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<td class="text-center font-bold">ID</td> <polyline points="6 9 12 15 18 9"></polyline>
<td class="text-center font-bold">DATUM</td> </svg>
<td class="text-center font-bold">GEBÄUDEADRESSE </td> </div>
<td class="text-center font-bold">PLZ </td> </summary>
<td class="text-center font-bold">ORT </td> <div class="p-4">
<td class="text-center font-bold">AUSWEIS</td> <table class="w-full mb-4 border-collapse border border-gray-300">
<td class="text-center font-bold w-48">BETRAG NETTO</td> <thead>
</tr> <tr class="bg-primary text-white w-full">
</thead> <td class="text-center p-2 font-bold">ID</td>
<tbody class="text-sm"> <td class="text-center p-2 font-bold">Datum</td>
<tr class="bg-secondary text-white"> <td class="text-center p-2 font-bold">Ausweis</td>
<td class="text-center font-bold" colspan="6">{months[dt.format("MM")]} {dt.format("YYYY")}</td> <td class="text-center p-2 font-bold">Provision in %</td>
<td class="text-right font-bold w-48" style="font-family: monospace;">{provisionMonat.toFixed(2)}</td> <td class="text-center p-2 font-bold">Betrag Netto</td>
</tr> </tr>
</tbody> </thead>
{#each bestellungenNachMonat[jahrMonat] as bestellung} <tbody class="text-sm">
{@const provisionBestellung = provisionen[bestellung.ausweis.ausweisart] || 0} {#each bestellungenNachMonat[jahrMonat] as bestellung}
<tr> {@const provisionBestellung = getProvision(bestellung.ausweis.ausweisart, bestellung.ausweis.ausweistyp, provisionen)}
<td class="text-center px-4 w-24" style="font-family: monospace;">{bestellung.id}</td> <tr class="border-b border-gray-300 hover:bg-gray-100">
<td class="text-center font-bold w-32">{moment(bestellung.created_at).format("DD.MM.YYYY")}</td> <td class="text-center py-2 px-4 w-24" style="font-family: monospace;">{bestellung.ausweis.id}</td>
<td class="text-left w-64">{bestellung.ausweis.aufnahme.objekt.adresse}</td> <td class="text-center py-2 font-bold w-32">{moment(bestellung.created_at).format("DD.MM.YYYY HH:mm")}</td>
<td class="text-center w-16">{bestellung.ausweis.aufnahme.objekt.plz}</td> <td class="text-center py-2 w-32">{bestellung.ausweis.ausweisart} {bestellung.ausweis.ausweistyp}</td>
<td class="text-left w-64">{bestellung.ausweis.aufnahme.objekt.ort}</td> <td class="text-center py-2 w-32">{provisionBestellung?.provision_prozent || 0} %</td>
<td class="text-center w-32">{bestellung.ausweis.ausweisart}</td> <td class="text-right py-2 w-24" style="font-family: monospace;">{provisionBestellung?.provision_betrag.toFixed(2)}</td>
<td class="text-right w-48" style="font-family: monospace;">{provisionBestellung.toFixed(2)}</td> </tr>
</tr> {/each}
{/each} </tbody>
</table> </table>
</div>
</details>
{:else if !bestellungenNachMonat[jahrMonat] || bestellungenNachMonat[jahrMonat].length === 0}
<details class="group">
<summary class="flex justify-between items-center cursor-pointer p-4 bg-gray-100 hover:bg-gray-200">
<span class="font-semibold">{moment(dt).format("MMMM YYYY")}</span>
<div class="flex flex-row gap-4 items-center">
<span class="text-gray-500">Keine Bestellungen gefunden</span>
<svg class="w-4 h-4 transition-transform duration-300 group-open:rotate-180" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</div>
</summary>
<div class="p-4 border-t border-gray-200">
<p class="text-gray-500">Für diesen Monat liegen uns keine Bestellungen über ihren Resellercode vor.</p>
</div>
</details>
{/if} {/if}
{/each} {/each}
</main> </main>

View File

@@ -11,6 +11,22 @@
export let objekt: ObjektClient; export let objekt: ObjektClient;
export let ausweisart: Enums.Ausweisart; export let ausweisart: Enums.Ausweisart;
function onlyAllowIntegerInput(event: KeyboardEvent) {
const charCode = event.which || event.keyCode;
// Allow only numbers (0-9) and control keys (e.g., backspace)
if (charCode < 48 || charCode > 57) {
event.preventDefault();
}
}
function onlyAllowPastingIntegers(event: ClipboardEvent) {
const clipboardData = event.clipboardData || (window as any).clipboardData;
const pastedData = clipboardData.getData("Text");
if (!/^\d+$/.test(pastedData)) {
event.preventDefault();
}
}
</script> </script>
<div <div
@@ -97,10 +113,11 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
data-test="flaeche" data-test="flaeche"
maxlength="4" maxlength="4"
type="number" type="number"
step="1"
required required
autocomplete="off" autocomplete="off"
data-rule-minlength="2" on:keypress={onlyAllowIntegerInput}
data-msg-minlength="min. 2 Zeichen" on:paste={onlyAllowPastingIntegers}
bind:value={aufnahme.flaeche} bind:value={aufnahme.flaeche}
/> />
@@ -124,7 +141,10 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
data-test="nutzflaeche" data-test="nutzflaeche"
maxlength="4" maxlength="4"
type="number" type="number"
step="1"
required required
on:keypress={onlyAllowIntegerInput}
on:paste={onlyAllowPastingIntegers}
bind:value={aufnahme.nutzflaeche} bind:value={aufnahme.nutzflaeche}
/> />

View File

@@ -60,6 +60,8 @@
return centeredValue; return centeredValue;
} }
let translation_1 = 0; let translation_1 = 0;
let translation_2 = 0; let translation_2 = 0;
$: { $: {
@@ -74,6 +76,7 @@
if (!result) { if (!result) {
return; return;
} }
translation_1 = Math.max( translation_1 = Math.max(
0, 0,
Math.min( Math.min(

View File

@@ -16,7 +16,7 @@ const brennstoffe: [
["Flüssiggas", "kg", 13.0, 1.1, 0.27], ["Flüssiggas", "kg", 13.0, 1.1, 0.27],
["Braunkohle", "kg", 5.5, 1.2, 0.43], ["Braunkohle", "kg", 5.5, 1.2, 0.43],
["Holzhackschnitzel", "SRm", 650.0, 0.2, 0.02], ["Holzhackschnitzel", "SRm", 650.0, 0.2, 0.02],
["Strommix", "kWh", 1.0, 2.4, 0.56], ["Strommix", "kWh", 1.0, 1.8, 0.56],
["Fernwärme KWK FB", "kWh", 1.0, 0.7, 0.3], ["Fernwärme KWK FB", "kWh", 1.0, 0.7, 0.3],
["Nahwärme KWK FB", "kWh", 1.0, 0.7, 0.3], ["Nahwärme KWK FB", "kWh", 1.0, 0.7, 0.3],
["Heizöl EL", "kWh", 1.0, 1.1, 0.31], ["Heizöl EL", "kWh", 1.0, 1.1, 0.31],
@@ -42,6 +42,9 @@ const brennstoffe: [
["Fernwärme HKW FB", "kWh", 1.0, 1.3, 0.4], ["Fernwärme HKW FB", "kWh", 1.0, 1.3, 0.4],
["Fernwärme HKW EB", "kWh", 1.0, 0.1, 0.06], ["Fernwärme HKW EB", "kWh", 1.0, 0.1, 0.06],
["Fernwärme Hamburg", "kWh", 1.0, 0.33, 0.064], ["Fernwärme Hamburg", "kWh", 1.0, 0.33, 0.064],
["Fernwärme Erfurt", "kWh", 1.0, 0.3, 0],
["Fernwärme Neumünster", "kWh", 1.0, 0.28, 0.0133],
["Fernwärme Pforzheim", "kWh", 1.0, 0.25, 0],
["Erdgas", "kWh", 1.0, 1.1, 0.24], ["Erdgas", "kWh", 1.0, 1.1, 0.24],
["Heizöl", "kWh", 1.0, 1.1, 0.31], ["Heizöl", "kWh", 1.0, 1.1, 0.31],
["Heizöl", "l", 10.0, 1.1, 0.31], ["Heizöl", "l", 10.0, 1.1, 0.31],

View File

@@ -5,7 +5,7 @@
CaretDown, CaretDown,
MagnifyingGlass, MagnifyingGlass,
} from "radix-svelte-icons"; } from "radix-svelte-icons";
import { Benutzer } from "#lib/server/prisma.js"; import { Benutzer, Enums } from "#lib/client/prisma.js";
export let lightTheme: boolean; export let lightTheme: boolean;
export let benutzer: Benutzer; export let benutzer: Benutzer;
@@ -38,6 +38,12 @@
{benutzer.name} {benutzer.name}
</div> </div>
<div class="text-base-content text-sm flex">{benutzer.email}</div> <div class="text-base-content text-sm flex">{benutzer.email}</div>
{#if benutzer.rolle === Enums.BenutzerRolle.RESELLER}
<div class="text-xs text-gray-500 flex">Reseller</div>
{/if}
{#if benutzer.rolle === Enums.BenutzerRolle.ADMIN}
<div class="text-xs text-gray-500 flex">Admin</div>
{/if}
<a href="/auth/logout" class="text-xs">Logout</a> <a href="/auth/logout" class="text-xs">Logout</a>
</div> </div>
</div> </div>
@@ -70,7 +76,9 @@
</div> </div>
</div> </div>
{/if} {/if}
<!-- <a href="/dashboard/abrechnung" class="button ">Monatliche Abrechnung</a> --> {#if benutzer.rolle === Enums.BenutzerRolle.RESELLER}
<a href="/dashboard/abrechnung" class="button ">Monatliche Abrechnung</a>
{/if}
</div> </div>
<hr class="border-gray-600" /> <hr class="border-gray-600" />

View File

@@ -2,9 +2,10 @@
import { dialogs } from "svelte-dialogs"; import { dialogs } from "svelte-dialogs";
import TicketPopup from "./TicketPopup.svelte"; import TicketPopup from "./TicketPopup.svelte";
import { addNotification } from "@ibcornelsen/ui"; import { addNotification } from "@ibcornelsen/ui";
export let userEmail: string = "";
async function showTicketPopup() { async function showTicketPopup() {
const success = await dialogs.modal(TicketPopup); const success = await dialogs.modal(TicketPopup, { email: userEmail });
console.log(success); console.log(success);

View File

@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { api } from "astro-typesafe-api/client"; import { api } from "astro-typesafe-api/client";
import { getClose } from "svelte-dialogs"; import { getClose } from "svelte-dialogs";
export let email: string = "";
const close = getClose(); const close = getClose();
@@ -12,7 +13,7 @@
email: email, email: email,
metadata: { metadata: {
category: category, category: category,
phone: phone, telefon: telefon,
}, },
titel: title, titel: title,
}) })
@@ -27,8 +28,8 @@
let category = ""; let category = "";
let title = ""; let title = "";
let description = ""; let description = "";
let email = ""; //let email = "";
let phone = ""; let telefon = "";
</script> </script>
<form class="max-w-lg" on:submit={createTicket}> <form class="max-w-lg" on:submit={createTicket}>
@@ -89,9 +90,9 @@
<input <input
class="input input-bordered" class="input input-bordered"
type="tel" type="tel"
placeholder="Ihre Telefonnumer" placeholder="Ihre Telefonnummer"
name="phone" name="telefon"
bind:value={phone} bind:value={telefon}
required required
/> />
</div> </div>

View File

@@ -1,7 +1,9 @@
<script lang="ts"> <script lang="ts">
import HelpLabel from "#components/labels/HelpLabel.svelte"; import HelpLabel from "#components/labels/HelpLabel.svelte";
import type { Enums } from "#lib/client/prisma.js"; import type { Enums } from "#lib/client/prisma.js";
import Cookies from "js-cookie";
import { tryCatch } from "#lib/tryCatch.js"; import { tryCatch } from "#lib/tryCatch.js";
import heic2any from "heic2any";
export let max: number = 2; export let max: number = 2;
export let min: number = 1; export let min: number = 1;
@@ -17,6 +19,7 @@
} from "./Ausweis/types.js"; } from "./Ausweis/types.js";
import { api } from "astro-typesafe-api/client"; import { api } from "astro-typesafe-api/client";
import { addNotification } from "./Notifications/shared.js"; import { addNotification } from "./Notifications/shared.js";
import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js";
export let images: BildClient[] = []; export let images: BildClient[] = [];
export let ausweis: export let ausweis:
@@ -36,8 +39,10 @@
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
const file = files[i]; const file = files[i];
console.log(file);
if (file.type !== "image/jpeg" && file.type !== "image/png") {
if (file.type !== "image/jpeg" && file.type !== "image/png" && file.type !== "image/webp" && file.type !== "image/heif" && file.type !== "image/heic") {
continue; continue;
} }
@@ -47,7 +52,7 @@
const reader = new FileReader(); const reader = new FileReader();
reader.onload = () => { reader.onload = async () => {
if (reader.readyState != reader.DONE) { if (reader.readyState != reader.DONE) {
return; return;
} }
@@ -57,6 +62,18 @@
} }
let blob = new Blob([reader.result as ArrayBuffer]); let blob = new Blob([reader.result as ArrayBuffer]);
if (file.type === "image/heif" || file.type === "image/heic") {
// Heic files are not supported by canvas, so we convert them to jpeg first
// using an external library.
// This is a workaround until all browsers support heic natively.
// For more information see: https://caniuse.com/?search=heic
// and https://developer.apple.com/documentation/imageio/reading_heif_and_heic_images_on_ios
// and https://stackoverflow.com/questions/65887402/how-to-convert-heic-to-jpeg-in-javascript
// and https://github.com/mbitsnbites/heic2any
blob = await heic2any({ blob, toType: "image/jpeg", quality: 0.8 });
}
let url = URL.createObjectURL(blob); let url = URL.createObjectURL(blob);
let image = new Image(); let image = new Image();
image.onload = async () => { image.onload = async () => {
@@ -85,6 +102,12 @@
data: dataURL, data: dataURL,
kategorie, kategorie,
name: file.name name: file.name
}, {
headers: {
"Authorization": `Bearer ${Cookies.get(
API_ACCESS_TOKEN_COOKIE_NAME
)}`
}
})) }))
if (error) { if (error) {
@@ -128,6 +151,7 @@
<div class="input-standard"> <div class="input-standard">
<input <input
type="file" type="file"
accept="image/*"
class="file-input file-input-ghost h-[38px]" class="file-input file-input-ghost h-[38px]"
bind:this={fileUpload} bind:this={fileUpload}
{name} {name}
@@ -144,6 +168,7 @@
<div class="input-standard"> <div class="input-standard">
<input <input
type="file" type="file"
accept="image/*"
class="file-input file-input-ghost h-[38px]" class="file-input file-input-ghost h-[38px]"
bind:this={fileUpload} bind:this={fileUpload}
{name} {name}

View File

@@ -11,7 +11,7 @@ export async function auditEndEnergie(ausweis: VerbrauchsausweisWohnenClient | V
if (aufnahme){ if (aufnahme){
if (aufnahme.flaeche && ausweis.verbrauch_1 && ausweis.verbrauch_2 && ausweis.verbrauch_3) { if (aufnahme.flaeche && ausweis.verbrauch_1 && ausweis.verbrauch_2 && ausweis.verbrauch_3) {
try { try {
const response = await getKlimafaktoren(ausweis.startdatum, objekt.plz); const response = await getKlimafaktoren(ausweis.startdatum as Date, objekt.plz as string);
// Alle Klimfaktoren konnten abgefragt werden. // Alle Klimfaktoren konnten abgefragt werden.
const eevva = await endEnergieVerbrauchVerbrauchsausweis_2016(ausweis, aufnahme, objekt); const eevva = await endEnergieVerbrauchVerbrauchsausweis_2016(ausweis, aufnahme, objekt);
if (eevva){ if (eevva){

View File

@@ -19,6 +19,7 @@ export type Lueftungskonzept = (typeof Lueftungskonzept)[keyof typeof Lueftungsk
export const BenutzerRolle = { export const BenutzerRolle = {
USER: "USER", USER: "USER",
ADMIN: "ADMIN", ADMIN: "ADMIN",
RESELLER: "RESELLER",
} as const; } as const;
export type BenutzerRolle = (typeof BenutzerRolle)[keyof typeof BenutzerRolle]; export type BenutzerRolle = (typeof BenutzerRolle)[keyof typeof BenutzerRolle];

View File

@@ -38,19 +38,19 @@ export const BedarfsausweisWohnenSchema = z.object({
volumen: z.number().nullish(), volumen: z.number().nullish(),
dicht: z.boolean().nullish(), dicht: z.boolean().nullish(),
fenster_flaeche_1: z.number().nullish(), fenster_flaeche_1: z.number().nullish(),
fenster_art_1: z.string().nullish(), fenster_art_1: z.number().nullish(),
fenster_flaeche_2: z.number().nullish(), fenster_flaeche_2: z.number().nullish(),
fenster_art_2: z.string().nullish(), fenster_art_2: z.number().nullish(),
dachfenster_flaeche: z.number().nullish(), dachfenster_flaeche: z.number().nullish(),
dachfenster_art: z.string().nullish(), dachfenster_art: z.number().nullish(),
haustuer_flaeche: z.number().nullish(), haustuer_flaeche: z.number().nullish(),
haustuer_art: z.string().nullish(), haustuer_art: z.number().nullish(),
dach_bauart: z.string().nullish(), dach_bauart: z.string().nullish(),
decke_bauart: z.string().nullish(), decke_bauart: z.string().nullish(),
dach_daemmung: z.string().nullish(), dach_daemmung: z.number().nullish(),
decke_daemmung: z.string().nullish(), decke_daemmung: z.number().nullish(),
aussenwand_daemmung: z.string().nullish(), aussenwand_daemmung: z.number().nullish(),
boden_daemmung: z.string().nullish(), boden_daemmung: z.number().nullish(),
aussenwand_bauart: z.string().nullish(), aussenwand_bauart: z.string().nullish(),
boden_bauart: z.string().nullish(), boden_bauart: z.string().nullish(),
warmwasser_verteilung: z.string().nullish(), warmwasser_verteilung: z.string().nullish(),

View File

@@ -17,6 +17,7 @@ export const BenutzerSchema = z.object({
rolle: z.nativeEnum(BenutzerRolle), rolle: z.nativeEnum(BenutzerRolle),
firma: z.string().nullish(), firma: z.string().nullish(),
lex_office_id: z.string().nullish(), lex_office_id: z.string().nullish(),
partner_code: z.string().nullish(),
verified: z.boolean(), verified: z.boolean(),
created_at: z.date(), created_at: z.date(),
updated_at: z.date(), updated_at: z.date(),

View File

@@ -7,5 +7,6 @@ export const BildSchema = z.object({
name: z.string(), name: z.string(),
created_at: z.date(), created_at: z.date(),
updated_at: z.date(), updated_at: z.date(),
benutzer_id: z.string().nullish(),
aufnahme_id: z.string().nullish(), aufnahme_id: z.string().nullish(),
}) })

View File

@@ -12,6 +12,7 @@ export * from "./gegnachweiswohnen"
export * from "./klimafaktoren" export * from "./klimafaktoren"
export * from "./objekt" export * from "./objekt"
export * from "./postleitzahlen" export * from "./postleitzahlen"
export * from "./provisionen"
export * from "./rechnung" export * from "./rechnung"
export * from "./refreshtokens" export * from "./refreshtokens"
export * from "./tickets" export * from "./tickets"

View File

@@ -0,0 +1,13 @@
import * as z from "zod"
import { Ausweisart, AusweisTyp } from "@prisma/client"
export const ProvisionenSchema = z.object({
id: z.number().int(),
ausweisart: z.nativeEnum(Ausweisart),
ausweistyp: z.nativeEnum(AusweisTyp),
provision_prozent: z.number(),
provision_betrag: z.number(),
benutzer_id: z.string().nullish(),
created_at: z.date(),
updated_at: z.date(),
})

View File

@@ -150,7 +150,7 @@ height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<Footer /> <Footer />
<NotificationWrapper client:load /> <NotificationWrapper client:load />
<TicketButton client:load></TicketButton> <TicketButton client:load userEmail={user?.email ?? ""}></TicketButton>
</body> </body>
</html> </html>

View File

@@ -10,9 +10,8 @@ export async function endEnergieVerbrauchVerbrauchsausweisGewerbe_2016_Client(
) { ) {
const startdatum = moment(ausweis.startdatum); const startdatum = moment(ausweis.startdatum);
let klimafaktoren = await getKlimafaktoren( let klimafaktoren = await getKlimafaktoren(
objekt.plz as string,
startdatum.toDate(), startdatum.toDate(),
startdatum.add(2, "years").toDate() objekt.plz as string,
); );
if (!klimafaktoren || klimafaktoren.length === 0) { if (!klimafaktoren || klimafaktoren.length === 0) {

View File

@@ -13,12 +13,14 @@ export async function endEnergieVerbrauchVerbrauchsausweis_2016_Client(
objekt: ObjektClient, objekt: ObjektClient,
) { ) {
const startdatum = moment(ausweis.startdatum); const startdatum = moment(ausweis.startdatum);
let klimafaktoren = await getKlimafaktoren( let klimafaktoren = await getKlimafaktoren(
objekt.plz as string,
startdatum.toDate(), startdatum.toDate(),
startdatum.add(2, "years").toDate() objekt.plz as string
); );
console.log("Klimafaktoren", klimafaktoren);
if (!klimafaktoren || klimafaktoren.length === 0) { if (!klimafaktoren || klimafaktoren.length === 0) {
klimafaktoren = [ klimafaktoren = [
{ {

View File

@@ -7,17 +7,13 @@ export const getKlimafaktoren = memoize(async (date: Date, plz: string) => {
return null; return null;
} }
try { const response = await api.klimafaktoren.GET.fetch({
const response = await api.klimafaktoren.GET.fetch({ plz,
plz, genauigkeit: "years",
genauigkeit: "years", // @ts-ignore Der Adapter nimmt z.coerce.date()
// @ts-ignore Der Adapter nimmt z.coerce.date() startdatum: moment(date).utc(true).toString(),
startdatum: moment(date).utc(true).toString(), // @ts-ignore Der Adapter nimmt z.coerce.date()
// @ts-ignore Der Adapter nimmt z.coerce.date() enddatum: moment(date).add(2, "years").utc(true).toString(),
enddatum: moment(date).add(2, "years").utc(true).toString(), });
}); return response;
return response;
} catch (e) {
return null;
}
}); });

View File

@@ -1,18 +1,21 @@
type MemoizedFunction<T> = (...args: any[]) => T; type MemoizedFunction<T extends (...args: any[]) => Promise<any> | any> = (...args: Parameters<T>) => Promise<ReturnType<T>> | ReturnType<T>;
export function memoize<T>(func: (...args: any[]) => T): MemoizedFunction<T> { export function memoize<T extends (...args: any[]) => Promise<any> | any>(func: T): MemoizedFunction<T> {
const cache = new Map<string, T>(); const cache = new Map<string, ReturnType<T>>();
return (...args: any[]): T => { return (...args: Parameters<T>): Promise<ReturnType<T>> | ReturnType<T> => {
const key = JSON.stringify(args); const key = JSON.stringify(args);
if (cache.has(key)) { if (cache.has(key)) {
return cache.get(key)!; return cache.get(key)!;
} }
const result = func(...args); const result = func(...args);
cache.set(key, result); if (result instanceof Promise) {
return result.then(resolved => {
return result; return resolved;
});
} else {
cache.set(key, result);
return result;
}
}; };
} }

View File

@@ -92,11 +92,14 @@ export async function authorizationMiddleware(input: any, ctx: TypesafeAPIContex
} }
export async function maybeAuthorizationMiddleware(input: any, ctx: TypesafeAPIContextWithRequest<any>) { export async function maybeAuthorizationMiddleware(input: any, ctx: TypesafeAPIContextWithRequest<any>) {
let user = null;
try { try {
return authorizationMiddleware(input, ctx) user = await authorizationMiddleware(input, ctx)
} catch(e) { } catch(e) {
return null; console.log(e);
} }
return user;
} }
export const authorizationHeaders = { export const authorizationHeaders = {

14
src/lib/provision.ts Normal file
View File

@@ -0,0 +1,14 @@
import { Enums, Provisionen } from "./client/prisma.js";
export function getProvision(ausweisart: Enums.Ausweisart, ausweistyp: Enums.AusweisTyp, provisionen: Provisionen[]): { provision_prozent: number, provision_betrag: number } {
const provision = provisionen.find(p => p.ausweisart === ausweisart && p.ausweistyp === ausweistyp);
return {
provision_prozent: provision?.provision_prozent || 0,
provision_betrag: provision?.provision_betrag || 0
};
}
export function getProductName(ausweisart: Enums.Ausweisart, ausweistyp: Enums.AusweisTyp): string {
return `${Enums.Ausweisart[ausweisart]} ${Enums.AusweisTyp[ausweistyp]}`;
}

View File

@@ -193,6 +193,11 @@ export function extrahiereAusweisAusFeldMitMehrerenAusweisen<T>(
: verbrauchsausweis_wohnen : verbrauchsausweis_wohnen
? Enums.Ausweisart.VerbrauchsausweisWohnen ? Enums.Ausweisart.VerbrauchsausweisWohnen
: Enums.Ausweisart.VerbrauchsausweisGewerbe, : Enums.Ausweisart.VerbrauchsausweisGewerbe,
ausweistyp: bedarfsausweis_wohnen
? bedarfsausweis_wohnen.ausweistyp
: verbrauchsausweis_wohnen
? verbrauchsausweis_wohnen.ausweistyp
: verbrauchsausweis_gewerbe?.ausweistyp || Enums.AusweisTyp.Standard,
}, },
} as { } as {
ausweis: ( ausweis: (

View File

@@ -12,7 +12,7 @@ export async function sendRegisterMail(
const verificationJwt = encodeToken({ const verificationJwt = encodeToken({
typ: TokenType.Verify, typ: TokenType.Verify,
exp: Date.now() + (15 * 60 * 1000), exp: Date.now() + (15 * 60 * 1000),
uid: user.uid id: user.id
}) })
await transport.sendMail({ await transport.sendMail({

View File

@@ -72,6 +72,7 @@
name="email" name="email"
class="px-2.5 py-1.5 rounded-lg border bg-gray-50" class="px-2.5 py-1.5 rounded-lg border bg-gray-50"
bind:value={email} bind:value={email}
on:keyup={() => (email = email.toLowerCase())}
required required
/> />
</div> </div>

View File

@@ -3,7 +3,6 @@
import { CrossCircled } from "radix-svelte-icons"; import { CrossCircled } from "radix-svelte-icons";
import { fade } from "svelte/transition"; import { fade } from "svelte/transition";
import { api } from "astro-typesafe-api/client"; import { api } from "astro-typesafe-api/client";
import NotificationProvider from "#components/NotificationProvider/NotificationProvider.svelte";
import NotificationWrapper from "#components/Notifications/NotificationWrapper.svelte"; import NotificationWrapper from "#components/Notifications/NotificationWrapper.svelte";
let passwort: string; let passwort: string;
@@ -13,10 +12,6 @@
export let redirect: string | null = null; export let redirect: string | null = null;
function handleInput(event) {
email = event.target.value.toLowerCase();
}
async function login(e: SubmitEvent) { async function login(e: SubmitEvent) {
e.preventDefault() e.preventDefault()
if (passwort.length < 8) { if (passwort.length < 8) {
@@ -30,7 +25,7 @@
} }
try { try {
const { uid } = await api.user.PUT.fetch({ const { id } = await api.user.PUT.fetch({
email, email,
passwort, passwort,
vorname, vorname,
@@ -87,7 +82,7 @@
name="email" name="email"
class="input input-bordered text-base text-base-content font-medium" class="input input-bordered text-base text-base-content font-medium"
bind:value={email} bind:value={email}
on:input={handleInput} on:keyup={() => (email = email.toLowerCase())}
required required
/> />
</div> </div>

View File

@@ -64,7 +64,7 @@ export const GET = defineApiRoute({
const { id } = ctx.params; const { id } = ctx.params;
const aufnahme = await prisma.aufnahme.findUnique({ const aufnahme = await prisma.aufnahme.findUnique({
where: user.rolle === Enums.BenutzerRolle.USER ? { where: user.rolle !== Enums.BenutzerRolle.ADMIN ? {
id, id,
benutzer_id: user.id benutzer_id: user.id
} : { id }, } : { id },

View File

@@ -63,7 +63,7 @@ export const GET = defineApiRoute({
const { id } = context.params; const { id } = context.params;
const aufnahme = await prisma.aufnahme.findUnique({ const aufnahme = await prisma.aufnahme.findUnique({
where: user.rolle === Enums.BenutzerRolle.USER ? { where: user.rolle !== Enums.BenutzerRolle.ADMIN ? {
id, id,
benutzer_id: user.id benutzer_id: user.id
} : { id }, } : { id },

View File

@@ -49,7 +49,7 @@ export const PATCH = defineApiRoute({
data: input data: input
}) })
if (user.rolle === Enums.BenutzerRolle.USER) { if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
await sendAusweisGespeichertMail(user, ctx.params.id as string) await sendAusweisGespeichertMail(user, ctx.params.id as string)
} }
}, },

View File

@@ -66,7 +66,7 @@ export const PUT = defineApiRoute({
} }
} }
}); });
if (user.rolle === Enums.BenutzerRolle.USER) { if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
await sendAusweisGespeichertMail(user, id) await sendAusweisGespeichertMail(user, id)
} }
return nachweis.id return nachweis.id

View File

@@ -48,7 +48,7 @@ export const PATCH = defineApiRoute({
}, },
data: input data: input
}) })
if (user.rolle === Enums.BenutzerRolle.USER) { if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
await sendAusweisGespeichertMail(user, ctx.params.id as string) await sendAusweisGespeichertMail(user, ctx.params.id as string)
} }
}, },

View File

@@ -73,7 +73,7 @@ export const PUT = defineApiRoute({
}, },
}, },
}); });
if (user.rolle === Enums.BenutzerRolle.USER) { if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
await sendAusweisGespeichertMail(user, id) await sendAusweisGespeichertMail(user, id)
} }
return id; return id;

View File

@@ -23,7 +23,8 @@ export const PUT = defineApiRoute({
output: z.object({ output: z.object({
id: z.string({ description: "Die id des Bildes." }), id: z.string({ description: "Die id des Bildes." }),
}), }),
async fetch(input) { middleware: maybeAuthorizationMiddleware,
async fetch(input, context, user) {
const data = input.data; const data = input.data;
if (!isBase64(data, { mimeRequired: true })) { if (!isBase64(data, { mimeRequired: true })) {
@@ -43,6 +44,7 @@ export const PUT = defineApiRoute({
id, id,
kategorie: input.kategorie, kategorie: input.kategorie,
name: input.name, name: input.name,
benutzer_id: user ? user.id : null,
}, },
}); });
@@ -91,11 +93,11 @@ export const DELETE = defineApiRoute({
await prisma.bild.delete({ await prisma.bild.delete({
where: { where: {
id: input.id, id: input.id,
aufnahme: { OR: [{
benutzer: { benutzer_id: user.id
id: user.id, }, {
}, benutzer_id: null
}, }]
}, },
}); });
} else { } else {
@@ -107,6 +109,8 @@ export const DELETE = defineApiRoute({
}); });
} }
} catch (e) { } catch (e) {
console.log(e);
throw new APIError({ throw new APIError({
code: "INTERNAL_SERVER_ERROR", code: "INTERNAL_SERVER_ERROR",
message: "Bild konnte nicht gelöscht werden.", message: "Bild konnte nicht gelöscht werden.",

View File

@@ -48,7 +48,7 @@ export const PATCH = defineApiRoute({
}, },
data: input data: input
}) })
if (user.rolle === Enums.BenutzerRolle.USER) { if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
await sendAusweisGespeichertMail(user, ctx.params.id as string) await sendAusweisGespeichertMail(user, ctx.params.id as string)
} }
}, },

View File

@@ -83,7 +83,7 @@ export const PUT = defineApiRoute({
}, },
}, },
}); });
if (user.rolle === Enums.BenutzerRolle.USER) { if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
await sendAusweisGespeichertMail(user, id) await sendAusweisGespeichertMail(user, id)
} }
return { return {

View File

@@ -48,7 +48,7 @@ export const PATCH = defineApiRoute({
}, },
data: input data: input
}) })
if (user.rolle === Enums.BenutzerRolle.USER) { if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
await sendAusweisGespeichertMail(user, ctx.params.id as string) await sendAusweisGespeichertMail(user, ctx.params.id as string)
} }
}, },

View File

@@ -83,7 +83,7 @@ export const PUT = defineApiRoute({
}, },
}, },
}); });
if (user.rolle === Enums.BenutzerRolle.USER) { if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
await sendAusweisGespeichertMail(user, id) await sendAusweisGespeichertMail(user, id)
} }
return { return {

View File

@@ -66,7 +66,7 @@ export const GET = defineApiRoute({
const { id } = ctx.params; const { id } = ctx.params;
const objekt = await prisma.objekt.findUnique({ const objekt = await prisma.objekt.findUnique({
where: user.rolle === Enums.BenutzerRolle.USER ? { where: user.rolle !== Enums.BenutzerRolle.ADMIN ? {
id, id,
benutzer_id: user.id benutzer_id: user.id
} : { id }, } : { id },

View File

@@ -51,7 +51,10 @@ export const PUT = defineApiRoute({
// Wir erstellen eine Mollie Payment Referenz und eine neue Rechnung in unserer Datenbank, daraufhin geben // Wir erstellen eine Mollie Payment Referenz und eine neue Rechnung in unserer Datenbank, daraufhin geben
// wir eine Checkout URL zurück auf die der Nutzer weitergeleitet werden kann. // wir eine Checkout URL zurück auf die der Nutzer weitergeleitet werden kann.
const { ausweis_id, ausweisart, bezahlmethode, services, partner_code } = input; let { ausweis_id, ausweisart, bezahlmethode, services, partner_code } = input;
// Manchmal startet der partner_code mit Gänsefüßchen, das entfernen wir hier.
partner_code = partner_code?.replace(/"/g, "") ?? null;
const adapter = getPrismaAusweisAdapter(ausweis_id); const adapter = getPrismaAusweisAdapter(ausweis_id);

View File

@@ -50,15 +50,15 @@ export const PUT = defineApiRoute({
const url = new URL("https://api.trello.com/1/cards") const url = new URL("https://api.trello.com/1/cards")
url.searchParams.append("name", input.titel) url.searchParams.append("name", input.titel)
url.searchParams.append("desc", `User: ${input.email}\n\nDescription: ${input.beschreibung}\n\nCategory: ${category}`) url.searchParams.append("desc", `User: ${input.email}\n\nDescription: ${input.beschreibung}\n\nKategorie: ${category}\n\nTelefon: ${(input.metadata as Record<string, string>).telefon || "Nicht angegeben"}`)
url.searchParams.append("pos", "top") url.searchParams.append("pos", "top")
url.searchParams.append("idLabels", url.searchParams.append("idLabels",
(category && (category &&
labels[category as keyof typeof labels]) || labels[category as keyof typeof labels]) ||
"650e909fdc09629a4d6d495d") "650e909fdc09629a4d6d495d")
url.searchParams.append("key", "e057eb39018368ea96e456c753ac41b4") url.searchParams.append("key", "e057eb39018368ea96e456c753ac41b4")
url.searchParams.append("idList", "650303186e721b4bef0c3980") url.searchParams.append("idList", "67d75ca7403fd22c49bc7447")
url.searchParams.append("token", "ATTA8b65b3587ab627167038cc32a3460650973eb181cde01dabb208ca1e90ed5467AC06A4F2") url.searchParams.append("token", "ATTA6f1774d98472db1897a2373ee7b55ab15f218c2445b6609dfef3071fe5203a90DB15678A")
// Wir laden das Ticket zu Trello hoch. // Wir laden das Ticket zu Trello hoch.
const result = await fetch(url, { const result = await fetch(url, {

View File

@@ -65,10 +65,10 @@ export const GET = defineApiRoute({
})), })),
output: z.array(BenutzerSchema), output: z.array(BenutzerSchema),
middleware: authorizationMiddleware, middleware: authorizationMiddleware,
async fetch(input, context, admin) { async fetch(input, context, benutzer) {
if ("id" in input) { if ("id" in input) {
//Only Admin can read other users // Nur Admins oder der Benutzer selbst kann einen einzelnen Benutzer lesen
if (admin.rolle != Enums.BenutzerRolle.ADMIN && input.id != admin.id) { if (benutzer.rolle != Enums.BenutzerRolle.ADMIN && input.id != benutzer.id) {
return; return;
} }
@@ -84,8 +84,8 @@ export const GET = defineApiRoute({
return [user]; return [user];
} else { } else {
//Only admin can read many users // Nur Admins können nach mehreren Benutzern suchen
if (admin.rolle != Enums.BenutzerRolle.ADMIN ) { if (benutzer.rolle != Enums.BenutzerRolle.ADMIN) {
return; return;
} }
@@ -114,9 +114,12 @@ export const PUT = defineApiRoute({
id: IDWithPrefix id: IDWithPrefix
}), }),
async fetch(input) { async fetch(input) {
let { email, passwort, vorname, name } = input;
email = email.toLowerCase();
const existingUser = await prisma.benutzer.findUnique({ const existingUser = await prisma.benutzer.findUnique({
where: { where: {
email: input.email email
} }
}) })
@@ -131,10 +134,10 @@ export const PUT = defineApiRoute({
const user = await prisma.benutzer.create({ const user = await prisma.benutzer.create({
data: { data: {
email: input.email, email,
passwort: hashPassword(input.passwort), passwort: hashPassword(passwort),
vorname: input.vorname, vorname,
name: input.name, name,
id id
} }
}) })

View File

@@ -46,7 +46,7 @@ export const PATCH = defineApiRoute({
data: input data: input
}) })
if (user.rolle === Enums.BenutzerRolle.USER) { if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
await sendAusweisGespeichertMail(user, ctx.params.id as string) await sendAusweisGespeichertMail(user, ctx.params.id as string)
} }
}, },
@@ -173,7 +173,7 @@ export const GET = defineApiRoute({
} }
const ausweis = await prisma.verbrauchsausweisGewerbe.findUnique({ const ausweis = await prisma.verbrauchsausweisGewerbe.findUnique({
where: user.rolle === Enums.BenutzerRolle.USER ? { where: user.rolle !== Enums.BenutzerRolle.ADMIN ? {
id, id,
benutzer_id: user.id benutzer_id: user.id
} : { id }, } : { id },

View File

@@ -47,7 +47,7 @@ export const PATCH = defineApiRoute({
data: input data: input
}) })
if (user.rolle === Enums.BenutzerRolle.USER) { if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
await sendAusweisGespeichertMail(user, ctx.params.id as string) await sendAusweisGespeichertMail(user, ctx.params.id as string)
} }
}, },
@@ -174,7 +174,7 @@ export const GET = defineApiRoute({
} }
const ausweis = await prisma.verbrauchsausweisWohnen.findUnique({ const ausweis = await prisma.verbrauchsausweisWohnen.findUnique({
where: user.rolle === Enums.BenutzerRolle.USER ? { where: user.rolle !== Enums.BenutzerRolle.ADMIN ? {
id, id,
benutzer_id: user.id benutzer_id: user.id
} : { id }, } : { id },

View File

@@ -103,7 +103,7 @@ export const PUT = defineApiRoute({
}, },
}); });
if (user.rolle === Enums.BenutzerRolle.USER) { if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
await sendAusweisGespeichertMail(user, id); await sendAusweisGespeichertMail(user, id);
} }
return { return {

View File

@@ -1,16 +1,26 @@
--- ---
import AbrechnungTable from "#components/Abrechnung/AbrechnungTable.svelte"; import AbrechnungTable from "#components/Abrechnung/AbrechnungTable.svelte";
import BlankLayout from "#layouts/BlankLayout.astro"; import BlankLayout from "#layouts/BlankLayout.astro";
import { getProvision } from "#lib/provision";
import { extrahiereAusweisAusFeldMitMehrerenAusweisen } from "#lib/server/ausweis"; import { extrahiereAusweisAusFeldMitMehrerenAusweisen } from "#lib/server/ausweis";
import { Enums, prisma } from "#lib/server/prisma"; import { Enums, prisma } from "#lib/server/prisma";
import { getCurrentUser } from "#lib/server/user"; import { getCurrentUser } from "#lib/server/user";
import moment from "moment"; import moment from "moment-timezone";
moment.locale("de");
moment.tz.setDefault("Europe/Berlin");
const start = moment(Astro.url.searchParams.get("start")); const start = moment(Astro.url.searchParams.get("start"));
const end = moment(Astro.url.searchParams.get("end")); const end = moment(Astro.url.searchParams.get("end"));
let startdatum = start.isValid() ? start.toDate() : moment().startOf("month").toDate(); let startdatum = start.isValid() ? start.startOf("day").toDate() : moment().startOf("month").toDate();
let enddatum = end.isValid() ? end.toDate() : moment().endOf("month").toDate(); let enddatum = end.isValid() ? end.endOf("day").toDate() : moment().endOf("month").toDate();
// Wir dürfen die Abrechnung erst ab Juni starten lassen.
if (startdatum < moment().set("year", 2025).set("month", 5).set("date", 1).toDate()) {
startdatum = moment().set("year", 2025).set("month", 5).set("date", 1).set("hour", 0).set("minute", 0).set("second", 0).toDate();
enddatum = moment().set("year", 2025).set("month", 5).set("date", 1).set("hour", 0).set("minute", 0).set("second", 0).endOf("month").toDate();
}
const benutzer = await getCurrentUser(Astro); const benutzer = await getCurrentUser(Astro);
@@ -18,19 +28,13 @@ if (!benutzer) {
return Astro.redirect("/404"); return Astro.redirect("/404");
} }
const provisionen = {
[Enums.Ausweisart.VerbrauchsausweisWohnen]: 10,
[Enums.Ausweisart.BedarfsausweisWohnen]: 10,
[Enums.Ausweisart.VerbrauchsausweisGewerbe]: 10,
};
// $kommission = db()->one("SELECT abr_va, abr_ba, abr_vanw FROM users WHERE resellercode = :resellercode", ["resellercode" => $resellercode]); // $kommission = db()->one("SELECT abr_va, abr_ba, abr_vanw FROM users WHERE resellercode = :resellercode", ["resellercode" => $resellercode]);
// Select every entry from database where user was involved. // Select every entry from database where user was involved.
let bestellungen; let bestellungen;
if (start.isValid() && end.isValid()) { if (start.isValid() && end.isValid()) {
bestellungen = await prisma.rechnung.findMany({ bestellungen = await prisma.rechnung.findMany({
where: { where: {
partner_code: "immowelt", partner_code: benutzer.partner_code,
OR: [ OR: [
{ {
verbrauchsausweis_gewerbe: { verbrauchsausweis_gewerbe: {
@@ -50,19 +54,19 @@ if (start.isValid() && end.isValid()) {
], ],
AND: [ AND: [
{ {
created_at: { erstellt_am: {
gte: startdatum, gte: startdatum,
}, },
}, },
{ {
created_at: { erstellt_am: {
lte: enddatum, lte: enddatum,
}, },
}, },
], ],
}, },
orderBy: { orderBy: {
created_at: "desc", erstellt_am: "desc",
}, },
include: { include: {
bedarfsausweis_wohnen: { bedarfsausweis_wohnen: {
@@ -97,7 +101,7 @@ if (start.isValid() && end.isValid()) {
} else { } else {
bestellungen = await prisma.rechnung.findMany({ bestellungen = await prisma.rechnung.findMany({
where: { where: {
partner_code: "immowelt", partner_code: benutzer.partner_code,
OR: [ OR: [
{ {
verbrauchsausweis_gewerbe: { verbrauchsausweis_gewerbe: {
@@ -117,7 +121,7 @@ if (start.isValid() && end.isValid()) {
], ],
}, },
orderBy: { orderBy: {
created_at: "desc", erstellt_am: "desc",
}, },
include: { include: {
bedarfsausweis_wohnen: { bedarfsausweis_wohnen: {
@@ -152,18 +156,19 @@ if (start.isValid() && end.isValid()) {
} }
// Wann wurde der partner_code zum ersten mal benutzt? // Wann wurde der partner_code zum ersten mal benutzt?
const partnerCodeErstesMal = ( if (!startdatum) {
await prisma.rechnung.findFirst({ startdatum = (
select: { await prisma.rechnung.findFirst({
created_at: true, select: {
}, erstellt_am: true,
where: { },
partner_code: "immowelt", where: {
OR: [ partner_code: benutzer.partner_code,
{ OR: [
verbrauchsausweis_gewerbe: { {
ausgestellt: true, verbrauchsausweis_gewerbe: {
}, ausgestellt: true,
},
}, },
{ {
bedarfsausweis_wohnen: { bedarfsausweis_wohnen: {
@@ -176,43 +181,41 @@ const partnerCodeErstesMal = (
}, },
}, },
], ],
created_at: { erstellt_am: {
gte: moment().set("year", 2020).set("dayOfYear", 1).toDate(), gte: moment().set("year", 2020).set("dayOfYear", 1).toDate(),
}, },
}, },
orderBy: { orderBy: {
created_at: "asc", erstellt_am: "asc",
}, },
}) })
)?.created_at; )?.erstellt_am || moment().startOf("month").toDate();
}
const provisionen = await prisma.provisionen.findMany({
where: {
benutzer_id: benutzer.id
}
})
bestellungen = bestellungen.map((bestellung) =>
extrahiereAusweisAusFeldMitMehrerenAusweisen(bestellung)
);
let provision = 0; let provision = 0;
const ausweisarten: string[] = [];
for (const bestellung of bestellungen) { for (const bestellung of bestellungen) {
if (bestellung.verbrauchsausweis_wohnen) { const { provision_betrag, provision_prozent } = getProvision(bestellung.ausweis.ausweisart, bestellung.ausweis.ausweistyp, provisionen);
ausweisarten.push(Enums.Ausweisart.VerbrauchsausweisWohnen); provision += provision_betrag;
provision += provisionen[Enums.Ausweisart.VerbrauchsausweisWohnen];
}
if (bestellung.bedarfsausweis_wohnen) {
ausweisarten.push(Enums.Ausweisart.BedarfsausweisWohnen);
provision += provisionen[Enums.Ausweisart.BedarfsausweisWohnen];
}
if (bestellung.verbrauchsausweis_gewerbe) {
ausweisarten.push(Enums.Ausweisart.VerbrauchsausweisGewerbe);
provision += provisionen[Enums.Ausweisart.VerbrauchsausweisGewerbe];
}
} }
--- ---
<BlankLayout title="Monatliche Abrechnung"> <BlankLayout title="Monatliche Abrechnung">
<AbrechnungTable <AbrechnungTable
bestellungen={bestellungen.map((bestellung) => bestellungen={bestellungen}
extrahiereAusweisAusFeldMitMehrerenAusweisen(bestellung)
)}
{provisionen} {provisionen}
{partnerCodeErstesMal} startdatum={startdatum}
startDate={startdatum} enddatum={enddatum}
endDate={enddatum}
email={benutzer.email} email={benutzer.email}
client:load client:load
/> />
@@ -220,15 +223,15 @@ for (const bestellung of bestellungen) {
<div class="fixed bottom-0 left-0 right-0 bg-white p-4 shadow"> <div class="fixed bottom-0 left-0 right-0 bg-white p-4 shadow">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<div> <div>
<p id="betrag_gesamt"> <p>
Abrechnungsbetrag gesamt: <b>{provision} €</b> Abrechnungsbetrag gesamt: <b>{provision.toFixed(2)} €</b>
</p> </p>
</div> </div>
<a <a
target="_blank" target="_blank"
rel="noreferrer noopener" rel="noreferrer noopener"
class="bg-secondary text-white px-4 py-2 rounded-lg hover:bg-secondary-focus" class="bg-secondary text-white px-4 py-2 rounded-lg hover:bg-secondary-focus"
href=`/user/abrechnung/pdf.php?month=${moment().subtract(1, "month").get("month")}&year=${moment().subtract(1, "month").get("year")}` href=`/dashboard/abrechnung/monatlich.pdf?d=${moment().subtract(1, "month").format("YYYY-MM")}`
>PDF für letzten Monat generieren.</a >PDF für letzten Monat generieren.</a
> >
</div> </div>

View File

@@ -0,0 +1,146 @@
---
import abrechnungTemplateHTML from "../../../templates/pdf/abrechnung.handlebars?raw";
import puppeteer from "puppeteer";
import Handlebars from "handlebars";
import moment from "moment";
import { getCurrentUser } from "#lib/server/user";
import { prisma } from "#lib/server/prisma";
import { extrahiereAusweisAusFeldMitMehrerenAusweisen } from "#lib/server/ausweis";
import { getProvision } from "#lib/provision";
moment.locale("de");
moment.tz.setDefault("Europe/Berlin");
const datum = moment(Astro.url.searchParams.get("d")).set("date", 1).set("hour", 0).set("minute", 0).set("second", 0);
const benutzer = await getCurrentUser(Astro);
// Wir dürfen die Abrechnung erst ab Juni starten lassen.
if (datum.isBefore(moment().set("year", 2025).set("month", 4).endOf("month"))) {
return Astro.redirect("/404")
}
if (!benutzer) {
return Astro.redirect("/404");
}
let bestellungen = await prisma.rechnung.findMany({
where: {
partner_code: benutzer.partner_code,
OR: [
{
verbrauchsausweis_gewerbe: {
ausgestellt: true,
},
},
{
bedarfsausweis_wohnen: {
ausgestellt: true,
},
},
{
verbrauchsausweis_wohnen: {
ausgestellt: true,
},
},
],
AND: [
{
erstellt_am: {
gte: datum.startOf("month").toDate(),
},
},
{
erstellt_am: {
lte: datum.endOf("month").toDate(),
},
},
],
},
orderBy: {
erstellt_am: "desc",
},
include: {
bedarfsausweis_wohnen: {
include: {
aufnahme: {
include: {
objekt: true,
},
},
},
},
verbrauchsausweis_gewerbe: {
include: {
aufnahme: {
include: {
objekt: true,
},
},
},
},
verbrauchsausweis_wohnen: {
include: {
aufnahme: {
include: {
objekt: true,
},
},
},
},
},
});
const provisionen = await prisma.provisionen.findMany({
where: {
benutzer_id: benutzer.id
}
})
const ausweisBestellungen = bestellungen.map(bestellung => extrahiereAusweisAusFeldMitMehrerenAusweisen(bestellung));
const browser = await puppeteer.launch({
headless: true,
args: ["--no-sandbox", "--disable-setuid-sandbox"],
});
const page = await browser.newPage();
// Wir splitten die Daten in Blöcke auf, der erste Block hat 11 Einträge, die folgenden Blöcke haben 15 Einträge.
const blocks = [];
const firstBlock = ausweisBestellungen.slice(0, 16);
const remainingBlocks = ausweisBestellungen.slice(16);
blocks.push(firstBlock);
for (let i = 0; i < remainingBlocks.length; i += 20) {
blocks.push(remainingBlocks.slice(i, i + 20));
}
Handlebars.registerHelper("get-provision-prozent", function (ausweisart, ausweistyp) {
const { provision_prozent } = getProvision(ausweisart, ausweistyp, provisionen);
return provision_prozent || 0;
});
Handlebars.registerHelper("get-provision-betrag", function (ausweisart, ausweistyp) {
const { provision_betrag } = getProvision(ausweisart, ausweistyp, provisionen);
return provision_betrag ? provision_betrag.toFixed(2) : "0.00";
});
const gesamt = ausweisBestellungen.reduce((acc, bestellung) => {
const { provision_betrag } = getProvision(bestellung.ausweis.ausweisart, bestellung.ausweis.ausweistyp, provisionen);
return acc + (provision_betrag || 0);
}, 0).toFixed(2);
const template = Handlebars.compile(abrechnungTemplateHTML);
const html = template({ monat: datum.format("MMMM YYYY"), bestellungen: blocks, heute: moment().format("DD.MM.YYYY"), plz: benutzer.plz, ort: benutzer.ort, adresse: benutzer.adresse, firma: benutzer.firma, email: benutzer.email, gesamt });
await page.goto(`data:text/html;charset=UTF-8,${encodeURIComponent(html)}`, {
waitUntil: "networkidle0",
});
const pdf = await page.pdf({ format: "A4" });
await browser.close();
return new Response(pdf, {
headers: {
"Content-Type": "application/pdf",
"Content-Disposition": `attachment; filename="Abrechnung_${datum.format("YYYY_MM")}.pdf"`,
},
});
---

View File

@@ -61,5 +61,5 @@ Astro.cookies.set(API_ACCESS_TOKEN_COOKIE_NAME, accessToken, {
expires: moment().add(30, "minutes").toDate() expires: moment().add(30, "minutes").toDate()
}) })
return Astro.redirect("/dashboard") return Astro.redirect("/dashboard");
--- ---

View File

@@ -28,7 +28,7 @@ if (!user) {
const aufnahme = await prisma.aufnahme.findUnique({ const aufnahme = await prisma.aufnahme.findUnique({
where: user.rolle === Enums.BenutzerRolle.USER ? { where: user.rolle !== Enums.BenutzerRolle.ADMIN ? {
benutzer: { benutzer: {
id: user.id id: user.id
}, },

View File

@@ -15,7 +15,7 @@ if (!user) {
const totalPageCount = await prisma.aufnahme.count({ const totalPageCount = await prisma.aufnahme.count({
where: where:
user.rolle === Enums.BenutzerRolle.USER user.rolle !== Enums.BenutzerRolle.ADMIN
? { ? {
benutzer: { benutzer: {
id: user.id, id: user.id,
@@ -27,7 +27,7 @@ const totalPageCount = await prisma.aufnahme.count({
let ausweis; let ausweis;
// Wir fragen den neuesten Ausweis ab // Wir fragen den neuesten Ausweis ab
// Falls der Nutzer ein Admin ist dann kommt der ganz neueste ansonsten der neueste des eingeloggten Benutzers. // Falls der Nutzer ein Admin ist dann kommt der ganz neueste ansonsten der neueste des eingeloggten Benutzers.
if (user.rolle === Enums.BenutzerRolle.USER) { if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
const adapter = getPrismaAusweisAdapter(id); const adapter = getPrismaAusweisAdapter(id);
ausweis = await adapter?.findUnique({ ausweis = await adapter?.findUnique({
where: { where: {

View File

@@ -1,8 +1,6 @@
--- ---
import { Enums, prisma } from "#lib/server/prisma"; import { Enums, prisma } from "#lib/server/prisma";
import UserLayout from "#layouts/DashboardLayout.astro";
import { getCurrentUser } from "#lib/server/user"; import { getCurrentUser } from "#lib/server/user";
import moment from "moment";
const page = Number(Astro.url.searchParams.get("p")); const page = Number(Astro.url.searchParams.get("p"));
@@ -14,7 +12,7 @@ if (!user) {
const totalPageCount = await prisma.aufnahme.count({ const totalPageCount = await prisma.aufnahme.count({
where: where:
user.rolle === Enums.BenutzerRolle.USER (user.rolle !== Enums.BenutzerRolle.ADMIN)
? { ? {
benutzer: { benutzer: {
id: user.id, id: user.id,
@@ -23,14 +21,17 @@ const totalPageCount = await prisma.aufnahme.count({
: {}, : {},
}); });
if (page < 1 || page > totalPageCount) {
if ((page < 1 || page > totalPageCount) && totalPageCount > 0) {
return Astro.redirect("/dashboard/objekte?p=1"); return Astro.redirect("/dashboard/objekte?p=1");
} else if (totalPageCount === 0) {
return Astro.redirect("/dashboard/objekte/leer");
} }
let result: { id: string; updated_at: Date }[] = []; let result: { id: string; updated_at: Date }[] = [];
// Wir fragen den neuesten Ausweis ab // Wir fragen den neuesten Ausweis ab
// Falls der Nutzer ein Admin ist dann kommt der ganz neueste ansonsten der neueste des eingeloggten Benutzers. // Falls der Nutzer ein Admin ist dann kommt der ganz neueste ansonsten der neueste des eingeloggten Benutzers.
if (user.rolle === Enums.BenutzerRolle.USER) { if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
result = result =
await prisma.$queryRaw`SELECT id, updated_at FROM "VerbrauchsausweisWohnen" WHERE benutzer_id = ${user.id} UNION ALL await prisma.$queryRaw`SELECT id, updated_at FROM "VerbrauchsausweisWohnen" WHERE benutzer_id = ${user.id} UNION ALL
SELECT id, updated_at FROM "VerbrauchsausweisGewerbe" WHERE benutzer_id = ${user.id} UNION ALL SELECT id, updated_at FROM "VerbrauchsausweisGewerbe" WHERE benutzer_id = ${user.id} UNION ALL
@@ -40,15 +41,6 @@ if (user.rolle === Enums.BenutzerRolle.USER) {
SELECT id, updated_at FROM "GEGNachweisGewerbe" WHERE benutzer_id = ${user.id} SELECT id, updated_at FROM "GEGNachweisGewerbe" WHERE benutzer_id = ${user.id}
ORDER BY updated_at DESC LIMIT 1 OFFSET ${page - 1}`; ORDER BY updated_at DESC LIMIT 1 OFFSET ${page - 1}`;
} else { } else {
const date = moment().subtract(2, "hours").toDate()
// SELECT id, updated_at FROM "VerbrauchsausweisWohnen" WHERE created_at >= ${date} AND bestellt = ${true} UNION ALL
// SELECT id, updated_at FROM "VerbrauchsausweisGewerbe" WHERE created_at >= ${date} AND bestellt = ${true} UNION ALL
// SELECT id, updated_at FROM "BedarfsausweisWohnen" WHERE created_at >= ${date} AND bestellt = ${true} UNION ALL
// SELECT id, updated_at FROM "BedarfsausweisGewerbe" WHERE created_at >= ${date} AND bestellt = ${true} UNION ALL
// SELECT id, updated_at FROM "GEGNachweisWohnen" WHERE created_at >= ${date} AND bestellt = ${true} UNION ALL
// SELECT id, updated_at FROM "GEGNachweisGewerbe" WHERE created_at >= ${date} AND bestellt = ${true}
result = result =
await prisma.$queryRaw`SELECT id, updated_at FROM "VerbrauchsausweisWohnen" WHERE ausgestellt = ${false} AND bestellt = ${true} UNION ALL await prisma.$queryRaw`SELECT id, updated_at FROM "VerbrauchsausweisWohnen" WHERE ausgestellt = ${false} AND bestellt = ${true} UNION ALL
SELECT id, updated_at FROM "VerbrauchsausweisGewerbe" WHERE ausgestellt = ${false} AND bestellt = ${true} UNION ALL SELECT id, updated_at FROM "VerbrauchsausweisGewerbe" WHERE ausgestellt = ${false} AND bestellt = ${true} UNION ALL
@@ -60,10 +52,6 @@ if (user.rolle === Enums.BenutzerRolle.USER) {
} }
if (result.length > 0) { if (result.length > 0) {
return Astro.redirect(`/dashboard/objekte/${result[0].id}?p=${page}`) return Astro.redirect(`/dashboard/objekte/${result[0].id}?p=${page}`);
} }
--- ---
<UserLayout title="Objekte" {user} besteller={null}>
<p>Keine Ausweise konnten gefunden werden.</p>
</UserLayout>

View File

@@ -0,0 +1,15 @@
---
import DashboardLayout from "#layouts/DashboardLayout.astro";
import { getCurrentUser } from "#lib/server/user";
const user = await getCurrentUser(Astro);
if (!user) {
return Astro.redirect("/auth/login");
}
---
<DashboardLayout title="Objekte" user={user} besteller={null}>
<p>Sie haben bisher keine Ausweise erstellt. Klicken sie <a href="/energieausweis-erstellen/verbrauchsausweis-wohngebaeude/">hier</a>, um einen neuen Ausweis zu erstellen.</p>
</DashboardLayout>

View File

@@ -0,0 +1,87 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Abrechnung</title>
<link href="https://unpkg.com/tailwindcss@2.0.1/dist/tailwind.min.css" rel="stylesheet">
</head>
<body>
{{#each bestellungen}}
<header class="p-12 flex flex-row items-center justify-between" style="border-bottom: 12px #f37e3c solid;">
<img src="https://online-energieausweis.org/images/header/logo-big.png" alt="" class="h-24">
<div class="flex flex-col">
<p>fon 040 · 209339850</p>
<p>fax 040 · 209339859</p>
<p class="font-semibold">online-energieausweis.org</p>
</div>
</header>
<article class="py-8 px-16">
{{#if @first}}
<div class="flex flex-col gap-4">
<div class="flex flex-row">
<p class="text-sm">IB Cornelsen · Katendeich 5A · 21035 Hamburg</p>
</div>
<div class="flex flex-col">
<p>{{ @root.firma }}</p>
<p>{{ @root.adresse }}</p>
<p>{{ @root.plz }} {{ @root.ort }}</p>
</div>
</div>
{{/if}}
<div class="flex flex-col gap-4 mt-12">
{{#if @first}}
<div class="flex flex-row justify-between items-center">
<div class="flex flex-col">
<p class="font-semibold">Erzielte Conversions {{ @root.monat }}</p>
<p>Erstellt am {{ @root.heute }}</p>
</div>
<p class="font-semibold">Gesamt {{ @root.gesamt }} €</p>
</div>
{{/if}}
<table class="table border-collapse border border-black">
<thead>
<tr class="h-16">
<th class="text-center text-sm border-black border" style="background-color: #f37e3c;">ID - Datum</th>
<th class="text-center text-sm border-black border" style="background-color: #f37e3c;">Produkt</th>
<th class="text-center text-sm border-black border" style="background-color: #f37e3c;">Produktpreis</th>
<th class="text-center text-sm border-black border" style="background-color: #f37e3c;">Provision in %</th>
<th class="text-center text-sm border-black border" style="background-color: #f37e3c;">Betrag Netto</th>
</tr>
</thead>
<tbody>
{{#each this}}
<tr>
{{#with ausweis}}
<td class="border-black border p-1">
<p class="text-sm">{{ id }}</p>
<p class="text-sm">{{ createdAt }}</p>
</td>
<td class=" border-black border p-1 text-sm text-center">
{{ ausweisart }}
</td>
{{/with}}
<td class=" border-black border p-1 font-semibold text-sm text-center">
{{ betrag }}
</td>
{{#with ausweis}}
<td class=" border-black border p-1 text-sm text-center">
{{get-provision-prozent ausweisart ausweistyp}} %
</td>
<td class=" border-black border p-1 text-sm text-center">
{{get-provision-betrag ausweisart ausweistyp}}
</td>
{{/with}}
</tr>
{{/each}}
</tbody>
</table>
</div>
</article>
<footer class="px-16 py-6 flex flex-row justify-between items-center fixed bottom-0 left-0 w-full" style="border-top: 12px #f37e3c solid;">
<p class="font-semibold">Copyright © 2018 · IB Cornelsen</p>
<p class="font-semibold">info@online-energieausweis.org</p>
</footer>
{{/each}}
</body>
</html>

View File

@@ -3,7 +3,7 @@
set -e set -e
# Config # Config
CONTAINER_NAME="online-energieausweis-database-1" CONTAINER_NAME="database"
DB_USER="main" DB_USER="main"
DB_NAME="main" DB_NAME="main"
TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S") TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S")
@@ -39,40 +39,16 @@ fi
if [[ "$SKIP_BACKUP" == false ]]; then if [[ "$SKIP_BACKUP" == false ]]; then
echo "📦 Backup wird erstellt..." echo "📦 Backup wird erstellt..."
docker exec -t "$CONTAINER_NAME" pg_dumpall -c -U "$DB_USER" | brotli > "$FILE_NAME" docker exec -t "$CONTAINER_NAME" pg_dumpall -c -U "$DB_USER" | brotli --quality=1 > "$FILE_NAME"
echo "✅ Backup abgeschlossen: $FILE_NAME" echo "✅ Backup abgeschlossen: $FILE_NAME"
fi fi
echo "🧨 Alle Daten aus allen Tabellen werden gelöscht..." echo "🧨 Alle Daten aus allen Tabellen werden gelöscht..."
# Generate and run TRUNCATE statements for all tables in the public schema # Generate and run TRUNCATE statements for all tables in the public schema
docker exec -i "$CONTAINER_NAME" psql -U "$DB_USER" "$DB_NAME" <<'EOSQL' docker exec -i "$CONTAINER_NAME" psql -U "$DB_USER" "postgres" <<'EOSQL'
DO $$ DROP DATABASE IF EXISTS main;
DECLARE CREATE DATABASE main WITH OWNER main ENCODING 'UTF8';
r RECORD;
sql TEXT := '';
BEGIN
-- Truncate all tables
FOR r IN
SELECT tablename
FROM pg_tables
WHERE schemaname = 'public'
LOOP
sql := sql || FORMAT('TRUNCATE TABLE public.%I CASCADE;', r.tablename);
END LOOP;
-- Drop all sequences
FOR r IN
SELECT sequence_name
FROM information_schema.sequences
WHERE sequence_schema = 'public'
LOOP
sql := sql || FORMAT('DROP SEQUENCE IF EXISTS public.%I CASCADE;', r.sequence_name);
END LOOP;
EXECUTE sql;
END
$$;
EOSQL EOSQL
echo "✅ Alle Tabellen gelöscht und Schema zurückgesetzt." echo "✅ Alle Tabellen gelöscht und Schema zurückgesetzt."