42 Commits

Author SHA1 Message Date
Moritz Utcke
dc0509cac2 Implementiert Nutzer Verifizierung
Fügt einen Mechanismus zur Nutzerverifizierung per E-Mail ein.

Nach der Registrierung wird eine E-Mail mit einem zeitbasierten Verifizierungscode versandt. Der Nutzer muss diesen Code eingeben, um sein Konto zu aktivieren.

Die Methode zur Erstellung des Codes ist zeitbasiert und ändert sich alle 15 Minuten.
2025-07-30 09:39:30 -05:00
Jens Cornelsen
056cbfa144 Fernwärme Hamburg hinzugefügt 2025-07-08 16:44:43 +02:00
Carl Mahnke
e50f54eedc Merge remote-tracking branch 'origin/UMBE' into Dev-Carl 2025-07-08 09:32:30 +02:00
Carl Mahnke
4eee179d37 Registration Email Force Lower Case 2025-07-08 09:32:08 +02:00
Robert Jagtiani
1e3b5dff60 Merge branch 'dev' into UMBE 2025-07-08 01:06:23 +02:00
Robert Jagtiani
7b894ffd00 widget 2025-07-08 00:52:22 +02:00
Robert Jagtiani
dbd725797c widget 2025-07-08 00:50:41 +02:00
Robert Jagtiani
d9b0d6ac95 widget 2025-07-08 00:33:46 +02:00
Moritz Utcke
5bb5fb9401 Datenbank Backup und Wipe 2025-06-16 15:09:09 -05:00
Moritz Utcke
c0d546fb9d Auto merge Fehler repariert. 2025-06-16 15:03:35 -05:00
Moritz Utcke
9a2359a993 Merge remote-tracking branch 'origin/dev' into dev 2025-06-16 14:43:30 -05:00
Moritz Utcke
ed20bb1c76 Automatisches löschen der Autoincrement Sequenzen 2025-06-16 14:43:25 -05:00
Carl Mahnke
fc6462c653 Refresh Token Create Logging 2025-06-16 17:19:52 +02:00
Carl Mahnke
df7d80e2e4 Versandadresse Ort Remove Read Only 2025-06-14 15:18:48 +02:00
Carl Mahnke
89e71c4641 Defaulting Versandadresse 2025-06-14 14:54:30 +02:00
Carl Mahnke
2d0e27075b Merge remote-tracking branch 'origin/dev' into Dev-Carl 2025-06-14 14:54:11 +02:00
Moritz Utcke
bd758e3793 Merge pull request #551 from IBCornelsen/staging
Auto-merge staging into main
2025-06-13 22:37:51 -04:00
Moritz Utcke
3e73203c94 dach_daemmung 2025-06-13 09:10:26 -05:00
Carl Mahnke
e9f51560f6 Rechnungsadresse ändern 2025-06-13 10:23:17 +02:00
Moritz Utcke
1e7fd7afe0 Fenster Art Float 2025-06-11 13:53:49 -05:00
Moritz Utcke
3aa3349d72 Merge branch 'main' into staging 2025-06-11 13:50:49 -05:00
Moritz Utcke
042bad25e0 Remove old check 2025-06-11 13:42:46 -05:00
Moritz Utcke
50f36dbc6c Workflow 2025-06-11 13:38:54 -05:00
Moritz Utcke
6894468e58 Merge pull request #546 from IBCornelsen/test-2
Backup Database Skript
2025-06-11 14:21:10 -04:00
Moritz Utcke
51257bbde8 Backup Database Skript 2025-06-11 13:20:29 -05:00
Moritz Utcke
5bd06fa0ef Merge remote-tracking branch 'origin/main' 2025-06-11 13:15:49 -05:00
Moritz Utcke
9a1625fa1b Enforce PR Source 2025-06-11 13:15:42 -05:00
Moritz Utcke
744c9c3f52 Merge pull request #544 from IBCornelsen/staging
test MERGE
2025-06-02 15:15:37 -04:00
Moritz Utcke
429ce4c4e5 Merge pull request #543 from IBCornelsen/UMBE
GTM
2025-06-02 15:11:22 -04:00
Robert Jagtiani
9d58502f29 GTM 2025-06-02 21:09:14 +02:00
Moritz Utcke
fcf12db850 Merge pull request #538 from IBCornelsen/staging
Merge remote-tracking branch 'origin/main' into staging
2025-06-01 17:43:51 -04:00
Moritz Utcke
4db097b544 Merge remote-tracking branch 'origin/main' into staging 2025-05-20 12:45:02 -03:00
Jens Cornelsen
4bf104fc76 Ausstellliste 2025-05-08 23:44:22 +02:00
Jens Cornelsen
7371fa8765 . 2025-05-05 14:21:15 +02:00
Jens Cornelsen
22a88599d1 Pfeilposition im PDF korrigiert 2025-05-05 14:20:26 +02:00
Jens Cornelsen
c6d7cbe661 Pfeilposition im PDF korrigiert 2025-05-05 14:12:49 +02:00
Jens Cornelsen
c67355ace7 Neue Bankverbindung 2025-05-02 17:28:15 +02:00
Moritz Utcke
ed37399c3c Merge pull request #533 from IBCornelsen/staging
Formular
2025-04-29 16:52:33 -03:00
Moritz Utcke
76943d8ef4 Merge branch 'dev' into staging 2025-04-29 16:51:47 -03:00
Moritz Utcke
ebabb8d667 Merge pull request #532 from IBCornelsen/dev
update staging
2025-04-29 15:20:05 -03:00
Moritz Utcke
a603c5d202 Update prevent-wrong-pr.yml 2025-04-29 14:16:02 -03:00
Moritz Utcke
75c4e3fa2b Merge pull request #530 from IBCornelsen/staging
Ausweis erstellen Fixes
2025-04-29 12:22:17 -03:00
30 changed files with 1156 additions and 471 deletions

View File

@@ -8,6 +8,8 @@ on:
jobs:
merge:
runs-on: ubuntu-latest
outputs:
reason: ${{ steps.check.outputs.reason }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
@@ -28,9 +30,15 @@ jobs:
id: check
run: |
git fetch origin
if [ $(git rev-list --count origin/staging..origin/main) -gt 0 ]; then
COUNT=$(git rev-list --count origin/staging..origin/main)
if [ "$COUNT" -gt 0 ]; then
echo "reason=ok" >> $GITHUB_OUTPUT
echo "❌ Staging is behind main and requires manual merging."
exit 1
elif [ "$COUNT" -eq 0 ]; then
echo "reason=identical" >> $GITHUB_OUTPUT
echo "✅ Staging and main are identical. Nothing to do."
exit 42
fi
- name: Create PR from staging to main
@@ -53,7 +61,7 @@ jobs:
notify_failure:
needs: merge
if: failure()
if: failure() && needs.merge.outputs.reason != 'identical'
runs-on: ubuntu-latest
steps:
- name: Send Discord notification on failure

18
.github/workflows/enforce-pr-source.yml vendored Normal file
View File

@@ -0,0 +1,18 @@
name: Enforce PR Source
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches:
- main
jobs:
check-pr-source:
runs-on: ubuntu-latest
steps:
- name: Enforce only staging → main
run: |
if [[ "${{ github.head_ref }}" != "staging" ]]; then
echo "ERROR: Only 'staging' branch may create PRs into 'main'!"
exit 1
fi

View File

@@ -59,7 +59,7 @@ 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 start bun --name "update-dwd-klimafaktoren-cron" --no-autorestart --cron "0 12 28 * *" -- src/cronjobs/update-dwd-klimafaktoren.ts
prod: install-dependencies prisma-studio backup-database-cronjob update-dwd-klimafaktoren-cron
bun run build

View File

@@ -13,13 +13,17 @@ export AWS_RESPONSE_CHECKSUM_VALIDATION=when_required
# Den Key dafür findet man auf https://dcd.ionos.com/latest/?lang=en#/key-management
docker exec -t online-energieausweis-database-1 pg_dump --data-only -U main main | brotli --best > $FILE_NAME
aws s3 cp $FILE_NAME s3://ibc-db-backup/ --profile ionos --endpoint-url https://s3-eu-central-1.ionoscloud.com --storage-class STANDARD
aws s3 cp $FILE_NAME s3://ibc-db-backup/ --profile ionos --endpoint-url https://s3.eu-central-3.ionoscloud.com --storage-class STANDARD
echo "Uploaded $FILE_NAME"
docker exec -t online-energieausweis-database-1 pg_dumpall -c -U main | brotli --best > $FILE_NAME_COMPLETE
aws s3 cp $FILE_NAME_COMPLETE s3://ibc-db-backup/ --profile ionos --endpoint-url https://s3-eu-central-1.ionoscloud.com --storage-class STANDARD
<<<<<<< HEAD
aws s3 cp $FILE_NAME_COMPLETE s3://ibc-db-backup/ --profile ionos --endpoint-url https://s3-eu-central-3.ionoscloud.com --storage-class STANDARD
=======
aws s3 cp $FILE_NAME_COMPLETE s3://ibc-db-backup/ --profile ionos --endpoint-url https://s3.eu-central-3.ionoscloud.com --storage-class STANDARD
>>>>>>> dev
echo "Uploaded $FILE_NAME_COMPLETE"

View File

@@ -1,4 +1,5 @@
version: '3'
name: database
services:
database:
build: ./

View File

@@ -41,19 +41,19 @@ model BedarfsausweisWohnen {
volumen Float?
dicht Boolean?
fenster_flaeche_1 Float?
fenster_art_1 String?
fenster_art_1 Float?
fenster_flaeche_2 Float?
fenster_art_2 String?
fenster_art_2 Float?
dachfenster_flaeche Float?
dachfenster_art String?
dachfenster_art Float?
haustuer_flaeche Float?
haustuer_art String?
haustuer_art Float?
dach_bauart String? @db.VarChar
decke_bauart String? @db.VarChar
dach_daemmung String?
decke_daemmung String?
aussenwand_daemmung String?
boden_daemmung String?
dach_daemmung Float?
decke_daemmung Float?
aussenwand_daemmung Float?
boden_daemmung Float?
aussenwand_bauart String? @db.VarChar
boden_bauart String? @db.VarChar
warmwasser_verteilung String? @db.VarChar

View File

@@ -2,13 +2,18 @@
# === Configuration ===
BUCKET_NAME="ibc-db-backup"
ENDPOINT_URL="https://s3-eu-central-1.ionoscloud.com"
LOCAL_DOWNLOAD_DIR="./" # Where to save the file
ENDPOINT_URL="https://s3.eu-central-3.ionoscloud.com"
LOCAL_DOWNLOAD_DIR="./"
# === Use filename from argument if provided ===
if [ -n "$1" ]; then
LATEST_FILE="$1"
else
echo "📡 No filename provided, fetching latest..."
# === Get latest file from IONOS S3 bucket ===
LATEST_FILE=$(aws s3api list-objects-v2 \
LATEST_FILE=$(aws --profile ionos s3api list-objects-v2 \
--bucket "$BUCKET_NAME" \
--prefix "data-dump" \
--prefix "full-dump" \
--endpoint-url "$ENDPOINT_URL" \
--query 'Contents | sort_by(@, &LastModified) | [-1].Key' \
--output text)
@@ -18,12 +23,13 @@ if [ "$LATEST_FILE" == "None" ] || [ -z "$LATEST_FILE" ]; then
echo "❌ No matching .sql.br file found."
exit 1
fi
fi
FILENAME=$(basename "$LATEST_FILE")
SQL_FILE="${FILENAME%.br}" # Remove .br suffix
echo "📥 Downloading $LATEST_FILE"
aws s3 cp "s3://$BUCKET_NAME/$LATEST_FILE" "$LOCAL_DOWNLOAD_DIR" \
aws --profile ionos s3 cp "s3://$BUCKET_NAME/$LATEST_FILE" "$LOCAL_DOWNLOAD_DIR" \
--endpoint-url "$ENDPOINT_URL"
# === Decompress with Brotli ===
@@ -31,8 +37,8 @@ echo "🗜️ Decompressing $FILENAME -> $SQL_FILE"
brotli -d "$FILENAME"
# === Import into Postgres inside Docker ===
echo "🐘 Importing into PostgreSQL (online-energieausweis-database-1:main)"
docker exec -i "online-energieausweis-database-1" env PGPASSWORD="hHMP8cd^N3SnzGRR" \
echo "🐘 Importing into PostgreSQL (database:main)"
docker exec -i "database" env PGPASSWORD="hHMP8cd^N3SnzGRR" \
psql -U "main" -d "main" < "$SQL_FILE"
echo "✅ Import complete."

View File

@@ -5,6 +5,7 @@ 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"),
"aufnahme": await import("../src/pages/api/aufnahme/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"),
@@ -12,13 +13,15 @@ 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"),
"aufnahme": await import("../src/pages/api/aufnahme/index.ts"),
"ausweise": await import("../src/pages/api/ausweise/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"),
"ausweise": await import("../src/pages/api/ausweise/index.ts"),
"auth/verification-code": await import("../src/pages/api/auth/verification-code.ts"),
"aufnahme": await import("../src/pages/api/aufnahme/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"),
"ausweise": await import("../src/pages/api/ausweise/index.ts"),
"bedarfsausweis-wohnen/[id]": await import("../src/pages/api/bedarfsausweis-wohnen/[id].ts"),
"bedarfsausweis-wohnen": await import("../src/pages/api/bedarfsausweis-wohnen/index.ts"),
"bilder/[id]": await import("../src/pages/api/bilder/[id].ts"),
@@ -31,15 +34,14 @@ export const createCaller = createCallerFactory({
"rechnung/anfordern": await import("../src/pages/api/rechnung/anfordern.ts"),
"rechnung": await import("../src/pages/api/rechnung/index.ts"),
"ticket": await import("../src/pages/api/ticket/index.ts"),
"user": await import("../src/pages/api/user/index.ts"),
"user/self": await import("../src/pages/api/user/self.ts"),
"verbrauchsausweis-wohnen/[id]": await import("../src/pages/api/verbrauchsausweis-wohnen/[id].ts"),
"verbrauchsausweis-wohnen": await import("../src/pages/api/verbrauchsausweis-wohnen/index.ts"),
"verbrauchsausweis-gewerbe/[id]": await import("../src/pages/api/verbrauchsausweis-gewerbe/[id].ts"),
"verbrauchsausweis-gewerbe": await import("../src/pages/api/verbrauchsausweis-gewerbe/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"),
"objekt/[id]": await import("../src/pages/api/objekt/[id]/index.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]/unterlagen": await import("../src/pages/api/aufnahme/[id]/unterlagen.ts"),
"objekt/[id]": await import("../src/pages/api/objekt/[id]/index.ts"),
})

View File

@@ -1,7 +1,6 @@
<script lang="ts">
import { BedarfsausweisWohnen, Enums, Rechnung, VerbrauchsausweisGewerbe, VerbrauchsausweisWohnen } from "#lib/server/prisma.js";
import moment from "moment";
import { format } from "sharp";
export let bestellungen: (Rechnung & {
verbrauchsausweis_wohnen: VerbrauchsausweisWohnen | null,

View File

@@ -41,6 +41,7 @@ const brennstoffe: [
["Fernwärme KWK EB", "kWh", 1.0, 0.0, 0.04],
["Fernwärme HKW FB", "kWh", 1.0, 1.3, 0.4],
["Fernwärme HKW EB", "kWh", 1.0, 0.1, 0.06],
["Fernwärme Hamburg", "kWh", 1.0, 0.33, 0.064],
["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

@@ -257,6 +257,12 @@
let bedarfsausweisFileInput: HTMLInputElement;
let bedarfsausweisAdditionalInput: HTMLInputElement;
let dropdownOpen = false;
function toggleDropdown() {
dropdownOpen = !dropdownOpen;
}
</script>
<div class="relative bg-base-200 border border-base-300 rounded-lg p-4 mx-2">
@@ -273,30 +279,41 @@
{/if}
<div class="card-body">
<div
class="flex justify-end mb-2 dropdown dropdown-bottom absolute top-4 right-4"
class="mb-2 dropdown dropdown-bottom absolute top-4 right-4 bg-base-100"
>
<button class="rounded-full p-2.5 hover:bg-base-100">
<button class="rounded-full p-2.5 hover:bg-base-100" on:click={toggleDropdown}>
<DotsVertical size={15} />
</button>
</div>
{#if dropdownOpen}
<ul
tabindex="-1"
class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-64 gap-2"
class="z-[1] menu p-2 shadow bg-base-100 rounded-box w-64 gap-2 border"
>
<li>
<button on:click={ausweisStornieren}
><CrossCircled size={15} />Ausweis Stornieren</button
<!-- <div>
<button on:click={ausweisStornieren} class="flex items-center gap-2" disabled
><CrossCircled size={15} />Stornieren</button
>
</li>
<li>
<button><Pencil2 size={15} /> Als Vorlage benutzen</button>
</li>
<li>
<button on:click={() => hilfeModal.showModal()}
</div>
<div>
<button class="flex items-center gap-2" disabled
><Pencil2 size={15} /> Als Vorlage</button>
</div>
<div>
<button on:click={() => hilfeModal.showModal()} class="flex items-center gap-2" disabled
><QuestionMarkCircled size={15} /> Hilfe Erhalten</button
>
</li>
</ul>
</div> -->
{#if ausweis.bestellt && rechnung}
<div>
<a href="/dashboard/rechnung/aendern?rechnungid={rechnung.id}"><button> Adresse ändern</button
></a>
</div>
{/if}
</ul>
{/if}
<div class="flex flex-row flex-wrap items-center gap-2">
{#if ausweis.ausgestellt}
<span class="bg-green-600 px-2 py-0.5 text-sm font-semibold rounded-lg text-white">Ausgestellt</span>

View File

@@ -1,9 +1,7 @@
<script lang="ts">
import { fade } from "svelte/transition";
import WidgetCardTemplate from "#components/widgets/immowelt/WidgetCardTemplate.svelte";
import { PRICES } from "#lib/constants.js";
import { Enums } from "#lib/client/prisma";
import { Enums } from "#lib/client/prisma.js";
let ausnahme: boolean = false;
let oneBOX: boolean = false;
@@ -25,7 +23,9 @@
$: ausnahme =
leerStand === "mehr als 30" ||
heizungsAlter === "< 3" ||
(baujahr === "vor 1978" && einheiten === "bis 4 Wohneinheiten" && sanierungsstatus === "unsaniert");
(baujahr === "vor 1978" &&
einheiten === "bis 4 Wohneinheiten" &&
sanierungsstatus === "unsaniert");
$: isTwoBoxReason = twoBoxReason.includes(anlass);
@@ -37,36 +37,32 @@ $: oneBOX =
(gebaeudetyp === "Gewerbegebäude" && leerStand === "mehr als 30");
$: threeBOX =
(ausnahme && gebaeudetyp === "Mischgebäude" && isTwoBoxReason && leerStand !== "mehr als 30");
$: standardXL =
(einheiten === "mehr als 4 Wohneinheiten")
ausnahme &&
gebaeudetyp === "Mischgebäude" &&
isTwoBoxReason &&
leerStand !== "mehr als 30";
$: standardXL = einheiten === "mehr als 4 Wohneinheiten";
</script>
<div id="IBC_app">
<input id="recode" type="hidden" value="widgetvorlage" />
<div id="OEA_input">
<div id="firstrow" class="firstrow"
<div
id="firstrow"
class="firstrow"
class:sm:grid-cols-3={isTwoBoxReason}
class:sm:grid-cols-2={!isTwoBoxReason}>
class:sm:grid-cols-2={!isTwoBoxReason}
>
<div class="auswahl">
<div class="titel">Anlass</div>
<select
id="anlass"
class="selectfeld"
bind:value={anlass}>
<select id="anlass" class="selectfeld" bind:value={anlass}>
>
<option selected disabled>bitte auswählen</option>
<option value="Vermietung/Verkauf">Vermietung/Verkauf</option>
<option value="Vermietung/Verkauf"
>Vermietung/Verkauf</option
>
<option value="Modernisierung">Modernisierung</option>
<option value="Neubau">Neubau</option>
<option value="Erweiterung">Erweiterung</option>
@@ -77,9 +73,7 @@ $: standardXL =
<div class="auswahl">
<div class="titel">Gebäudetyp</div>
<select
class="selectfeld"
bind:value={gebaeudetyp}>
<select class="selectfeld" bind:value={gebaeudetyp}>
>
<option selected disabled>bitte auswählen</option>
<option value="Einfamilienhaus">Einfamilienhaus</option>
@@ -93,10 +87,7 @@ $: standardXL =
{#if isTwoBoxReason}
<div class="auswahl">
<div class="titel">Sanierungsstand</div>
<select
class="selectfeld"
bind:value={sanierungsstatus}>
<select class="selectfeld" bind:value={sanierungsstatus}>
>
<option selected disabled>bitte auswählen</option>
<option value="saniert">saniert</option>
@@ -104,7 +95,6 @@ $: standardXL =
</select>
</div>
{/if}
</div>
{#if isTwoBoxReason}
@@ -115,7 +105,6 @@ $: standardXL =
id="baujahr"
class="selectfeld"
bind:value={baujahr}
>
<option selected disabled>bitte auswählen</option>
<option value="vor 1978">vor 1978</option>
@@ -125,10 +114,7 @@ $: standardXL =
<div class="auswahl">
<div class="titel">Heizungsalter</div>
<select
class="selectfeld"
bind:value={heizungsAlter}
>
<select class="selectfeld" bind:value={heizungsAlter}>
<option selected disabled>bitte auswählen</option>
<option value="< 3">jünger als 3 Jahre</option>
<option value=">= 3">3 Jahre oder älter</option>
@@ -137,10 +123,7 @@ $: standardXL =
<div class="auswahl">
<div class="titel">Wohneinheiten</div>
<select
class="selectfeld"
bind:value={einheiten}
>
<select class="selectfeld" bind:value={einheiten}>
<option selected disabled>bitte auswählen</option>
<option value="bis 4 Wohneinheiten"
>bis 4 Wohneinheiten</option
@@ -153,10 +136,7 @@ $: standardXL =
<div class="OEA_item4">
<div class="titel">Leerstand</div>
<select
class="selectfeld ausnahmen"
bind:value={leerStand}
>
<select class="selectfeld ausnahmen" bind:value={leerStand}>
<option selected disabled>bitte auswählen</option>
<option value="bis 30">bis 30%</option>
<option value="mehr als 30">mehr als 30%</option>
@@ -165,20 +145,34 @@ $: standardXL =
</div>
{/if}
<div id="thirdrow" class="thirdrow"
<div
id="thirdrow"
class="thirdrow"
class:grid-cols-1={oneBOX}
class:md:grid-cols-6={threeBOX}
class:md:grid-cols-4={!oneBOX && !threeBOX}
>
{#if isTwoBoxReason && (gebaeudetyp != "Gewerbegebäude") && (ausnahme === false)}
{#if isTwoBoxReason && gebaeudetyp != "Gewerbegebäude" && ausnahme === false}
<WidgetCardTemplate
name="Verbrauchsausweis Wohngebäude"
price={PRICES.VerbrauchsausweisWohnen[Enums.AusweisTyp.Standard] + (standardXL ? 10 : 0)}
src={'https://online-energieausweis.org/images/partner/'+partner+'/wohngebaeude.svg'}
price={PRICES.VerbrauchsausweisWohnen[
standardXL
? Enums.AusweisTyp.standardXL
: Enums.AusweisTyp.Standard
]}
price1={PRICES.VerbrauchsausweisWohnen[
standardXL
? Enums.AusweisTyp.BeratungXL
: Enums.AusweisTyp.Beratung
]}
price2={PRICES.VerbrauchsausweisWohnen[
standardXL
? Enums.AusweisTyp.OfflineXL
: Enums.AusweisTyp.Offline
]}
src={"https://online-energieausweis.org/images/partner/" +
partner +
"/wohngebaeude.svg"}
alt="Wohnhaus Verbrauchsausweis"
variant="einfach"
empfehlung="nein"
@@ -186,22 +180,40 @@ $: standardXL =
services={[
["3&nbsp;Jahresverbräuche der Heizung benötigt.", true],
["Zulässig bei Vermietung oder Verkauf.", true],
["Unzulässig bei unsanierten Gebäuden vor 1978.", false],
[
"Unzulässig bei unsanierten Gebäuden vor 1978.",
false,
],
["Ungenau durch individuelles Heizverhalten.", false],
["Wird nicht immer bei den Banken akzeptiert.", false]
["Wird nicht immer bei den Banken akzeptiert.", false],
]}
href_buy={"https://online-energieausweis.org/"+partner+"/energieausweis-erstellen/verbrauchsausweis-wohngebaeude/"}{standardXL ? '?ausweistyp=standardXL' : ''}
href_overview={"https://online-energieausweis.org/"+partner+"/energieausweis-erstellen/verbrauchsausweis-wohngebaeude/produkt-uebersicht/"}{standardXL ? '?ausweistyp=standardXL' : ''}
href_buy1={`https://online-energieausweis.org/${partner}/energieausweis-erstellen/verbrauchsausweis-wohngebaeude/${standardXL ? "?ausweistyp=standardXL" : ""}`}
href_buy2={`https://online-energieausweis.org/${partner}/energieausweis-erstellen/verbrauchsausweis-wohngebaeude/${standardXL ? "?ausweistyp=BeratungXL" : "?ausweistyp=Beratung"}`}
href_buy3={`https://online-energieausweis.org/${partner}/energieausweis-erstellen/verbrauchsausweis-wohngebaeude/${standardXL ? "?ausweistyp=OfflineXL" : "?ausweistyp=Offline"}`}
></WidgetCardTemplate>
{/if}
{#if isTwoBoxReason && (gebaeudetyp != "Gewerbegebäude")}
{#if isTwoBoxReason && gebaeudetyp != "Gewerbegebäude"}
<WidgetCardTemplate
name="Bedarfsausweis Wohngebäude"
price={PRICES.BedarfsausweisWohnen[Enums.AusweisTyp.Standard] + (standardXL ? 25 : 0)}
src={'https://online-energieausweis.org/images/partner/'+partner+'/wohngebaeude.svg'}
price={PRICES.BedarfsausweisWohnen[
standardXL
? Enums.AusweisTyp.standardXL
: Enums.AusweisTyp.Standard
]}
price1={PRICES.BedarfsausweisWohnen[
standardXL
? Enums.AusweisTyp.BeratungXL
: Enums.AusweisTyp.Beratung
]}
price2={PRICES.BedarfsausweisWohnen[
standardXL
? Enums.AusweisTyp.OfflineXL
: Enums.AusweisTyp.Offline
]}
src={"https://online-energieausweis.org/images/partner/" +
partner +
"/wohngebaeude.svg"}
alt="Wohnhaus Bedarfsausweis"
variant="fundiert"
empfehlung="ja"
@@ -211,145 +223,240 @@ $: standardXL =
["Für Vermietung, Verkauf und Finanzierung.", true],
["Zulässig auch für unsanierte Objekte.", true],
["Kann als Grundlage für den ISFP dienen.", true],
["Objektivere Berechnungsmethode nach DIN 18599.", true],
[
"Objektivere Berechnungsmethode nach DIN 18599.",
true,
],
]}
href_buy={"https://online-energieausweis.org/"+partner+"/energieausweis-erstellen/bedarfsausweis-wohngebaeude/"}{standardXL ? '?ausweistyp=standardXL' : ''}
href_overview={"https://online-energieausweis.org/"+partner+"/energieausweis-erstellen/bedarfsausweis-wohngebaeude/produkt-uebersicht"}{standardXL ? '?ausweistyp=standardXL' : ''}
href_buy1={`https://online-energieausweis.org/${partner}/energieausweis-erstellen/bedarfsausweis-wohngebaeude/${standardXL ? "?ausweistyp=standardXL" : ""}`}
href_buy2={`https://online-energieausweis.org/${partner}/energieausweis-erstellen/bedarfsausweis-wohngebaeude/${standardXL ? "?ausweistyp=BeratungXL" : "?ausweistyp=Beratung"}`}
href_buy3={`https://online-energieausweis.org/${partner}/energieausweis-erstellen/bedarfsausweis-wohngebaeude/${standardXL ? "?ausweistyp=OfflineXL" : "?ausweistyp=Offline"}`}
></WidgetCardTemplate>
{/if}
{#if isTwoBoxReason && isGewerbe && (leerStand != "mehr als 30")}
{#if isTwoBoxReason && isGewerbe && leerStand != "mehr als 30"}
<WidgetCardTemplate
name="Verbrauchsausweis Gewerbegebäude"
price={PRICES.VerbrauchsausweisGewerbe[Enums.AusweisTyp.Standard] + (standardXL ? 15 : 0)}
src={'https://online-energieausweis.org/images/partner/'+partner+'/gewerbegebaeude.svg'}
price={PRICES.VerbrauchsausweisGewerbe[
standardXL
? Enums.AusweisTyp.standardXL
: Enums.AusweisTyp.Standard
]}
price1={PRICES.VerbrauchsausweisGewerbe[
standardXL
? Enums.AusweisTyp.BeratungXL
: Enums.AusweisTyp.Beratung
]}
price2={PRICES.VerbrauchsausweisGewerbe[
standardXL
? Enums.AusweisTyp.OfflineXL
: Enums.AusweisTyp.Offline
]}
src={"https://online-energieausweis.org/images/partner/" +
partner +
"/gewerbegebaeude.svg"}
alt="Gewerbe Verbrauchsausweis"
variant="einfach"
empfehlung="nein"
cta="jetzt&nbsp;online erstellen"
services={[
["3&nbsp;Jahresverbräuche von Heizung Gebäudestrom&nbsp;nötig.", true],
[
"3&nbsp;Jahresverbräuche von Heizung Gebäudestrom&nbsp;nötig.",
true,
],
["Zulässig bei Vermietung oder Verkauf.", true],
["Für bauliche und energetische Maßnahmen ungeeignet.", false],
[
"Für bauliche und energetische Maßnahmen ungeeignet.",
false,
],
["Wird nicht immer bei den Banken akzeptiert.", false],
["Ungenau durch individuelles Heizverhalten", false],
]}
href_buy={"https://online-energieausweis.org/"+partner+"/energieausweis-erstellen/verbrauchsausweis-gewerbe/"}{standardXL ? '?ausweistyp=standardXL' : ''}
href_overview={"https://online-energieausweis.org/"+partner+"/energieausweis-erstellen/verbrauchsausweis-gewerbe/produkt-uebersicht/"}{standardXL ? '?ausweistyp=standardXL' : ''}
href_buy1={`https://online-energieausweis.org/${partner}/energieausweis-erstellen/verbrauchsausweis-gewerbe/${standardXL ? "?ausweistyp=standardXL" : ""}`}
href_buy2={`https://online-energieausweis.org/${partner}/energieausweis-erstellen/verbrauchsausweis-gewerbe/${standardXL ? "?ausweistyp=BeratungXL" : "?ausweistyp=Beratung"}`}
href_buy3={`https://online-energieausweis.org/${partner}/energieausweis-erstellen/verbrauchsausweis-gewerbe/${standardXL ? "?ausweistyp=OfflineXL" : "?ausweistyp=Offline"}`}
></WidgetCardTemplate>
{/if}
{#if isTwoBoxReason && isGewerbe}
<WidgetCardTemplate
name="Bedarfsausweis Gewerbegebäude"
price={PRICES.BedarfsausweisGewerbe[Enums.AusweisTyp.Standard]}
src={'https://online-energieausweis.org/images/partner/'+partner+'/gewerbegebaeude.svg'}
price={PRICES.BedarfsausweisGewerbe[
standardXL
? Enums.AusweisTyp.standardXL
: Enums.AusweisTyp.Standard
]}
price1={PRICES.BedarfsausweisGewerbe[
standardXL
? Enums.AusweisTyp.BeratungXL
: Enums.AusweisTyp.Beratung
]}
price2={PRICES.BedarfsausweisGewerbe[
standardXL
? Enums.AusweisTyp.OfflineXL
: Enums.AusweisTyp.Offline
]}
src={"https://online-energieausweis.org/images/partner/" +
partner +
"/gewerbegebaeude.svg"}
alt="Gewerbe Bedarfsausweis"
variant="fundiert"
empfehlung="ja"
cta="Angebot anfragen"
services={[
["Mehrzonenmodell nach DIN 18599.", true],
["Zulässig bei Vermietung oder Verkauf.", true],
["Grundlage für Sanierung-Varianten.", true],
["Objektiveres, besser vergleichbares Ergebnis.", true],
["Zulässig bei Leerstand oder fehlenden Verbräuchen", true],
[
"Zulässig bei Leerstand oder fehlenden Verbräuchen",
true,
],
]}
href_buy={"https://online-energieausweis.org/"+partner+"/angebot-anfragen/bedarfsausweis-gewerbe-anfragen/"}
href_overview={"https://online-energieausweis.org/"+partner+"/angebot-anfragen/bedarfsausweis-gewerbe-anfragen/produkt-uebersicht/"}
href_buy1={`https://online-energieausweis.org/${partner}/angebot-anfragen/bedarfsausweis-gewerbe-anfragen/${standardXL ? "?ausweistyp=standardXL" : ""}`}
href_buy2={`https://online-energieausweis.org/${partner}/angebot-anfragen/bedarfsausweis-gewerbe-anfragen/${standardXL ? "?ausweistyp=BeratungXL" : "?ausweistyp=Beratung"}`}
></WidgetCardTemplate>
{/if}
{#if (anlass != "bitte auswählen") && !isTwoBoxReason && (gebaeudetyp != "Gewerbegebäude")}
{#if anlass != "bitte auswählen" && !isTwoBoxReason && gebaeudetyp != "Gewerbegebäude"}
<WidgetCardTemplate
name="GEG-Nachweis Wohngebäude"
price={PRICES.GEGNachweisWohnen[Enums.AusweisTyp.Standard]}
src={'https://online-energieausweis.org/images/partner/'+partner+'/wohngebaeude.svg'}
price={PRICES.GEGNachweisWohnen[
standardXL
? Enums.AusweisTyp.standardXL
: Enums.AusweisTyp.Standard
]}
price1={PRICES.GEGNachweisWohnen[
standardXL
? Enums.AusweisTyp.BeratungXL
: Enums.AusweisTyp.Beratung
]}
price2={PRICES.GEGNachweisWohnen[
standardXL
? Enums.AusweisTyp.OfflineXL
: Enums.AusweisTyp.Offline
]}
src={"https://online-energieausweis.org/images/partner/" +
partner +
"/wohngebaeude.svg"}
alt="GEG-Nachweis-Wohnen"
variant="Bauvorlage"
empfehlung="ja"
cta="Angebot anfragen"
services={[
["Nachweis fürs Bauamt bei Neubau oder Modernisierung.", true],
["Beinhaltet die Effizienz der Bauteile und Anlagentechnik.", true],
["Erstellung des Energieausweises nach Abschluss inklusive.", true],
["Berechnung und Bilanzierung nach aktueller DIN 18599.", true],
["Zonierung und Erstellung eines 3D Gebäudemodells.", true],
[
"Nachweis fürs Bauamt bei Neubau oder Modernisierung.",
true,
],
[
"Beinhaltet die Effizienz der Bauteile und Anlagentechnik.",
true,
],
[
"Erstellung des Energieausweises nach Abschluss inklusive.",
true,
],
[
"Berechnung und Bilanzierung nach aktueller DIN 18599.",
true,
],
[
"Zonierung und Erstellung eines 3D Gebäudemodells.",
true,
],
]}
href_buy={"https://online-energieausweis.org/"+partner+"/angebot-anfragen/geg-nachweis-wohnen-anfragen/"}
href_overview={"https://online-energieausweis.org/"+partner+"/angebot-anfragen/geg-nachweis-wohnen-anfragen/produkt-uebersicht/"}
href_buy1={`https://online-energieausweis.org/${partner}/angebot-anfragen/geg-nachweis-wohnen-anfragen/${standardXL ? "?ausweistyp=standardXL" : ""}`}
href_buy2={`https://online-energieausweis.org/${partner}/angebot-anfragen/geg-nachweis-wohnen-anfragen/${standardXL ? "?ausweistyp=BeratungXL" : "?ausweistyp=Beratung"}`}
></WidgetCardTemplate>
{/if}
{#if (anlass != "bitte auswählen") && !isTwoBoxReason && isGewerbe}
{#if anlass != "bitte auswählen" && !isTwoBoxReason && isGewerbe}
<WidgetCardTemplate
name="GEG-Nachweis Gewerbegebäude"
price={PRICES.GEGNachweisGewerbe[Enums.AusweisTyp.Standard]}
src={'https://online-energieausweis.org/images/partner/'+partner+'/gewerbegebaeude.svg'}
price={PRICES.GEGNachweisGewerbe[
standardXL
? Enums.AusweisTyp.standardXL
: Enums.AusweisTyp.Standard
]}
price1={PRICES.GEGNachweisGewerbe[
standardXL
? Enums.AusweisTyp.BeratungXL
: Enums.AusweisTyp.Beratung
]}
price2={PRICES.GEGNachweisGewerbe[
standardXL
? Enums.AusweisTyp.OfflineXL
: Enums.AusweisTyp.Offline
]}
src={"https://online-energieausweis.org/images/partner/" +
partner +
"/gewerbegebaeude.svg"}
alt="GEG-Nachweis-Gewerbe"
variant="Bauvorlage"
empfehlung="ja"
cta="Angebot anfragen"
services={[
["Nachweis fürs Bauamt bei Neubau oder Modernisierung.", true],
["Beinhaltet die Effizienz der Bauteile und Anlagentechnik.", true],
["Erstellung des Energieausweises nach Abschluss inklusive.", true],
["Berechnung und Bilanzierung nach aktueller DIN 18599.", true],
["Mehrzonenmodell inkl. Erstellung eines 3D Gebäudemodells.", true],
[
"Nachweis fürs Bauamt bei Neubau oder Modernisierung.",
true,
],
[
"Beinhaltet die Effizienz der Bauteile und Anlagentechnik.",
true,
],
[
"Erstellung des Energieausweises nach Abschluss inklusive.",
true,
],
[
"Berechnung und Bilanzierung nach aktueller DIN 18599.",
true,
],
[
"Mehrzonenmodell inkl. Erstellung eines 3D Gebäudemodells.",
true,
],
]}
href_buy={"https://online-energieausweis.org/"+partner+"/angebot-anfragen/geg-nachweis-gewerbe-anfragen/"}
href_overview={"https://online-energieausweis.org/"+partner+"/angebot-anfragen/geg-nachweis-gewerbe-anfragen/produkt-uebersicht/"}
href_buy1={`https://online-energieausweis.org/${partner}/angebot-anfragen/geg-nachweis-gewerbe-anfragen/${standardXL ? "?ausweistyp=standardXL" : ""}`}
href_buy2={`https://online-energieausweis.org/${partner}/angebot-anfragen/geg-nachweis-gewerbe-anfragen/${standardXL ? "?ausweistyp=BeratungXL" : "?ausweistyp=Beratung"}`}
></WidgetCardTemplate>
{/if}
</div>
</div>
</div>
<style lang="postcss">
#IBC_app {
@font-face {
font-family: "immo Sans";
src: url('/fonts/Immo-Sans/immoSans-Regular.eot');
src: url('/fonts/Immo-Sans/immoSans-Regular.eot?#iefix') format('embedded-opentype'),
url('/fonts/Immo-Sans/immoSans-Regular.woff2') format('woff2'),
url('/fonts/Immo-Sans/immoSans-Regular.woff') format('woff');
src: url("/fonts/Immo-Sans/immoSans-Regular.eot");
src:
url("/fonts/Immo-Sans/immoSans-Regular.eot?#iefix")
format("embedded-opentype"),
url("/fonts/Immo-Sans/immoSans-Regular.woff2") format("woff2"),
url("/fonts/Immo-Sans/immoSans-Regular.woff") format("woff");
font-style: normal;
font-weight: 400;
}
@font-face {
font-family: "immo Sans Bold";
src: url('/fonts/Immo-Sans/immoSans-Bold.eot');
src: url('/fonts/Immo-Sans/immoSans-Bold.eot?#iefix') format('embedded-opentype'), url('../../Fonts/Immo-Sans/immoSans-Bold.woff2') format('woff2'), url('../../Fonts/Immo-Sans/immoSans-Bold.woff') format('woff');
src: url("/fonts/Immo-Sans/immoSans-Bold.eot");
src:
url("/fonts/Immo-Sans/immoSans-Bold.eot?#iefix")
format("embedded-opentype"),
url("../../Fonts/Immo-Sans/immoSans-Bold.woff2") format("woff2"),
url("../../Fonts/Immo-Sans/immoSans-Bold.woff") format("woff");
font-style: normal;
font-weight: 700;
}
@font-face {
font-family: 'Antique Olive Compact bold';
font-family: "Antique Olive Compact bold";
font-weight: 700;
font-style: normal;
font-display: swap;
src: url("/fonts/Antique Olive Std Compact.woff2") format('woff2');
src: url("/fonts/Antique Olive Std Compact.woff2") format("woff2");
}
@apply min-w-[320px] max-w-[1920px] p-[4px]
@@ -357,35 +464,42 @@ $: standardXL =
font-family: "immo Sans";
select option {
font-family: "immo Sans",sans-serif;}
font-family: "immo Sans", sans-serif;
}
.firstrow{@apply grid grid-cols-1 gap-x-4 gap-y-2
.firstrow {
@apply grid grid-cols-1 gap-x-4 gap-y-2
sm:grid-cols-2 sm:gap-x-4 sm:gap-y-2;
.titel{@apply text-black font-bold bg-[#ffcc00] px-2 py-1 rounded-[0.25rem];}
.selectfeld{@apply w-full px-2 py-1 min-h-[38px] ring-1 ring-black/15}
.titel {
@apply text-black font-bold bg-[#ffcc00] px-2 py-1 rounded-[0.25rem];
}
.secondrow{@apply grid grid-cols-2 gap-x-4 gap-y-2 mt-4
.selectfeld {
@apply w-full px-2 py-1 min-h-[38px] ring-1 ring-black/15;
}
}
.secondrow {
@apply grid grid-cols-2 gap-x-4 gap-y-2 mt-4
sm:grid-cols-4 sm:gap-x-4 sm:gap-y-2;
.titel{@apply text-black font-bold bg-[#cccccc] px-2 py-1 rounded-[0.25rem];}
.selectfeld{@apply w-full px-2 py-1 min-h-[38px] ring-1 ring-black/15}
.titel {
@apply text-black font-bold bg-[#cccccc] px-2 py-1 rounded-[0.25rem];
}
#OEA_input{@apply grid}
.selectfeld {
@apply w-full px-2 py-1 min-h-[38px] ring-1 ring-black/15;
}
}
.thirdrow{@apply grid grid-cols-1 gap-x-4 gap-y-2 col-start-1
#OEA_input {
@apply grid;
}
.thirdrow {
@apply grid grid-cols-1 gap-x-4 gap-y-2 col-start-1
md:grid-cols-4 md:gap-x-4 md:gap-y-2;
}
}
</style>

View File

@@ -1,104 +1,140 @@
<script lang="ts">
import { fade } from "svelte/transition";
export let price: number;
export let price1: number;
export let price2: number;
export let name: string;
export let variant: string;
export let services: [string, boolean][];
export let href_buy: string;
export let href_overview: string;
export let href_buy1: string;
export let href_buy2: string;
export let href_buy3: string = "";
export let src: string;
export let alt: string;
export let empfehlung: string;
export let cta: string;
</script>
<div
class="produktbox"
transition:fade={{ duration: 0 }}
>
<div class="produktbox" transition:fade={{ duration: 0 }}>
{#if empfehlung === "ja"}
<div class="empfehlung" aria-label="Empfohlenes Produkt">Empfehlung</div>
<div class="empfehlung" aria-label="Empfohlenes Produkt">
Empfehlung
</div>
{/if}
<h2 class="titel sm:mb-2">{name}</h2>
<div class="sumCent">
<div class="variante">{variant}</div>
<img
class="image"
{src}
{alt}
/>
<img class="image" {src} {alt} />
<div class="">
<p class="price">
ab {price}
</p>
</div>
</div>
<div class="sumCent buttoncols">
<a
href={href_buy}
class="buttoncol"
aria-label="Jetzt {name} kaufen"
target="_blank"
>{cta}
</a>
<a
href={href_overview}
class="buttoncol"
aria-label="{name} Produkt-Übersicht"
target="_blank"
>Produkt-Übersicht</a
>
</div>
<hr class="col-span-2 w-full md:w-[50%] md:m-auto bg-[#ffcc00] h-[2px]" />
<div class="sumRows forServices">
{#each services as [service, check]}
<div class="services">
<span>{@html service}</span>
<span class={check ? "check" : "check-no"}>{check ? "✔" : "✘"}</span>
<span class={check ? "check" : "check-no"}
>{check ? "✔" : "✘"}</span
>
</div>
{/each}
</div>
<hr class="col-span-2 w-full md:w-[50%] md:m-auto bg-[#ffcc00] h-[2px]" />
<div
class="sumCent buttoncols"
class:md:grid-cols-3={href_buy3}
class:md:grid-cols-2={!href_buy3}
>
<a
href={href_buy1}
class="buttoncol"
aria-label="Jetzt {name} kaufen"
target="_blank"
>mach selbst (<span class="inside-price">{price}</span>&nbsp;€)
</a>
<a
href={href_buy2}
class="buttoncol"
aria-label="{name} Produkt-Übersicht"
target="_blank"
>wir helfen (<span class="inside-price">{price1}</span>&nbsp;€)
</a>
{#if href_buy3}
<a
href={href_buy3}
class="buttoncol"
aria-label="{name} Produkt-Übersicht"
target="_blank"
>wir machen (<span class="inside-price">{price2}</span>&nbsp;€)
</a>
{/if}
</div>
</div>
<style lang="postcss">
.produktbox{@apply grid grid-cols-subgrid col-span-2 grid-rows-subgrid row-span-3 md:row-span-12 bg-black/5 rounded-lg
.produktbox {
@apply grid grid-cols-subgrid col-span-2 grid-rows-subgrid row-span-3 md:row-span-12 bg-black/5 rounded-lg
px-2 py-2 mt-5;
box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.25);
.sumCent{@apply justify-self-center col-span-2}
.sumRows{@apply hidden sm:grid grid-rows-subgrid row-span-5 items-center}
.forServices{@apply grid-rows-subgrid row-span-5 items-center col-span-2 justify-center px-6}
.sumCent {
@apply justify-self-center col-span-2;
}
.sumRows {
@apply hidden sm:grid grid-rows-subgrid row-span-5 items-center;
}
.forServices {
@apply grid-rows-subgrid row-span-5 items-center col-span-2 justify-center px-6;
}
.image{@apply w-[75%] mx-auto
md:w-[75%] md:pl-0}
.image {
@apply w-[75%] mx-auto
md:w-[75%] md:pl-0;
}
.buttoncols{@apply grid grid-cols-1 gap-x-4 w-full mb-4
md:grid-cols-2 md:w-[auto]}
.buttoncols {
@apply grid grid-cols-1 gap-x-4 w-full my-4
md:grid-cols-3 md:w-[auto];
}
.buttoncol{@apply mt-2 md:mt-0 text-center text-black bg-[#ffcc00] rounded-md px-3 py-1 no-underline
hover:bg-[#222222] hover:text-white}
.buttoncol {
@apply mt-2 md:mt-0 text-center text-black bg-[#ffcc00] rounded-md px-3 py-1 no-underline
hover:bg-[#222222] hover:text-white;
}
.inside-price {
@apply font-bold;
}
.price{@apply tracking-tighter text-[2rem] text-[#222222] pl-12 m-0 -mt-7 text-nowrap text-left;
font-family: "Antique Olive Compact bold";}
.price {
@apply tracking-tighter text-[2rem] text-[#222222] pl-12 m-0 -mt-7 text-nowrap text-left;
font-family: "Antique Olive Compact bold";
}
.titel {@apply col-span-2 text-center [font-size:_clamp(20px,2.5vw,28px)]}
.empfehlung{@apply -mt-4 absolute justify-self-end rounded-md bg-red-700 text-white w-fit h-fit px-2 py-1 rotate-1 text-[0.65rem] ring-4 ring-white mr-6}
.titel {
@apply col-span-2 text-center [font-size:_clamp(20px,2.5vw,28px)];
}
.empfehlung {
@apply -mt-4 absolute justify-self-end rounded-md bg-red-700 text-white w-fit h-fit px-2 py-1 rotate-1 text-[0.65rem] ring-4 ring-white mr-6;
}
.variante {
@apply w-fit italic col-span-2 -mt-2 -mb-4 text-[1rem] text-[#222222] justify-self-start ring-2 ring-[#ffcc00] rounded-md pl-[4px] pr-[6px] py-[0px];
}
.services {
@apply hidden text-start py-1 md:grid grid-rows-subgrid row-span-1 items-center md:grid-cols-[1fr_50px]
@apply hidden text-start py-1 md:grid grid-rows-subgrid row-span-1 items-center md:grid-cols-[1fr_50px];
}
.services:not(:last-child) {
@apply border-b-[1px] border-gray-200;
@@ -109,7 +145,5 @@ hover:bg-[#222222] hover:text-white}
.check-no {
@apply justify-self-end self-center font-bold text-red-700;
}
}
</style>

View File

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

View File

@@ -0,0 +1,29 @@
import { uptime } from "os"
import crypto from "crypto";
/**
* Generiert einen zeitbasierten Hash der sich alle 15 Minuten ändert und an die Uptime des servers gekoppelt ist.
* @param email - Die E-Mail-Adresse des Benutzers, die als Teil des Hashes verwendet wird.
* @param time - Die Zeit in Millisekunden, die seit dem Start des Servers vergangen ist (Standard ist die Uptime des Servers).
* @param length - Die Länge des zurückgegebenen Hashes (Standard ist 6 Zeichen).
* @returns Ein zeitbasierter Hash, der sich alle 15 Minuten ändert und auf der E-Mail-Adresse basiert.
*/
export function createTimeBasedHash(email: string, time: number = uptime(), length: number = 6): string {
const now = Date.now();
const elapsed = now - time;
const window = Math.floor(elapsed / (15 * 60 * 1000)); // 15 minute windows
const data = `${email}:${window}`;
// Use a cryptographic hash (you can also use HMAC with a secret if you want)
const hash = crypto.createHash('sha256').update(data).digest();
// Convert part of the hash to an integer
const int = hash.readUInt32BE(0); // take first 4 bytes
// Modulo to get 6 digits
const pin = (int % 1000000).toString().padStart(6, '0');
return pin;
}

View File

@@ -0,0 +1,41 @@
import { transport } from "#lib/mail.js";
import {
Benutzer,
} from "#lib/client/prisma.js";
import { createTimeBasedHash } from "#lib/auth/time-based-hash.js";
export async function sendVerificationCodeMail(
user: Benutzer
) {
const code = createTimeBasedHash(user.email)
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>
<p>Um Ihre Registrierung abzuschließen, geben Sie folgenden Bestätigungscode auf der Website ein, um Ihre E-Mail-Adresse zu bestätigen:<br><br>
<b>${code}</b>
</p>
<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

@@ -5,6 +5,7 @@ import {
} from "#lib/client/prisma.js";
import { encodeToken } from "#lib/auth/token.js";
import { TokenType } from "#lib/auth/types.js";
import { createTimeBasedHash } from "#lib/auth/time-based-hash.js";
export async function sendRegisterMail(
user: Benutzer
@@ -12,9 +13,11 @@ export async function sendRegisterMail(
const verificationJwt = encodeToken({
typ: TokenType.Verify,
exp: Date.now() + (15 * 60 * 1000),
uid: user.uid
id: user.id
})
const code = createTimeBasedHash(user.email)
await transport.sendMail({
from: `"IBCornelsen" <info@online-energieausweis.org>`,
to: user.email,
@@ -22,10 +25,11 @@ export async function sendRegisterMail(
bcc: "info@online-energieausweis.org",
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>
<a href="${BASE_URI}/auth/verify?t=${verificationJwt}">E-Mail-Adresse bestätigen</a><br></p>
<a href="${BASE_URI}/auth/verify?t=${verificationJwt}">E-Mail-Adresse bestätigen</a><br><br>
Oder geben Sie folgenden Bestätigungscode auf der Website ein:<br>
<b>${code}</b>
</p>
<p>
Mit freundlichen Grüßen,
<br>

View File

@@ -46,9 +46,8 @@
export let rechnung: RechnungClient | null = null;
export let ausweisart: Enums.Ausweisart;
export let aktiveBezahlmethode: Bezahlmethoden = Enums.Bezahlmethoden.paypal;
export let partner_code: string;
export let nurRechnungsadresseUpdate: Boolean | null = false;
let email: string, vorname: string, name: string, empfaenger: string, strasse: string, plz: string, ort: string, zusatzzeile: string, telefon: string;
@@ -67,9 +66,18 @@
vorname = impersonatedUser.vorname || "";
name = impersonatedUser.name || "";
telefon = impersonatedUser.telefon || "";
email = impersonatedUser.email || "";
if (rechnung){
empfaenger = rechnung?.empfaenger || "";
strasse = rechnung?.strasse || "";
plz = rechnung?.plz || "";
ort = rechnung?.ort || "";
zusatzzeile = rechnung?.zusatzzeile || "";
email = rechnung?.email || "";
}
}
let abweichende_versand_adresse = JSON.parse(localStorage.getItem("kundendaten.abweichende_versand_adresse") || "false")
let abweichende_versand_adresse = rechnung?.abweichende_versand_adresse ?? JSON.parse(localStorage.getItem("kundendaten.abweichende_versand_adresse") || "false")
let versand_email: string | undefined,
versand_zusatzzeile: string | undefined,
@@ -77,16 +85,13 @@
versand_strasse: string | undefined,
versand_plz: string | undefined,
versand_ort: string | undefined;
$: {
if (!abweichende_versand_adresse) {
versand_email = email
versand_zusatzzeile = zusatzzeile
versand_empfaenger = empfaenger
versand_strasse = strasse
versand_plz = plz
versand_ort = ort
}
}
versand_email = "";//Todo Datenbankfeld fehlt noch
versand_zusatzzeile = rechnung?.versand_zusatzzeile ?? zusatzzeile;
versand_empfaenger = rechnung?.versand_empfaenger ?? empfaenger;
versand_strasse = rechnung?.versand_strasse ?? strasse;
versand_plz = rechnung?.versand_plz ?? plz;
versand_ort = rechnung?.versand_ort ?? ort;
$: {
// Wir speichern jede Änderung an den Kundendaten im localStorage ab.
@@ -207,6 +212,12 @@
}
try {
const merged_versand_empfaenger = versand_empfaenger || empfaenger;
const merged_versand_strasse = versand_strasse || strasse;
const merged_versand_plz = versand_plz || plz;
const merged_versand_ort = versand_ort || ort;
const merged_versand_zusatzzeile = versand_zusatzzeile || zusatzzeile;
const { id } = await api.rechnung.anfordern.PUT.fetch(
{
email: email,
@@ -214,10 +225,11 @@
strasse: strasse,
plz: plz,
ort: ort,
versand_empfaenger: versand_empfaenger,
versand_strasse: versand_strasse,
versand_plz: versand_plz,
versand_ort: versand_ort,
versand_empfaenger: merged_versand_empfaenger,
versand_strasse: merged_versand_strasse,
versand_plz: merged_versand_plz,
versand_ort: merged_versand_ort,
versand_zusatzzeile: merged_versand_zusatzzeile,
telefon: telefon,
nachweis_id: result.nachweis_id
},
@@ -274,6 +286,45 @@
resultUser = await benutzerSpeichern(benutzerObjekt);
let id: string, checkout_url: string | undefined;
if (rechnung) {
const merged_versand_empfaenger = versand_empfaenger || empfaenger;
const merged_versand_strasse = versand_strasse || strasse;
const merged_versand_plz = versand_plz || plz;
const merged_versand_ort = versand_ort || ort;
const merged_versand_zusatzzeile = versand_zusatzzeile || zusatzzeile;
const result = await api.rechnung._id.PATCH.fetch({
bezahlmethode: aktiveBezahlmethode,
abweichende_versand_adresse: abweichende_versand_adresse,
empfaenger,
strasse,
plz,
ort,
telefon,
email,
zusatzzeile,
versand_empfaenger: merged_versand_empfaenger,
versand_strasse: merged_versand_strasse,
versand_plz: merged_versand_plz,
versand_ort: merged_versand_ort,
versand_zusatzzeile: merged_versand_zusatzzeile
}, {
params: {
id: rechnung.id
},
headers: {
Authorization: `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`,
},
});
id = result.id;
checkout_url = result.checkout_url;
}
} catch(e) {
addNotification({
dismissable: true,
@@ -361,6 +412,12 @@
try {
let id: string, checkout_url: string | undefined;
const merged_versand_empfaenger = versand_empfaenger || empfaenger;
const merged_versand_strasse = versand_strasse || strasse;
const merged_versand_plz = versand_plz || plz;
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,
@@ -369,12 +426,13 @@
strasse: strasse,
plz: plz,
ort: ort,
versand_empfaenger: versand_empfaenger,
versand_strasse: versand_strasse,
versand_plz: versand_plz,
versand_ort: versand_ort,
telefon: telefon,
versand_zusatzzeile: versand_zusatzzeile
zusatzzeile: zusatzzeile,
versand_empfaenger: merged_versand_empfaenger,
versand_strasse: merged_versand_strasse,
versand_plz: merged_versand_plz,
versand_ort: merged_versand_ort,
versand_zusatzzeile: merged_versand_zusatzzeile
}, {
params: {
id: rechnung.id
@@ -399,13 +457,16 @@
strasse: strasse,
plz: plz,
ort: ort,
versand_empfaenger: versand_empfaenger,
versand_strasse: versand_strasse,
versand_plz: versand_plz,
versand_ort: versand_ort,
zusatzzeile: zusatzzeile,
versand_empfaenger: merged_versand_empfaenger,
versand_strasse: merged_versand_strasse,
versand_plz: merged_versand_plz,
versand_ort: merged_versand_ort,
versand_zusatzzeile: merged_versand_zusatzzeile,
telefon: telefon,
ausweis_id: ausweis.id,
partner_code
partner_code,
abweichende_versand_adresse: abweichende_versand_adresse
},
{
headers: {
@@ -445,6 +506,7 @@
let form: HTMLFormElement;
</script>
{#if !nurRechnungsadresseUpdate}
<div
id="skala"
class="bg-white grid grid-cols-1 gap-x-8 gap-y-4 px-0 sm:p-4
@@ -475,7 +537,7 @@
<Progressbar active={1} {ausweisart} ausweistyp={ausweis.ausweistyp} anliegen={"Energieausweis erstellen"} />
{/if}
</div>
{/if}
<form id="formInput-2" bind:this={form}>
<div id="formular-box" class="formular-boxen ring-0">
@@ -743,7 +805,6 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
<input
name="versand_ort"
type="text"
readonly
required
bind:value={versand_ort}
/>
@@ -780,7 +841,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
</div>
<!-- E-mail -->
<!-- Versand Email wird derzeit nicht verwendet
<div class="input-standard order-12 md:order-12 xl:order-12">
<InputLabel title="E-mail *"></InputLabel>
@@ -797,7 +858,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
</HelpLabel>
</div>
</div>
-->
<!-- Telefon
<div class="input-standard order-[13] md:order-[13] xl:order-[13]">
@@ -821,7 +882,7 @@ xl:grid-cols-3 xl:gap-x-8 xl:gap-y-8
</Bereich
>
{#if !gegAnfrage}
{#if !gegAnfrage && !nurRechnungsadresseUpdate}
<Bereich bereich="3" title="Bezahlmethode">
<div
id="bezahlung"
@@ -932,7 +993,7 @@ grid-cols-3 sm:grid-cols-5 justify-around justify-items-center items-center"
{/if}
<!-- Falls wir es mit einem Ausweis zu tun haben und der Nutzer keine Hilfe bestellt hat dann zeigen wir eine Box mit sachen an die vielleicht unklar sind. -->
{#if (ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen || ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe || ausweisart === Enums.Ausweisart.BedarfsausweisWohnen) && (ausweis.ausweistyp === Enums.AusweisTyp.Standard || ausweis.ausweistyp === Enums.AusweisTyp.standardXL)}
{#if (ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen || ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe || ausweisart === Enums.Ausweisart.BedarfsausweisWohnen) && (ausweis.ausweistyp === Enums.AusweisTyp.Standard || ausweis.ausweistyp === Enums.AusweisTyp.standardXL) && (!nurRechnungsadresseUpdate)}
<div class="grid grid-cols-1 sm:grid-cols-1 gap-x-6 my-6">
<div class="pruefpunkte bereich-box bg-white">
<h3>Bitte untenstehende Punkte bestätigen. Bitte gehen Sie gegebenenfalls zurück zum Formular und überprüfen bzw. korrigieren Ihre Eingaben.</h3>
@@ -1115,6 +1176,7 @@ sm:grid-cols-[min-content_min-content_min-content] sm:justify-self-end sm:mt-8"
<button class="order-2 button" type="button" on:click={() => speichern()}>Speichern</button>
{#if !nurRechnungsadresseUpdate}
{#if rechnung && rechnung.status === "paid"}
<!-- Von einer GEG Anfrage sollte man sowieso nicht noch mal auf die Kundendaten Seite gelangen, also brauchen wir das hier nicht. -->
<button
@@ -1140,6 +1202,7 @@ sm:grid-cols-[min-content_min-content_min-content] sm:justify-self-end sm:mt-8"
>
{/if}
{/if}
{/if}
{#if user.rolle === Enums.BenutzerRolle.ADMIN}
<button class="button text-sm" on:click={() => {

View File

@@ -13,6 +13,10 @@
export let redirect: string | null = null;
function handleInput(event) {
email = event.target.value.toLowerCase();
}
async function login(e: SubmitEvent) {
e.preventDefault()
if (passwort.length < 8) {
@@ -26,7 +30,7 @@
}
try {
const { uid } = await api.user.PUT.fetch({
await api.user.PUT.fetch({
email,
passwort,
vorname,
@@ -38,7 +42,7 @@
return
}
window.location.href = "/auth/login";
window.location.href = `/auth/code?email=${email}`;
} catch (e) {
errorHidden = false;
}
@@ -83,6 +87,7 @@
name="email"
class="input input-bordered text-base text-base-content font-medium"
bind:value={email}
on:input={handleInput}
required
/>
</div>

View File

@@ -0,0 +1,89 @@
<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";
export let redirect: string | null = null;
export let email: string;
function verify(e: SubmitEvent) {
e.preventDefault();
const code = numbers.join("");
if (code.length !== 6) {
addNotification({
message: "Bitte geben Sie einen gültigen Verifizierungscode ein.",
dismissable: true,
timeout: 3000,
type: "error"
});
return;
}
api.auth["verification-code"].POST.fetch({ code, email }).then(() => {
if (redirect) {
window.location.href = redirect;
} else {
window.location.href = "/";
}
}).catch(() => {
errorHidden = false;
});
}
// TODO
function codeErneutAnfordern() {
api.auth["verification-code"].GET.fetch(null, {
headers: {
"Authorization": "Bearer"
}
})
}
let numbers = new Array(6).fill("");
let errorHidden = true;
</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">Verifizierung</h1>
<p>Wir haben ihnen einen Verifizierungscode per Email geschickt, bitte geben sie diesen ein um ihre Registrierung fertigzustellen.</p>
<form on:submit={verify}>
<div class="flex flex-row gap-4 w-full justify-center my-12">
{#each { length: 6 } as _, i}
<input
type="text"
class="input input-bordered text-4xl text-base-content font-medium w-12 text-center"
bind:value={numbers[i]}
maxlength="1"
on:input={function(e) {
if (i !== 5) {
e.target.nextSibling.focus()
}
}}
required
/>
{/each}
</div>
<div class="flex justify-between">
<button type="submit" class="button"
>Abschicken</button
>
<!-- <button type="button" on:click={codeErneutAnfordern} class="button"
>Code erneut anfordern</button
> -->
</div>
{#if !errorHidden}
<div class="flex flex-row gap-4 mt-8" in:fade out:fade={{delay: 400}}>
<CrossCircled size={24} />
<span class="font-semibold"> Da ist wohl etwas schiefgelaufen. Der eingegebene Verifizierungscode ist ungültig.</span>
</div>
{/if}
</form>
<NotificationWrapper></NotificationWrapper>
</div>

View File

@@ -1,7 +1,5 @@
---
import AusweisLayout from "#layouts/AusweisLayoutDatenPartner.astro";
import VerbrauchsausweisWohnenModule from "#modules/VerbrauchsausweisWohnen/VerbrauchsausweisWohnenModule.svelte";
import { AufnahmeClient, BildClient, ObjektClient, UploadedGebaeudeBild, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types";
import { Aufnahme, BedarfsausweisGewerbe, Bild, Enums, Objekt, Unterlage, VerbrauchsausweisWohnen } from "#lib/server/prisma";
import { getAufnahme, getBedarfsausweisGewerbe, getBilder, getObjekt, getUnterlagen, getVerbrauchsausweisWohnen } from "#lib/server/db";
import { getCurrentUser } from "#lib/server/user";
@@ -9,7 +7,7 @@ import BedarfsausweisGewerbeModule from "#modules/angebot-anfragen/Bedarfsauswei
const id = Astro.url.searchParams.get("id");
const aufnahme_id = Astro.url.searchParams.get("aufnahme")
let nachweistyp = Astro.url.searchParams.get("nachweistyp") as Enums.AusweisTyp || Enums.AusweisTyp.Standard;
let nachweistyp = Astro.url.searchParams.get("ausweistyp") as Enums.AusweisTyp || Enums.AusweisTyp.Standard;
let nachweis: BedarfsausweisGewerbe = {} as BedarfsausweisGewerbe;
let aufnahme: Aufnahme = {} as Aufnahme;

View File

@@ -7,7 +7,7 @@ import GEGNachweisGewerbeModule from "#modules/angebot-anfragen/GEGNachweisGewer
const id = Astro.url.searchParams.get("id");
const aufnahme_id = Astro.url.searchParams.get("aufnahme")
let nachweistyp = Astro.url.searchParams.get("nachweistyp") as Enums.AusweisTyp || Enums.AusweisTyp.Standard;
let nachweistyp = Astro.url.searchParams.get("ausweistyp") as Enums.AusweisTyp || Enums.AusweisTyp.Standard;
let nachweis: GEGNachweisGewerbe = {} as GEGNachweisGewerbe;
let aufnahme: Aufnahme = {} as Aufnahme;

View File

@@ -62,6 +62,7 @@ export const GET = defineApiRoute({
exp: refreshTokenExpiry.valueOf(),
});
console.log("Creating refresh token for user ID:", user.id);
const { id } = await prisma.refreshTokens.create({
data: {
token: refreshToken,

View File

@@ -0,0 +1,70 @@
import { z } from "zod";
import { prisma } from "#lib/server/prisma.js";
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
import { sendVerificationCodeMail } from "#lib/server/mail/code.js";
import { authorizationMiddleware } from "#lib/middleware/authorization.js";
import { createTimeBasedHash } from "#lib/auth/time-based-hash.js";
export const GET = defineApiRoute({
meta: {
description:
"Fragt einen neuen Verifizierungscode per Mail an.",
tags: ["Benutzer"],
summary: "Verifizierungscode anfragen.",
},
middleware: authorizationMiddleware,
output: z.void(),
async fetch(input, ctx) {
// Falls der Nutzer nicht existiert, wird eine Fehlermeldung zurückgegeben.
const user = await prisma.benutzer.findUnique({
where: {
email: input.email.toLowerCase(),
},
});
if (!user) {
throw new APIError({
code: "BAD_REQUEST",
message: "Benutzer konnte nicht gefunden werden.",
});
}
await sendVerificationCodeMail(user);
},
});
export const POST = defineApiRoute({
meta: {
description:
"Versucht den Nutzer mithilfe des abgeschickten Codes zu verifizieren.",
tags: ["Benutzer"],
summary: "Verifizieren.",
},
input: z.object({
code: z.string(),
email: z.string().email().toLowerCase(),
}),
output: z.void(),
async fetch({ code, email }, ctx) {
const generatedCode = createTimeBasedHash(email);
console.log(generatedCode, code);
if (code !== generatedCode) {
throw new APIError({
code: "BAD_REQUEST",
message: "Der eingegebene Verifizierungscode ist ungültig.",
});
}
await prisma.benutzer.update({
where: {
email: email.toLowerCase(),
},
data: {
verified: true,
},
});
},
});

View File

@@ -16,12 +16,14 @@ export const PATCH = defineApiRoute({
strasse: true,
telefon: true,
empfaenger: true,
zusatzzeile: true,
versand_empfaenger: true,
versand_ort: true,
versand_plz: true,
versand_strasse: true,
versand_zusatzzeile: true,
abweichende_versand_adresse: true
abweichende_versand_adresse: true,
email: true
}),
output: z.object({
checkout_url: z.string().optional(),
@@ -31,7 +33,9 @@ export const PATCH = defineApiRoute({
headers: authorizationHeaders,
async fetch(input, context, user) {
// Wir holen uns die Rechnung
const rechnung = await prisma.rechnung.findUnique({
let rechnung;
if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
rechnung = await prisma.rechnung.findUnique({
where: {
id: context.params.id,
benutzer: {
@@ -47,6 +51,21 @@ export const PATCH = defineApiRoute({
verbrauchsausweis_wohnen: true
}
})
} else {
rechnung = await prisma.rechnung.findUnique({
where: {
id: context.params.id
},
include: {
bedarfsausweis_gewerbe: true,
bedarfsausweis_wohnen: true,
geg_nachweis_gewerbe: true,
geg_nachweis_wohnen: true,
verbrauchsausweis_gewerbe: true,
verbrauchsausweis_wohnen: true
}
})
}
if (!rechnung) {
throw new APIError({
@@ -66,12 +85,14 @@ export const PATCH = defineApiRoute({
strasse: input.strasse,
telefon: input.telefon,
empfaenger: input.empfaenger,
zusatzzeile: input.zusatzzeile,
versand_empfaenger: input.versand_empfaenger,
versand_ort: input.versand_ort,
versand_plz: input.versand_plz,
versand_strasse: input.versand_strasse,
versand_zusatzzeile: input.versand_zusatzzeile,
abweichende_versand_adresse: input.abweichende_versand_adresse,
email: input.email
}
})

24
src/pages/auth/code.astro Normal file
View File

@@ -0,0 +1,24 @@
---
import CodeModule from "../../modules/auth/CodeModule.svelte";
import MinimalLayout from "#layouts/MinimalLayout.astro";
import { validateAccessTokenServer } from "#server/lib/validateAccessToken";
const valid = await validateAccessTokenServer(Astro)
if (valid) {
return Astro.redirect("/dashboard")
}
const redirect = Astro.url.searchParams.get("redirect");
const email = Astro.url.searchParams.get("email");
if (!email) {
return Astro.redirect("/");
}
---
<MinimalLayout title="Verifizierung - IBCornelsen">
<CodeModule client:load {redirect} {email}></CodeModule>
</MinimalLayout>

View File

@@ -0,0 +1,108 @@
---
import { encodeToken } from "#lib/auth/token";
import { TokenType } from "#lib/auth/types";
import { API_ACCESS_TOKEN_COOKIE_NAME, API_REFRESH_TOKEN_COOKIE_NAME } from "#lib/constants";
import { Enums, prisma } from "#lib/server/prisma";
import moment from "moment";
import { createCaller } from "src/astro-typesafe-api-caller";
import KundendatenModule from "#modules/KundendatenModule.svelte";
import AusweisLayout from "#layouts/AusweisLayoutPruefung.astro";
import { getCurrentUser, getOtherUser } from "#lib/server/user";
import { getAusweisartFromId } from "#components/Ausweis/types";
import { getAufnahme, getBedarfsausweisWohnen, getBilder, getObjekt, getRechnung, getUnterlagen, getVerbrauchsausweisGewerbe, getVerbrauchsausweisWohnen } from "#lib/server/db";
import { BenutzerClient } from "#components/Ausweis/types.js";
function getExistingAusweis(rechnung) {
if (rechnung.verbrauchsausweis_wohnen) {
return rechnung.verbrauchsausweis_wohnen;
}
if (rechnung.verbrauchsausweis_gewerbe) {
return rechnung.verbrauchsausweis_gewerbe;
}
if (rechnung.bedarfsausweis_wohnen) {
return rechnung.bedarfsausweis_wohnen ;
}
if (rechnung.bedarfsausweis_gewerbe) {
return rechnung.bedarfsausweis_gewerbe;
}
if (rechnung.geg_nachweis_gewerbe) {
return rechnung.geg_nachweis_gewerbe;
}
if (rechnung.geg_nachweis_wohnen) {
return rechnung.geg_nachweis_wohnen;
}
return null;
}
const user = await getCurrentUser(Astro);
if (!user) {
return Astro.redirect("/auth/login")
}
const rechnungid = Astro.url.searchParams.get("rechnungid")
if (!rechnungid) {
return Astro.redirect("/404")
}
let rechnung;
//Only Admin can read foreign invoices
if (user.rolle !== Enums.BenutzerRolle.ADMIN) {
rechnung = await prisma.rechnung.findUnique({
where: {
id: rechnungid,
benutzer_id: user.id
},
include: {
verbrauchsausweis_wohnen: true,
verbrauchsausweis_gewerbe: true,
bedarfsausweis_wohnen: true,
bedarfsausweis_gewerbe: true,
geg_nachweis_gewerbe: true,
geg_nachweis_wohnen: true
}
})
} else {
rechnung = await prisma.rechnung.findUnique({
where: {
id: rechnungid,
},
include: {
verbrauchsausweis_wohnen: true,
verbrauchsausweis_gewerbe: true,
bedarfsausweis_wohnen: true,
bedarfsausweis_gewerbe: true,
geg_nachweis_gewerbe: true,
geg_nachweis_wohnen: true
}
})
}
if (!rechnung) {
return Astro.redirect("/404")
}
const ausweis = getExistingAusweis(rechnung);
const ausweisart = getAusweisartFromId(ausweis.id)
let aufnahme, objekt, bilder, unterlagen, partner_code;
aufnahme = await getAufnahme(ausweis.aufnahme_id)
objekt = await getObjekt(aufnahme?.objekt_id)
bilder = await getBilder(ausweis.aufnahme_id)
unterlagen = await getUnterlagen(ausweis.aufnahme_id)
let impersonatedUser: Partial<BenutzerClient> | null = null;
if (user){
if (user.id !== ausweis.benutzer_id && ausweis.benutzer_id !== undefined){
impersonatedUser = await getOtherUser(Astro, ausweis.benutzer_id) || {}
}
}
---
<AusweisLayout title="Kundendaten Aufnehmen - IBCornelsen">
<KundendatenModule {user} {impersonatedUser} {ausweis} {objekt} {aufnahme} {bilder} {rechnung} {ausweisart} {unterlagen} {partner_code} aktiveBezahlmethode={Enums.Bezahlmethoden.paypal} nurRechnungsadresseUpdate={true} client:only ></KundendatenModule>
</AusweisLayout>

View File

@@ -3,12 +3,28 @@
set -e
# Config
CONTAINER_NAME="online-energieausweis-database-1"
CONTAINER_NAME="database"
DB_USER="main"
DB_NAME="main"
TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S")
FILE_NAME="backup-$TIMESTAMP.sql.br"
# Wir holen uns den parameter --skip-backup, um zu entscheiden, ob wir ein Backup machen wollen
SKIP_BACKUP=false
while [[ $# -gt 0 ]]; do
case $1 in
--skip-backup)
SKIP_BACKUP=true
shift # Remove the argument from the list
;;
*)
echo "Unbekannter Parameter: $1"
exit 1
;;
esac
done
REQUIRED_CONFIRMATION='Ja, ich möchte alle Daten unwiderruflich löschen.'
echo "⚠️ WARNUNG: Diese Aktion wird alle Tabellen und Einträge in der Datenbank vollständig löschen!"
@@ -21,9 +37,11 @@ if [[ "$USER_CONFIRMATION" != "$REQUIRED_CONFIRMATION" ]]; then
exit 1
fi
if [[ "$SKIP_BACKUP" == false ]]; then
echo "📦 Backup wird erstellt..."
docker exec -t "$CONTAINER_NAME" pg_dumpall -c -U "$DB_USER" | brotli > "$FILE_NAME"
echo "✅ Backup abgeschlossen: $FILE_NAME"
fi
echo "🧨 Alle Daten aus allen Tabellen werden gelöscht..."
@@ -34,12 +52,22 @@ DECLARE
r RECORD;
sql TEXT := '';
BEGIN
-- Truncate all tables
FOR r IN
SELECT tablename
FROM pg_tables
WHERE schemaname = 'public'
LOOP
sql := sql || FORMAT('TRUNCATE TABLE public.%I CASCADE;', r.tablename);
sql := sql || FORMAT('DROP TABLE public.%I CASCADE;', r.tablename);
END LOOP;
-- Drop all sequences
FOR r IN
SELECT sequence_name
FROM information_schema.sequences
WHERE sequence_schema = 'public'
LOOP
sql := sql || FORMAT('DROP SEQUENCE IF EXISTS public.%I CASCADE;', r.sequence_name);
END LOOP;
EXECUTE sql;