25 Commits
main ... dev

Author SHA1 Message Date
Moritz Utcke
f95e87b450 Empfaenger 2025-10-18 11:53:39 -04:00
Moritz Utcke
1de949a08e Update production pipeline 2025-10-17 18:19:11 -04:00
Moritz Utcke
af4bc5b282 Empfänger und Stammdaten speichern 2025-10-17 18:05:53 -04:00
Moritz Utcke
77a71d8f6c Adressdaten werden bei Bestellung nicht direkt übernommen 2025-10-17 13:32:17 -04:00
Moritz Utcke
946a991176 Verbesserungen
1. Info Box direkt beim Login anzeigen anstatt kleine Box unten in der Ecke

2. Pflichtfelder bei Schritt 2 noch nicht in rot markiert
2025-10-17 13:13:43 -04:00
Moritz Utcke
38386ed830 Registriermail 2025-10-17 10:06:40 -04:00
Moritz Utcke
0b01a76953 Verbesserungen Registrierung 2025-10-17 10:05:07 -04:00
Moritz Utcke
462fa79592 Automatische Registrierung 2025-10-16 18:09:18 -04:00
Moritz Utcke
5df588e8e9 Registrierungsprozess verschlankt 2025-10-16 17:34:26 -04:00
Moritz Utcke
5cca857358 Vereinfachter Registrierungsprozess
Nicht-eingeloggte Nutzer sollen sich einfacher registrieren/authentifizieren können.

Speichern oder Bestellen nicht-eingeloggte Nutzer auf der Kundendaten-Seite, so soll ein Popup aufgehen, wo sie einen Bestätigungscode eingeben können. Dieser Code wird umgehend an ihre eingegene Email gesendet.

Wird der Code efolgreich eingegeben gibt es zwei Fälle:

Unter der Email existiert bereits ein Account → Der Ausweis wird dann diesem User zugewiesen

Unter der Email existiet noch kein Account → Ein neuer Account wird angelegt und der Ausweise dem neuen Account zugewiesen

Wird ein neuner Account automatisch angelegt, so benötigen wir auch noch einen Prozess, wie der Nutzer dann sein Passwort vergeben kann. Idealerweise erhält er in seiner Willkommensmail einen Link zur Passwort setzung. Alternativ nutzt er einfach die bestehende Passwortrücksetzen-Funktion auf der Webseite.

Um bei der Erstregistrierung soll ein Zahlencode an die eingegebene E-Mail verschickt werden. Dieser muss dann vom User eingegeben werden um die Registrierung bzw. meistens ja dann die Erstbestellung abzuschließen. Der Zahlencode kann ja dann das Passwort sein. Wir weisen den Kunden darauf hin sich ein eigenes Passwort zu vergeben.
2025-10-16 10:24:35 -04:00
Moritz Utcke
882d2d2f60 Rahmen bleiben rot wenn man Feld ausgefüllt hat 2025-10-14 18:25:07 -04:00
Moritz Utcke
1ef0b5e15a Rechnungsdaten Updaten 2025-10-14 12:18:48 -04:00
Moritz Utcke
5f97e1f37e Daten weg im Bestellptozess nach Passwort anfordern. Joachim Weggler
Nachdem ein neues Passwort angefragt wurde wird der Kunde nun automatisch zurück zum Formular geleitet.
2025-10-14 11:59:15 -04:00
Moritz Utcke
4aa0d125e7 Besseres Error Logging 2025-10-14 11:44:18 -04:00
Moritz Utcke
ecedaaa716 Merge remote-tracking branch 'origin/dev' into dev 2025-10-14 11:43:05 -04:00
Moritz Utcke
817f0075d1 Anzeige nicht ausgefüllte Pflichtfelder bei weiter oft nicht eindeutig. 2025-10-14 11:42:58 -04:00
Jens Cornelsen
944a632495 Fernwärme Pforzheim hinzugefuegt 2025-10-14 17:32:57 +02:00
Moritz Utcke
950bd95c2a Bildupload 2025-10-13 13:33:23 -04:00
Moritz Utcke
4381578205 Bugfixes & Features
1. Wenn pdf hochgeladen wird und abgespeichert wird erscheint es nicht im Formular aber im Dashboard.

2. Loading Spinner beim Hochladen von Dateien damit man sieht das was passiert.

3. Bei Bedarfsausweis Gewerbe anfragen sind in der Liste bei Gebäudetyp nur Wohngebäude!!
2025-10-13 11:33:45 -04:00
Moritz Utcke
d4793af2a4 Autostart bei Datenbank Backup rausgenommen 2025-10-12 19:24:31 -04:00
Moritz Utcke
6f192e816d Update Makefile 2025-10-12 19:04:32 -04:00
Moritz Utcke
4198669b94 DWD Klimafaktoren 2025-10-12 18:55:23 -04:00
Moritz Utcke
2a6e02f395 Fix: Geschosshöhe wird nicht gefetched 2025-10-12 17:43:50 -04:00
Moritz Utcke
fec28de07f Merge remote-tracking branch 'origin/dev' into dev 2025-10-12 17:00:07 -04:00
Moritz Utcke
c41cfe43f0 Email zweimal eingeben 2025-10-12 16:03:47 -04:00
87 changed files with 839 additions and 489 deletions

2
.env
View File

@@ -4,6 +4,8 @@
# 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
SECRET="jg57cya4mrNkGlnb2R85X1gI8LdY7iNZ9MF0Jbn0K5zQBshOxv"
POSTGRES_DB=main
POSTGRES_HOST=localhost
POSTGRES_PORT=5432

View File

@@ -5,8 +5,33 @@ on:
branches: [main]
jobs:
deploy:
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:
name: Deploy to production
runs-on: ubuntu-latest
needs: check-migrations
if: needs.check-migrations.outputs.has_new_migrations == 'false'
steps:
- uses: actions/checkout@v2
- name: Install Bun
@@ -29,3 +54,15 @@ jobs:
git pull origin main
git status
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,6 +15,8 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
error.log*
combined.log*
# lockfile
pnpm-lock.yaml

View File

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

View File

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

View File

@@ -1,5 +1,10 @@
#!/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_COMPLETE=full-dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql.br
DATABASE_NAME=database

View File

@@ -29,7 +29,6 @@
"fontkit": "^2.0.4",
"handlebars": "^4.7.8",
"heic2any": "^0.0.4",
"highlight.run": "^9.14.0",
"is-base64": "^1.1.0",
"js-cookie": "^3.0.5",
"js-interpolate": "^1.3.2",
@@ -56,7 +55,7 @@
"tailwindcss": "^3.4.17",
"trpc-openapi": "^1.2.0",
"uuid": "^9.0.1",
"winston": "^3.17.0",
"winston": "^3.18.3",
"zod": "^3.24.1",
},
"devDependencies": {
@@ -270,7 +269,7 @@
"@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.3", "", { "dependencies": { "colorspace": "1.1.x", "enabled": "2.0.x", "kuler": "^2.0.0" } }, "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA=="],
"@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=="],
"@daybrush/utils": ["@daybrush/utils@1.13.0", "", {}, "sha512-ALK12C6SQNNHw1enXK+UO8bdyQ+jaWNQ1Af7Z3FNxeAwjYhQT7do+TRE4RASAJ3ObaS2+TJ7TXR3oz2Gzbw0PQ=="],
@@ -714,6 +713,8 @@
"@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=="],
"@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=="],
@@ -1106,8 +1107,6 @@
"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=="],
"comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="],
@@ -1560,8 +1559,6 @@
"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=="],
"hotkeys-js": ["hotkeys-js@3.13.9", "", {}, "sha512-3TRCj9u9KUH6cKo25w4KIdBfdBfNRjfUwrljCLDC2XhmPDG0SjAZFcFZekpUZFmXzfYoGhFDcdx2gX/vUVtztQ=="],
@@ -2768,7 +2765,7 @@
"widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="],
"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": ["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-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=="],
@@ -2888,6 +2885,8 @@
"@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=="],
"@types/ssh2/@types/node": ["@types/node@18.19.86", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-fifKayi175wLyKyc5qUfyENhQ1dCNI1UNjp653d8kuYcPQN5JhX3dGuP/XmvPTg/xRBn1VTLpbmi+H/Mr7tLfQ=="],
@@ -2934,8 +2933,6 @@
"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=="],
"cypress/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
@@ -3180,6 +3177,10 @@
"@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=="],
"@typescript-eslint/typescript-estree/globby/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
@@ -3200,8 +3201,6 @@
"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/send/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="],
@@ -3348,12 +3347,14 @@
"@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/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=="],
"npm-packlist/glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],

View File

@@ -43,7 +43,6 @@
"fontkit": "^2.0.4",
"handlebars": "^4.7.8",
"heic2any": "^0.0.4",
"highlight.run": "^9.14.0",
"is-base64": "^1.1.0",
"js-cookie": "^3.0.5",
"js-interpolate": "^1.3.2",
@@ -70,7 +69,7 @@
"tailwindcss": "^3.4.17",
"trpc-openapi": "^1.2.0",
"uuid": "^9.0.1",
"winston": "^3.17.0",
"winston": "^3.18.3",
"zod": "^3.24.1"
},
"devDependencies": {

View File

@@ -0,0 +1,9 @@
/*
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)
adresse String? @db.VarChar(150)
telefon String? @db.VarChar(50)
anrede String? @db.VarChar(50)
empfaenger String? @db.VarChar(100)
rolle BenutzerRolle @default(USER)
firma String?
lex_office_id String?

View File

@@ -5,12 +5,6 @@ export const createCaller = createCallerFactory({
"klimafaktoren": await import("../src/pages/api/klimafaktoren.ts"),
"postleitzahlen": await import("../src/pages/api/postleitzahlen.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/bedarfsausweis-ausstellen": await import("../src/pages/api/admin/bedarfsausweis-ausstellen.ts"),
"admin/bestellbestaetigung": await import("../src/pages/api/admin/bestellbestaetigung.ts"),
@@ -18,16 +12,25 @@ export const createCaller = createCallerFactory({
"admin/nicht-ausstellen": await import("../src/pages/api/admin/nicht-ausstellen.ts"),
"admin/registriernummer": await import("../src/pages/api/admin/registriernummer.ts"),
"admin/stornieren": await import("../src/pages/api/admin/stornieren.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": await import("../src/pages/api/geg-nachweis-wohnen/index.ts"),
"objekt": await import("../src/pages/api/objekt/index.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"),
"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"),
"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"),
"rechnung/[id]": await import("../src/pages/api/rechnung/[id].ts"),
"rechnung/anfordern": await import("../src/pages/api/rechnung/anfordern.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/self": await import("../src/pages/api/user/self.ts"),
"ticket": await import("../src/pages/api/ticket/index.ts"),
@@ -35,9 +38,6 @@ export const createCaller = createCallerFactory({
"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": 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"),
"aufnahme/[id]/bilder": await import("../src/pages/api/aufnahme/[id]/bilder.ts"),
"aufnahme/[id]": await import("../src/pages/api/aufnahme/[id]/index.ts"),

View File

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

View File

@@ -170,7 +170,6 @@ export async function benutzerSpeichern(benutzer: Partial<Benutzer>): Promise<st
email: benutzer.email,
passwort: "",
adresse: benutzer.adresse ?? null,
anrede: benutzer.anrede ?? null,
firma: benutzer.firma ?? null,
vorname: benutzer.vorname ?? 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>
{#if ausweisart==Enums.Ausweisart.VerbrauchsausweisWohnen || ausweisart === Enums.Ausweisart.GEGNachweisWohnen || ausweisart === Enums.Ausweisart.BedarfsausweisWohnen || ausweisart === Enums.Ausweisart.BedarfsausweisGewerbe}
{#if ausweisart==Enums.Ausweisart.VerbrauchsausweisWohnen || ausweisart === Enums.Ausweisart.GEGNachweisWohnen || ausweisart === Enums.Ausweisart.BedarfsausweisWohnen}
<option value="Einfamilienhaus">Einfamilienhaus</option>
<option value="Freistehendes Einfamilienhaus">Freistehendes Einfamilienhaus</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="Winkelbungalow">Winkelbungalow</option>
{:else if ausweisart==Enums.Ausweisart.VerbrauchsausweisGewerbe || ausweisart=== Enums.Ausweisart.GEGNachweisGewerbe}
{:else if ausweisart==Enums.Ausweisart.VerbrauchsausweisGewerbe || ausweisart=== Enums.Ausweisart.GEGNachweisGewerbe || ausweisart === Enums.Ausweisart.BedarfsausweisGewerbe}
<option value="Verwaltungsgebäude (allgemein)">Verwaltungsgebäude (allgemein)</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>
@@ -186,6 +186,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
onlyUnique={true}
minlength={4}
maxlength={4}
required={true}
onFocusIn={() => {
addNotification({
message: "Info",
@@ -249,6 +250,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
minlength={4}
maxlength={4}
onlyUnique={true}
required={true}
onFocusIn={() => {
addNotification({
message: "Info",
@@ -287,6 +289,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
onlyUnique={true}
minlength={4}
maxlength={4}
required={true}
onFocusIn={() => {
addNotification({
message: "Info",

View File

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

View File

@@ -1,9 +1,8 @@
<script lang="ts">
import HelpLabel from "#components/labels/HelpLabel.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 { AufnahmeClient, ObjektClient } from "./types.js";
@@ -27,6 +26,8 @@
event.preventDefault();
}
}
let ortInput: HTMLInputElement;
</script>
<div
@@ -69,9 +70,12 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
<div class="input-noHelp">
<Inputlabel title="PLZ *"></Inputlabel>
<ZipSearch
<PlzSuche
bind:zip={objekt.plz}
bind:city={objekt.ort}
onchange={(e) => {
ortInput.dispatchEvent(new Event('change'));
}}
name="plz"
/>
</div>
@@ -85,6 +89,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
readonly={false}
required
bind:value={objekt.ort}
bind:this={ortInput}
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>
{#each arrayRange(2.1, 4.5, 0.1) as step}
<option value={step}>{step.toFixed(2)} m</option>
<option value={step} selected={ausweis.geschosshoehe === step}>{step.toFixed(2)} m</option>
{/each}
</select>

View File

@@ -44,6 +44,7 @@ const brennstoffe: [
["Fernwärme Hamburg", "kWh", 1.0, 0.33, 0.064],
["Fernwärme Erfurt", "kWh", 1.0, 0.3, 0],
["Fernwärme Neumünster", "kWh", 1.0, 0.28, 0.0133],
["Fernwärme Pforzheim", "kWh", 1.0, 0.25, 0],
["Erdgas", "kWh", 1.0, 1.1, 0.24],
["Heizöl", "kWh", 1.0, 1.1, 0.31],
["Heizöl", "l", 10.0, 1.1, 0.31],

View File

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

View File

@@ -1,10 +1,10 @@
<script lang="ts">
import { BedarfsausweisWohnenClient, GEGNachweisWohnenClient, ObjektClient, UnterlageClient, VerbrauchsausweisGewerbeClient, VerbrauchsausweisWohnenClient } from "./Ausweis/types.js";
import { BedarfsausweisWohnenClient, ObjektClient, VerbrauchsausweisGewerbeClient, VerbrauchsausweisWohnenClient } from "./Ausweis/types.js";
import { Trash, Upload } from "radix-svelte-icons";
import HelpLabel from "#components/labels/HelpLabel.svelte";
export let kategorie: string = "";
export let files: Unterlage[] = [];
export let files: (Unterlage & { preview?: boolean })[] = [];
export let max: number = Infinity;
export let min: number = 1;
export let name: string = "";
@@ -26,6 +26,8 @@
for (let i = 0; i < fileArray.length; 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) {
break;
@@ -62,7 +64,13 @@
name: file.name
})
files.push({ id, kategorie, name: file.name, mime: mimeType, aufnahme_id: null });
const placeholder = files.find((f) => f.name === fileArray[i].name && f.preview === true && f.kategorie === kategorie);
if (!placeholder) {
return;
}
placeholder!.preview = false;
placeholder!.id = id;
placeholder!.aufnahme_id = ausweis.id;
files = files;
@@ -110,6 +118,19 @@
<div class="grid grid-cols-2 gap-2">
{#each files as file, i}
{#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="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"
@@ -128,6 +149,7 @@
</div>
</div>
{/if}
{/if}
{/each}
<!-- 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 { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js";
export let images: BildClient[] = [];
export let images: (BildClient & { preview?: boolean })[] = [];
export let max: number = Infinity;
export let min: number = 1;
export let name: string = "";
@@ -35,6 +35,18 @@
<div class="grid grid-cols-2 gap-2">
{#each images as image, i}
{#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">
<img
src="/bilder/{image.id}.jpg"
@@ -51,24 +63,15 @@
>
<Trash size={20} color="#fff"></Trash>
</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>
{/if}
{/if}
{/each}
<!-- 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}
<div class="relative group">
<img
src="/placeholder.png"

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,11 @@ import { prisma } from "#lib/server/prisma.js";
import moment from "moment";
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.
// Das machen wir, indem wir die Ausweise nach Jahr und Monat sortieren und dann den letzten Eintrag nehmen.
const newestDate = await prisma.klimafaktoren.findFirst({

View File

@@ -13,7 +13,7 @@ export const BenutzerSchema = z.object({
ort: z.string().nullish(),
adresse: z.string().nullish(),
telefon: z.string().nullish(),
anrede: z.string().nullish(),
empfaenger: z.string().nullish(),
rolle: z.nativeEnum(BenutzerRolle),
firma: z.string().nullish(),
lex_office_id: z.string().nullish(),

View File

@@ -18,20 +18,6 @@ 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
// }
// });
// }
window.addEventListener("scroll", () => {
const skala = document.getElementById("skala");

View File

@@ -17,22 +17,6 @@ export interface 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>
<html lang="de">

View File

@@ -34,22 +34,6 @@ 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>
<html lang="de">
<head>

View File

@@ -45,31 +45,6 @@ const schema = JSON.stringify({
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">
<head>
<meta charset="UTF-8" />

View File

@@ -19,19 +19,6 @@ 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
// }
// })
// }
/*
window.addEventListener("scroll", (event) => {

View File

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

25
src/lib/logger.ts Normal file
View File

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

View File

@@ -0,0 +1,45 @@
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 || "",
},
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 || "",
},
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,31 +1,30 @@
import { BASE_URI } from "#lib/constants.js";
import { transport } from "#lib/mail.js";
import {
Benutzer,
} from "#lib/client/prisma.js";
import { encodeToken } from "#lib/auth/token.js";
import { TokenType } from "#lib/auth/types.js";
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({
from: `"IBCornelsen" <info@online-energieausweis.org>`,
to: user.email,
subject: `Ihre Registrierung bei IBCornelsen`,
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>
Um Ihre Registrierung abzuschließen, klicken Sie bitte auf den folgenden Link, um Ihre E-Mail-Adresse zu bestätigen:<br><br>
Nachfolgend finden Sie Ihre Zugangsdaten:<br><br>
E-Mail: ${user.email}<br>
Passwort: ${passwort}<br><br>
<a href="${BASE_URI}/auth/verify?t=${verificationJwt}">E-Mail-Adresse bestätigen</a><br></p>
Aus Sicherheitsgründen empfehlen wir Ihnen, Ihr Passwort nach dem ersten Login zu ändern.<br><br>
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>
Mit freundlichen Grüßen,
<br>

View File

@@ -7,7 +7,7 @@ export async function sendAusweisGespeichertMail(user: Benutzer, ausweis_id: str
from: `"IBCornelsen" <info@online-energieausweis.org>`,
to: user.email,
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>
Ihre Vorgänge und Ausweise können Sie in Ihrem Kundenkonto einsehen und bearbeiten:<br><br>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,5 @@
<script lang="ts">
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 passwort: string;
@@ -13,7 +11,8 @@
const response = await loginClient(email, passwort);
if (response === null) {
errorHidden = false;
error = true
errorMessage = "Das hat leider nicht geklappt, haben sie ihr Passwort und ihre Email Adresse richtig eingegeben?"
} else {
if (redirect) {
window.location.href = redirect;
@@ -24,7 +23,8 @@
}
}
let errorHidden = true;
let error = false;
let errorMessage = "";
</script>
<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"
name="email"
bind:value={email}
on:focus={() => (errorHidden = true)}
on:focus={() => (error = false)}
required
/>
</div>
@@ -51,20 +51,19 @@
placeholder="********"
name="passwort"
bind:value={passwort}
on:focus={() => (errorHidden = true)}
on:focus={() => (error = false)}
required
/>
</div>
{#if !errorHidden}
<div role="alert" class="alert alert-error" in:fade out:fade={{delay: 400}}>
<CrossCircled size={24} />
<span class="font-semibold">Das hat leider nicht geklappt, haben sie ihr Passwort und ihre Email Adresse richtig eingegeben?</span>
{#if error}
<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">Einloggen</button>
<div class="flex-row justify-between" style="margin-top: 10px">
<a class="link link-hover" href="/auth/signup{redirect ? `?redirect=${redirect}` : ""}">Registrieren</a>
<a class="link link-hover" href="/auth/passwort-vergessen{redirect ? `?redirect=${redirect}` : ""}"
<div class="flex 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/passwort-vergessen{redirect ? `?r=${redirect}` : ""}"
>Passwort Vergessen?</a
>
</div>

View File

@@ -1,55 +1,58 @@
<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 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 vorname: 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;
async function login(e: SubmitEvent) {
e.preventDefault()
if (passwort.length < 8) {
addNotification({
message: "Passwort muss mindestens 6 Zeichen enthalten.",
dismissable: true,
timeout: 3000,
type: "error"
})
if (email !== repeatEmail) {
error = true;
errorMessage = "Die eingegebenen Email Adressen stimmen nicht überein.";
return;
}
try {
const { id } = await api.user.PUT.fetch({
email,
passwort,
const { id, passwort } = await api.user.PUT.fetch({
vorname,
name
name, email,
adresse, plz, ort, empfaenger
})
await loginClient(email, passwort)
if (redirect) {
window.location.href = redirect
return
}
window.location.href = "/auth/login";
window.location.href = "/dashboard";
} catch (e) {
errorHidden = false;
error = true
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 errorHidden = true;
let error = false;
let errorMessage = "";
</script>
<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>
<form class="flex flex-col gap-4" on:submit={login}>
<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 mt-8" on:submit={login}>
<div class="flex flex-row gap-4 w-full">
<div class="w-1/2 flex flex-col gap-2">
<h4>Vorname</h4>
@@ -74,11 +77,51 @@
/>
</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">
<h4>Email</h4>
<input
type="text"
placeholder="Email"
placeholder="max.mustermann@email.de"
name="email"
class="input input-bordered text-base text-base-content font-medium"
bind:value={email}
@@ -87,31 +130,29 @@
/>
</div>
<div class="flex flex-col gap-2">
<h4>Passwort</h4>
<h4>Email erneut eingeben</h4>
<input
type="password"
placeholder="********"
minlength="8"
name="passwort"
type="text"
placeholder="max.mustermann@email.de"
name="email"
class="input input-bordered text-base text-base-content font-medium"
bind:value={passwort}
bind:value={repeatEmail}
on:keyup={() => (repeatEmail = repeatEmail.toLowerCase())}
required
/>
</div>
{#if !errorHidden}
<div role="alert" class="alert alert-error" in:fade out:fade={{delay: 400}}>
<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>
{#if error}
<div class="bg-red-200 p-4 rounded-lg w-full">
<p class="text-red-800">{errorMessage}</p>
</div>
{/if}
<button type="submit" class="button"
>Registrieren</button
>
<div class="flex-row justify-between" style="margin-top: 10px">
>Registrieren</button>
<div class="flex flex-row justify-between" style="margin-top: 10px">
<a class="link link-hover"
href="/auth/login{redirect ? `?redirect=${redirect}` : ""}">Einloggen</a
href="/auth/login{redirect ? `?r=${redirect}` : ""}">Einloggen</a
>
<a class="link link-hover" href="/auth/passwort-vergessen{redirect ? `?redirect=${redirect}` : ""}">Passwort Vergessen?</a>
<a class="link link-hover" href="/auth/passwort-vergessen{redirect ? `?r=${redirect}` : ""}">Passwort Vergessen?</a>
</div>
</form>
<NotificationWrapper></NotificationWrapper>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -294,7 +294,7 @@ export const GET = defineApiRoute({
if (rechnung.status === Enums.Rechnungsstatus.paid) {
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. ${
post ? "Zusätzlich haben wir Ihren Ausweis per Post verschickt" : ""
@@ -323,7 +323,7 @@ export const GET = defineApiRoute({
</p>`;
} else {
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. ${
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) {
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. ${
post ? "Zusätzlich haben wir Ihren Ausweis per Post verschickt" : ""
@@ -332,7 +332,7 @@ export const POST = defineApiRoute({
</p>`;
} else {
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. ${
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,
bcc: "info@online-energieausweis.org",
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.
<br>

View File

@@ -9,7 +9,8 @@ import { transport } from "#lib/mail.js";
export const GET = defineApiRoute({
input: z.object({
email: z.string().email()
email: z.string().email(),
redirect: z.string().optional()
}),
output: z.void(),
async fetch(input, context, transfer) {
@@ -42,7 +43,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:
https://online-energieausweis.org/auth/passwort-zuruecksetzen?t=${resetToken}
https://online-energieausweis.org/auth/passwort-zuruecksetzen?t=${resetToken}${input.redirect ? `&r=${input.redirect}` : ""}
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,6 +10,7 @@ import { PutObjectCommand } from "@aws-sdk/client-s3";
import { s3Client } from "#lib/s3.js";
import { generateIDWithPrefix } from "#lib/db.js";
import { VALID_UUID_PREFIXES } from "#lib/constants.js";
import { logger } from "#lib/logger.js";
export const PUT = defineApiRoute({
input: BildSchema.pick({
@@ -69,6 +70,7 @@ export const PUT = defineApiRoute({
id,
},
});
logger.error("Fehler beim Speichern des Bildes in S3: " + e);
// Und geben einen Fehler zurück
throw new APIError({
code: "INTERNAL_SERVER_ERROR",
@@ -109,8 +111,7 @@ export const DELETE = defineApiRoute({
});
}
} catch (e) {
console.log(e);
logger.error("Fehler beim Löschen des Bildes: " + e);
throw new APIError({
code: "INTERNAL_SERVER_ERROR",
message: "Bild konnte nicht gelöscht werden.",

View File

@@ -96,6 +96,41 @@ 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) {
return { id: rechnung.id }
}

View File

@@ -61,7 +61,7 @@ export const PUT = defineApiRoute({
if (!adapter) {
throw new APIError({
code: "BAD_REQUEST",
message: "Ungültige Ausweis UID"
message: "Ungültige Ausweis ID"
})
}
@@ -214,6 +214,41 @@ 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) {
return { id }
}

View File

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

View File

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

View File

@@ -1,10 +0,0 @@
---
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")
}
const redirect = Astro.url.searchParams.get("redirect")
const redirect = Astro.url.searchParams.get("r")
---
<MinimalLayout title="Login">

View File

@@ -5,11 +5,13 @@ import MinimalLayout from "#layouts/MinimalLayout.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) {
return Astro.redirect("/dashboard")
}
const redirect = Astro.url.searchParams.get("redirect")
---
<MinimalLayout title="Passwort Vergessen">

View File

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

View File

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

View File

@@ -1,32 +0,0 @@
---
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>
<h3 class="text-xl font-semibold text-gray-800 mb-2">Kontakt & Gebäudedaten</h3>
<ul class="list-disc list-inside text-gray-700">
<li>Name, Anrede & E-Mail des Ansprechpartners</li>
<li>Name & E-Mail des Ansprechpartners</li>
<li>Rechnungs- & Versandadresse</li>
<li>Gebäudeangaben: Wohnfläche, Baujahr, Anzahl Geschosse</li>
</ul>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -23,7 +23,7 @@ const caller = createCaller(Astro);
if (uid) {
if (!valid) {
return Astro.redirect(
`/auth/login?redirect=${Astro.url.toString()}`
`/auth/login?r=${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
nicht statt. Folgende Daten werden im Rahmen des Registrierungsprozesses erhoben:</p>
<ol>
<li>Anrede, Vorname, Name und E-Mail des Ansprechpartners</li>
<li>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>
<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
der Eingabemaske eingegeben Daten an uns übermittelt und gespeichert. Diese Daten sind:</p>
<ol>
<li>Anrede, Vorname, Name und E-Mail des Ansprechpartners</li>
<li>Vorname, Name und E-Mail des Ansprechpartners</li>
</ol> Im Zeitpunkt der Absendung der Nachricht werden zudem folgende Daten gespeichert:<ol>
<li>Die IP-Adresse des Nutzers</li>
<li>Datum und Uhrzeit der Registrierung</li>

View File

@@ -55,7 +55,7 @@ import Layout from "#layouts/Layout.astro";
<div>
<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">
<li>Anrede, Vorname, Name und E-Mail des Ansprechpartners</li>
<li>Vorname, Name und E-Mail des Ansprechpartners</li>
<li>Empfänger, E-Mail, Straße, PLZ und Ort der Rechnungsanschrift</li>
<li>Optionale Versandanschrift</li>
<li>Details zur Wohnfläche, Heizung, Baujahr usw.</li>

View File

@@ -279,3 +279,31 @@ article {
/*SIDEBAR-RIGHT*/
/*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,7 +45,6 @@ Papa.parse(file, {
email: user.email,
passwort: user.password,
adresse: user.adresse,
anrede: user.anrede,
name: user.name,
vorname: user.vorname,
ort: user.ort,

View File

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

View File

@@ -45,7 +45,14 @@ fi
echo "🧨 Alle Daten aus allen Tabellen werden gelöscht..."
# Generate and run TRUNCATE statements for all tables in the public schema
# Erst müssen wir alle Verbindungen zur Datenbank trennen
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'
DROP DATABASE IF EXISTS main;
CREATE DATABASE main WITH OWNER main ENCODING 'UTF8';