4 Commits
dev ... 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
86 changed files with 487 additions and 836 deletions

2
.env
View File

@@ -4,8 +4,6 @@
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB. # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings # See the documentation for all the connection string options: https://pris.ly/d/connection-strings
SECRET="jg57cya4mrNkGlnb2R85X1gI8LdY7iNZ9MF0Jbn0K5zQBshOxv"
POSTGRES_DB=main POSTGRES_DB=main
POSTGRES_HOST=localhost POSTGRES_HOST=localhost
POSTGRES_PORT=5432 POSTGRES_PORT=5432

View File

@@ -5,33 +5,8 @@ on:
branches: [main] branches: [main]
jobs: jobs:
check-migrations:
name: Check for new Prisma migrations
runs-on: ubuntu-latest
outputs:
has_new_migrations: ${{ steps.diff.outputs.has_new_migrations }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # get full history so git diff works properly
- name: Detect new migration files
id: diff
run: |
# Compare the last two commits on main
if git diff --quiet HEAD~1 -- prisma/migrations/; then
echo "✅ No new Prisma migrations detected."
echo "has_new_migrations=false" >> $GITHUB_OUTPUT
else
echo "⚠️ New Prisma migrations detected! Blocking deployment."
echo "has_new_migrations=true" >> $GITHUB_OUTPUT
fi
deploy: deploy:
name: Deploy to production
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: check-migrations
if: needs.check-migrations.outputs.has_new_migrations == 'false'
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Install Bun - name: Install Bun
@@ -54,15 +29,3 @@ jobs:
git pull origin main git pull origin main
git status git status
make prod make prod
block-deploy:
name: Block deployment (new migrations detected)
runs-on: ubuntu-latest
needs: check-migrations
if: needs.check-migrations.outputs.has_new_migrations == 'true'
steps:
- name: Stop deploy
run: |
echo "🚫 Deployment blocked because new Prisma migrations were detected."
echo "Please apply migrations on staging and verify before deploying to production."
exit 1

2
.gitignore vendored
View File

@@ -15,8 +15,6 @@ npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
pnpm-debug.log* pnpm-debug.log*
error.log*
combined.log*
# lockfile # lockfile
pnpm-lock.yaml pnpm-lock.yaml

View File

@@ -62,8 +62,7 @@ all:
bun run dev 2>&1 | tee ~/logs/`date '+%d-%m-%Y_%H:%M:%S'`.log bun run dev 2>&1 | tee ~/logs/`date '+%d-%m-%Y_%H:%M:%S'`.log
update-dwd-klimafaktoren-cron: update-dwd-klimafaktoren-cron:
- pm2 delete update-dwd-klimafaktoren-cron pm2 start bun --name "update-dwd-klimafaktoren-cron" --cron "0 12 28 * *" -- src/cronjobs/update-dwd-klimafaktoren.ts
pm2 start --no-autorestart bun --name "update-dwd-klimafaktoren-cron" --cron "0 12 28 * *" -- src/cronjobs/update-dwd-klimafaktoren.ts
prod: prod-no-backup backup-database-cronjob prod: prod-no-backup backup-database-cronjob

View File

@@ -5,9 +5,6 @@ import tailwind from "@astrojs/tailwind";
import node from "@astrojs/node"; import node from "@astrojs/node";
import mdx from "@astrojs/mdx"; import mdx from "@astrojs/mdx";
import astroTypesafeAPI from "astro-typesafe-api" import astroTypesafeAPI from "astro-typesafe-api"
import { logger } from "./src/lib/logger";
logger.info("Astro config loaded");
// https://astro.build/config // https://astro.build/config
export default defineConfig({ export default defineConfig({

View File

@@ -1,10 +1,5 @@
#!/bin/bash #!/bin/bash
if [[ -z "$prev_restart_delay" && -n "$cron_restart" ]]; then
echo "skipping initial launch...."
exit 0
fi
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 DATABASE_NAME=database

View File

@@ -29,6 +29,7 @@
"fontkit": "^2.0.4", "fontkit": "^2.0.4",
"handlebars": "^4.7.8", "handlebars": "^4.7.8",
"heic2any": "^0.0.4", "heic2any": "^0.0.4",
"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",
"js-interpolate": "^1.3.2", "js-interpolate": "^1.3.2",
@@ -55,7 +56,7 @@
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
"trpc-openapi": "^1.2.0", "trpc-openapi": "^1.2.0",
"uuid": "^9.0.1", "uuid": "^9.0.1",
"winston": "^3.18.3", "winston": "^3.17.0",
"zod": "^3.24.1", "zod": "^3.24.1",
}, },
"devDependencies": { "devDependencies": {
@@ -269,7 +270,7 @@
"@cypress/xvfb": ["@cypress/xvfb@1.2.4", "", { "dependencies": { "debug": "^3.1.0", "lodash.once": "^4.1.1" } }, "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q=="], "@cypress/xvfb": ["@cypress/xvfb@1.2.4", "", { "dependencies": { "debug": "^3.1.0", "lodash.once": "^4.1.1" } }, "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q=="],
"@dabh/diagnostics": ["@dabh/diagnostics@2.0.8", "", { "dependencies": { "@so-ric/colorspace": "^1.1.6", "enabled": "2.0.x", "kuler": "^2.0.0" } }, "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q=="], "@dabh/diagnostics": ["@dabh/diagnostics@2.0.3", "", { "dependencies": { "colorspace": "1.1.x", "enabled": "2.0.x", "kuler": "^2.0.0" } }, "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA=="],
"@daybrush/utils": ["@daybrush/utils@1.13.0", "", {}, "sha512-ALK12C6SQNNHw1enXK+UO8bdyQ+jaWNQ1Af7Z3FNxeAwjYhQT7do+TRE4RASAJ3ObaS2+TJ7TXR3oz2Gzbw0PQ=="], "@daybrush/utils": ["@daybrush/utils@1.13.0", "", {}, "sha512-ALK12C6SQNNHw1enXK+UO8bdyQ+jaWNQ1Af7Z3FNxeAwjYhQT7do+TRE4RASAJ3ObaS2+TJ7TXR3oz2Gzbw0PQ=="],
@@ -713,8 +714,6 @@
"@smithy/util-waiter": ["@smithy/util-waiter@4.0.2", "", { "dependencies": { "@smithy/abort-controller": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-piUTHyp2Axx3p/kc2CIJkYSv0BAaheBQmbACZgQSSfWUumWNW+R1lL+H9PDBxKJkvOeEX+hKYEFiwO8xagL8AQ=="], "@smithy/util-waiter": ["@smithy/util-waiter@4.0.2", "", { "dependencies": { "@smithy/abort-controller": "^4.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-piUTHyp2Axx3p/kc2CIJkYSv0BAaheBQmbACZgQSSfWUumWNW+R1lL+H9PDBxKJkvOeEX+hKYEFiwO8xagL8AQ=="],
"@so-ric/colorspace": ["@so-ric/colorspace@1.1.6", "", { "dependencies": { "color": "^5.0.2", "text-hex": "1.0.x" } }, "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw=="],
"@svelte-plugins/datepicker": ["@svelte-plugins/datepicker@1.0.11", "", {}, "sha512-Tqc07QLyRkCpc3Glg6oRLTUApLtCrOh52d6vJ7L32QI17HrwvcDDjaH3LF3X1SBm3CWdMrnqfJp3xjUZmB4wzw=="], "@svelte-plugins/datepicker": ["@svelte-plugins/datepicker@1.0.11", "", {}, "sha512-Tqc07QLyRkCpc3Glg6oRLTUApLtCrOh52d6vJ7L32QI17HrwvcDDjaH3LF3X1SBm3CWdMrnqfJp3xjUZmB4wzw=="],
"@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@2.5.3", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^1.0.4", "debug": "^4.3.4", "deepmerge": "^4.3.1", "kleur": "^4.1.5", "magic-string": "^0.30.3", "svelte-hmr": "^0.15.3", "vitefu": "^0.2.4" }, "peerDependencies": { "svelte": "^3.54.0 || ^4.0.0 || ^5.0.0-next.0", "vite": "^4.0.0" } }, "sha512-erhNtXxE5/6xGZz/M9eXsmI7Pxa6MS7jyTy06zN3Ck++ldrppOnOlJwHHTsMC7DHDQdgUp4NAc4cDNQ9eGdB/w=="], "@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@2.5.3", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^1.0.4", "debug": "^4.3.4", "deepmerge": "^4.3.1", "kleur": "^4.1.5", "magic-string": "^0.30.3", "svelte-hmr": "^0.15.3", "vitefu": "^0.2.4" }, "peerDependencies": { "svelte": "^3.54.0 || ^4.0.0 || ^5.0.0-next.0", "vite": "^4.0.0" } }, "sha512-erhNtXxE5/6xGZz/M9eXsmI7Pxa6MS7jyTy06zN3Ck++ldrppOnOlJwHHTsMC7DHDQdgUp4NAc4cDNQ9eGdB/w=="],
@@ -1107,6 +1106,8 @@
"colorette": ["colorette@2.0.19", "", {}, "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ=="], "colorette": ["colorette@2.0.19", "", {}, "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ=="],
"colorspace": ["colorspace@1.1.4", "", { "dependencies": { "color": "^3.1.3", "text-hex": "1.0.x" } }, "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w=="],
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
"comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="],
@@ -1559,6 +1560,8 @@
"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=="],
"hosted-git-info": ["hosted-git-info@2.8.9", "", {}, "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw=="], "hosted-git-info": ["hosted-git-info@2.8.9", "", {}, "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw=="],
"hotkeys-js": ["hotkeys-js@3.13.9", "", {}, "sha512-3TRCj9u9KUH6cKo25w4KIdBfdBfNRjfUwrljCLDC2XhmPDG0SjAZFcFZekpUZFmXzfYoGhFDcdx2gX/vUVtztQ=="], "hotkeys-js": ["hotkeys-js@3.13.9", "", {}, "sha512-3TRCj9u9KUH6cKo25w4KIdBfdBfNRjfUwrljCLDC2XhmPDG0SjAZFcFZekpUZFmXzfYoGhFDcdx2gX/vUVtztQ=="],
@@ -2765,7 +2768,7 @@
"widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="], "widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="],
"winston": ["winston@3.18.3", "", { "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.8", "async": "^3.2.3", "is-stream": "^2.0.0", "logform": "^2.7.0", "one-time": "^1.0.0", "readable-stream": "^3.4.0", "safe-stable-stringify": "^2.3.1", "stack-trace": "0.0.x", "triple-beam": "^1.3.0", "winston-transport": "^4.9.0" } }, "sha512-NoBZauFNNWENgsnC9YpgyYwOVrl2m58PpQ8lNHjV3kosGs7KJ7Npk9pCUE+WJlawVSe8mykWDKWFSVfs3QO9ww=="], "winston": ["winston@3.17.0", "", { "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.2", "async": "^3.2.3", "is-stream": "^2.0.0", "logform": "^2.7.0", "one-time": "^1.0.0", "readable-stream": "^3.4.0", "safe-stable-stringify": "^2.3.1", "stack-trace": "0.0.x", "triple-beam": "^1.3.0", "winston-transport": "^4.9.0" } }, "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw=="],
"winston-transport": ["winston-transport@4.9.0", "", { "dependencies": { "logform": "^2.7.0", "readable-stream": "^3.6.2", "triple-beam": "^1.3.0" } }, "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A=="], "winston-transport": ["winston-transport@4.9.0", "", { "dependencies": { "logform": "^2.7.0", "readable-stream": "^3.6.2", "triple-beam": "^1.3.0" } }, "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A=="],
@@ -2885,8 +2888,6 @@
"@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=="],
"@so-ric/colorspace/color": ["color@5.0.2", "", { "dependencies": { "color-convert": "^3.0.1", "color-string": "^2.0.0" } }, "sha512-e2hz5BzbUPcYlIRHo8ieAhYgoajrJr+hWoceg6E345TPsATMUKqDgzt8fSXZJJbxfpiPzkWyphz8yn8At7q3fA=="],
"@sveltejs/vite-plugin-svelte/vitefu": ["vitefu@0.2.5", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" }, "optionalPeers": ["vite"] }, "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q=="], "@sveltejs/vite-plugin-svelte/vitefu": ["vitefu@0.2.5", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" }, "optionalPeers": ["vite"] }, "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q=="],
"@types/ssh2/@types/node": ["@types/node@18.19.86", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-fifKayi175wLyKyc5qUfyENhQ1dCNI1UNjp653d8kuYcPQN5JhX3dGuP/XmvPTg/xRBn1VTLpbmi+H/Mr7tLfQ=="], "@types/ssh2/@types/node": ["@types/node@18.19.86", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-fifKayi175wLyKyc5qUfyENhQ1dCNI1UNjp653d8kuYcPQN5JhX3dGuP/XmvPTg/xRBn1VTLpbmi+H/Mr7tLfQ=="],
@@ -2933,6 +2934,8 @@
"co-body/qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="], "co-body/qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="],
"colorspace/color": ["color@3.2.1", "", { "dependencies": { "color-convert": "^1.9.3", "color-string": "^1.6.0" } }, "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA=="],
"csvtojson/strip-bom": ["strip-bom@2.0.0", "", { "dependencies": { "is-utf8": "^0.2.0" } }, "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g=="], "csvtojson/strip-bom": ["strip-bom@2.0.0", "", { "dependencies": { "is-utf8": "^0.2.0" } }, "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g=="],
"cypress/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], "cypress/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
@@ -3177,10 +3180,6 @@
"@prisma/internals/globby/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], "@prisma/internals/globby/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
"@so-ric/colorspace/color/color-convert": ["color-convert@3.1.2", "", { "dependencies": { "color-name": "^2.0.0" } }, "sha512-UNqkvCDXstVck3kdowtOTWROIJQwafjOfXSmddoDrXo4cewMKmusCeF22Q24zvjR8nwWib/3S/dfyzPItPEiJg=="],
"@so-ric/colorspace/color/color-string": ["color-string@2.1.2", "", { "dependencies": { "color-name": "^2.0.0" } }, "sha512-RxmjYxbWemV9gKu4zPgiZagUxbH3RQpEIO77XoSSX0ivgABDZ+h8Zuash/EMFLTI4N9QgFPOJ6JQpPZKFxa+dA=="],
"@types/ssh2/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], "@types/ssh2/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
"@typescript-eslint/typescript-estree/globby/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], "@typescript-eslint/typescript-estree/globby/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
@@ -3201,6 +3200,8 @@
"boxen/wrap-ansi/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], "boxen/wrap-ansi/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
"colorspace/color/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
"express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"express/send/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="], "express/send/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="],
@@ -3347,14 +3348,12 @@
"@prisma/internals/@prisma/debug/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], "@prisma/internals/@prisma/debug/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="],
"@so-ric/colorspace/color/color-convert/color-name": ["color-name@2.0.2", "", {}, "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A=="],
"@so-ric/colorspace/color/color-string/color-name": ["color-name@2.0.2", "", {}, "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A=="],
"boxen/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], "boxen/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
"boxen/wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], "boxen/wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
"colorspace/color/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
"form-render/color/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], "form-render/color/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
"npm-packlist/glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], "npm-packlist/glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],

View File

@@ -43,6 +43,7 @@
"fontkit": "^2.0.4", "fontkit": "^2.0.4",
"handlebars": "^4.7.8", "handlebars": "^4.7.8",
"heic2any": "^0.0.4", "heic2any": "^0.0.4",
"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",
"js-interpolate": "^1.3.2", "js-interpolate": "^1.3.2",
@@ -69,7 +70,7 @@
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
"trpc-openapi": "^1.2.0", "trpc-openapi": "^1.2.0",
"uuid": "^9.0.1", "uuid": "^9.0.1",
"winston": "^3.18.3", "winston": "^3.17.0",
"zod": "^3.24.1" "zod": "^3.24.1"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1,9 +0,0 @@
/*
Warnings:
- You are about to drop the column `anrede` on the `benutzer` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "benutzer" DROP COLUMN "anrede",
ADD COLUMN "empfaenger" VARCHAR(100);

View File

@@ -17,7 +17,7 @@ model Benutzer {
ort String? @db.VarChar(50) ort String? @db.VarChar(50)
adresse String? @db.VarChar(150) adresse String? @db.VarChar(150)
telefon String? @db.VarChar(50) telefon String? @db.VarChar(50)
empfaenger String? @db.VarChar(100) anrede String? @db.VarChar(50)
rolle BenutzerRolle @default(USER) rolle BenutzerRolle @default(USER)
firma String? firma String?
lex_office_id String? lex_office_id String?

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,25 +18,16 @@ 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"),
"ausweise": await import("../src/pages/api/ausweise/index.ts"),
"aufnahme": await import("../src/pages/api/aufnahme/index.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/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"),
"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"),
"bilder/[id]": await import("../src/pages/api/bilder/[id].ts"), "bilder/[id]": await import("../src/pages/api/bilder/[id].ts"),
"geg-nachweis-wohnen/[id]": await import("../src/pages/api/geg-nachweis-wohnen/[id].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"), "geg-nachweis-wohnen": await import("../src/pages/api/geg-nachweis-wohnen/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"),
"objekt": await import("../src/pages/api/objekt/index.ts"), "objekt": await import("../src/pages/api/objekt/index.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/refresh-token": await import("../src/pages/api/auth/refresh-token.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"),
"user/autocreate": await import("../src/pages/api/user/autocreate.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"), "ticket": await import("../src/pages/api/ticket/index.ts"),
@@ -38,6 +35,9 @@ export const createCaller = createCallerFactory({
"verbrauchsausweis-wohnen": await import("../src/pages/api/verbrauchsausweis-wohnen/index.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": 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"),
"aufnahme/[id]/bilder": await import("../src/pages/api/aufnahme/[id]/bilder.ts"), "aufnahme/[id]/bilder": await import("../src/pages/api/aufnahme/[id]/bilder.ts"),
"aufnahme/[id]": await import("../src/pages/api/aufnahme/[id]/index.ts"), "aufnahme/[id]": await import("../src/pages/api/aufnahme/[id]/index.ts"),

View File

@@ -1,6 +1,14 @@
import { api } from "astro-typesafe-api/client"; import { api } from "astro-typesafe-api/client";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js"; import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js";
import {
AufnahmeClient,
BedarfsausweisWohnenClient,
BildClient,
ObjektClient,
VerbrauchsausweisGewerbeClient,
VerbrauchsausweisWohnenClient,
} from "#components/Ausweis/types.js";
import { import {
Aufnahme, Aufnahme,
BedarfsausweisWohnen, BedarfsausweisWohnen,

View File

@@ -170,6 +170,7 @@ export async function benutzerSpeichern(benutzer: Partial<Benutzer>): Promise<st
email: benutzer.email, email: benutzer.email,
passwort: "", passwort: "",
adresse: benutzer.adresse ?? null, adresse: benutzer.adresse ?? null,
anrede: benutzer.anrede ?? null,
firma: benutzer.firma ?? null, firma: benutzer.firma ?? null,
vorname: benutzer.vorname ?? null, vorname: benutzer.vorname ?? null,
ort: benutzer.ort ?? null, ort: benutzer.ort ?? null,

View File

@@ -75,7 +75,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
> >
<option disabled selected value={null}>Bitte auswählen</option> <option disabled selected value={null}>Bitte auswählen</option>
{#if ausweisart==Enums.Ausweisart.VerbrauchsausweisWohnen || ausweisart === Enums.Ausweisart.GEGNachweisWohnen || ausweisart === Enums.Ausweisart.BedarfsausweisWohnen} {#if ausweisart==Enums.Ausweisart.VerbrauchsausweisWohnen || ausweisart === Enums.Ausweisart.GEGNachweisWohnen || ausweisart === Enums.Ausweisart.BedarfsausweisWohnen || ausweisart === Enums.Ausweisart.BedarfsausweisGewerbe}
<option value="Einfamilienhaus">Einfamilienhaus</option> <option value="Einfamilienhaus">Einfamilienhaus</option>
<option value="Freistehendes Einfamilienhaus">Freistehendes Einfamilienhaus</option> <option value="Freistehendes Einfamilienhaus">Freistehendes Einfamilienhaus</option>
<option value="Freistehendes Zweifamilienhaus">Freistehendes Zweifamilienhaus</option> <option value="Freistehendes Zweifamilienhaus">Freistehendes Zweifamilienhaus</option>
@@ -87,7 +87,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
<option value="Atrium-Bungalow">Atrium-Bungalow</option> <option value="Atrium-Bungalow">Atrium-Bungalow</option>
<option value="Winkelbungalow">Winkelbungalow</option> <option value="Winkelbungalow">Winkelbungalow</option>
{:else if ausweisart==Enums.Ausweisart.VerbrauchsausweisGewerbe || ausweisart=== Enums.Ausweisart.GEGNachweisGewerbe || ausweisart === Enums.Ausweisart.BedarfsausweisGewerbe} {:else if ausweisart==Enums.Ausweisart.VerbrauchsausweisGewerbe || ausweisart=== Enums.Ausweisart.GEGNachweisGewerbe}
<option value="Verwaltungsgebäude (allgemein)">Verwaltungsgebäude (allgemein)</option> <option value="Verwaltungsgebäude (allgemein)">Verwaltungsgebäude (allgemein)</option>
<option value="Parlaments- und Gerichtsgebäude">Parlaments- und Gerichtsgebäude</option> <option value="Parlaments- und Gerichtsgebäude">Parlaments- und Gerichtsgebäude</option>
<option value="Ministerien u. Ämter u. Behörden">Ministerien u. Ämter u. Behörden</option> <option value="Ministerien u. Ämter u. Behörden">Ministerien u. Ämter u. Behörden</option>
@@ -186,7 +186,6 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
onlyUnique={true} onlyUnique={true}
minlength={4} minlength={4}
maxlength={4} maxlength={4}
required={true}
onFocusIn={() => { onFocusIn={() => {
addNotification({ addNotification({
message: "Info", message: "Info",
@@ -250,7 +249,6 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
minlength={4} minlength={4}
maxlength={4} maxlength={4}
onlyUnique={true} onlyUnique={true}
required={true}
onFocusIn={() => { onFocusIn={() => {
addNotification({ addNotification({
message: "Info", message: "Info",
@@ -289,7 +287,6 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
onlyUnique={true} onlyUnique={true}
minlength={4} minlength={4}
maxlength={4} maxlength={4}
required={true}
onFocusIn={() => { onFocusIn={() => {
addNotification({ addNotification({
message: "Info", message: "Info",

View File

@@ -49,12 +49,6 @@
if (!value && element.required) { if (!value && element.required) {
element.setCustomValidity("Eine Auswahl ist verpflichtend.") element.setCustomValidity("Eine Auswahl ist verpflichtend.")
element.dataset["isinvalid"] = "true"
element.addEventListener("change", () => {
element.setCustomValidity("")
element.dataset["isinvalid"] = "false"
})
} else { } else {
element.setCustomValidity("") element.setCustomValidity("")
} }
@@ -184,15 +178,7 @@ sm:grid-cols-[1fr_min-content_min-content_min-content] sm:justify-self-end">
<Overlay bind:hidden={loginOverlayHidden}> <Overlay bind:hidden={loginOverlayHidden}>
<div class="bg-white w-full max-w-screen-sm py-8"> <div class="bg-white w-full max-w-screen-sm py-8">
<EmbeddedAuthFlowModule onLogin={loginAction} email={""} route="signup" title={ <EmbeddedAuthFlowModule onLogin={loginAction} email={""}></EmbeddedAuthFlowModule>
{
login: "Ausweisdaten speichern",
signup: "Ausweisdaten speichern"
}
} buttonText={{
login: "Speichern",
signup: "Speichern"
}}></EmbeddedAuthFlowModule>
</div> </div>
</Overlay> </Overlay>

View File

@@ -1,8 +1,9 @@
<script lang="ts"> <script lang="ts">
import HelpLabel from "#components/labels/HelpLabel.svelte"; import HelpLabel from "#components/labels/HelpLabel.svelte";
import Inputlabel from "#components/labels/InputLabel.svelte"; import Inputlabel from "#components/labels/InputLabel.svelte";
import PlzSuche from "#components/PlzSuche.svelte";
import ZipSearch from "#components/PlzSuche.svelte";
import { Enums } from "#lib/client/prisma.js"; import { Enums } from "#lib/client/prisma.js";
import { AufnahmeClient, ObjektClient } from "./types.js"; import { AufnahmeClient, ObjektClient } from "./types.js";
@@ -26,8 +27,6 @@
event.preventDefault(); event.preventDefault();
} }
} }
let ortInput: HTMLInputElement;
</script> </script>
<div <div
@@ -70,12 +69,9 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
<div class="input-noHelp"> <div class="input-noHelp">
<Inputlabel title="PLZ *"></Inputlabel> <Inputlabel title="PLZ *"></Inputlabel>
<PlzSuche <ZipSearch
bind:zip={objekt.plz} bind:zip={objekt.plz}
bind:city={objekt.ort} bind:city={objekt.ort}
onchange={(e) => {
ortInput.dispatchEvent(new Event('change'));
}}
name="plz" name="plz"
/> />
</div> </div>
@@ -89,7 +85,6 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
readonly={false} readonly={false}
required required
bind:value={objekt.ort} bind:value={objekt.ort}
bind:this={ortInput}
type="text" type="text"
/> />

View File

@@ -186,7 +186,7 @@ xl:grid-cols-2 xl:gap-x-8 xl:gap-y-8
> >
<option>Bitte auswählen</option> <option>Bitte auswählen</option>
{#each arrayRange(2.1, 4.5, 0.1) as step} {#each arrayRange(2.1, 4.5, 0.1) as step}
<option value={step} selected={ausweis.geschosshoehe === step}>{step.toFixed(2)} m</option> <option value={step}>{step.toFixed(2)} m</option>
{/each} {/each}
</select> </select>

View File

@@ -746,7 +746,6 @@
<div class="card-body"> <div class="card-body">
<div class="flex flex-col flex-wrap items-left gap-2"> <div class="flex flex-col flex-wrap items-left gap-2">
{#if aufnahme.bilder.length > 0}
<h3 class="font-semibold text-lg">Bilder</h3> <h3 class="font-semibold text-lg">Bilder</h3>
<div class="grid grid-cols-[1fr] md:grid-cols-[1fr,1fr,1fr] lg:grid-cols-[1fr,1fr,1fr] justify-start items-center gap-2"> <div class="grid grid-cols-[1fr] md:grid-cols-[1fr,1fr,1fr] lg:grid-cols-[1fr,1fr,1fr] justify-start items-center gap-2">
{#each aufnahme.bilder as bild, i (i)} {#each aufnahme.bilder as bild, i (i)}
@@ -754,8 +753,6 @@
{/each} {/each}
</div> </div>
<hr> <hr>
{/if}
{#if aufnahme.unterlagen.length > 0}
<h3 class="font-semibold text-lg">Unterlagen</h3> <h3 class="font-semibold text-lg">Unterlagen</h3>
<div class="text-sm"> <div class="text-sm">
{#if aufnahme.unterlagen.length > 0} {#if aufnahme.unterlagen.length > 0}
@@ -764,11 +761,9 @@
{/each} {/each}
{/if} {/if}
</div> </div>
{/if}
</div> </div>
<!-- Benachrichtigungen --> <div class="dropdown dropdown-top items-end absolute bottom-4 right-4 z-50">
<!-- <div class="dropdown dropdown-top items-end absolute bottom-4 right-4 z-50">
<div class="indicator"> <div class="indicator">
{#if Object.keys($notifications).length > 0} {#if Object.keys($notifications).length > 0}
<span class="indicator-item badge badge-accent text-xs" <span class="indicator-item badge badge-accent text-xs"
@@ -787,7 +782,7 @@
> >
<NotificationProvider component={DashboardNotification} /> <NotificationProvider component={DashboardNotification} />
</ul> </ul>
</div> --> </div>
</div> </div>
</div> </div>

View File

@@ -1,10 +1,10 @@
<script lang="ts"> <script lang="ts">
import { BedarfsausweisWohnenClient, ObjektClient, VerbrauchsausweisGewerbeClient, VerbrauchsausweisWohnenClient } from "./Ausweis/types.js"; import { BedarfsausweisWohnenClient, GEGNachweisWohnenClient, ObjektClient, UnterlageClient, VerbrauchsausweisGewerbeClient, VerbrauchsausweisWohnenClient } from "./Ausweis/types.js";
import { Trash, Upload } from "radix-svelte-icons"; import { Trash, Upload } from "radix-svelte-icons";
import HelpLabel from "#components/labels/HelpLabel.svelte"; import HelpLabel from "#components/labels/HelpLabel.svelte";
export let kategorie: string = ""; export let kategorie: string = "";
export let files: (Unterlage & { preview?: boolean })[] = []; export let files: Unterlage[] = [];
export let max: number = Infinity; export let max: number = Infinity;
export let min: number = 1; export let min: number = 1;
export let name: string = ""; export let name: string = "";
@@ -26,8 +26,6 @@
for (let i = 0; i < fileArray.length; i++) { for (let i = 0; i < fileArray.length; i++) {
const file = fileArray[i]; const file = fileArray[i];
files.push({ preview: true, kategorie, name: file.name, mime: file.type, aufnahme_id: null, id: "" });
files = files;
if (i == max) { if (i == max) {
break; break;
@@ -64,13 +62,7 @@
name: file.name name: file.name
}) })
const placeholder = files.find((f) => f.name === fileArray[i].name && f.preview === true && f.kategorie === kategorie); files.push({ id, kategorie, name: file.name, mime: mimeType, aufnahme_id: null });
if (!placeholder) {
return;
}
placeholder!.preview = false;
placeholder!.id = id;
placeholder!.aufnahme_id = ausweis.id;
files = files; files = files;
@@ -118,19 +110,6 @@
<div class="grid grid-cols-2 gap-2"> <div class="grid grid-cols-2 gap-2">
{#each files as file, i} {#each files as file, i}
{#if file.kategorie == kategorie} {#if file.kategorie == kategorie}
{#if file.preview === true}
<!-- Show loading spinner -->
<div class="relative group">
<div
class="h-full max-h-96 w-full rounded-lg border-2 group-hover:contrast-50 object-cover transition-all text-center items-center justify-center flex p-4 text-opacity-75 text-black"
>
<div class="flex flex-row gap-4">
<span>{file.name}</span>
<span class="loader"></span>
</div>
</div>
</div>
{:else}
<div class="relative group"> <div class="relative group">
<div <div
class="h-full max-h-96 w-full rounded-lg border-2 group-hover:contrast-50 object-cover transition-all text-center items-center flex p-4 text-opacity-75 text-black" class="h-full max-h-96 w-full rounded-lg border-2 group-hover:contrast-50 object-cover transition-all text-center items-center flex p-4 text-opacity-75 text-black"
@@ -149,7 +128,6 @@
</div> </div>
</div> </div>
{/if} {/if}
{/if}
{/each} {/each}
<!-- Wir zeigen Platzhalter an, damit der Nutzer sieht wie viele Bilder er hochladen soll --> <!-- Wir zeigen Platzhalter an, damit der Nutzer sieht wie viele Bilder er hochladen soll -->

View File

@@ -7,7 +7,7 @@
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js"; import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js";
export let images: (BildClient & { preview?: boolean })[] = []; export let images: BildClient[] = [];
export let max: number = Infinity; export let max: number = Infinity;
export let min: number = 1; export let min: number = 1;
export let name: string = ""; export let name: string = "";
@@ -35,18 +35,6 @@
<div class="grid grid-cols-2 gap-2"> <div class="grid grid-cols-2 gap-2">
{#each images as image, i} {#each images as image, i}
{#if image.kategorie == kategorie} {#if image.kategorie == kategorie}
{#if image.preview === true}
<!-- Show loading spinner -->
<div class="relative group">
<div
class="h-full max-h-96 w-full rounded-lg border-2 group-hover:contrast-50 object-cover transition-all text-center items-center justify-center flex p-4 text-opacity-75 text-black"
>
<div class="flex flex-row gap-4">
<span class="loader"></span>
</div>
</div>
</div>
{:else}
<div class="relative group"> <div class="relative group">
<img <img
src="/bilder/{image.id}.jpg" src="/bilder/{image.id}.jpg"
@@ -63,15 +51,24 @@
> >
<Trash size={20} color="#fff"></Trash> <Trash size={20} color="#fff"></Trash>
</button> </button>
<!-- <button
type="button"
class="rounded-full w-[30px] h-[30px] flex items-center justify-center p-0 bg-[rgba(0,0,0,0.4)]"
on:click={async () => {
let image = await rotateImage(images[i]);
images[i] = image;
images = images
}}
>
<RotateCounterClockwise size={20} color="#fff"></RotateCounterClockwise>
</button> -->
</div> </div>
</div> </div>
{/if} {/if}
{/if}
{/each} {/each}
<!-- Wir zeigen Platzhalter an, damit der Nutzer sieht wie viele Bilder er hochladen soll --> <!-- Wir zeigen Platzhalter an, damit der Nutzer sieht wie viele Bilder er hochladen soll -->
{#each { length: Math.max(0, Math.min(max, 4) - images.filter(image => image.kategorie === kategorie).length) } as _, i} {#each { length: Math.max(0, Math.min(max, 4) - images.filter(image => image.kategorie === kategorie).length) } as _, i}
<div class="relative group"> <div class="relative group">
<img <img
src="/placeholder.png" src="/placeholder.png"

View File

@@ -5,7 +5,7 @@
export let readonly: boolean = false; export let readonly: boolean = false;
export let city: string | null | undefined; export let city: string | null | undefined;
export let zip: string | null = ""; export let zip: string | null = "";
export let onchange: (event: Event) => void = () => {};
let hideZipDropdown: boolean = true; let hideZipDropdown: boolean = true;
let zipCodes: inferOutput<API["postleitzahlen"]["GET"]> = []; let zipCodes: inferOutput<API["postleitzahlen"]["GET"]> = [];

View File

@@ -19,7 +19,6 @@
export let onFocusIn: () => any = () => {}; export let onFocusIn: () => any = () => {};
export let onFocusOut: () => any = () => {}; export let onFocusOut: () => any = () => {};
export let className: string = ""; export let className: string = "";
export let required: boolean = false;
function addTag(tag: string) { function addTag(tag: string) {
if ((onlyUnique && tags.indexOf(tag) > -1) || maxTags == tags.length) { if ((onlyUnique && tags.indexOf(tag) > -1) || maxTags == tags.length) {
@@ -49,8 +48,6 @@
tag = "" tag = ""
} }
} }
$: required = tags.length > 0 ? false : required;
</script> </script>
<div <div
@@ -86,7 +83,6 @@
class="input input-bordered h-10 px-2 py-1.5 {className}" class="input input-bordered h-10 px-2 py-1.5 {className}"
{minlength} {minlength}
{maxlength} {maxlength}
{required}
disabled={disable} disabled={disable}
readonly={readonly} readonly={readonly}
autocomplete="off" autocomplete="off"

View File

@@ -21,7 +21,7 @@
import { addNotification } from "./Notifications/shared.js"; import { addNotification } from "./Notifications/shared.js";
import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js"; import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js";
export let images: (BildClient & { preview?: boolean })[] = []; export let images: BildClient[] = [];
export let ausweis: export let ausweis:
| VerbrauchsausweisWohnenClient | VerbrauchsausweisWohnenClient
| VerbrauchsausweisGewerbeClient | VerbrauchsausweisGewerbeClient
@@ -39,6 +39,8 @@
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" && file.type !== "image/webp" && file.type !== "image/heif" && file.type !== "image/heic") { if (file.type !== "image/jpeg" && file.type !== "image/png" && file.type !== "image/webp" && file.type !== "image/heif" && file.type !== "image/heic") {
continue; continue;
@@ -48,9 +50,6 @@
break; break;
} }
images.push({ preview: true, benutzer_id: null, kategorie, created_at: new Date(), updated_at: new Date(), id: "" });
images = images;
const reader = new FileReader(); const reader = new FileReader();
reader.onload = async () => { reader.onload = async () => {
@@ -120,19 +119,8 @@
dismissable: true dismissable: true
}) })
} else { } else {
const index = images.findIndex((img) => img.preview === true); images.push({ id: result.id, kategorie });
images = images;
if (index !== -1) {
delete images[index];
images = images.filter((img) => img);
}
images = [...images, {
benutzer_id: null,
created_at: new Date(),
updated_at: new Date(),
id: result.id,
kategorie
}];
if (i == Math.min(files.length, max) - 1) { if (i == Math.min(files.length, max) - 1) {
this.value = ""; this.value = "";

View File

@@ -2,11 +2,6 @@ import { prisma } from "#lib/server/prisma.js";
import moment from "moment"; import moment from "moment";
import csv from "csvtojson" import csv from "csvtojson"
if (!process.env.prev_restart_delay && process.env.cron_restart) {
console.log('skipping initial launch....');
process.exit(0);
}
// Als erstes schauen wir, welches das letzte Jahr ist, für das wir einen Verbrauchsausweis haben. // Als erstes schauen wir, welches das letzte Jahr ist, für das wir einen Verbrauchsausweis haben.
// Das machen wir, indem wir die Ausweise nach Jahr und Monat sortieren und dann den letzten Eintrag nehmen. // Das machen wir, indem wir die Ausweise nach Jahr und Monat sortieren und dann den letzten Eintrag nehmen.
const newestDate = await prisma.klimafaktoren.findFirst({ const newestDate = await prisma.klimafaktoren.findFirst({

View File

@@ -13,7 +13,7 @@ export const BenutzerSchema = z.object({
ort: z.string().nullish(), ort: z.string().nullish(),
adresse: z.string().nullish(), adresse: z.string().nullish(),
telefon: z.string().nullish(), telefon: z.string().nullish(),
empfaenger: z.string().nullish(), anrede: z.string().nullish(),
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(),

View File

@@ -18,6 +18,20 @@ const { title } = Astro.props;
--- ---
<script> <script>
// import { H } from "highlight.run";
// if (import.meta.env.PROD) {
// H.init("1jdkoe52", {
// serviceName: "online-energieausweis",
// backendUrl: "https://highlight-backend.online-energieausweis.org/public",
// tracingOrigins: true,
// networkRecording: {
// enabled: true,
// recordHeadersAndBody: true
// }
// });
// }
window.addEventListener("scroll", () => { window.addEventListener("scroll", () => {
const skala = document.getElementById("skala"); const skala = document.getElementById("skala");

View File

@@ -17,6 +17,22 @@ export interface Props {
const { title } = Astro.props; const { title } = Astro.props;
--- ---
<script>
// import { H } from "highlight.run";
// if (import.meta.env.PROD) {
// H.init("1jdkoe52", {
// serviceName: "online-energieausweis",
// backendUrl: "https://highlight-backend.online-energieausweis.org/public",
// tracingOrigins: true,
// networkRecording: {
// enabled: true,
// recordHeadersAndBody: true
// }
// })
// }
</script>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="de"> <html lang="de">

View File

@@ -34,6 +34,22 @@ const schema = JSON.stringify({
}); });
--- ---
<script>
// import { H } from "highlight.run";
// if (import.meta.env.PROD) {
// H.init("1jdkoe52", {
// serviceName: "online-energieausweis",
// backendUrl: "https://highlight-backend.online-energieausweis.org/public",
// tracingOrigins: true,
// networkRecording: {
// enabled: true,
// recordHeadersAndBody: true
// }
// })
// }
</script>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="de"> <html lang="de">
<head> <head>

View File

@@ -45,6 +45,31 @@ const schema = JSON.stringify({
let lightTheme = Astro.cookies.get("theme")?.value === "light"; let lightTheme = Astro.cookies.get("theme")?.value === "light";
--- ---
<script >
// import { H } from "highlight.run";
// const user = JSON.parse(document.body.dataset.user);
// if (import.meta.env.PROD) {
// H.init("1jdkoe52", {
// serviceName: "online-energieausweis",
// backendUrl:
// "https://highlight-backend.online-energieausweis.org/public",
// tracingOrigins: true,
// networkRecording: {
// enabled: true,
// recordHeadersAndBody: true,
// }
// });
// if (user) {
// H.identify(user.email, {
// id: user.id
// })
// }
// }
</script>
<html lang="de"> <html lang="de">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />

View File

@@ -19,6 +19,19 @@ const { title } = Astro.props;
--- ---
<script> <script>
// import { H } from "highlight.run";
// if (import.meta.env.PROD) {
// H.init("1jdkoe52", {
// serviceName: "online-energieausweis",
// backendUrl: "https://highlight-backend.online-energieausweis.org/public",
// tracingOrigins: true,
// networkRecording: {
// enabled: true,
// recordHeadersAndBody: true
// }
// })
// }
/* /*
window.addEventListener("scroll", (event) => { window.addEventListener("scroll", (event) => {

View File

@@ -3,7 +3,7 @@ import { memoize } from "./Memoization.js";
import { api } from "astro-typesafe-api/client" import { api } from "astro-typesafe-api/client"
export const getKlimafaktoren = memoize(async (date: Date, plz: string) => { export const getKlimafaktoren = memoize(async (date: Date, plz: string) => {
if (!plz || plz.length < 5 || !date) { if (!plz || !date) {
return null; return null;
} }

View File

@@ -1,25 +0,0 @@
import winston, { format} from "winston";
const { combine, timestamp } = format;
const loggingFormat = format.printf(({ level, message, timestamp }) => {
return `${timestamp} [${level}]: ${message}`;
});
export const logger = winston.createLogger({
level: "info",
format: combine(
timestamp(),
loggingFormat
),
transports: [
new winston.transports.File({ filename: "error.log", level: "error" }),
new winston.transports.File({ filename: "combined.log" }),
],
});
if (process.env.NODE_ENV !== "production") {
logger.add(new winston.transports.Console({
format: combine(timestamp(), loggingFormat),
}));
}

View File

@@ -2,7 +2,6 @@ import os from "os"
import fs from "fs" import fs from "fs"
export const PERSISTENT_DIR = `${os.homedir()}/persistent/online-energieausweis` export const PERSISTENT_DIR = `${os.homedir()}/persistent/online-energieausweis`
export const SECRET = process.env.SECRET as string
if (!fs.existsSync(PERSISTENT_DIR)) { if (!fs.existsSync(PERSISTENT_DIR)) {
fs.mkdirSync(PERSISTENT_DIR, {recursive: true}) fs.mkdirSync(PERSISTENT_DIR, {recursive: true})

View File

@@ -1,45 +0,0 @@
import { transport } from "#lib/mail.js";
import {
Benutzer,
} from "#lib/client/prisma.js";
export async function sendAutoRegisterMail(
user: Benutzer,
password: string
) {
await transport.sendMail({
from: `"IBCornelsen" <info@online-energieausweis.org>`,
to: user.email,
subject: `Ihre Registrierung bei IBCornelsen`,
bcc: "info@online-energieausweis.org",
html: `<p>Sehr geehrte/r Kund/in,</p>
<p>vielen Dank für Ihre Registrierung bei IBCornelsen. Ihr Benutzerkonto wurde erfolgreich erstellt.<br><br>
Nachfolgend finden Sie Ihre Zugangsdaten:<br><br>
E-Mail: ${user.email}<br>
Passwort: ${password}<br><br>
Sollten Sie diese Registrierung nicht vorgenommen haben, können Sie diese E-Mail einfach ignorieren. Ihr Benutzerkonto wird in diesem Fall nicht aktiviert.<br><br>
Falls Sie Fragen haben oder Unterstützung benötigen, stehen wir Ihnen gerne zur Verfügung. Kontaktieren Sie uns einfach unter <a href="mailto:support@online-energieausweis.org">support@online-energieausweis.org</a>.
<p>
Mit freundlichen Grüßen,
<br>
Dipl.-Ing. Jens Cornelsen
<br>
<br>
<strong>IB Cornelsen</strong>
<br>
Katendeich 5A
<br>
21035 Hamburg
<br>
www.online-energieausweis.org
<br>
<br>
fon 040 · 209339850
<br>
fax 040 · 209339859
</p>`
});
}

View File

@@ -75,6 +75,6 @@ export async function sendInvoiceMail(
name: rechnung.empfaenger || "", name: rechnung.empfaenger || "",
}, },
bcc: "info@online-energieausweis.org", bcc: "info@online-energieausweis.org",
html: `<p>Sehr geehrte/r ${user.vorname} ${user.name},</p>` + getPaymentInvoiceBody(ausweis, rechnung, ausweisart), html: `<p>Sehr geehrte*r ${user.vorname} ${user.name},</p>` + getPaymentInvoiceBody(ausweis, rechnung, ausweisart),
}); });
} }

View File

@@ -80,6 +80,6 @@ export async function sendPaymentSuccessMail(
name: rechnung.empfaenger || "", name: rechnung.empfaenger || "",
}, },
bcc: "info@online-energieausweis.org", bcc: "info@online-energieausweis.org",
html: `<p>Sehr geehrte/r ${user.vorname} ${user.name},</p>` + getPaymentSuccessBody(ausweis, rechnung, ausweisart), html: `<p>Sehr geehrte*r ${user.vorname} ${user.name},</p>` + getPaymentSuccessBody(ausweis, rechnung, ausweisart),
}); });
} }

View File

@@ -1,30 +1,31 @@
import { BASE_URI } from "#lib/constants.js";
import { transport } from "#lib/mail.js"; import { transport } from "#lib/mail.js";
import { import {
Benutzer, Benutzer,
} from "#lib/client/prisma.js"; } from "#lib/client/prisma.js";
import { encodeToken } from "#lib/auth/token.js";
import { TokenType } from "#lib/auth/types.js";
export async function sendRegisterMail( export async function sendRegisterMail(
user: Benutzer, user: Benutzer
passwort: string
) { ) {
const verificationJwt = encodeToken({
typ: TokenType.Verify,
exp: Date.now() + (15 * 60 * 1000),
id: user.id
})
await transport.sendMail({ await transport.sendMail({
from: `"IBCornelsen" <info@online-energieausweis.org>`, from: `"IBCornelsen" <info@online-energieausweis.org>`,
to: user.email, to: user.email,
subject: `Ihre Registrierung bei IBCornelsen`, subject: `Ihre Registrierung bei IBCornelsen`,
bcc: "info@online-energieausweis.org", bcc: "info@online-energieausweis.org",
html: `<p>Sehr geehrte/r ${user.vorname} ${user.name},</p> html: `<p>Sehr geehrte*r ${user.vorname} ${user.name},</p>
<p>vielen Dank für Ihre Registrierung bei IBCornelsen. Ihr Benutzerkonto wurde erfolgreich erstellt.<br><br> <p>vielen Dank für Ihre Registrierung bei IBCornelsen. Ihr Benutzerkonto wurde erfolgreich erstellt.<br><br>
Nachfolgend finden Sie Ihre Zugangsdaten:<br><br> Um Ihre Registrierung abzuschließen, klicken Sie bitte auf den folgenden Link, um Ihre E-Mail-Adresse zu bestätigen:<br><br>
E-Mail: ${user.email}<br>
Passwort: ${passwort}<br><br>
Aus Sicherheitsgründen empfehlen wir Ihnen, Ihr Passwort nach dem ersten Login zu ändern.<br><br> <a href="${BASE_URI}/auth/verify?t=${verificationJwt}">E-Mail-Adresse bestätigen</a><br></p>
Um sich anzumelden, besuchen Sie bitte unsere Website unter <a href="https://online-energieausweis.org/auth/login">online-energieausweis.org/auth/login</a>.<br><br>
Sollten Sie diese Registrierung nicht vorgenommen haben, können Sie diese E-Mail einfach ignorieren. Ihr Benutzerkonto wird in diesem Fall nicht aktiviert.<br><br>
Falls Sie Fragen haben oder Unterstützung benötigen, stehen wir Ihnen gerne zur Verfügung. Kontaktieren Sie uns einfach unter <a href="mailto:support@online-energieausweis.org">support@online-energieausweis.org</a>.
<p> <p>
Mit freundlichen Grüßen, Mit freundlichen Grüßen,
<br> <br>

View File

@@ -7,7 +7,7 @@ export async function sendAusweisGespeichertMail(user: Benutzer, ausweis_id: str
from: `"IBCornelsen" <info@online-energieausweis.org>`, from: `"IBCornelsen" <info@online-energieausweis.org>`,
to: user.email, to: user.email,
subject: `Ihr Ausweis wurde gespeichert - IBCornelsen - (ID: ${ausweis_id})`, subject: `Ihr Ausweis wurde gespeichert - IBCornelsen - (ID: ${ausweis_id})`,
html: `<p>Sehr geehrte/r ${user.vorname} ${user.name},</p> html: `<p>Sehr geehrte*r ${user.vorname} ${user.name},</p>
<p>Ihr Energieausweis wurde erfolgreich in Ihrem Konto gespeichert. Sie können ihn jederzeit in Ihrem Kundenbereich abrufen.<br><br> <p>Ihr Energieausweis wurde erfolgreich in Ihrem Konto gespeichert. Sie können ihn jederzeit in Ihrem Kundenbereich abrufen.<br><br>
Ihre Vorgänge und Ausweise können Sie in Ihrem Kundenkonto einsehen und bearbeiten:<br><br> Ihre Vorgänge und Ausweise können Sie in Ihrem Kundenkonto einsehen und bearbeiten:<br><br>

View File

@@ -13,7 +13,6 @@
try { try {
sent = true sent = true
const response = await api.auth["passwort-vergessen"].GET.fetch({ const response = await api.auth["passwort-vergessen"].GET.fetch({
redirect,
email email
}) })
@@ -58,7 +57,7 @@
{#if showEmailSuccess} {#if showEmailSuccess}
<div class="flex-row justify-between" style="margin-top: 10px"> <div class="flex-row justify-between" style="margin-top: 10px">
<a class="link link-hover" <a class="link link-hover"
href="/auth/login{redirect ? `?r=${redirect}` : ""}">Einloggen</a href="/auth/login{redirect ? `?redirect=${redirect}` : ""}">Einloggen</a
> >
</div> </div>
{/if} {/if}

View File

@@ -7,7 +7,6 @@
let passwortWiederholen: string; let passwortWiederholen: string;
export let token: string; export let token: string;
export let redirect: string | null = null;
let disabled = false; let disabled = false;
@@ -29,7 +28,7 @@
}) })
setTimeout(() => { setTimeout(() => {
window.location.href = "/auth/login?r=" + encodeURIComponent(redirect ?? "/dashboard") window.location.href = "/auth/login"
}, 5000) }, 5000)
} catch (e) { } catch (e) {
disabled = false disabled = false

View File

@@ -1,5 +1,8 @@
<script lang="ts"> <script lang="ts">
import { import {
Reader,
EnvelopeClosed,
Cube,
Person, Person,
} from "radix-svelte-icons"; } from "radix-svelte-icons";
import { Tabs, Tab, TabList, TabPanel } from "../../components/Tabs/index.js"; import { Tabs, Tab, TabList, TabPanel } from "../../components/Tabs/index.js";

View File

@@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import { addNotification } from "#components/Notifications/shared.js";
import { loginClient } from "#lib/login.js"; import { loginClient } from "#lib/login.js";
import EmbeddedLoginModule from "./EmbeddedLoginModule.svelte" import EmbeddedLoginModule from "./EmbeddedLoginModule.svelte"
import EmbeddedRegisterModule from "./EmbeddedRegisterModule.svelte" import EmbeddedRegisterModule from "./EmbeddedRegisterModule.svelte"
@@ -8,31 +7,22 @@
export let email: string = ""; export let email: string = "";
export let password: string = ""; export let password: string = "";
export let route: "login" | "signup" = "login" export let route: "login" | "signup" = "login"
export let title: Record<typeof route, string> = {
login: "Einloggen",
signup: "Registrieren"
}
export let buttonText: Record<typeof route, string> = {
login: "Einloggen",
signup: "Registrieren"
}
const navigate = (target: string) => { const navigate = (target: string) => {
route = target as typeof route; route = target as typeof route;
} }
const loginData = {
email,
passwort: "",
}
</script> </script>
{#if route == "login"} {#if route == "login"}
<EmbeddedLoginModule onLogin={onLogin} bind:email bind:password {navigate} title={title.login} buttonText={buttonText.login} /> <EmbeddedLoginModule onLogin={onLogin} bind:email bind:password {navigate} />
{:else if route == "signup"} {:else}
<EmbeddedRegisterModule bind:email onRegister={(response) => { <EmbeddedRegisterModule bind:email bind:password onRegister={(response) => {
addNotification({ email = response.email
message: "Registrierung erfolgreich", navigate("login")
subtext: "Ein Passwort wurde an ihre Email Adresse gesendet, sie werden nun automatisch weitergeleitet..", }} {navigate} />
type: "success",
timeout: 6000,
dismissable: true
})
onLogin(response)
}} {navigate} title={title.signup} buttonText={buttonText.signup} />
{/if} {/if}

View File

@@ -1,11 +1,10 @@
<script lang="ts"> <script lang="ts">
import { addNotification } from "@ibcornelsen/ui";
import { loginClient } from "#lib/login.js"; import { loginClient } from "#lib/login.js";
export let navigate: (target: string) => void; export let navigate: (target: string) => void;
export let email: string; export let email: string;
export let password: string; export let password: string;
export let title: string = "Einloggen";
export let buttonText: string = "Einloggen";
export let onLogin: (response: Awaited<ReturnType<typeof loginClient>>) => any; export let onLogin: (response: Awaited<ReturnType<typeof loginClient>>) => any;
@@ -14,19 +13,21 @@
const response = await loginClient(email, password) const response = await loginClient(email, password)
if (response === null) { if (response === null) {
error = true; addNotification({
errorMessage = "Das hat leider nicht geklappt, haben sie ihr Passwort und ihre Email Adresse richtig eingegeben?" message: "Ups...",
subtext: "Das hat leider nicht geklappt, haben sie ihr Passwort und ihre Email Adresse richtig eingegeben?",
type: "error",
timeout: 6000,
dismissable: true
})
} else { } else {
onLogin(response); onLogin(response);
} }
} }
let error = false;
let errorMessage = "";
</script> </script>
<form class="max-w-md mx-auto" on:submit={login} name="login"> <form style="width:50%;margin: 0 auto" on:submit={login} name="login">
<h1 class="text-3xl mb-4 p-0">{title}</h1> <h1 class="text-2xl font-semibold mb-6">Einloggen</h1>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div> <div>
<h4>Email</h4> <h4>Email</h4>
@@ -36,7 +37,6 @@
placeholder="Email" placeholder="Email"
name="email" name="email"
bind:value={email} bind:value={email}
on:focus={() => (error = false)}
required required
/> />
</div> </div>
@@ -48,19 +48,13 @@
placeholder="********" placeholder="********"
name="passwort" name="passwort"
bind:value={password} bind:value={password}
on:focus={() => (error = false)}
required required
/> />
</div> </div>
{#if error} <button class="button" type="submit">Einloggen</button>
<div class="bg-red-200 p-4 rounded-lg w-full">
<p class="text-red-800">{errorMessage}</p>
</div>
{/if}
<button class="button" type="submit">{buttonText}</button>
<div class="flex flex-row justify-between" style="margin-top: 10px"> <div class="flex flex-row justify-between" style="margin-top: 10px">
<a on:click={() => navigate("signup")} class="cursor-pointer" data-cy="registrieren">Registrieren</a> <a on:click={() => navigate("signup")} class="cursor-pointer" data-cy="registrieren">Registrieren</a>
<a href="/auth/passwort-vergessen?r={window.location.href}">Passwort Vergessen?</a> <a href="/user/passwort_vergessen">Passwort Vergessen?</a>
</div> </div>
</div> </div>
</form> </form>

View File

@@ -1,51 +1,74 @@
<script lang="ts"> <script lang="ts">
import { loginClient } from "#lib/login.js"; import { addNotification } from "@ibcornelsen/ui";
import { api } from "astro-typesafe-api/client"; import { api } from "astro-typesafe-api/client";
export let navigate: (target: string) => void; export let navigate: (target: string) => void;
export let onRegister: (response: Awaited<ReturnType<typeof loginClient>>) => void; export let onRegister: (response: { email: string, name: string, vorname: string }) => void;
export let password: string;
export let email: string; export let email: string;
export let title: string = "Registrieren"; let vorname: string;
export let buttonText: string = "Registrieren"; let name: string;
let repeatEmail: string;
async function signup(e: SubmitEvent) { async function signUp(e: SubmitEvent) {
e.preventDefault() e.preventDefault()
if (email !== repeatEmail) {
error = true;
errorMessage = "Die eingegebenen Email Adressen stimmen nicht überein.";
return;
}
try { try {
const { id, passwort } = await api.user.autocreate.PUT.fetch({ const response = await api.user.PUT.fetch({
email, email,
passwort: password,
vorname,
name,
}); });
const response = await loginClient(email, passwort) onRegister({
onRegister(response); email,
name,
vorname
})
} catch (e) { } catch (e) {
error = true; addNotification({
errorMessage = "Sie besitzen bereits ein Konto bei IBC. Bitte loggen Sie sich mit Ihrem Passwort ein oder vergeben sich über “Passwort vergessen” ein neues." message: "Ups...",
navigate("login"); subtext:
"Da ist wohl etwas schiefgelaufen. Diese Email Adresse ist bereits in Benutzung, haben sie vielleicht bereits ein Konto bei uns?",
type: "error",
timeout: 0,
dismissable: true,
});
} }
} }
let error: boolean = false;
let errorMessage: string = "";
</script> </script>
<form class="max-w-md mx-auto" name="signup" on:submit={signup}> <form style="width:50%;margin: 0 auto" name="signup" on:submit={signUp}>
<h1 class="text-3xl mb-4 p-0">{title}</h1> <h1>Registrieren</h1>
<p class="p-0 text-base">Ihre Ausweisdaten werden bei uns gespeichert und Ihnen wird ein vorläufiges Passwort erstellt. Sollte Ihre E-Mail bereits bei uns registriert sein, dann loggen Sie sich bitte mit Ihrem vorhandenen Passwort ein oder vergeben sich ein neues über “Passwort vergessen”.</p>
<hr>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="flex flex-row gap-4 w-full">
<div class="w-1/2">
<h4>Vorname</h4>
<input
type="text"
placeholder="Vorname"
name="vorname"
class="px-2.5 py-1.5 rounded-lg border bg-gray-50"
bind:value={vorname}
required
/>
</div>
<div class="w-1/2">
<h4>Nachname</h4>
<input
type="text"
placeholder="Nachname"
name="nachname"
class="px-2.5 py-1.5 rounded-lg border bg-gray-50"
bind:value={name}
required
/>
</div>
</div>
<div> <div>
<h4>Email</h4> <h4>Email</h4>
<input <input
type="email" type="email"
placeholder="max.mustermann@email.de" placeholder="Email"
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}
@@ -53,27 +76,21 @@
required required
/> />
</div> </div>
<div class="flex flex-col gap-2"> <div>
<h4>Email erneut eingeben</h4> <h4>Passwort</h4>
<input <input
type="text" type="password"
placeholder="max.mustermann@email.de" placeholder="********"
name="email" name="passwort"
class="input input-bordered text-base text-base-content font-medium" class="px-2.5 py-1.5 rounded-lg border bg-gray-50"
bind:value={repeatEmail} bind:value={password}
on:keyup={() => (repeatEmail = repeatEmail.toLowerCase())}
required required
/> />
</div> </div>
{#if error} <button class="button" type="submit">Registrieren</button>
<div class="bg-red-200 p-4 rounded-lg w-full"> <div class="flex-row justify-between" style="margin-top: 10px">
<p class="text-red-800">{errorMessage}</p>
</div>
{/if}
<button class="button" type="submit">{buttonText}</button>
<div class="flex flex-row justify-between" style="margin-top: 10px">
<button on:click={() => navigate("login")}>Einloggen</button> <button on:click={() => navigate("login")}>Einloggen</button>
<a href="/auth/passwort-vergessen?r={window.location.href}">Passwort Vergessen?</a> <a href="/user/passwort_vergessen">Passwort Vergessen?</a>
</div> </div>
</div> </div>
</form> </form>

View File

@@ -2,7 +2,7 @@
import PerformanceScore from "#components/Ausweis/PerformanceScore.svelte"; import PerformanceScore from "#components/Ausweis/PerformanceScore.svelte";
import Progressbar from "#components/Ausweis/Progressbar.svelte"; import Progressbar from "#components/Ausweis/Progressbar.svelte";
import Bereich from "#components/labels/Bereich.svelte"; import Bereich from "#components/labels/Bereich.svelte";
import type { BedarfsausweisGewerbe, BedarfsausweisWohnen, Benutzer, Bezahlmethoden, GEGNachweisGewerbe, GEGNachweisWohnen, Unterlage, VerbrauchsausweisGewerbe, VerbrauchsausweisWohnen } from "#lib/client/prisma.js"; import type { BedarfsausweisGewerbe, BedarfsausweisWohnen, Bezahlmethoden, GEGNachweisGewerbe, GEGNachweisWohnen, Unterlage, VerbrauchsausweisGewerbe, VerbrauchsausweisWohnen } from "#lib/client/prisma.js";
import { Enums } from "#lib/client/prisma.js"; import { Enums } from "#lib/client/prisma.js";
import { import {
API_ACCESS_TOKEN_COOKIE_NAME, API_ACCESS_TOKEN_COOKIE_NAME,
@@ -34,6 +34,7 @@
import { endEnergieVerbrauchVerbrauchsausweisGewerbe_2016_Client } from "#lib/Berechnungen/VerbrauchsausweisGewerbe/VerbrauchsausweisGewerbe_2016_Client.js"; import { endEnergieVerbrauchVerbrauchsausweisGewerbe_2016_Client } from "#lib/Berechnungen/VerbrauchsausweisGewerbe/VerbrauchsausweisGewerbe_2016_Client.js";
import { benutzerSpeichern } from "#client/lib/speichern.js"; import { benutzerSpeichern } from "#client/lib/speichern.js";
import { benutzerLesen } from "#client/lib/lesen.js"; import { benutzerLesen } from "#client/lib/lesen.js";
import { exclude } from "#lib/exclude.js";
export let user: Partial<BenutzerClient>; export let user: Partial<BenutzerClient>;
export let impersonatedUser: Partial<BenutzerClient> | null = null; export let impersonatedUser: Partial<BenutzerClient> | null = null;
@@ -55,7 +56,7 @@
email = rechnung?.email || localStorage.getItem("kundendaten.email") || user.email || ""; email = rechnung?.email || localStorage.getItem("kundendaten.email") || user.email || "";
vorname = localStorage.getItem("kundendaten.vorname") || user.vorname || ""; vorname = localStorage.getItem("kundendaten.vorname") || user.vorname || "";
name = localStorage.getItem("kundendaten.name") || user.name || ""; name = localStorage.getItem("kundendaten.name") || user.name || "";
empfaenger = rechnung?.empfaenger || localStorage.getItem("kundendaten.empfaenger") || user.empfaenger empfaenger = rechnung?.empfaenger || localStorage.getItem("kundendaten.empfaenger") || (user.vorname && user.name ? `${user.vorname} ${user.name}` : "")
strasse = rechnung?.strasse || localStorage.getItem("kundendaten.strasse") || user.adresse || ""; strasse = rechnung?.strasse || localStorage.getItem("kundendaten.strasse") || user.adresse || "";
plz = rechnung?.plz || localStorage.getItem("kundendaten.plz") || user.plz || ""; plz = rechnung?.plz || localStorage.getItem("kundendaten.plz") || user.plz || "";
ort = rechnung?.ort || localStorage.getItem("kundendaten.ort") || user.ort || ""; ort = rechnung?.ort || localStorage.getItem("kundendaten.ort") || user.ort || "";
@@ -170,7 +171,6 @@
async function anfordern() { async function anfordern() {
if (!form.checkValidity()) { if (!form.checkValidity()) {
displayFormValidity()
addNotification({ addNotification({
dismissable: true, dismissable: true,
message: "Fehlende Daten.", message: "Fehlende Daten.",
@@ -345,30 +345,8 @@
} }
} }
function displayFormValidity() { async function bestellen(authuser = null) {
(form.querySelectorAll("select[name][required], input[name][required]") as NodeListOf<HTMLInputElement | HTMLSelectElement>).forEach((element) => {
if (element.willValidate && !element.checkValidity()) {
element.dataset["isinvalid"] = "true"
const onChange = () => {
console.log(element, element.value, element.checkValidity());
if (!element.checkValidity()) {
return;
}
element.dataset["isinvalid"] = "false"
element.removeEventListener("change", onChange)
}
element.addEventListener("change", onChange)
}
})
}
async function bestellen(authuser: Benutzer | null = null) {
if (!form.checkValidity()) { if (!form.checkValidity()) {
displayFormValidity()
addNotification({ addNotification({
dismissable: true, dismissable: true,
message: "Fehlende Daten.", message: "Fehlende Daten.",
@@ -440,7 +418,6 @@
const merged_versand_ort = versand_ort || ort; const merged_versand_ort = versand_ort || ort;
const merged_versand_zusatzzeile = versand_zusatzzeile || zusatzzeile; const merged_versand_zusatzzeile = versand_zusatzzeile || zusatzzeile;
if (rechnung) { if (rechnung) {
const result = await api.rechnung._id.PATCH.fetch({ const result = await api.rechnung._id.PATCH.fetch({
bezahlmethode: aktiveBezahlmethode, bezahlmethode: aktiveBezahlmethode,
@@ -527,14 +504,6 @@
let loginOverlayHidden = true; let loginOverlayHidden = true;
let loginAction = () => {}; let loginAction = () => {};
let form: HTMLFormElement; let form: HTMLFormElement;
let ortInput: HTMLInputElement;
$: {
if (ort && ortInput) {
ortInput.value = ort
ortInput.dispatchEvent(new Event("change"))
}
}
</script> </script>
{#if !nurRechnungsadresseUpdate} {#if !nurRechnungsadresseUpdate}
@@ -693,9 +662,6 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
name="rechnung_plz" name="rechnung_plz"
bind:zip={plz} bind:zip={plz}
bind:city={ort} bind:city={ort}
onchange={(e) => {
ortInput.dispatchEvent(new Event('change'));
}}
/> />
</div> </div>
@@ -707,7 +673,6 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
type="text" type="text"
required required
bind:value={ort} bind:value={ort}
bind:this={ortInput}
/> />
<div class="help-label"> <div class="help-label">
@@ -1248,24 +1213,14 @@ sm:grid-cols-[min-content_min-content_min-content] sm:justify-self-end sm:mt-8"
</div> </div>
</div> </div>
</form>
<Overlay bind:hidden={loginOverlayHidden}> <Overlay bind:hidden={loginOverlayHidden}>
<div class="bg-white w-full max-w-screen-sm py-8"> <div class="bg-white w-full max-w-screen-sm py-8">
<EmbeddedAuthFlowModule onLogin={loginAction} email={email} route="signup" title={ <EmbeddedAuthFlowModule onLogin={loginAction} email={email} route="signup"></EmbeddedAuthFlowModule>
{
login: "Ausweis bestellen",
signup: "Ausweis bestellen"
}
} buttonText={{
login: "Bestellen",
signup: "Bestellen"
}}></EmbeddedAuthFlowModule>
</div> </div>
</Overlay> </Overlay>
</form>
<NotificationWrapper></NotificationWrapper> <NotificationWrapper></NotificationWrapper>

View File

@@ -1,5 +1,7 @@
<script lang="ts"> <script lang="ts">
import { loginClient } from "#lib/login.js"; import { loginClient } from "#lib/login.js";
import CrossCircled from "radix-svelte-icons/src/lib/icons/CrossCircled.svelte";
import { fade } from "svelte/transition";
let email: string; let email: string;
let passwort: string; let passwort: string;
@@ -11,8 +13,7 @@
const response = await loginClient(email, passwort); const response = await loginClient(email, passwort);
if (response === null) { if (response === null) {
error = true errorHidden = false;
errorMessage = "Das hat leider nicht geklappt, haben sie ihr Passwort und ihre Email Adresse richtig eingegeben?"
} else { } else {
if (redirect) { if (redirect) {
window.location.href = redirect; window.location.href = redirect;
@@ -23,8 +24,7 @@
} }
} }
let error = false; let errorHidden = true;
let errorMessage = "";
</script> </script>
<div class="mx-auto w-1/3 bg-base-200 p-8 border border-base-300 rounded-lg"> <div class="mx-auto w-1/3 bg-base-200 p-8 border border-base-300 rounded-lg">
@@ -38,7 +38,7 @@
placeholder="nutzer@email.com" placeholder="nutzer@email.com"
name="email" name="email"
bind:value={email} bind:value={email}
on:focus={() => (error = false)} on:focus={() => (errorHidden = true)}
required required
/> />
</div> </div>
@@ -51,19 +51,20 @@
placeholder="********" placeholder="********"
name="passwort" name="passwort"
bind:value={passwort} bind:value={passwort}
on:focus={() => (error = false)} on:focus={() => (errorHidden = true)}
required required
/> />
</div> </div>
{#if error} {#if !errorHidden}
<div class="bg-red-200 p-4 rounded-lg w-full"> <div role="alert" class="alert alert-error" in:fade out:fade={{delay: 400}}>
<p class="text-red-800">{errorMessage}</p> <CrossCircled size={24} />
<span class="font-semibold">Das hat leider nicht geklappt, haben sie ihr Passwort und ihre Email Adresse richtig eingegeben?</span>
</div> </div>
{/if} {/if}
<button class="button" type="submit">Einloggen</button> <button class="button" type="submit">Einloggen</button>
<div class="flex flex-row justify-between" style="margin-top: 10px"> <div class="flex-row justify-between" style="margin-top: 10px">
<a class="link link-hover" href="/auth/signup{redirect ? `?r=${redirect}` : ""}">Registrieren</a> <a class="link link-hover" href="/auth/signup{redirect ? `?redirect=${redirect}` : ""}">Registrieren</a>
<a class="link link-hover" href="/auth/passwort-vergessen{redirect ? `?r=${redirect}` : ""}" <a class="link link-hover" href="/auth/passwort-vergessen{redirect ? `?redirect=${redirect}` : ""}"
>Passwort Vergessen?</a >Passwort Vergessen?</a
> >
</div> </div>

View File

@@ -1,58 +1,55 @@
<script lang="ts"> <script lang="ts">
import { addNotification } from "#components/Notifications/shared.js";
import { CrossCircled } from "radix-svelte-icons";
import { fade } from "svelte/transition";
import { api } from "astro-typesafe-api/client"; import { api } from "astro-typesafe-api/client";
import NotificationWrapper from "#components/Notifications/NotificationWrapper.svelte"; import NotificationWrapper from "#components/Notifications/NotificationWrapper.svelte";
import { loginClient } from "#lib/login.js";
import PlzSuche from "#components/PlzSuche.svelte";
let passwort: string;
let email: string; let email: string;
let vorname: string; let vorname: string;
let name: string; let name: string;
let repeatEmail: string;
let adresse: string;
let plz: string;
let ort: string;
let empfaenger: string;
export let redirect: string | null = null; export let redirect: string | null = null;
async function login(e: SubmitEvent) { async function login(e: SubmitEvent) {
e.preventDefault() e.preventDefault()
if (passwort.length < 8) {
if (email !== repeatEmail) { addNotification({
error = true; message: "Passwort muss mindestens 6 Zeichen enthalten.",
errorMessage = "Die eingegebenen Email Adressen stimmen nicht überein."; dismissable: true,
timeout: 3000,
type: "error"
})
return; return;
} }
try { try {
const { id, passwort } = await api.user.PUT.fetch({ const { id } = await api.user.PUT.fetch({
email,
passwort,
vorname, vorname,
name, email, name
adresse, plz, ort, empfaenger
}) })
await loginClient(email, passwort)
if (redirect) { if (redirect) {
window.location.href = redirect window.location.href = redirect
return return
} }
window.location.href = "/dashboard"; window.location.href = "/auth/login";
} catch (e) { } catch (e) {
error = true errorHidden = false;
errorMessage = "Sie besitzen bereits ein Konto bei IBC. Bitte loggen Sie sich mit Ihrem Passwort ein oder vergeben sich über “Passwort vergessen” ein neues."
} }
} }
let error = false;
let errorMessage = ""; let errorHidden = true;
</script> </script>
<div class="mx-auto w-1/3 bg-base-200 p-8 border border-base-300 rounded-lg"> <div class="mx-auto w-1/3 bg-base-200 p-8 border border-base-300 rounded-lg">
<h1 class="text-3xl mb-4">Registrieren</h1> <h1 class="text-3xl mb-4">Registrieren</h1>
<p>Bitte geben sie Email, Ansprechpartner und Adressdaten ein, um einen Account beim IBC zu erstellen, ein Passwort wird ihnen per Email zugesendet, dieses können sie im Nachhinein jederzeit ändern.</p> <form class="flex flex-col gap-4" on:submit={login}>
<form class="flex flex-col gap-4 mt-8" on:submit={login}>
<div class="flex flex-row gap-4 w-full"> <div class="flex flex-row gap-4 w-full">
<div class="w-1/2 flex flex-col gap-2"> <div class="w-1/2 flex flex-col gap-2">
<h4>Vorname</h4> <h4>Vorname</h4>
@@ -77,51 +74,11 @@
/> />
</div> </div>
</div> </div>
<div class="flex flex-row gap-4 w-full">
<div class="w-1/2 flex flex-col gap-2">
<h4>Straße</h4>
<input
type="text"
placeholder="Straße"
name="strasse"
class="input input-bordered text-base text-base-content font-medium"
bind:value={adresse}
required
/>
</div>
<div class="w-1/2 flex flex-col gap-2">
<h4>PLZ</h4>
<PlzSuche bind:zip={plz} bind:city={ort} name="plz" readonly={false} />
</div>
<div class="w-1/2 flex flex-col gap-2">
<h4>Ort</h4>
<input
type="text"
placeholder="Ort"
name="ort"
class="input input-bordered text-base text-base-content font-medium"
bind:value={ort}
required
/>
</div>
</div>
<div class="flex flex-col gap-2">
<h4>Empfänger</h4>
<input
type="text"
placeholder="Max Mustermann"
name="empfaenger"
class="input input-bordered text-base text-base-content font-medium"
bind:value={empfaenger}
required
/>
</div>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<h4>Email</h4> <h4>Email</h4>
<input <input
type="text" type="text"
placeholder="max.mustermann@email.de" placeholder="Email"
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}
@@ -130,29 +87,31 @@
/> />
</div> </div>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<h4>Email erneut eingeben</h4> <h4>Passwort</h4>
<input <input
type="text" type="password"
placeholder="max.mustermann@email.de" placeholder="********"
name="email" minlength="8"
name="passwort"
class="input input-bordered text-base text-base-content font-medium" class="input input-bordered text-base text-base-content font-medium"
bind:value={repeatEmail} bind:value={passwort}
on:keyup={() => (repeatEmail = repeatEmail.toLowerCase())}
required required
/> />
</div> </div>
{#if error} {#if !errorHidden}
<div class="bg-red-200 p-4 rounded-lg w-full"> <div role="alert" class="alert alert-error" in:fade out:fade={{delay: 400}}>
<p class="text-red-800">{errorMessage}</p> <CrossCircled size={24} />
<span class="font-semibold">Da ist wohl etwas schiefgelaufen. Diese Email Adresse ist bereits in Benutzung, haben sie vielleicht bereits ein Konto bei uns?</span>
</div> </div>
{/if} {/if}
<button type="submit" class="button" <button type="submit" class="button"
>Registrieren</button> >Registrieren</button
<div class="flex flex-row justify-between" style="margin-top: 10px">
<a class="link link-hover"
href="/auth/login{redirect ? `?r=${redirect}` : ""}">Einloggen</a
> >
<a class="link link-hover" href="/auth/passwort-vergessen{redirect ? `?r=${redirect}` : ""}">Passwort Vergessen?</a> <div class="flex-row justify-between" style="margin-top: 10px">
<a class="link link-hover"
href="/auth/login{redirect ? `?redirect=${redirect}` : ""}">Einloggen</a
>
<a class="link link-hover" href="/auth/passwort-vergessen{redirect ? `?redirect=${redirect}` : ""}">Passwort Vergessen?</a>
</div> </div>
</form> </form>
<NotificationWrapper></NotificationWrapper> <NotificationWrapper></NotificationWrapper>

View File

@@ -20,7 +20,7 @@ const user = await getCurrentUser(Astro)
if (id) { if (id) {
if (!user) { if (!user) {
return Astro.redirect( return Astro.redirect(
`/auth/login?r=${Astro.url.toString()}` `/auth/login?redirect=${Astro.url.toString()}`
); );
} }
@@ -57,7 +57,7 @@ if (id) {
} else if (aufnahme_id) { } else if (aufnahme_id) {
if (!user) { if (!user) {
return Astro.redirect( return Astro.redirect(
`/auth/login?r=${Astro.url.toString()}` `/auth/login?redirect=${Astro.url.toString()}`
); );
} }

View File

@@ -20,7 +20,7 @@ const user = await getCurrentUser(Astro)
if (id) { if (id) {
if (!user) { if (!user) {
return Astro.redirect( return Astro.redirect(
`/auth/login?r=${Astro.url.toString()}` `/auth/login?redirect=${Astro.url.toString()}`
); );
} }
@@ -57,7 +57,7 @@ if (id) {
} else if (aufnahme_id) { } else if (aufnahme_id) {
if (!user) { if (!user) {
return Astro.redirect( return Astro.redirect(
`/auth/login?r=${Astro.url.toString()}` `/auth/login?redirect=${Astro.url.toString()}`
); );
} }

View File

@@ -21,7 +21,7 @@ const user = await getCurrentUser(Astro)
if (id) { if (id) {
if (!user) { if (!user) {
return Astro.redirect( return Astro.redirect(
`/auth/login?r=${Astro.url.toString()}` `/auth/login?redirect=${Astro.url.toString()}`
); );
} }
@@ -58,7 +58,7 @@ if (id) {
} else if (aufnahme_id) { } else if (aufnahme_id) {
if (!user) { if (!user) {
return Astro.redirect( return Astro.redirect(
`/auth/login?r=${Astro.url.toString()}` `/auth/login?redirect=${Astro.url.toString()}`
); );
} }

View File

@@ -57,7 +57,7 @@ let loadFromDatabase = false;
if (typ === AusstellungsTyp.Neuausstellung) { if (typ === AusstellungsTyp.Neuausstellung) {
if (!user) { if (!user) {
return Astro.redirect(`/auth/login?r=${Astro.url.toString()}`); return Astro.redirect(`/auth/login?redirect=${Astro.url.toString()}`);
} }
if (!ausweis_id) { if (!ausweis_id) {
@@ -115,7 +115,7 @@ if (typ === AusstellungsTyp.Neuausstellung) {
loadFromDatabase = true; loadFromDatabase = true;
} else if (typ === AusstellungsTyp.Speichern) { } else if (typ === AusstellungsTyp.Speichern) {
if (!user) { if (!user) {
return Astro.redirect(`/auth/login?r=${Astro.url.toString()}`); return Astro.redirect(`/auth/login?redirect=${Astro.url.toString()}`);
} }
if (!ausweis_id) { if (!ausweis_id) {
@@ -151,7 +151,7 @@ if (typ === AusstellungsTyp.Neuausstellung) {
loadFromDatabase = true; loadFromDatabase = true;
} else if (typ === AusstellungsTyp.Alternativdokument) { } else if (typ === AusstellungsTyp.Alternativdokument) {
if (!user) { if (!user) {
return Astro.redirect(`/auth/login?r=${Astro.url.toString()}`); return Astro.redirect(`/auth/login?redirect=${Astro.url.toString()}`);
} }
if (!ausweis_id) { if (!ausweis_id) {

View File

@@ -20,7 +20,7 @@ const user = await getCurrentUser(Astro)
if (id) { if (id) {
if (!user) { if (!user) {
return Astro.redirect( return Astro.redirect(
`/auth/login?r=${Astro.url.toString()}` `/auth/login?redirect=${Astro.url.toString()}`
); );
} }
@@ -58,7 +58,7 @@ if (id) {
} else if (aufnahme_id) { } else if (aufnahme_id) {
if (!user) { if (!user) {
return Astro.redirect( return Astro.redirect(
`/auth/login?r=${Astro.url.toString()}` `/auth/login?redirect=${Astro.url.toString()}`
); );
} }

View File

@@ -20,7 +20,7 @@ const user = await getCurrentUser(Astro)
if (id) { if (id) {
if (!user) { if (!user) {
return Astro.redirect( return Astro.redirect(
`/auth/login?r=${Astro.url.toString()}` `/auth/login?redirect=${Astro.url.toString()}`
); );
} }
@@ -58,7 +58,7 @@ if (id) {
} else if (aufnahme_id) { } else if (aufnahme_id) {
if (!user) { if (!user) {
return Astro.redirect( return Astro.redirect(
`/auth/login?r=${Astro.url.toString()}` `/auth/login?redirect=${Astro.url.toString()}`
); );
} }

View File

@@ -21,7 +21,7 @@ const user = await getCurrentUser(Astro)
if (id) { if (id) {
if (!user) { if (!user) {
return Astro.redirect( return Astro.redirect(
`/auth/login?r=${Astro.url.toString()}` `/auth/login?redirect=${Astro.url.toString()}`
); );
} }
@@ -59,7 +59,7 @@ if (id) {
} else if (aufnahme_id) { } else if (aufnahme_id) {
if (!user) { if (!user) {
return Astro.redirect( return Astro.redirect(
`/auth/login?r=${Astro.url.toString()}` `/auth/login?redirect=${Astro.url.toString()}`
); );
} }

View File

@@ -294,7 +294,7 @@ export const GET = defineApiRoute({
if (rechnung.status === Enums.Rechnungsstatus.paid) { if (rechnung.status === Enums.Rechnungsstatus.paid) {
html = ` html = `
<p>Sehr geehrte/r ${rechnung.empfaenger},</p> <p>Sehr geehrte*r ${rechnung.empfaenger},</p>
<p>im Anhang finden Sie Ihren geprüften Energieusweis inkl. Rechnung als PDF-Datei. ${ <p>im Anhang finden Sie Ihren geprüften Energieusweis inkl. Rechnung als PDF-Datei. ${
post ? "Zusätzlich haben wir Ihren Ausweis per Post verschickt" : "" post ? "Zusätzlich haben wir Ihren Ausweis per Post verschickt" : ""
@@ -323,7 +323,7 @@ export const GET = defineApiRoute({
</p>`; </p>`;
} else { } else {
html = ` html = `
<p>Sehr geehrte/r ${rechnung.empfaenger},</p> <p>Sehr geehrte*r ${rechnung.empfaenger},</p>
<p>im Anhang finden Sie Ihren geprüften Energieusweis inkl. Rechnung als PDF-Datei. ${ <p>im Anhang finden Sie Ihren geprüften Energieusweis inkl. Rechnung als PDF-Datei. ${
post ? "Zusätzlich haben wir Ihren Ausweis per Post verschickt" : "" post ? "Zusätzlich haben wir Ihren Ausweis per Post verschickt" : ""

View File

@@ -303,7 +303,7 @@ export const POST = defineApiRoute({
if (rechnung.status === Enums.Rechnungsstatus.paid) { if (rechnung.status === Enums.Rechnungsstatus.paid) {
html = ` html = `
<p>Sehr geehrte/r ${rechnung.empfaenger},</p> <p>Sehr geehrte*r ${rechnung.empfaenger},</p>
<p>im Anhang finden Sie Ihren geprüften Energieusweis inkl. Rechnung als PDF-Datei. ${ <p>im Anhang finden Sie Ihren geprüften Energieusweis inkl. Rechnung als PDF-Datei. ${
post ? "Zusätzlich haben wir Ihren Ausweis per Post verschickt" : "" post ? "Zusätzlich haben wir Ihren Ausweis per Post verschickt" : ""
@@ -332,7 +332,7 @@ export const POST = defineApiRoute({
</p>`; </p>`;
} else { } else {
html = ` html = `
<p>Sehr geehrte/r ${rechnung.empfaenger},</p> <p>Sehr geehrte*r ${rechnung.empfaenger},</p>
<p>im Anhang finden Sie Ihren geprüften Energieusweis inkl. Rechnung als PDF-Datei. ${ <p>im Anhang finden Sie Ihren geprüften Energieusweis inkl. Rechnung als PDF-Datei. ${
post ? "Zusätzlich haben wir Ihren Ausweis per Post verschickt" : "" post ? "Zusätzlich haben wir Ihren Ausweis per Post verschickt" : ""

View File

@@ -120,7 +120,7 @@ export const PUT = defineApiRoute({
to: rechnung.email || user.email, to: rechnung.email || user.email,
bcc: "info@online-energieausweis.org", bcc: "info@online-energieausweis.org",
subject: `Stornierung des Energieausweises vom Ingenieurbüro Cornelsen (ID: ${ausweis.id})`, subject: `Stornierung des Energieausweises vom Ingenieurbüro Cornelsen (ID: ${ausweis.id})`,
html: `<p>Sehr geehrte/r ${user.vorname} ${user.name},</p> html: `<p>Sehr geehrte*r ${user.vorname} ${user.name},</p>
<p>Ihr Energieausweis wurde soeben storniert. <p>Ihr Energieausweis wurde soeben storniert.
<br> <br>

View File

@@ -9,8 +9,7 @@ import { transport } from "#lib/mail.js";
export const GET = defineApiRoute({ export const GET = defineApiRoute({
input: z.object({ input: z.object({
email: z.string().email(), email: z.string().email()
redirect: z.string().optional()
}), }),
output: z.void(), output: z.void(),
async fetch(input, context, transfer) { async fetch(input, context, transfer) {
@@ -43,7 +42,7 @@ export const GET = defineApiRoute({
sie haben eine Anfrage zum Zurücksetzen ihres Passworts gestellt. Klicken sie auf den folgenden Link, um ein neues Passwort festzulegen: sie haben eine Anfrage zum Zurücksetzen ihres Passworts gestellt. Klicken sie auf den folgenden Link, um ein neues Passwort festzulegen:
https://online-energieausweis.org/auth/passwort-zuruecksetzen?t=${resetToken}${input.redirect ? `&r=${input.redirect}` : ""} https://online-energieausweis.org/auth/passwort-zuruecksetzen?t=${resetToken}
Dieser Link ist für die nächsten 15 Minuten gültig. Falls du diese Anfrage nicht gestellt hast, kannst du diese E-Mail ignorieren - dein Passwort bleibt unverändert. Dieser Link ist für die nächsten 15 Minuten gültig. Falls du diese Anfrage nicht gestellt hast, kannst du diese E-Mail ignorieren - dein Passwort bleibt unverändert.

View File

@@ -10,7 +10,6 @@ import { PutObjectCommand } from "@aws-sdk/client-s3";
import { s3Client } from "#lib/s3.js"; import { s3Client } from "#lib/s3.js";
import { generateIDWithPrefix } from "#lib/db.js"; import { generateIDWithPrefix } from "#lib/db.js";
import { VALID_UUID_PREFIXES } from "#lib/constants.js"; import { VALID_UUID_PREFIXES } from "#lib/constants.js";
import { logger } from "#lib/logger.js";
export const PUT = defineApiRoute({ export const PUT = defineApiRoute({
input: BildSchema.pick({ input: BildSchema.pick({
@@ -70,7 +69,6 @@ export const PUT = defineApiRoute({
id, id,
}, },
}); });
logger.error("Fehler beim Speichern des Bildes in S3: " + e);
// Und geben einen Fehler zurück // Und geben einen Fehler zurück
throw new APIError({ throw new APIError({
code: "INTERNAL_SERVER_ERROR", code: "INTERNAL_SERVER_ERROR",
@@ -111,7 +109,8 @@ export const DELETE = defineApiRoute({
}); });
} }
} catch (e) { } catch (e) {
logger.error("Fehler beim Löschen des Bildes: " + 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

@@ -96,41 +96,6 @@ export const PATCH = defineApiRoute({
} }
}) })
if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
// Wir aktualisieren auch die Rechnungsdaten des Benutzers
// Das sollte allerdings nur passieren, falls diese noch nicht gesetzt sind.
const data = {
ort: input.ort,
plz: input.plz,
adresse: input.strasse,
telefon: input.telefon,
empfaenger: input.empfaenger
}
if (user.ort) {
delete data.ort;
}
if (user.plz) {
delete data.plz;
}
if (user.adresse) {
delete data.adresse;
}
if (user.telefon) {
delete data.telefon;
}
if (user.empfaenger) {
delete data.empfaenger;
}
await prisma.benutzer.update({
data,
where: {
id: user.id
}
})
}
if (input.bezahlmethode === Enums.Bezahlmethoden.rechnung) { if (input.bezahlmethode === Enums.Bezahlmethoden.rechnung) {
return { id: rechnung.id } return { id: rechnung.id }
} }

View File

@@ -61,7 +61,7 @@ export const PUT = defineApiRoute({
if (!adapter) { if (!adapter) {
throw new APIError({ throw new APIError({
code: "BAD_REQUEST", code: "BAD_REQUEST",
message: "Ungültige Ausweis ID" message: "Ungültige Ausweis UID"
}) })
} }
@@ -214,41 +214,6 @@ export const PUT = defineApiRoute({
} }
}) })
if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
// Wir aktualisieren auch die Rechnungsdaten des Benutzers
// Das sollte allerdings nur passieren, falls diese noch nicht gesetzt sind.
const data = {
ort: input.ort,
plz: input.plz,
adresse: input.strasse,
telefon: input.telefon,
empfaenger: input.empfaenger
}
if (user.ort) {
delete data.ort;
}
if (user.plz) {
delete data.plz;
}
if (user.adresse) {
delete data.adresse;
}
if (user.telefon) {
delete data.telefon;
}
if (user.empfaenger) {
delete data.empfaenger;
}
await prisma.benutzer.update({
data,
where: {
id: user.id
}
})
}
if (bezahlmethode === Enums.Bezahlmethoden.rechnung) { if (bezahlmethode === Enums.Bezahlmethoden.rechnung) {
return { id } return { id }
} }

View File

@@ -1,64 +0,0 @@
import { IDWithPrefix } from "#components/Ausweis/types.js";
import { VALID_UUID_PREFIXES } from "#lib/constants.js";
import { generateIDWithPrefix } from "#lib/db.js";
import { hashPassword } from "#lib/password.js";
import { createLexOfficeCustomer } from "#lib/server/lexoffice.js";
import { sendAutoRegisterMail } from "#lib/server/mail/auto-registrierung.js";
import { sendRegisterMail } from "#lib/server/mail/registrierung.js";
import { prisma } from "#lib/server/prisma.js";
import { defineApiRoute, APIError } from "astro-typesafe-api/server";
import { z } from "astro:content";
export const PUT = defineApiRoute({
input: z.object({
email: z.string().email(),
}),
output: z.object({
email: z.string().email(),
id: IDWithPrefix,
passwort: z.string().min(8).max(100)
}),
async fetch(input) {
let { email } = input;
email = email.toLowerCase();
const existingUser = await prisma.benutzer.findUnique({
where: {
email
}
})
if (existingUser) {
throw new APIError({
code: "CONFLICT",
message: "Email Adresse ist bereits vergeben."
})
}
const id = generateIDWithPrefix(9, VALID_UUID_PREFIXES.User);
const passwort = crypto.randomUUID().slice(0, 8);
const user = await prisma.benutzer.create({
data: {
email,
passwort: hashPassword(passwort),
id
}
})
const lex_office_id = await createLexOfficeCustomer(user);
await prisma.benutzer.update({
where: {
id: user.id
},
data: {
lex_office_id
}
})
await sendRegisterMail(user, passwort)
return { id, email: user.email, passwort }
},
})

View File

@@ -1,11 +1,11 @@
import { IDWithPrefix } from "#components/Ausweis/types.js"; import { IDWithPrefix } from "#components/Ausweis/types.js";
import { VALID_UUID_PREFIXES } from "#lib/constants.js"; import { VALID_UUID_PREFIXES } from "#lib/constants.js";
import { generateIDWithPrefix } from "#lib/db.js"; import { generateIDWithPrefix } from "#lib/db.js";
import { authorizationMiddleware } from "#lib/middleware/authorization.js"; import { adminMiddleware, authorizationMiddleware } from "#lib/middleware/authorization.js";
import { hashPassword } from "#lib/password.js"; import { hashPassword } from "#lib/password.js";
import { createLexOfficeCustomer } from "#lib/server/lexoffice.js"; import { createLexOfficeCustomer } from "#lib/server/lexoffice.js";
import { sendRegisterMail } from "#lib/server/mail/registrierung.js"; import { sendRegisterMail } from "#lib/server/mail/registrierung.js";
import { prisma } from "#lib/server/prisma.js"; import { Benutzer, prisma } from "#lib/server/prisma.js";
import { APIError, defineApiRoute } from "astro-typesafe-api/server"; import { APIError, defineApiRoute } from "astro-typesafe-api/server";
import { BenutzerSchema } from "src/generated/zod/benutzer.js"; import { BenutzerSchema } from "src/generated/zod/benutzer.js";
import { z } from "zod"; import { z } from "zod";
@@ -26,6 +26,7 @@ export const POST = defineApiRoute({
const updateData: any = {}; const updateData: any = {};
updateData.id = user.id; updateData.id = user.id;
if (input.adresse) updateData.adresse = input.adresse; if (input.adresse) updateData.adresse = input.adresse;
if (input.anrede) updateData.anrede = input.anrede;
if (input.email) updateData.email = input.email; if (input.email) updateData.email = input.email;
if (input.firma) updateData.firma = input.firma; if (input.firma) updateData.firma = input.firma;
if (input.name) updateData.name = input.name; if (input.name) updateData.name = input.name;
@@ -105,19 +106,15 @@ export const GET = defineApiRoute({
export const PUT = defineApiRoute({ export const PUT = defineApiRoute({
input: z.object({ input: z.object({
email: z.string().email(), email: z.string().email(),
passwort: z.string().min(8),
vorname: z.string(), vorname: z.string(),
name: z.string(), name: z.string()
adresse: z.string(),
plz: z.string(),
ort: z.string(),
empfaenger: z.string()
}), }),
output: z.object({ output: z.object({
id: IDWithPrefix, id: IDWithPrefix
passwort: z.string().min(8).max(100)
}), }),
async fetch(input) { async fetch(input) {
let { email, vorname, name, adresse, plz, ort, empfaenger } = input; let { email, passwort, vorname, name } = input;
email = email.toLowerCase(); email = email.toLowerCase();
const existingUser = await prisma.benutzer.findUnique({ const existingUser = await prisma.benutzer.findUnique({
@@ -134,18 +131,13 @@ export const PUT = defineApiRoute({
} }
const id = generateIDWithPrefix(9, VALID_UUID_PREFIXES.User); const id = generateIDWithPrefix(9, VALID_UUID_PREFIXES.User);
const passwort = crypto.randomUUID().slice(0, 8);
const user = await prisma.benutzer.create({ const user = await prisma.benutzer.create({
data: { data: {
email, email,
vorname,
passwort: hashPassword(passwort), passwort: hashPassword(passwort),
vorname,
name, name,
adresse,
plz,
ort,
empfaenger,
id id
} }
}) })
@@ -161,8 +153,8 @@ export const PUT = defineApiRoute({
} }
}) })
await sendRegisterMail(user, passwort) await sendRegisterMail(user)
return { id, passwort } return { id }
}, },
}) })

View File

@@ -0,0 +1,9 @@
---
import BlankLayout from "#layouts/BlankLayout.astro";
import EmbeddedLoginModule from "#modules/EmbeddedLoginModule.svelte";
---
<BlankLayout title="Login - IBCornelsen">
<EmbeddedLoginModule client:only></EmbeddedLoginModule>
</BlankLayout>

View File

@@ -0,0 +1,10 @@
---
import BlankLayout from "#layouts/BlankLayout.astro";
import EmbeddedRegisterModule from "#modules/EmbeddedRegisterModule.svelte";
const redirect = Astro.url.searchParams.get("redirect");
---
<BlankLayout title="Registrieren - IBCornelsen">
<EmbeddedRegisterModule client:only></EmbeddedRegisterModule>
</BlankLayout>

View File

@@ -9,7 +9,7 @@ if (valid) {
return Astro.redirect("/dashboard") return Astro.redirect("/dashboard")
} }
const redirect = Astro.url.searchParams.get("r") const redirect = Astro.url.searchParams.get("redirect")
--- ---
<MinimalLayout title="Login"> <MinimalLayout title="Login">

View File

@@ -5,13 +5,11 @@ import MinimalLayout from "#layouts/MinimalLayout.astro";
const valid = await validateAccessTokenServer(Astro) const valid = await validateAccessTokenServer(Astro)
// Es kann sein, dass ein Nutzer von dem Ausweisformular kommt und sein Passwort vergessen hat.
// In dem Fall sollte er auch auf das Formular zurückgeleitet werden wenn er sein Passwort zurückgesetzt hat.
const redirect = Astro.url.searchParams.get("r")
if (valid) { if (valid) {
return Astro.redirect("/dashboard") return Astro.redirect("/dashboard")
} }
const redirect = Astro.url.searchParams.get("redirect")
--- ---
<MinimalLayout title="Passwort Vergessen"> <MinimalLayout title="Passwort Vergessen">

View File

@@ -12,7 +12,6 @@ if (valid) {
} }
const token = Astro.url.searchParams.get("t") const token = Astro.url.searchParams.get("t")
const redirect = Astro.url.searchParams.get("r")
if (!token) { if (!token) {
return Astro.redirect("/") return Astro.redirect("/")
@@ -26,5 +25,5 @@ if (!decoded.exp || decoded.exp < Date.now() || decoded.typ !== TokenType.Reset)
--- ---
<MinimalLayout title="Passwort Vergessen"> <MinimalLayout title="Passwort Vergessen">
<PasswortZuruecksetzenModule token={token} redirect={redirect} client:load></PasswortZuruecksetzenModule> <PasswortZuruecksetzenModule token={token} client:load></PasswortZuruecksetzenModule>
</MinimalLayout> </MinimalLayout>

View File

@@ -9,7 +9,7 @@ if (valid) {
return Astro.redirect("/dashboard") return Astro.redirect("/dashboard")
} }
const redirect = Astro.url.searchParams.get("r"); const redirect = Astro.url.searchParams.get("redirect");
--- ---

View File

@@ -0,0 +1,32 @@
---
import Layout from "#layouts/Layout.astro";
import { decodeToken } from "#lib/auth/token";
import { TokenType } from "#lib/auth/types";
import { prisma } from "#lib/server/prisma";
const token = Astro.url.searchParams.get("t");
if (!token) {
return Astro.redirect("/")
}
const payload = decodeToken(token)
if (payload.typ !== TokenType.Verify || !payload.uid || !payload.exp || payload.exp < Date.now()) {
return Astro.redirect("/")
}
await prisma.benutzer.update({
where: {
uid: payload.uid
},
data: {
verified: true
}
})
---
<Layout title="Bestätigung">
<h1>Vielen Dank</h1>
<p>Ihre Email Adresse wurde bestätigt, sie können diese Seite nun schließen.</p>
</Layout>

View File

@@ -53,7 +53,7 @@ import Layout from "#layouts/Layout.astro";
<div> <div>
<h3 class="text-xl font-semibold text-gray-800 mb-2">Kontakt & Gebäudedaten</h3> <h3 class="text-xl font-semibold text-gray-800 mb-2">Kontakt & Gebäudedaten</h3>
<ul class="list-disc list-inside text-gray-700"> <ul class="list-disc list-inside text-gray-700">
<li>Name & E-Mail des Ansprechpartners</li> <li>Name, Anrede & E-Mail des Ansprechpartners</li>
<li>Rechnungs- & Versandadresse</li> <li>Rechnungs- & Versandadresse</li>
<li>Gebäudeangaben: Wohnfläche, Baujahr, Anzahl Geschosse</li> <li>Gebäudeangaben: Wohnfläche, Baujahr, Anzahl Geschosse</li>
</ul> </ul>

View File

@@ -2,6 +2,7 @@
import UserLayout from "#layouts/DashboardLayout.astro"; import UserLayout from "#layouts/DashboardLayout.astro";
import { getCurrentUser } from "#lib/server/user"; import { getCurrentUser } from "#lib/server/user";
import DashboardEinstellungenModule from "#modules/Dashboard/DashboardEinstellungenModule.svelte"; import DashboardEinstellungenModule from "#modules/Dashboard/DashboardEinstellungenModule.svelte";
import { validateAccessTokenServer } from "#server/lib/validateAccessToken";
const user = await getCurrentUser(Astro) const user = await getCurrentUser(Astro)

View File

@@ -53,21 +53,5 @@ if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
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}`);
} else {
return Astro.redirect("/dashboard/objekte/leer");
} }
--- ---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>

View File

@@ -8,7 +8,6 @@ import {
Enums, Enums,
Objekt, Objekt,
prisma, prisma,
Unterlage,
VerbrauchsausweisGewerbe, VerbrauchsausweisGewerbe,
VerbrauchsausweisWohnen, VerbrauchsausweisWohnen,
} from "#lib/server/prisma"; } from "#lib/server/prisma";
@@ -17,7 +16,6 @@ import {
getBedarfsausweisWohnen, getBedarfsausweisWohnen,
getBilder, getBilder,
getObjekt, getObjekt,
getUnterlagen,
getVerbrauchsausweisGewerbe, getVerbrauchsausweisGewerbe,
getVerbrauchsausweisWohnen, getVerbrauchsausweisWohnen,
} from "#lib/server/db"; } from "#lib/server/db";
@@ -54,18 +52,14 @@ let ausweis:
let aufnahme: Aufnahme | null = {} as Aufnahme; let aufnahme: Aufnahme | null = {} as Aufnahme;
let objekt: Objekt | null = {} as Objekt; let objekt: Objekt | null = {} as Objekt;
let bilder: Bild[] = []; let bilder: Bild[] = [];
let unterlagen: Unterlage[] = [];
let loadFromDatabase = false; let loadFromDatabase = false;
if (typ === AusstellungsTyp.Neuausstellung) { if (typ === AusstellungsTyp.Neuausstellung) {
if (!user) { if (!user) {
// Der Nutzer muss eingeloggt sein um eine Neuausstellung anzufordern, return Astro.redirect(`/auth/login?redirect=${Astro.url.toString()}`);
// sonst können wir nicht sicher sein, dass der Nutzer berechtigt ist auf den Ausweis zuzugreifen.
return Astro.redirect(`/auth/login?r=${Astro.url.toString()}`);
} }
if (!ausweis_id) { if (!ausweis_id) {
// Falls es keine Ausweis ID gibt können wir nicht fortfahren.
return Astro.redirect("/400"); return Astro.redirect("/400");
} }
@@ -100,10 +94,9 @@ if (typ === AusstellungsTyp.Neuausstellung) {
return Astro.redirect("/405"); return Astro.redirect("/405");
} }
// Wir setzen alle Daten vom Ausweis zurück, sonst könnte es passieren, dass der Ausweis als der alte Ausweis gespeichert wird. ausweis.id = null;
ausweis.id = ""; aufnahme.id = null;
aufnahme.id = ""; delete aufnahme.erstellungsdatum;
aufnahme.erstellungsdatum = null;
ausweis.created_at = new Date() ausweis.created_at = new Date()
ausweis.updated_at = new Date(); ausweis.updated_at = new Date();
ausweis.alte_ausweis_id = null; ausweis.alte_ausweis_id = null;
@@ -121,7 +114,7 @@ if (typ === AusstellungsTyp.Neuausstellung) {
loadFromDatabase = true; loadFromDatabase = true;
} else if (typ === AusstellungsTyp.Speichern) { } else if (typ === AusstellungsTyp.Speichern) {
if (!user) { if (!user) {
return Astro.redirect(`/auth/login?r=${Astro.url.toString()}`); return Astro.redirect(`/auth/login?redirect=${Astro.url.toString()}`);
} }
if (!ausweis_id) { if (!ausweis_id) {
@@ -136,11 +129,6 @@ if (typ === AusstellungsTyp.Neuausstellung) {
ausweis = await getBedarfsausweisWohnen(ausweis_id); ausweis = await getBedarfsausweisWohnen(ausweis_id);
} }
if (!ausweis) {
// Falls der Ausweis nicht gefunden wurde, können wir nicht fortfahren.
return Astro.redirect("/404");
}
ausweistyp = ausweis.ausweistyp; ausweistyp = ausweis.ausweistyp;
aufnahme = (await getAufnahme(ausweis.aufnahme_id)) as Aufnahme; aufnahme = (await getAufnahme(ausweis.aufnahme_id)) as Aufnahme;
@@ -159,11 +147,10 @@ if (typ === AusstellungsTyp.Neuausstellung) {
} }
bilder = await getBilder(aufnahme.id); bilder = await getBilder(aufnahme.id);
unterlagen = await getUnterlagen(aufnahme.id);
loadFromDatabase = true; loadFromDatabase = true;
} else if (typ === AusstellungsTyp.Alternativdokument) { } else if (typ === AusstellungsTyp.Alternativdokument) {
if (!user) { if (!user) {
return Astro.redirect(`/auth/login?r=${Astro.url.toString()}`); return Astro.redirect(`/auth/login?redirect=${Astro.url.toString()}`);
} }
if (!ausweis_id) { if (!ausweis_id) {
@@ -201,8 +188,8 @@ if (typ === AusstellungsTyp.Neuausstellung) {
return Astro.redirect("/405"); return Astro.redirect("/405");
} }
ausweis.id = ""; ausweis.id = null;
aufnahme.erstellungsdatum = null; delete aufnahme.erstellungsdatum;
ausweis.created_at = new Date() ausweis.created_at = new Date()
ausweis.updated_at = new Date(); ausweis.updated_at = new Date();
ausweis.alte_ausweis_id = null; ausweis.alte_ausweis_id = null;
@@ -262,7 +249,6 @@ if (typ === AusstellungsTyp.Neuausstellung) {
{aufnahme} {aufnahme}
{bilder} {bilder}
{ausweistyp} {ausweistyp}
{unterlagen}
{ausweis_id} {ausweis_id}
{user} {user}
{loadFromDatabase} {loadFromDatabase}

View File

@@ -20,7 +20,7 @@ const caller = createCaller(Astro);
if (uid) { if (uid) {
if (!valid) { if (!valid) {
return Astro.redirect( return Astro.redirect(
`/auth/login?r=${Astro.url.toString()}` `/auth/login?redirect=${Astro.url.toString()}`
); );
} }

View File

@@ -20,7 +20,7 @@ const caller = createCaller(Astro);
if (uid) { if (uid) {
if (!valid) { if (!valid) {
return Astro.redirect( return Astro.redirect(
`/auth/login?r=${Astro.url.toString()}` `/auth/login?redirect=${Astro.url.toString()}`
); );
} }

View File

@@ -23,7 +23,7 @@ const caller = createCaller(Astro);
if (uid) { if (uid) {
if (!valid) { if (!valid) {
return Astro.redirect( return Astro.redirect(
`/auth/login?r=${Astro.url.toString()}` `/auth/login?redirect=${Astro.url.toString()}`
); );
} }

View File

@@ -19,7 +19,7 @@ const caller = createCaller(Astro);
if (uid) { if (uid) {
if (!valid) { if (!valid) {
return Astro.redirect( return Astro.redirect(
`/auth/login?r=${Astro.url.toString()}` `/auth/login?redirect=${Astro.url.toString()}`
); );
} }

View File

@@ -23,7 +23,7 @@ const caller = createCaller(Astro);
if (uid) { if (uid) {
if (!valid) { if (!valid) {
return Astro.redirect( return Astro.redirect(
`/auth/login?r=${Astro.url.toString()}` `/auth/login?redirect=${Astro.url.toString()}`
); );
} }

View File

@@ -23,7 +23,7 @@ const caller = createCaller(Astro);
if (uid) { if (uid) {
if (!valid) { if (!valid) {
return Astro.redirect( return Astro.redirect(
`/auth/login?r=${Astro.url.toString()}` `/auth/login?redirect=${Astro.url.toString()}`
); );
} }

View File

@@ -363,7 +363,7 @@ import Layout from "#layouts/Layout.astro";
eingegeben und an uns übermittelt und gespeichert. Eine Weitergabe der Daten an Dritte findet eingegeben und an uns übermittelt und gespeichert. Eine Weitergabe der Daten an Dritte findet
nicht statt. Folgende Daten werden im Rahmen des Registrierungsprozesses erhoben:</p> nicht statt. Folgende Daten werden im Rahmen des Registrierungsprozesses erhoben:</p>
<ol> <ol>
<li>Vorname, Name und E-Mail des Ansprechpartners</li> <li>Anrede, Vorname, Name und E-Mail des Ansprechpartners</li>
<li>Empfänger, E-Mail, Straße, <span class="caps">PLZ</span> und Ort der Rechnungsanschrift <li>Empfänger, E-Mail, Straße, <span class="caps">PLZ</span> und Ort der Rechnungsanschrift
</li> </li>
<li>Optional die Versandanschrift</li> <li>Optional die Versandanschrift</li>
@@ -417,7 +417,7 @@ import Layout from "#layouts/Layout.astro";
Kontaktaufnahme genutzt werden kann. Nimmt ein Nutzer diese Möglichkeit wahr, so werden die in Kontaktaufnahme genutzt werden kann. Nimmt ein Nutzer diese Möglichkeit wahr, so werden die in
der Eingabemaske eingegeben Daten an uns übermittelt und gespeichert. Diese Daten sind:</p> der Eingabemaske eingegeben Daten an uns übermittelt und gespeichert. Diese Daten sind:</p>
<ol> <ol>
<li>Vorname, Name und E-Mail des Ansprechpartners</li> <li>Anrede, Vorname, Name und E-Mail des Ansprechpartners</li>
</ol> Im Zeitpunkt der Absendung der Nachricht werden zudem folgende Daten gespeichert:<ol> </ol> Im Zeitpunkt der Absendung der Nachricht werden zudem folgende Daten gespeichert:<ol>
<li>Die IP-Adresse des Nutzers</li> <li>Die IP-Adresse des Nutzers</li>
<li>Datum und Uhrzeit der Registrierung</li> <li>Datum und Uhrzeit der Registrierung</li>

View File

@@ -55,7 +55,7 @@ import Layout from "#layouts/Layout.astro";
<div> <div>
<h3 class="text-xl font-semibold text-gray-800 mb-2">Kontakt und Gebäudedaten</h3> <h3 class="text-xl font-semibold text-gray-800 mb-2">Kontakt und Gebäudedaten</h3>
<ul class="list-disc list-inside text-gray-700"> <ul class="list-disc list-inside text-gray-700">
<li>Vorname, Name und E-Mail des Ansprechpartners</li> <li>Anrede, Vorname, Name und E-Mail des Ansprechpartners</li>
<li>Empfänger, E-Mail, Straße, PLZ und Ort der Rechnungsanschrift</li> <li>Empfänger, E-Mail, Straße, PLZ und Ort der Rechnungsanschrift</li>
<li>Optionale Versandanschrift</li> <li>Optionale Versandanschrift</li>
<li>Details zur Wohnfläche, Heizung, Baujahr usw.</li> <li>Details zur Wohnfläche, Heizung, Baujahr usw.</li>

View File

@@ -279,31 +279,3 @@ article {
/*SIDEBAR-RIGHT*/ /*SIDEBAR-RIGHT*/
/*FOOTER*/ /*FOOTER*/
/* LOADERS */
.loader {
width: 24px;
height: 24px;
border: 3px solid #444f94;
border-bottom-color: transparent;
border-radius: 50%;
display: inline-block;
box-sizing: border-box;
animation: rotation 1s linear infinite;
}
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* FORM VALIDATION MESSAGE */
[data-isinvalid="true"] {
border: 2px solid red !important;
}

View File

@@ -45,6 +45,7 @@ Papa.parse(file, {
email: user.email, email: user.email,
passwort: user.password, passwort: user.password,
adresse: user.adresse, adresse: user.adresse,
anrede: user.anrede,
name: user.name, name: user.name,
vorname: user.vorname, vorname: user.vorname,
ort: user.ort, ort: user.ort,

View File

@@ -338,6 +338,7 @@ export function fakeBenutzer() {
ort: undefined, ort: undefined,
adresse: undefined, adresse: undefined,
telefon: undefined, telefon: undefined,
anrede: undefined,
firma: undefined, firma: undefined,
lex_office_id: undefined, lex_office_id: undefined,
}; };
@@ -355,6 +356,7 @@ export function fakeBenutzerComplete() {
ort: undefined, ort: undefined,
adresse: undefined, adresse: undefined,
telefon: undefined, telefon: undefined,
anrede: undefined,
rolle: BenutzerRolle.USER, rolle: BenutzerRolle.USER,
firma: undefined, firma: undefined,
lex_office_id: undefined, lex_office_id: undefined,

View File

@@ -45,14 +45,7 @@ fi
echo "🧨 Alle Daten aus allen Tabellen werden gelöscht..." echo "🧨 Alle Daten aus allen Tabellen werden gelöscht..."
# Erst müssen wir alle Verbindungen zur Datenbank trennen # Generate and run TRUNCATE statements for all tables in the public schema
docker exec -i "$CONTAINER_NAME" psql -U "$DB_USER" "postgres" <<'EOSQL'
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname = 'main' AND pid <> pg_backend_pid();
EOSQL
# Dann löschen wir die Datenbank und erstellen sie neu
docker exec -i "$CONTAINER_NAME" psql -U "$DB_USER" "postgres" <<'EOSQL' docker exec -i "$CONTAINER_NAME" psql -U "$DB_USER" "postgres" <<'EOSQL'
DROP DATABASE IF EXISTS main; DROP DATABASE IF EXISTS main;
CREATE DATABASE main WITH OWNER main ENCODING 'UTF8'; CREATE DATABASE main WITH OWNER main ENCODING 'UTF8';