WIP on dev-moritz

This commit is contained in:
Moritz Utcke
2025-01-21 12:35:20 +07:00
parent 5a551c0f33
commit de8c859826
59 changed files with 1221 additions and 397 deletions

4
.gitignore vendored
View File

@@ -30,5 +30,5 @@ bun.lockb
public/fonts/
*.Identifier
src/lib/Berechnungen/BedarfsausweisWohnen/18599-Tabellenverfahren-Wohngebaeude-komplett-ocr.pdf
src/lib/Berechnungen/BedarfsausweisWohnen/18599-Tabellenverfahren-Wohngebaeude-komplett-ocr.pdf

View File

@@ -5,12 +5,13 @@ import tailwind from "@astrojs/tailwind";
import node from "@astrojs/node";
import mdx from "@astrojs/mdx";
import dsv from "@rollup/plugin-dsv"
import astroTypesafeAPI from "astro-typesafe-api"
import { fileURLToPath } from "url";
// https://astro.build/config
export default defineConfig({
integrations: [svelte(), tailwind(), mdx()],
integrations: [svelte(), tailwind(), mdx(), astroTypesafeAPI()],
outDir: "./dist",
output: "server",
vite: {

1
openapi.json Normal file
View File

@@ -0,0 +1 @@
{"openapi":"3.0.3","info":{"title":"Title","version":"1.0.0","description":""},"paths":{"[id]":{"patch":{"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}}}}},"index":{"post":{"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}}}}}}}

View File

@@ -25,40 +25,41 @@
"@ibcornelsen/database": "link:@ibcornelsen/database",
"@ibcornelsen/ui": "^0.0.2",
"@mollie/api-client": "^3.7.0",
"@pdfme/common": "^5.1.7",
"@pdfme/generator": "^5.2.11",
"@pdfme/ui": "^5.1.7",
"@pdfme/common": "^5.2.16",
"@pdfme/generator": "^5.2.16",
"@pdfme/ui": "^5.2.16",
"@trpc/client": "^10.45.2",
"@trpc/server": "^10.45.2",
"astro": "^4.16.10",
"astro": "^4.16.18",
"body-scroll-lock": "^4.0.0-beta.0",
"buffer": "^6.0.3",
"bun": "^1.1.34",
"bun": "^1.1.45",
"csvtojson": "^2.0.10",
"express": "^4.21.1",
"express": "^4.21.2",
"flag-icons": "^6.15.0",
"fontkit": "^2.0.4",
"js-cookie": "^3.0.5",
"js-interpolate": "^1.3.2",
"jsonwebtoken": "^9.0.2",
"moment": "^2.30.1",
"moment-timezone": "^0.5.46",
"pdf-lib": "^1.17.1",
"postcss-nested": "^7.0.2",
"radix-svelte-icons": "^1.0.0",
"sass": "^1.80.6",
"sass": "^1.83.4",
"svelte": "^3.59.2",
"svelte-dialogs": "^1.2.2",
"svelte-preprocess": "^5.1.4",
"svelte-ripple-action": "^1.0.6",
"tailwindcss": "^3.4.14",
"tailwindcss": "^3.4.17",
"trpc-openapi": "^1.2.0",
"uuid": "^9.0.1",
"zod": "^3.23.8"
"zod": "^3.24.1"
},
"devDependencies": {
"@faker-js/faker": "^8.4.1",
"@rollup/plugin-dsv": "^3.0.5",
"@tailwindcss/typography": "^0.5.15",
"@tailwindcss/typography": "^0.5.16",
"@types/body-scroll-lock": "^3.1.2",
"@types/express": "^5.0.0",
"@types/fontkit": "^2.0.7",
@@ -67,13 +68,13 @@
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"autoprefixer": "^10.4.20",
"bun-types": "^1.1.34",
"cypress": "^13.15.2",
"bun-types": "^1.1.45",
"cypress": "^13.17.0",
"cypress-file-upload": "^5.0.8",
"daisyui": "^4.12.14",
"daisyui": "^4.12.23",
"eslint": "~8.15.0",
"eslint-config-prettier": "8.1.0",
"postcss": "^8.4.49",
"postcss": "^8.5.1",
"postcss-import": "^16.1.0",
"postcss-nesting": "^13.0.1",
"prettier": "^2.8.8",

View File

@@ -0,0 +1,16 @@
import { createCallerFactory } from "astro-typesafe-api/server";
import { type AstroGlobal } from "astro";
export const createCaller = createCallerFactory({
"aufnahme/[id]": await import("../src/pages/api/aufnahme/[id].ts"),
"aufnahme": await import("../src/pages/api/aufnahme/index.ts"),
"auth/access-token": await import("../src/pages/api/auth/access-token.ts"),
"auth/refresh-token": await import("../src/pages/api/auth/refresh-token.ts"),
"user/self": await import("../src/pages/api/user/self.ts"),
"bedarfsausweis-wohnen": await import("../src/pages/api/bedarfsausweis-wohnen/index.ts"),
"objekt/[id]": await import("../src/pages/api/objekt/[id].ts"),
"objekt": await import("../src/pages/api/objekt/index.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"),
})

View File

@@ -22,6 +22,8 @@ export async function verbrauchsausweisWohnenSpeichern(
// Anscheinend wurde der Ausweis bereits erstellt und hat eine UID.
// Jetzt müssen wir ihn nun nur noch abspeichern.
try {
await client.v1.objekt.speichern.mutate()
await client.v1.verbrauchsausweisWohnen[2016].speichern.mutate({
...ausweis,
gebaeude_aufnahme_allgemein: {
@@ -41,7 +43,6 @@ export async function verbrauchsausweisWohnenSpeichern(
return { uid: ausweis.uid, gebaeude_uid: gebaeude.uid, gebaeude_aufnahme_uid: gebaeude_aufnahme_allgemein.uid };
} catch (e) {
// TODO: Ticket mit Fehldermeldung abschicken.
}
} else {
// Wir speichern den Ausweis ab und leiten auf die "ausweis-gespeichert" Seite weiter.
@@ -69,7 +70,6 @@ export async function verbrauchsausweisWohnenSpeichern(
ausweis,
}),
});
// TODO: Ticket mit Fehldermeldung abschicken.
}
}

View File

@@ -1,8 +1,8 @@
<script lang="ts">
import HelpLabel from "#labels/HelpLabel.svelte";
import Inputlabel from "#labels/InputLabel.svelte";
import HelpLabel from "#components/labels/HelpLabel.svelte";
import Inputlabel from "#components/labels/InputLabel.svelte";
//import Label from "#components/Label.svelte";

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import HelpLabel from "#labels/HelpLabel.svelte";
import Inputlabel from "#labels/InputLabel.svelte";
import HelpLabel from "#components/labels/HelpLabel.svelte";
import Inputlabel from "#components/labels/InputLabel.svelte";
import HeizungImage from "./HeizungImage.svelte";
import AusweisPreviewContainer from "./AusweisPreviewContainer.svelte";

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import HelpLabel from "#labels/HelpLabel.svelte";
import Inputlabel from "#labels/InputLabel.svelte";
import HelpLabel from "#components/labels/HelpLabel.svelte";
import Inputlabel from "#components/labels/InputLabel.svelte";
import ZipSearch from "#components/PlzSuche.svelte";
import { Enums } from "@ibcornelsen/database/client"

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import HelpLabel from "#labels/HelpLabel.svelte";
import Inputlabel from "#labels/InputLabel.svelte";
import HelpLabel from "#components/labels/HelpLabel.svelte";
import Inputlabel from "#components/labels/InputLabel.svelte";
export let gebaeude_aufnahme_allgemein: GebaeudeAufnahmeClient;

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import HelpLabel from "#labels/HelpLabel.svelte";
import HelpLabel from "#components/labels/HelpLabel.svelte";
export let checked: boolean | null | undefined;
export let name: string;

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import HelpLabel from "#labels/HelpLabel.svelte";
import Inputlabel from "#labels/InputLabel.svelte";
import HelpLabel from "#components/labels/HelpLabel.svelte";
import Inputlabel from "#components/labels/InputLabel.svelte";
import FensterImage from "./FensterImage.svelte";

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import HelpLabel from "#labels/HelpLabel.svelte";
import Inputlabel from "#labels/InputLabel.svelte";
import HelpLabel from "#components/labels/HelpLabel.svelte";
import Inputlabel from "#components/labels/InputLabel.svelte";
import HeizungImage from "./HeizungImage.svelte";

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import HelpLabel from "#labels/HelpLabel.svelte";
import Inputlabel from "#labels/InputLabel.svelte";
import HelpLabel from "#components/labels/HelpLabel.svelte";
import Inputlabel from "#components/labels/InputLabel.svelte";
import DaemmungImage from "./DaemmungImage.svelte";

View File

@@ -1,8 +1,8 @@
<script lang="ts">
import HelpLabel from "#labels/HelpLabel.svelte";
import Inputlabel from "#labels/InputLabel.svelte";
import Verbrauchslabel from "#labels/VerbrauchsLabel.svelte";
import VerbrauchsHelpLabel from "#labels/VerbrauchsHelpLabel.svelte";
import HelpLabel from "#components/labels/HelpLabel.svelte";
import Inputlabel from "#components/labels/InputLabel.svelte";
import Verbrauchslabel from "#components/labels/VerbrauchsLabel.svelte";
import VerbrauchsHelpLabel from "#components/labels/VerbrauchsHelpLabel.svelte";
import Label from "../Label.svelte";
@@ -44,12 +44,12 @@
];
const startDate = moment(
ausweis.gebaeude_aufnahme_allgemein.erstellungsdatum || Date.now()
ausweis.aufnahme.erstellungsdatum || Date.now()
)
.subtract(4, "years")
.subtract(6, "months");
const endDate = moment(
ausweis.gebaeude_aufnahme_allgemein.erstellungsdatum || Date.now()
ausweis.aufnahme.erstellungsdatum || Date.now()
).subtract(3, "years");
for (let m = moment(startDate); m.isBefore(endDate); m.add(1, "month")) {

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import HelpLabel from "#labels/HelpLabel.svelte";
import Inputlabel from "#labels/InputLabel.svelte";
import HelpLabel from "#components/labels/HelpLabel.svelte";
import Inputlabel from "#components/labels/InputLabel.svelte";
export let ausweis;

View File

@@ -1,8 +1,4 @@
<script lang="ts">
import { get_current_component } from "svelte/internal";
const component = get_current_component();
export let image: string;
export let name: string;
export let description: string;
@@ -25,10 +21,6 @@
<div class="join">
<button class="p-3.5 border rounded-lg" disabled={!removable && quantity == 1} on:click={() => {
quantity--
if ((quantity == 0) && removable) {
component.$destroy();
}
}}>-</button>
<button class="p-3.5 border rounded-lg">{quantity}</button>
<button class="p-3.5 border rounded-lg" disabled={quantity <= maxQuantity} on:click={() => quantity++}>+</button>

View File

@@ -1,5 +1,5 @@
---
import HeaderLogin from "#header/HeaderLogin.svelte";
import HeaderLogin from "#components/design/header/HeaderLogin.svelte";
---
<header id="header">

View File

@@ -1,5 +1,5 @@
---
import HeaderLogin from "#header/HeaderLogin.svelte";
import HeaderLogin from "#components/design/header/HeaderLogin.svelte";
---
<header id="header">

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import { loginClient } from "#lib/login";
import { loginClient } from "#lib/login.js";
import { CrossCircled } from "radix-svelte-icons";
import { fade } from "svelte/transition";

View File

@@ -1,7 +1,7 @@
---
import CardNavigation from "#sidebarCards/cardNavigation.svelte";
import CardPriceiInfo from "#sidebarCards/cardPriceiInfo.svelte";
import CardProduktSidebar from "#sidebarCards/CardProduktSidebar.svelte";
import CardNavigation from "#components/design/sidebars/cards/cardNavigation.svelte";
import CardPriceiInfo from "#components/design/sidebars/cards/cardPriceiInfo.svelte";
import CardProduktSidebar from "#components/design/sidebars/cards/CardProduktSidebar.svelte";
import { PRICES } from "#lib/constants";
---

View File

@@ -1,7 +1,7 @@
---
import CardContact from "#sidebarCards/cardContact.svelte";
import CardPriceiInfo from "#sidebarCards/cardPriceiInfo.svelte";
import CardProduktSidebar from "#sidebarCards/CardProduktSidebar.svelte";
import CardContact from "#components/design/sidebars/cards/cardContact.svelte";
import CardPriceiInfo from "#components/design/sidebars/cards/cardPriceiInfo.svelte";
import CardProduktSidebar from "#components/design/sidebars/cards/CardProduktSidebar.svelte";
import { PRICES } from "#lib/constants";
---

View File

@@ -1,11 +1,11 @@
---
import Login from "#sidebarCards/cardlogin_1.svelte";
import Contact from "#sidebarCards/cardcontact.svelte";
import Review from "#sidebarCards/cardreview.svelte";
import VApromo from "#sidebarCards/cardVApromo.svelte";
import VAGpromo from "#sidebarCards/cardVAGpromo.svelte";
import BApromo from "#sidebarCards/cardBApromo.svelte";
import BAGpromo from "#sidebarCards/cardBAGpromo.svelte";
import Login from "#components/design/sidebars/cards/cardlogin_1.svelte";
import Contact from "#components/design/sidebars/cards/cardcontact.svelte";
import Review from "#components/design/sidebars/cards/cardreview.svelte";
import VApromo from "#components/design/sidebars/cards/cardVApromo.svelte";
import VAGpromo from "#components/design/sidebars/cards/cardVAGpromo.svelte";
import BApromo from "#components/design/sidebars/cards/cardBApromo.svelte";
import BAGpromo from "#components/design/sidebars/cards/cardBAGpromo.svelte";
---
<div class="hidden

6
src/env.d.ts vendored
View File

@@ -1,8 +1,12 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />
/// <reference types="../.astro/astro-typesafe-api.d.ts" />
/// <reference path="../.astro-i18n/generated.d.ts" />
/// <reference path="../.astro/astro-typesafe-api.d.ts" />
declare module "*.csv" {
export default <{ [key: string]: any }>Array;
}
}

View File

@@ -2,9 +2,9 @@
import "../style/global.css";
import "../style/formular.css";
import "../../svelte-dialogs.config"
import Header from "#header/AusweisHeader.astro";
import Footer from "#footer/Footer.astro";
import SidebarLeft from "#sidebarLeft/SidebarLeft.astro";
import Header from "#components/design/header/AusweisHeader.astro";
import Footer from "#components/design/footer/Footer.astro";
import SidebarLeft from "#components/design/sidebars/left/SidebarLeft.astro";
import { NotificationWrapper } from "@ibcornelsen/ui";
export interface Props {

View File

@@ -1,10 +1,10 @@
---
import "../style/global.css";
import "../../svelte-dialogs.config"
import Header from "#header/Header.astro";
import Footer from "#footer/Footer.astro";
import SidebarLeft from "#sidebarLeft/SidebarLeft.astro";
import SidebarRight from "#sidebarRight/SidebarRight.astro";
import Header from "#components/design/header/Header.astro";
import Footer from "#components/design/footer/Footer.astro";
import SidebarLeft from "#components/design/sidebars/left/SidebarLeft.astro";
import SidebarRight from "#components/design/sidebars/right/SidebarRight.astro";
import { NotificationWrapper } from "@ibcornelsen/ui";
export interface Props {

View File

@@ -1,10 +1,10 @@
---
import "../style/global.css";
import "../../svelte-dialogs.config"
import Header from "#header/Header_1.astro";
import Footer from "#footer/Footer.astro";
import SidebarLeft from "#sidebarLeft/SidebarLeft.astro";
import SidebarRight from "#sidebarRight/SidebarRight_1.astro";
import Header from "#components/design/header/Header_1.astro";
import Footer from "#components/design/footer/Footer.astro";
import SidebarLeft from "#components/design/sidebars/left/SidebarLeft.astro";
import SidebarRight from "#components/design/sidebars/right/SidebarRight_1.astro";
import { NotificationWrapper } from "@ibcornelsen/ui";
export interface Props {

View File

@@ -1,10 +1,10 @@
---
import "../style/global.css";
import "../../svelte-dialogs.config"
import Header from "#header/Header.astro";
import Footer from "#footer/Footer.astro";
import SidebarLeft from "#sidebarLeft/SidebarLeft.astro";
import SidebarRight from "#sidebarRight/SidebarRight.astro";
import Header from "#components/design/header/Header.astro";
import Footer from "#components/design/footer/Footer.astro";
import SidebarLeft from "#components/design/sidebars/left/SidebarLeft.astro";
import SidebarRight from "#components/design/sidebars/right/SidebarRight.astro";
import { NotificationWrapper } from "@ibcornelsen/ui";
export interface Props {

View File

@@ -1,9 +1,9 @@
import { BedarfsausweisWohnenClient, GebaeudeAufnahmeClient } from "#components/Ausweis/types.js";
import { berechnungNutzenergiebedarfTrinkwasser } from "./BerechnungNutzenergiebedarfTrinkwarmwasser.js";
import { berechnungNutzenergiebedarfTrinkwarmwasser } from "./BerechnungNutzenergiebedarfTrinkwarmwasser.js";
import { FixedLengthArray } from "./types.js";
export function berechnungWaermequellenAusAnlagentechnikTrinkwasser(ausweis: BedarfsausweisWohnenClient, gebaeude_aufnahme: GebaeudeAufnahmeClient) {
const trinkwasserWaermebedarf = berechnungNutzenergiebedarfTrinkwasser(ausweis, gebaeude_aufnahme);
const trinkwasserWaermebedarf = berechnungNutzenergiebedarfTrinkwarmwasser(ausweis, gebaeude_aufnahme);
const result = new Array(12).fill(0) as unknown as FixedLengthArray<number, 12>

View File

@@ -1,7 +1,6 @@
// Funktion zur Berechnung der Bilanzinnentemperatur aus Tabelle 8 EFH oder Tabelle 10 MFH
import { cubicSplineInterpolation, nevillePolynomialInterpolation } from "js-interpolate";
import { any } from "node_modules/cypress/types/bluebird/index.js";
import { cubicSplineInterpolation } from "js-interpolate";
// aus Eingabeformular
let wohneinheiten = 3;
@@ -149,7 +148,7 @@ const HeizLast = [0, 5, 10, 25, 50, 75, 100, 125, 150];
// jede einzeln interpolieren und dann zwischen den Tabellen interpolieren.
// Falls wir also den Wert an Stelle Heizlast: 120, Zeitkonstante 100, Monat:
// Januar haben wollen:
export function funktionBilanzInnentemperatur(heizlast: number, zeitkonstane: number, monat: keyof typeof dataset): number {
export function funktionBilanzInnentemperatur(heizlast: number, zeitkonstane: number, monat: keyof typeof dataset): number {
const data = dataset[monat]
const interpolations: number[] = []

22
src/lib/auth/token.ts Normal file
View File

@@ -0,0 +1,22 @@
import jwt from "jsonwebtoken";
export enum TokenType {
Refresh,
Access,
Reset
}
export type TokenData = { uid: string, typ: TokenType, exp: number }
export function encodeToken(data: TokenData) {
const token = jwt.sign(data, "yIvbgS$k7Bfc+mpV%TWDZAhje9#uJad4", {
algorithm: "HS256"
});
return token;
}
export function decodeToken(token: string): Partial<TokenData> {
return jwt.verify(token, "yIvbgS$k7Bfc+mpV%TWDZAhje9#uJad4", {
algorithms: ["HS256"]
}) as Partial<TokenData>;
}

View File

@@ -1,12 +1,10 @@
import { AppRouter } from "@ibcornelsen/api";
import { inferProcedureOutput } from "@trpc/server";
import Cookies from "js-cookie";
import { client } from "src/trpc"
import { API_ACCESS_TOKEN_COOKIE_NAME, API_REFRESH_TOKEN_COOKIE_NAME } from "./constants";
import { API_ACCESS_TOKEN_COOKIE_NAME, API_REFRESH_TOKEN_COOKIE_NAME } from "./constants.js";
import { API, api, inferOutput } from "astro-typesafe-api/client";
export async function loginClient(email: string, passwort: string): Promise<inferProcedureOutput<AppRouter["v1"]["benutzer"]["getRefreshToken"]> | null> {
export async function loginClient(email: string, passwort: string): Promise<inferOutput<API["auth"]["refresh-token"]["GET"]> | null> {
try {
const response = await client.v1.benutzer.getRefreshToken.query({
const response = await api.auth["refresh-token"].GET.fetch({
email,
passwort
})

View File

@@ -0,0 +1,81 @@
import { decodeToken } from "#lib/auth/token.js";
import { hashPassword } from "#lib/password.js";
import { prisma } from "@ibcornelsen/database/server";
import { APIError, TypesafeAPIContextWithRequest } from "astro-typesafe-api/server";
export async function authorizationMiddleware(input: any, context: TypesafeAPIContextWithRequest<any>) {
const authorization: string | undefined = context.request.headers.get("Authorization");
if (!context.request.headers.has("Authorization") || !authorization) {
throw new APIError({
code: "BAD_REQUEST",
message: "Request is missing an 'Authorization' header."
})
}
if (authorization.startsWith("Basic")) {
const payload = btoa(authorization.split(" ")[1]).split(":");
if (payload.length !== 2) {
throw new APIError({
code: "BAD_REQUEST",
message: "Malformed 'Authorization' header."
})
}
const [email, password] = payload;
const user = await prisma.benutzer.findUnique({
where: {
email
}
})
if (!user || user.passwort !== hashPassword(password)) {
throw new APIError({
code: "UNAUTHORIZED",
message: "Unknown combination of email and password."
})
}
return user;
} else if (authorization.startsWith("Bearer")) {
const token = authorization.split(" ")[1]
if (!token) {
throw new APIError({
code: "BAD_REQUEST",
message: "Malformed 'Authorization' header."
})
}
const payload = decodeToken(token)
if ((payload.exp || 0) < Date.now()) {
throw new APIError({
code: "UNAUTHORIZED",
message: "Access Token has expired."
})
}
const user = await prisma.benutzer.findUnique({
where: {
uid: payload.uid
}
})
if (!user) {
throw new APIError({
code: "UNAUTHORIZED",
message: "Invalid Bearer Token."
})
}
return user;
}
throw new APIError({
code: "BAD_REQUEST",
message: "Invalid authorization method in 'Authorization' header."
})
}

19
src/lib/password.ts Normal file
View File

@@ -0,0 +1,19 @@
import * as crypto from "crypto";
export function hashPassword(password: string): string {
const salt = crypto.randomBytes(16).toString("hex");
const hash = hashWithGivenSalt(password, salt) + salt;
return hash;
}
export function hashWithGivenSalt(password: string, salt: string): string {
const hash = crypto.scryptSync(password, salt, 32).toString("hex");
return hash;
}
export function validatePassword(known: string, unknown: string): boolean {
const salt = known.slice(64);
const originalPasswordHash = known.slice(0, 64);
const currentPasswordHash = hashWithGivenSalt(unknown, salt)
return originalPasswordHash == currentPasswordHash;
}

View File

@@ -4,7 +4,7 @@ import { PDFElement } from './PDFElement.js';
export class Checkbox extends PDFElement {
private borderWidth: number = 1;
constructor(protected _width: number, protected _height: number) {
constructor(protected _width: number, protected _height: number, public checked: boolean = false) {
super();
}
@@ -22,5 +22,15 @@ export class Checkbox extends PDFElement {
borderColor: rgb(0, 0, 0),
borderWidth: this.borderWidth
});
if (this.checked) {
page.drawSvgPath(`<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M20.285 2l-11.285 11.567-5.286-5.011-3.714 3.716 9 8.728 15-15.285z"/></svg>`, {
x: x + this.borderWidth,
y: y - this.borderWidth * 1.5,
borderColor: rgb(0,0,0),
color: rgb(0,0,0),
scale: this._width / 24
})
}
}
}

View File

@@ -69,7 +69,7 @@ export function xml2pdf(xml: string, fonts: Record<string, PDFFont> & { "default
throw new Error("Missing height or width property in Checkbox creation.")
}
const checkbox = new Checkbox(parseFloat(child.attributes.width), parseFloat(child.attributes.height))
const checkbox = new Checkbox(parseFloat(child.attributes.width), parseFloat(child.attributes.height), (child.attributes.hasOwnProperty("checked") && child.attributes.checked !== "false") || false)
parent.addChild(checkbox);
} else if (child.tagName === "layout") {

View File

@@ -1,176 +0,0 @@
import { VerbrauchsausweisWohnenClient } from "#components/Ausweis/types.js";
import { endEnergieVerbrauchVerbrauchsausweis_2016 } from "#lib/Berechnungen/VerbrauchsausweisWohnen/VerbrauchsausweisWohnen_2016.js";
import * as fs from "fs"
import { PDFDocument, rgb, StandardFonts, TextAlignment } from "pdf-lib";
import { checkbox, flex, text } from "./elements/index.js";
import { xml2pdf } from "./elements/xml2pdf.js";
/* -------------------------------- Pdf Tools ------------------------------- */
export async function pdfDatenblatt(ausweis: VerbrauchsausweisWohnenClient) {
const VerbrauchsausweisWohnenGEG2024PDF = fs.readFileSync(new URL("./templates/Leerseite_Datenblatt.pdf", import.meta.url), "base64");
const pdf = await PDFDocument.load(VerbrauchsausweisWohnenGEG2024PDF)
const pages = pdf.getPages()
// const template = VerbrauchsausweisWohnen2016Template as Template;
const berechnungen = await endEnergieVerbrauchVerbrauchsausweis_2016(ausweis);
const height = pages[0].getHeight()
const width = pages[0].getWidth()
const font = await pdf.embedFont(StandardFonts.Helvetica)
const bold = await pdf.embedFont(StandardFonts.HelveticaBold)
const form = pdf.getForm()
form.updateFieldAppearances(font)
const marginX = 45;
const marginY = 150;
const benutzer: typeof ausweis.benutzer = ausweis.benutzer || {
vorname: "Max",
name: "Mustermann",
adresse: "Musterstraße 123",
plz: "12345",
ort: "Beispielhausen"
};
const layout = xml2pdf(`<layout height="${pages[0].getHeight()}" width="${pages[0].getWidth()}" marginTop="150" marginLeft="45" marginRight="45">
<text size="12" lineHeight="14">${benutzer.vorname} ${benutzer.name}</text>
<text size="12" lineHeight="14">${benutzer.adresse}</text>
<text size="12" lineHeight="14">${benutzer.plz} ${benutzer.ort}</text>
<flex direction="row" justify="space-between" marginTop="55" width="${pages[0].getWidth() - 90}">
<text size="12" font="bold">Datenblatt Energieausweis</text>
<text size="12">Ausweis ID: ${ausweis.uid}</text>
</flex>
<text size="12" lineHeight="14" font="bold" marginTop="10">Gebäudedaten</text>
<text size="12" lineHeight="14">Adresse: ${ausweis.gebaeude_aufnahme_allgemein.adresse}, ${ausweis.gebaeude_aufnahme_allgemein.plz} ${ausweis.gebaeude_aufnahme_allgemein.ort}</text>
<flex direction="row" justify="space-between" width="${pages[0].getWidth() - 90}" marginTop="25">
<flex direction="row" gap="5" align="center">
<checkbox width="8" height="8"></checkbox>
<text size="12">Neubau</text>
</flex>
<flex direction="row" gap="5" align="center">
<checkbox width="8" height="8"></checkbox>
<text size="12">Vermietung/Verkauf</text>
</flex>
<flex direction="row" gap="5" align="center">
<checkbox width="8" height="8"></checkbox>
<text size="12">Modernisierung</text>
</flex>
<flex direction="row" gap="5" align="center">
<checkbox width="8" height="8"></checkbox>
<text size="12">Sonstiges</text>
</flex>
</flex>
<flex direction="row" marginTop="25" gap="15">
<flex direction="column" width="${(pages[0].getWidth() - 90) / 2 - 7.5}">
<flex direction="row" align="center" justify="space-between" width="${(pages[0].getWidth() - 90) / 2 - 7.5}">
<text size="12" lineHeight="14">Gebäudetyp:</text>
<text size="12" lineHeight="14">${ausweis.gebaeude_aufnahme_allgemein.gebaeudetyp}</text>
</flex>
<flex direction="row" align="center" justify="space-between" width="${(pages[0].getWidth() - 90) / 2 - 7.5}">
<text size="12" lineHeight="14">Wohnfläche:</text>
<text size="12" lineHeight="14">DIN Wohnfläche innen ${ausweis.gebaeude_aufnahme_allgemein.flaeche} m²</text>
</flex>
<flex direction="row" align="center" justify="space-between" width="${(pages[0].getWidth() - 90) / 2 - 7.5}">
<text size="12" lineHeight="14">Leerstand:</text>
<text size="12" lineHeight="14">${ausweis.gebaeude_aufnahme_allgemein.leerstand || 0}%</text>
</flex>
<flex direction="row" align="center" justify="space-between" width="${(pages[0].getWidth() - 90) / 2 - 7.5}">
<text size="12" lineHeight="14">Wohnungen:</text>
<text size="12" lineHeight="14">${ausweis.gebaeude_aufnahme_allgemein.einheiten}</text>
</flex>
</flex>
<flex direction="column" width="${(pages[0].getWidth() - 90) / 2 - 7.5}">
<flex direction="row" align="center" justify="space-between" width="${(pages[0].getWidth() - 90) / 2 - 7.5}">
<text size="12" lineHeight="14">Dachgeschoss:</text>
<text size="12" lineHeight="14">${ausweis.gebaeude_aufnahme_allgemein.dachgeschoss}</text>
</flex>
<flex direction="row" align="center" justify="space-between" width="${(pages[0].getWidth() - 90) / 2 - 7.5}">
<text size="12" lineHeight="14">Keller:</text>
<text size="12" lineHeight="14">${ausweis.gebaeude_aufnahme_allgemein.keller}</text>
</flex>
</flex>
</flex>
</layout>`, {
"default": font,
bold: bold
})
layout.draw(pages[0], 0, pages[0].getHeight())
// const containerWidth = width - marginX;
// const layout = flex([
// flex([
// checkbox(8, 8), text("Neubau", {
// color: rgb(0,0,0),
// font,
// fontSize: 12
// })
// ], {
// align: "center",
// justify: "center",
// gap: 5,
// height: 12,
// page: pages[0]
// }),
// flex([
// checkbox(8, 8), text("Vermietung/Verkauf", {
// color: rgb(0,0,0),
// font,
// fontSize: 12
// })
// ], {
// align: "center",
// justify: "center",
// gap: 5,
// height: 12,
// page: pages[0]
// }),
// flex([
// checkbox(8, 8), text("Modernisierung", {
// color: rgb(0,0,0),
// font,
// fontSize: 12
// })
// ], {
// align: "center",
// justify: "center",
// gap: 5,
// height: 12,
// page: pages[0]
// }),
// flex([
// checkbox(8, 8), text("Sonstiges", {
// color: rgb(0,0,0),
// font,
// fontSize: 12
// })
// ], {
// align: "center",
// justify: "center",
// gap: 5,
// height: 12,
// page: pages[0]
// })
// ], {
// align: "center",
// justify: "space-between",
// gap: 15,
// x: marginX,
// y: height - marginY - 165,
// height: 12,
// width: containerWidth
// })
// layout.draw(pages[0])
// pdf.getForm().flatten()
return pdf.save();
}

View File

@@ -0,0 +1,366 @@
import { VerbrauchsausweisWohnenClient } from "#components/Ausweis/types.js";
import { endEnergieVerbrauchVerbrauchsausweis_2016 } from "#lib/Berechnungen/VerbrauchsausweisWohnen/VerbrauchsausweisWohnen_2016.js";
import * as fs from "fs"
import { PDFDocument, rgb, StandardFonts, TextAlignment } from "pdf-lib";
import { checkbox, flex, text } from "./elements/index.js";
import { xml2pdf } from "./elements/xml2pdf.js";
import moment from "moment";
import { Heizungsstatus } from "@ibcornelsen/database/server";
/* -------------------------------- Pdf Tools ------------------------------- */
export async function pdfDatenblattVerbrauchsausweisWohnen(ausweis: VerbrauchsausweisWohnenClient) {
const VerbrauchsausweisWohnenGEG2024PDF = fs.readFileSync(new URL("./templates/Leerseite_Datenblatt.pdf", import.meta.url), "base64");
const pdf = await PDFDocument.load(VerbrauchsausweisWohnenGEG2024PDF)
const pages = pdf.getPages()
// const template = VerbrauchsausweisWohnen2016Template as Template;
const berechnungen = await endEnergieVerbrauchVerbrauchsausweis_2016(ausweis);
const height = pages[0].getHeight()
const width = pages[0].getWidth()
const font = await pdf.embedFont(StandardFonts.Helvetica)
const bold = await pdf.embedFont(StandardFonts.HelveticaBold)
const form = pdf.getForm()
form.updateFieldAppearances(font)
const marginX = 60;
const marginY = 150;
const benutzer: typeof ausweis.benutzer = ausweis.benutzer || {
vorname: "Max",
name: "Mustermann",
adresse: "Musterstraße 123",
plz: "12345",
ort: "Beispielhausen"
};
const translateHeizungsstatus: Record<Heizungsstatus, string> = {
BEHEIZT: "beheizt",
NICHT_VORHANDEN: "nicht vorhanden",
UNBEHEIZT: "unbeheizt"
}
const innerWidth = pages[0].getWidth() - marginX * 2;
const layout = xml2pdf(`<layout height="${pages[0].getHeight()}" width="${pages[0].getWidth()}" marginTop="150" marginLeft="${marginX}" marginRight="${marginX}">
<text size="12" lineHeight="14">${benutzer.vorname} ${benutzer.name}</text>
<text size="12" lineHeight="14">${benutzer.adresse}</text>
<text size="12" lineHeight="14">${benutzer.plz} ${benutzer.ort}</text>
<flex direction="row" justify="space-between" marginTop="55" width="${innerWidth}">
<text size="12" font="bold">Datenblatt Energieausweis</text>
<text size="12">Ausweis ID: ${ausweis.uid}</text>
</flex>
<text size="12" lineHeight="14" font="bold" marginTop="10">Gebäudedaten</text>
<text size="12" lineHeight="14">Adresse: ${ausweis.gebaeude_aufnahme_allgemein.adresse}, ${ausweis.gebaeude_aufnahme_allgemein.plz} ${ausweis.gebaeude_aufnahme_allgemein.ort}</text>
<flex direction="row" justify="space-between" width="${innerWidth}" marginTop="25">
<flex direction="row" gap="5" align="center">
<checkbox width="8" height="8" checked="${ausweis.ausstellgrund === "Neubau"}"></checkbox>
<text size="12">Neubau</text>
</flex>
<flex direction="row" gap="5" align="center">
<checkbox width="8" height="8" checked="${ausweis.ausstellgrund === "Verkauf" || ausweis.ausstellgrund === "Vermietung"}"></checkbox>
<text size="12">Vermietung/Verkauf</text>
</flex>
<flex direction="row" gap="5" align="center">
<checkbox width="8" height="8" checked="${ausweis.ausstellgrund === "Modernisierung"}"></checkbox>
<text size="12">Modernisierung</text>
</flex>
<flex direction="row" gap="5" align="center">
<checkbox width="8" height="8" checked="${ausweis.ausstellgrund === "Sonstiges"}"></checkbox>
<text size="12">Sonstiges</text>
</flex>
</flex>
<flex direction="row" marginTop="25" gap="15">
<flex direction="column" width="${(innerWidth) / 2 - 7.5}">
<flex direction="row" align="center" justify="space-between" width="${(innerWidth) / 2 - 7.5}">
<text size="12" lineHeight="14">Gebäudetyp:</text>
<text size="12" lineHeight="14">${ausweis.gebaeude_aufnahme_allgemein.gebaeudetyp}</text>
</flex>
<flex direction="row" align="center" justify="space-between" width="${(innerWidth) / 2 - 7.5}">
<text size="12" lineHeight="14">Wohnfläche:</text>
<text size="12" lineHeight="14">DIN Wohnfläche innen ${ausweis.gebaeude_aufnahme_allgemein.flaeche} m²</text>
</flex>
<flex direction="row" align="center" justify="space-between" width="${(innerWidth) / 2 - 7.5}">
<text size="12" lineHeight="14">Leerstand:</text>
<text size="12" lineHeight="14">${ausweis.gebaeude_aufnahme_allgemein.leerstand || 0}%</text>
</flex>
<flex direction="row" align="center" justify="space-between" width="${(innerWidth) / 2 - 7.5}">
<text size="12" lineHeight="14">Wohnungen:</text>
<text size="12" lineHeight="14">${ausweis.gebaeude_aufnahme_allgemein.einheiten}</text>
</flex>
</flex>
<flex direction="column" width="${(innerWidth) / 2 - 7.5}">
<flex direction="row" align="center" justify="space-between" width="${(innerWidth) / 2 - 7.5}">
<text size="12" lineHeight="14">Dachgeschoss:</text>
<text size="12" lineHeight="14">${translateHeizungsstatus[ausweis.gebaeude_aufnahme_allgemein.dachgeschoss || "NICHT_VORHANDEN"]}</text>
</flex>
<flex direction="row" align="center" justify="space-between" width="${(innerWidth) / 2 - 7.5}">
<text size="12" lineHeight="14">Keller:</text>
<text size="12" lineHeight="14">${translateHeizungsstatus[ausweis.gebaeude_aufnahme_allgemein.keller || "NICHT_VORHANDEN"]}</text>
</flex>
</flex>
</flex>
<text size="12" lineHeight="14" font="bold" marginTop="25">Verbrauch</text>
<flex direction="row" justify="space-between" align="center" width="${(innerWidth)}" marginTop="5">
<flex direction="column" gap="4">
<text></text>
<text></text>
<text size="12">Von: ${moment(ausweis.startdatum).format("DD.MM.YYYY")}</text>
<text size="12">Von: ${moment(ausweis.startdatum).add("1", "years").format("DD.MM.YYYY")}</text>
<text size="12">Von: ${moment(ausweis.startdatum).add("2", "years").format("DD.MM.YYYY")}</text>
</flex>
<flex direction="column" gap="4">
<text></text>
<text></text>
<text size="12">Bis: ${moment(ausweis.startdatum).add("1", "year").format("DD.MM.YYYY")}</text>
<text size="12">Bis: ${moment(ausweis.startdatum).add("2", "years").format("DD.MM.YYYY")}</text>
<text size="12">Bis: ${moment(ausweis.startdatum).add("3", "years").format("DD.MM.YYYY")}</text>
</flex>
<flex direction="column" gap="4">
<text></text>
<text size="12" font="bold">${ausweis.gebaeude_aufnahme_allgemein.brennstoff_1}</text>
<text size="12">${ausweis.verbrauch_1} ${ausweis.einheit_1}</text>
<text size="12">${ausweis.verbrauch_2} ${ausweis.einheit_1}</text>
<text size="12">${ausweis.verbrauch_3} ${ausweis.einheit_1}</text>
</flex>
<flex direction="column" gap="4">
<text size="12">zusätzliche Heizquelle</text>
<text size="12" font="bold">${ausweis.gebaeude_aufnahme_allgemein.brennstoff_2 || ""}</text>
<text size="12">${ausweis.verbrauch_4 || ""} ${ausweis.einheit_2 || ""}</text>
<text size="12">${ausweis.verbrauch_5 || ""} ${ausweis.einheit_2 || ""}</text>
<text size="12">${ausweis.verbrauch_6 || ""} ${ausweis.einheit_2 || ""}</text>
</flex>
</flex>
<text size="12" marginTop="5">Warmwasseranteil: ${ausweis.anteil_warmwasser_1}%</text>
</layout>`, {
"default": font,
bold: bold
})
const layoutPage2 = xml2pdf(`<layout height="${pages[1].getHeight()}" width="${pages[1].getWidth()}" marginTop="150" marginLeft="60" marginRight="60">
<text size="12" font="bold">Stand der Technik:</text>
<text size="12" marginTop="15">Heizungsanlage</text>
<flex direction="row" justify="space-between" width="${pages[1].getWidth() - 120}" marginTop="15">
<flex direction="column" gap="4">
<flex direction="row" gap="4" align="center">
<checkbox width="8" height="8" checked="${ausweis.gebaeude_aufnahme_allgemein.zentralheizung || false}"></checkbox>
<text size="12">Zentralheizung</text>
</flex>
<flex direction="row" gap="4" align="center">
<checkbox width="8" height="8" checked="${ausweis.gebaeude_aufnahme_allgemein.einzelofen || false}"></checkbox>
<text size="12">Einzelöfen</text>
</flex>
<flex direction="row" gap="4" align="center">
<checkbox width="8" height="8" checked="${ausweis.gebaeude_aufnahme_allgemein.durchlauf_erhitzer || false}"></checkbox>
<text size="12">Durchlauferhitzer</text>
</flex>
<flex direction="row" gap="4" align="center">
<checkbox width="8" height="8" checked="${ausweis.gebaeude_aufnahme_allgemein.standard_kessel || false}"></checkbox>
<text size="12">Standardkessel</text>
</flex>
</flex>
<flex direction="column" gap="4">
<flex direction="row" gap="4" align="center">
<checkbox width="8" height="8" checked="${ausweis.gebaeude_aufnahme_allgemein.solarsystem_warmwasser || false}"></checkbox>
<text size="12">Solarsystem für Warmwasser</text>
</flex>
<flex direction="row" gap="4" align="center">
<checkbox width="8" height="8" checked="${ausweis.gebaeude_aufnahme_allgemein.waermepumpe || false}"></checkbox>
<text size="12">Wärmepumpe</text>
</flex>
<flex direction="row" gap="4" align="center">
<checkbox width="8" height="8" checked="${ausweis.gebaeude_aufnahme_allgemein.niedertemperatur_kessel || false}"></checkbox>
<text size="12">Niedertemperaturkessel</text>
</flex>
<flex direction="row" gap="4" align="center">
<checkbox width="8" height="8" checked="${ausweis.gebaeude_aufnahme_allgemein.brennwert_kessel || false}"></checkbox>
<text size="12">Brennwertkessel</text>
</flex>
</flex>
<flex direction="column" gap="4">
<flex direction="row" gap="4" align="center">
<checkbox width="8" height="8" checked="${ausweis.gebaeude_aufnahme_allgemein.warmwasser_rohre_gedaemmt || false}"></checkbox>
<text size="12">Warmwasserrohre gedämmt</text>
</flex>
<flex direction="row" gap="4" align="center">
<checkbox width="8" height="8" checked="${ausweis.gebaeude_aufnahme_allgemein.heizungsrohre_gedaemmt || false}"></checkbox>
<text size="12">Heizungsrohre gedämmt</text>
</flex>
<flex direction="row" gap="4" align="center">
<checkbox width="8" height="8" checked="${ausweis.gebaeude_aufnahme_allgemein.zirkulation || false}"></checkbox>
<text size="12">Zirkulation</text>
</flex>
<flex direction="row" gap="4" align="center">
<checkbox width="8" height="8" checked="${ausweis.gebaeude_aufnahme_allgemein.raum_temperatur_regler || false}"></checkbox>
<text size="12">Raumtemperaturregelung</text>
</flex>
</flex>
</flex>
<text size="12" marginTop="15">Fenster/Dachfenster/Türen</text>
<flex direction="row" justify="space-between" width="${pages[1].getWidth() - 120}" marginTop="15">
<flex direction="column" gap="4">
<flex direction="row" gap="4" align="center">
<checkbox width="8" height="8" checked="${ausweis.gebaeude_aufnahme_allgemein.einfach_verglasung || false}"></checkbox>
<text size="12">Einfachglas</text>
</flex>
<flex direction="row" gap="4" align="center">
<checkbox width="8" height="8" checked="${ausweis.gebaeude_aufnahme_allgemein.doppel_verglasung || false}"></checkbox>
<text size="12">Doppelverglasung</text>
</flex>
<flex direction="row" gap="4" align="center">
<checkbox width="8" height="8" checked="${ausweis.gebaeude_aufnahme_allgemein.isolier_verglasung || false}"></checkbox>
<text size="12">Isolierverglasung</text>
</flex>
</flex>
<flex direction="column" gap="4">
<flex direction="row" gap="4" align="center">
<checkbox width="8" height="8" checked="${false}"></checkbox>
<text size="12">Passivhausfenster</text>
</flex>
<flex direction="row" gap="4" align="center">
<checkbox width="8" height="8" checked="${ausweis.gebaeude_aufnahme_allgemein.fenster_dicht || false}"></checkbox>
<text size="12">Alle Fenster dicht</text>
</flex>
<flex direction="row" gap="4" align="center">
<checkbox width="8" height="8" checked="${ausweis.gebaeude_aufnahme_allgemein.fenster_teilweise_undicht || false}"></checkbox>
<text size="12">Fenster teilweise undicht</text>
</flex>
</flex>
<flex direction="column" gap="4">
<flex direction="row" gap="4" align="center">
<checkbox width="8" height="8" checked="${ausweis.gebaeude_aufnahme_allgemein.tueren_undicht || false}"></checkbox>
<text size="12">Türen teilweise undicht</text>
</flex>
<flex direction="row" gap="4" align="center">
<checkbox width="8" height="8" checked="${ausweis.gebaeude_aufnahme_allgemein.tueren_dicht || false}"></checkbox>
<text size="12">Alle Türen dicht</text>
</flex>
<flex direction="row" gap="4" align="center">
<checkbox width="8" height="8" checked="${ausweis.gebaeude_aufnahme_allgemein.rolllaeden_kaesten_gedaemmt || false}"></checkbox>
<text size="12">Rollladenkästen gedämmt</text>
</flex>
</flex>
</flex>
<text size="12" marginTop="15">Wärmedämmung</text>
<flex direction="row" justify="space-between" width="${pages[1].getWidth() - 120}" marginTop="15">
<flex direction="column" gap="4">
<flex direction="row" gap="4" align="center">
<checkbox width="8" height="8" checked="${ausweis.gebaeude_aufnahme_allgemein.keller_wand_gedaemmt || false}"></checkbox>
<text size="12">Kelleraußenwand gedämmt</text>
</flex>
<flex direction="row" gap="4" align="center">
<checkbox width="8" height="8" checked="${ausweis.gebaeude_aufnahme_allgemein.keller_decke_gedaemmt || false}"></checkbox>
<text size="12">Kellerdecke gedämmt</text>
</flex>
<flex direction="row" gap="4" align="center">
<checkbox width="8" height="8" checked="${ausweis.gebaeude_aufnahme_allgemein.aussenwand_gedaemmt || false}"></checkbox>
<text size="12">Außenwand gedämmt</text>
</flex>
</flex>
<flex direction="column" gap="4">
<flex direction="row" gap="4" align="center">
<checkbox width="8" height="8" checked="${ausweis.gebaeude_aufnahme_allgemein.dachgeschoss_min_12cm_gedaemmt || false}"></checkbox>
<text size="12">Dachgeschoss min. 12cm gedämmt</text>
</flex>
<flex direction="row" gap="4" align="center">
<checkbox width="8" height="8" checked="${ausweis.gebaeude_aufnahme_allgemein.dachgeschoss_gedaemmt || false}"></checkbox>
<text size="12">Dachgeschoss gedämmt</text>
</flex>
<flex direction="row" gap="4" align="center">
<checkbox width="8" height="8" checked="${ausweis.gebaeude_aufnahme_allgemein.oberste_geschossdecke_gedaemmt || false}"></checkbox>
<text size="12">Oberste Geschossdecke gedämmt</text>
</flex>
<flex direction="row" gap="4" align="center">
<checkbox width="8" height="8" checked="${ausweis.gebaeude_aufnahme_allgemein.oberste_geschossdecke_min_12cm_gedaemmt || false}"></checkbox>
<text size="12">Oberste Geschossdecke min. 12cm gedämmt</text>
</flex>
</flex>
</flex>
</layout>`, {
"default": font,
bold
})
layout.draw(pages[0], 0, pages[0].getHeight())
layoutPage2.draw(pages[1], 0, pages[1].getHeight())
// const containerWidth = width - marginX;
// const layout = flex([
// flex([
// checkbox(8, 8), text("Neubau", {
// color: rgb(0,0,0),
// font,
// fontSize: 12
// })
// ], {
// align: "center",
// justify: "center",
// gap: 5,
// height: 12,
// page: pages[0]
// }),
// flex([
// checkbox(8, 8), text("Vermietung/Verkauf", {
// color: rgb(0,0,0),
// font,
// fontSize: 12
// })
// ], {
// align: "center",
// justify: "center",
// gap: 5,
// height: 12,
// page: pages[0]
// }),
// flex([
// checkbox(8, 8), text("Modernisierung", {
// color: rgb(0,0,0),
// font,
// fontSize: 12
// })
// ], {
// align: "center",
// justify: "center",
// gap: 5,
// height: 12,
// page: pages[0]
// }),
// flex([
// checkbox(8, 8), text("Sonstiges", {
// color: rgb(0,0,0),
// font,
// fontSize: 12
// })
// ], {
// align: "center",
// justify: "center",
// gap: 5,
// height: 12,
// page: pages[0]
// })
// ], {
// align: "center",
// justify: "space-between",
// gap: 15,
// x: marginX,
// y: height - marginY - 165,
// height: 12,
// width: containerWidth
// })
// layout.draw(pages[0])
// pdf.getForm().flatten()
return pdf.save();
}

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { addNotification } from "@ibcornelsen/ui";
import { CrossCircled } from "radix-svelte-icons";
import {client} from "src/trpc";
import {client} from "src/trpc.js";
import { fade } from "svelte/transition";
let passwort: string;

View File

@@ -1,13 +1,9 @@
<script lang="ts">
import PerformanceScore from "#components/Ausweis/PerformanceScore.svelte";
import ProgressBar from "#components/Ausweis/Progressbar.svelte";
//import Hilfe from "#components/Ausweis/Hilfe.svelte";
import { PRICES } from "#lib/constants";
import Progressbar from "#components/Ausweis/Progressbar.svelte";
import { PRICES } from "#lib/constants.js";
import Bereich from "#labels/Bereich.svelte";
//import HelpLabel from "#labels/HelpLabel.svelte";
//import Inputlabel from "#labels/InputLabel.svelte";
//import Label from "#components/Label.svelte";
import Bereich from "#components/labels/Bereich.svelte";
import ButtonSpaeterHilfe from "#components/Ausweis/ButtonSpaeterHilfe.svelte";
import ButtonWeiterHilfe from "#components/Ausweis/ButtonWeiterHilfe.svelte";
@@ -24,13 +20,10 @@
import SanierungszustandWaermedammung from "#components/Ausweis/SanierungszustandWaermedammung.svelte";
import AusweisPreviewContainer from "#components/Ausweis/AusweisPreviewContainer.svelte";
//import ZipSearch from "#components/PlzSuche.svelte";
import {
RawNotificationWrapper,
RawNotification,
notifications,
deleteNotification,
} from "#components/Notifications/index.js";
import { auditHeizungGebaeudeBaujahr } from "#components/Verbrauchsausweis/audits/HeizungGebaeudeBaujahr.js";
import { auditHeizungJuengerDreiJahre } from "#components/Verbrauchsausweis/audits/HeizungJuengerDreiJahre.js";
@@ -48,38 +41,33 @@
import { auditVerbrauchAbweichung } from "#components/Verbrauchsausweis/audits/VerbrauchAbweichung.js";
import { auditEndEnergie } from "#components/Verbrauchsausweis/audits/EndEnergie.js";
import { auditWohnflaecheGroesserGesamtflaeche } from "#components/Verbrauchsausweis/audits/WohnflaecheGroesserGesamtflaeche.js";
//import { Enums } from "@ibcornelsen/database/client"
import Overlay from "#components/Overlay.svelte";
import AusweisGespeichertModule from "#modules/VerbrauchsausweisWohnen/AusweisGespeichertModule.svelte";
import {
import type {
VerbrauchsausweisWohnenClient,
BenutzerClient,
UploadedGebaeudeBild,
} from "#components/Ausweis/types.js";
import { verbrauchsausweisWohnenSpeichern } from "src/client/lib/verbrauchsausweisWohnenSpeichern.js";
//import AusweisWeiter from "./AusweisWeiter.svelte";
import { Enums } from "@ibcornelsen/database/client";
import { OpenInNewWindow } from "radix-svelte-icons";
// TODO: Vom Server sollte ein volles Objekt kommen, dass alle Subobjekte enthält, weil es sonst zu Problemen führen kann
// wenn gebaeude_aufnahme_allgemein oder gebaeude_stammdaten nicht existiert...
export let ausweis: VerbrauchsausweisWohnenClient;
export let user: BenutzerClient = {} as BenutzerClient;
export let Energieausweis = "Verbrauchsausweis Wohngebäude";
let gebaeude_aufnahme_allgemein = ausweis.gebaeude_aufnahme_allgemein || {};
let gebaeude =
ausweis.gebaeude_aufnahme_allgemein?.gebaeude_stammdaten || {};
let aufnahme = ausweis.aufnahme || {};
let objekt =
ausweis.aufnahme?.objekt || {};
let images: (UploadedGebaeudeBild & { base64?: string })[] =
ausweis.gebaeude_aufnahme_allgemein?.gebaeude_stammdaten
ausweis.aufnahme?.objekt
?.gebaeude_bilder || [];
async function spaeterWeitermachen() {
const result = await verbrauchsausweisWohnenSpeichern(
ausweis,
gebaeude,
gebaeude_aufnahme_allgemein,
objekt,
aufnahme,
images,
user
);
@@ -88,8 +76,8 @@
// Falls der Nutzer zurück navigiert, sollte er wieder auf seinen Vorgang kommen.
// Sonst müsste er alles neu eingeben...
ausweis.uid = result.uid;
gebaeude.uid = result.gebaeude_uid;
gebaeude_aufnahme_allgemein.uid = result.gebaeude_aufnahme_uid;
objekt.uid = result.gebaeude_uid;
aufnahme.uid = result.gebaeude_aufnahme_uid;
window.history.pushState(
{},
"",
@@ -100,35 +88,35 @@
}
function automatischAusfüllen() {
gebaeude_aufnahme_allgemein.baujahr_gebaeude = [1962];
gebaeude_aufnahme_allgemein.baujahr_heizung = [1952];
gebaeude_aufnahme_allgemein.saniert = true;
gebaeude_aufnahme_allgemein.einheiten = 1;
gebaeude_aufnahme_allgemein.gebaeudetyp = "Einfamilienhaus";
gebaeude_aufnahme_allgemein.keller =
aufnahme.baujahr_gebaeude = [1962];
aufnahme.baujahr_heizung = [1952];
aufnahme.saniert = true;
aufnahme.einheiten = 1;
aufnahme.gebaeudetyp = "Einfamilienhaus";
aufnahme.keller =
Enums.Heizungsstatus.NICHT_VORHANDEN;
gebaeude_aufnahme_allgemein.dachgeschoss =
aufnahme.dachgeschoss =
Enums.Heizungsstatus.NICHT_VORHANDEN;
gebaeude_aufnahme_allgemein.lueftung = "Fensterlüftung";
gebaeude_aufnahme_allgemein.kuehlung = "1";
aufnahme.lueftung = "Fensterlueftung";
aufnahme.kuehlung = "1";
ausweis.ausstellgrund = "Vermietung";
ausweis.verbrauch_1 = 15000;
ausweis.verbrauch_2 = 14000;
ausweis.verbrauch_3 = 16000;
gebaeude_aufnahme_allgemein.flaeche = 152;
gebaeude_aufnahme_allgemein.nutzflaeche = 172;
aufnahme.flaeche = 152;
aufnahme.nutzflaeche = 172;
ausweis.keller_beheizt = true;
gebaeude_aufnahme_allgemein.brennstoff_1 = "Erdgas H";
aufnahme.brennstoff_1 = "Erdgas H";
ausweis.einheit_1 = "kWh";
ausweis.anteil_warmwasser_1 = 18;
ausweis.startdatum = moment("01.01.2021").toDate();
gebaeude_aufnahme_allgemein.plz = "21039";
gebaeude_aufnahme_allgemein.ort = "Hamburg";
gebaeude_aufnahme_allgemein.adresse = "Curslacker Deich 170";
gebaeude_aufnahme_allgemein.gebaeudeteil = "Gesamtgebäude";
objekt.plz = "21039";
objekt.ort = "Hamburg";
objekt.adresse = "Curslacker Deich 170";
aufnahme.gebaeudeteil = "Gesamtgebäude";
gebaeude = gebaeude;
objekt = objekt;
ausweis = ausweis;
}
@@ -136,8 +124,8 @@
if (e && e.preventDefault) e.preventDefault();
const result = await verbrauchsausweisWohnenSpeichern(
ausweis,
gebaeude,
gebaeude_aufnahme_allgemein,
objekt,
aufnahme,
images,
user
);
@@ -146,8 +134,8 @@
// Falls der Nutzer zurück navigiert, sollte er wieder auf seinen Vorgang kommen.
// Sonst müsste er alles neu eingeben...
ausweis.uid = result.uid;
gebaeude.uid = result.gebaeude_uid;
gebaeude_aufnahme_allgemein.uid = result.gebaeude_aufnahme_uid;
objekt.uid = result.gebaeude_uid;
aufnahme.uid = result.gebaeude_aufnahme_uid;
window.history.pushState(
{},
"",
@@ -162,13 +150,13 @@
$: {
if (
gebaeude_aufnahme_allgemein.saniert &&
gebaeude_aufnahme_allgemein.oberste_geschossdecke_gedaemmt ===
aufnahme.saniert &&
aufnahme.oberste_geschossdecke_gedaemmt ===
undefined &&
gebaeude_aufnahme_allgemein.dachgeschoss_gedaemmt === undefined
aufnahme.dachgeschoss_gedaemmt === undefined
) {
gebaeude_aufnahme_allgemein.oberste_geschossdecke_gedaemmt = true;
gebaeude_aufnahme_allgemein.dachgeschoss_gedaemmt = true;
aufnahme.oberste_geschossdecke_gedaemmt = true;
aufnahme.dachgeschoss_gedaemmt = true;
}
}
@@ -198,8 +186,8 @@
<div id="performance-box" class="w-full box relative px-4 order-2 2xl:order-1 self-stretch grid grid-cols-1">
<PerformanceScore
bind:ausweis
bind:gebaeude_aufnahme_allgemein
bind:gebaeude
bind:gebaeude_aufnahme_allgemein={aufnahme}
bind:gebaeude={objekt}
/>
</div>
@@ -223,8 +211,8 @@
<Bereich bereich="A" title="Prüfung der Ausweisart">
<Ausweisart
bind:gebaeude
bind:gebaeude_aufnahme_allgemein
bind:gebaeude={objekt}
bind:gebaeude_aufnahme_allgemein={aufnahme}
bind:ausweis
{Energieausweis}
/>
@@ -235,7 +223,7 @@
<Bereich
bereich="B"
title="Eingabe der Gebäudeadresse - Angaben zu Wohnfläche, Keller und Dachgeschoss"
><GebaeudeDaten bind:gebaeude_aufnahme_allgemein /></Bereich
><GebaeudeDaten bind:gebaeude_aufnahme_allgemein={aufnahme} /></Bereich
>
<!-- C Eingabe von 3 zusammenhängenden Verbrauchsjahren -->
@@ -244,8 +232,8 @@
bereich="C"
title="Eingabe von 3 zusammenhängenden Verbrauchsjahren"
><Verbrauch
bind:gebaeude
bind:gebaeude_aufnahme_allgemein
bind:gebaeude={objekt}
bind:gebaeude_aufnahme_allgemein={aufnahme}
bind:ausweis
/></Bereich
>
@@ -263,7 +251,7 @@
<Bereich
bereich="E"
title="Eingabe von Gebäudeteil, Lüftung, Kühlung und Leerstand"
><LueftungundLeerstand bind:gebaeude_aufnahme_allgemein /></Bereich
><LueftungundLeerstand bind:gebaeude_aufnahme_allgemein={aufnahme} /></Bereich
>
<!-- F Angaben zur Heizungsanlage -->
@@ -271,8 +259,8 @@
<Bereich bereich="F" title="Angaben zur Heizunganlage"
><SanierungszustandHeizungsanlage
bind:images
bind:gebaeude
bind:gebaeude_aufnahme_allgemein
bind:gebaeude={objekt}
bind:gebaeude_aufnahme_allgemein={aufnahme}
bind:ausweis
/></Bereich
>
@@ -282,8 +270,8 @@
<Bereich bereich="G" title="Angaben zu Fenster, Dachfenster und Türen"
><SanierungszustandFensterTueren
bind:images
bind:gebaeude
bind:gebaeude_aufnahme_allgemein
bind:gebaeude={objekt}
bind:gebaeude_aufnahme_allgemein={aufnahme}
bind:ausweis
/></Bereich
>
@@ -292,8 +280,8 @@
<Bereich bereich="H" title="Angaben zur Wärmedämmung"
><SanierungszustandWaermedammung
bind:images
bind:gebaeude
bind:gebaeude_aufnahme_allgemein
bind:gebaeude={objekt}
bind:gebaeude_aufnahme_allgemein={aufnahme}
bind:ausweis
/></Bereich
>
@@ -304,7 +292,7 @@
><AusweisPreviewContainer
bind:images
bind:ausweis
bind:gebaeude
bind:gebaeude={objekt}
/></Bereich
>
</div>
@@ -313,8 +301,8 @@
bind:ausweis
bind:images
bind:user
bind:gebaeude
bind:gebaeude_aufnahme_allgemein
bind:gebaeude={objekt}
bind:gebaeude_aufnahme_allgemein={aufnahme}
>
</ButtonWeiterHilfe>
@@ -330,7 +318,7 @@
</RawNotification>
{/each}
{#if auditBedarfsausweisBenoetigt(ausweis, gebaeude_aufnahme_allgemein)}
{#if auditBedarfsausweisBenoetigt(ausweis, aufnahme)}
<RawNotification
notification={{
message: "Bedarfsausweis benötigt!",
@@ -346,7 +334,7 @@
</RawNotification>
{/if}
{#if gebaeude_aufnahme_allgemein.nutzflaeche && gebaeude_aufnahme_allgemein.flaeche && gebaeude_aufnahme_allgemein.flaeche === gebaeude_aufnahme_allgemein.nutzflaeche}
{#if aufnahme.nutzflaeche && aufnahme.flaeche && aufnahme.flaeche === aufnahme.nutzflaeche}
<RawNotification
notification={{
message: "Plausibilitätsprüfung",
@@ -361,7 +349,7 @@
</RawNotification>
{/if}
{#if typeof gebaeude_aufnahme_allgemein.einheiten === "number" && gebaeude_aufnahme_allgemein.einheiten < 1}
{#if typeof aufnahme.einheiten === "number" && aufnahme.einheiten < 1}
<RawNotification
notification={{
message: "Plausibilitätsprüfung",
@@ -376,7 +364,7 @@
</RawNotification>
{/if}
{#await auditPlzNichtErkannt(gebaeude_aufnahme_allgemein) then result}
{#await auditPlzNichtErkannt(aufnahme) then result}
{#if result}
<RawNotification
notification={{
@@ -394,7 +382,7 @@
{/if}
{/await}
{#if auditHeizungGebaeudeBaujahr(gebaeude_aufnahme_allgemein)}
{#if auditHeizungGebaeudeBaujahr(aufnahme)}
<RawNotification
notification={{
message: "Plausibilitätsprüfung",
@@ -403,7 +391,7 @@
dismissable: true,
onUserDismiss: () => {
hidden.add(AuditType.HEIZUNG_GEBAEUDE_BAUJAHR);
gebaeude_aufnahme_allgemein = gebaeude_aufnahme_allgemein;
aufnahme = aufnahme;
},
type: "warning",
}}
@@ -413,7 +401,7 @@
</RawNotification>
{/if}
{#if auditHeizungJuengerDreiJahre(gebaeude_aufnahme_allgemein)}
{#if auditHeizungJuengerDreiJahre(aufnahme)}
<RawNotification
notification={{
message: "Plausibilitätsprüfung",
@@ -422,7 +410,7 @@
dismissable: true,
onUserDismiss: () => {
hidden.add(AuditType.HEIZUNG_JUENGER_DREI_JAHRE);
gebaeude = gebaeude;
objekt = objekt;
},
type: "warning",
}}
@@ -436,7 +424,7 @@
</RawNotification>
{/if}
{#if auditZeitraumAktuell(ausweis, gebaeude)}
{#if auditZeitraumAktuell(ausweis, objekt)}
<RawNotification
notification={{
message: "Plausibilitätsprüfung",
@@ -445,7 +433,7 @@
dismissable: true,
onUserDismiss: () => {
hidden.add(AuditType.ZEITRAUM_AKTUELL);
gebaeude = gebaeude;
objekt = objekt;
},
type: "warning",
}}
@@ -458,7 +446,7 @@
</RawNotification>
{/if}
{#await auditKlimaFaktoren(ausweis, gebaeude) then result}
{#await auditKlimaFaktoren(ausweis, objekt) then result}
{#if result}
<RawNotification
notification={{
@@ -468,7 +456,7 @@
dismissable: true,
onUserDismiss: () => {
hidden.add(AuditType.KLIMA_FAKTOREN);
gebaeude = gebaeude;
objekt = objekt;
},
type: "warning",
}}
@@ -483,7 +471,7 @@
{/if}
{/await}
{#if auditWohnFlaeche(gebaeude_aufnahme_allgemein)}
{#if auditWohnFlaeche(aufnahme)}
<RawNotification
notification={{
message: "Plausibilitätsprüfung",
@@ -492,7 +480,7 @@
dismissable: true,
onUserDismiss: () => {
hidden.add(AuditType.WOHN_FLAECHE);
gebaeude = gebaeude;
objekt = objekt;
},
type: "warning",
}}
@@ -512,7 +500,7 @@
dismissable: true,
onUserDismiss: () => {
hidden.add(AuditType.WARM_WASSER);
gebaeude = gebaeude;
objekt = objekt;
},
type: "warning",
}}
@@ -522,7 +510,7 @@
</RawNotification>
{/if}
{#if auditLeerStand(gebaeude_aufnahme_allgemein)}
{#if auditLeerStand(aufnahme)}
<RawNotification
notification={{
message: "Plausibilitätsprüfung",
@@ -531,7 +519,7 @@
dismissable: true,
onUserDismiss: () => {
hidden.add(AuditType.LEER_STAND);
gebaeude = gebaeude;
objekt = objekt;
},
type: "warning",
}}
@@ -542,7 +530,7 @@
</RawNotification>
{/if}
{#if auditVerbrauchAbweichung(ausweis, gebaeude_aufnahme_allgemein).length > 0}
{#if auditVerbrauchAbweichung(ausweis, aufnahme).length > 0}
<RawNotification
notification={{
message: "Plausibilitätsprüfung",
@@ -551,23 +539,23 @@
dismissable: true,
onUserDismiss: () => {
hidden.add(AuditType.VERBRAUCH_ABWEICHUNG);
gebaeude = gebaeude;
objekt = objekt;
},
type: "warning",
}}
>
Die Abweichung der Verbräuche zwischen Zeitraum {auditVerbrauchAbweichung(
ausweis,
gebaeude_aufnahme_allgemein
aufnahme
)[0]} und {auditVerbrauchAbweichung(
ausweis,
gebaeude_aufnahme_allgemein
aufnahme
)[1]} beträgt mehr als 30% und sie haben keinen Leerstand angegeben.
Sind sie sich sicher, dass das stimmt?
</RawNotification>
{/if}
{#await auditEndEnergie(ausweis, gebaeude, gebaeude_aufnahme_allgemein) then result}
{#await auditEndEnergie(ausweis, objekt, aufnahme) then result}
{#if result}
<RawNotification
notification={{
@@ -577,7 +565,7 @@
dismissable: true,
onUserDismiss: () => {
hidden.add(AuditType.END_ENERGIE);
gebaeude = gebaeude;
objekt = objekt;
},
type: "warning",
}}
@@ -589,7 +577,7 @@
{/if}
{/await}
{#if auditWohnflaecheGroesserGesamtflaeche(gebaeude_aufnahme_allgemein)}
{#if auditWohnflaecheGroesserGesamtflaeche(aufnahme)}
<RawNotification
notification={{
message: "Plausibilitätsprüfung",

View File

@@ -3,7 +3,7 @@ layout: ../layouts/Layout.astro
title: "Welcher Energieausweis?"
---
import WelcherAusweisWidget from "#widgets/WelcherAusweisWidget.svelte";
import WelcherAusweisWidget from "#components/widgets/WelcherAusweisWidget.svelte";
import TextboxCardTemplate from "#components/design/content/TextboxCardTemplate.svelte";
# Welcher Energieausweis ist der richtige?

View File

@@ -0,0 +1,46 @@
import { authorizationMiddleware } from "#lib/middleware/authorization.js";
import { prisma } from "@ibcornelsen/database/server";
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
export const PATCH = defineApiRoute({
fetch(input, context) {},
});
export const GET = defineApiRoute({
meta: {
description: "Gibt eine spezifische Aufnhame eines Objektes des Benutzers zurück.",
tags: ["Aufnahme"],
headers: {
"Authorization": {
description: "Ein gültiger Authentifizierungstoken",
required: true,
allowEmptyValue: false,
examples: {
Bearer: {
value: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
}
}
},
middleware: authorizationMiddleware,
async fetch(input, context, user) {
const { uid } = context.params;
const aufnahme = await prisma.aufnahme.findUnique({
where: {
uid,
benutzer_id: user.id
},
});
if (!aufnahme) {
throw new APIError({
code: "NOT_FOUND",
message: "Aufnahme mit dieser UID existiert nicht oder gehört nicht dem aktuellen Benutzer."
})
}
return aufnahme
},
});

View File

View File

@@ -0,0 +1,125 @@
import { z } from "zod";
import moment from "moment";
import { prisma } from "@ibcornelsen/database/server";
import { TokenType, encodeToken } from "../../../lib/auth/token.js";
import { TRPCError } from "@trpc/server";
import { defineApiRoute } from "astro-typesafe-api/server";
export const tRPC_V1_BenutzerGetAccessTokenProcedure = defineApiRoute({
meta: {
description:
"Erstellt, basierend auf einem existierenden und gültigen Refresh Tokens, einen neuen Access Token, welcher zur Authentifizierung genutzt werden kann. Der resultierende Access Token ist nur 2 Tage gültig und muss danach neu generiert werden. Diese Funktion gibt ebenfalls einen neuen Refresh Token zurück, der alte wird dadurch invalidiert.",
tags: ["Benutzer"],
summary: "Access Token anfragen.",
},
input: z.object({
refreshToken: z.string(),
}),
output: z.object({
accessToken: z.string(),
accessTokenExpiry: z.number(),
refreshToken: z.string(),
refreshTokenExpiry: z.number(),
}),
async fetch(input, ctx) {
/**
* Wir benutzen rolling refresh tokens, also löschen wir den alten Token und stellen einen neuen aus,
* damit dieser nicht geklaut werden kann.
*/
const response = await prisma.refreshTokens.findUnique({
where: {
token: input.refreshToken,
},
});
if (!response) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Der gegebene refresh token ist nicht gültig.",
});
}
if (response.expiry < new Date()) {
// Falls der Token abgelaufen ist, müssen wir ihn löschen.
await prisma.refreshTokens.delete({
where: {
id: response.id,
},
});
throw new TRPCError({
code: "BAD_REQUEST",
message: "Der gegebene refresh token ist nicht gültig.",
});
}
// TODO: Das können wir später implementieren, falls wir das wirklich brauchen.
// if (response.ip !== opts.ctx.ip) {
// // Falls der Token nicht von der selben IP Adresse kommt, müssen wir ihn löschen.
// await prisma.refreshTokens.delete({
// where: {
// id: response.id
// }
// })
// throw new TRPCError({ code: "BAD_REQUEST", message: "Der gegebene refresh token wurde von einer anderen IP-Adresse ausgestellt, aus Sicherheitsgründen haben wir uns entschieden diesen zu invalidieren." });
// }
const user = await prisma.benutzer.findUnique({
where: {
id: response.benutzer_id,
},
});
if (!user) {
// Falls der Nutzer nicht mehr existiert müssen wir den Refresh Token invalidieren.
await prisma.refreshTokens.delete({
where: {
id: response.id,
},
});
throw new TRPCError({
code: "BAD_REQUEST",
message: "Der gegebene refresh token ist nicht mehr gültig.",
});
}
// Wir löschen den alten Token.
await prisma.refreshTokens.delete({
where: {
id: response.id,
},
});
const refreshTokenExpiry = moment().add(30, "days");
const refreshToken = encodeToken({
uid: user.uid,
typ: TokenType.Refresh,
exp: refreshTokenExpiry.unix(),
});
// Und erstellen einen neuen
await prisma.refreshTokens.create({
data: {
benutzer_id: user.id,
expiry: refreshTokenExpiry.toDate(),
ip: ctx.clientAddress ?? "",
token: refreshToken,
},
});
const accessTokenExpiry = moment().add(2, "days").unix();
const accessToken = encodeToken({
uid: user.uid,
typ: TokenType.Access,
exp: accessTokenExpiry,
});
return {
accessToken,
accessTokenExpiry: accessTokenExpiry,
refreshToken,
refreshTokenExpiry: refreshTokenExpiry.unix(),
};
},
});

View File

@@ -0,0 +1,82 @@
import { z } from "zod";
import moment from "moment";
import { prisma } from "@ibcornelsen/database/server";
import { TokenType, encodeToken } from "../../../lib/auth/token.js";
import { hashPassword, validatePassword } from "../../../lib/password.js";
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
export const GET = defineApiRoute({
meta: {
description:
"Erstellt sowohl einen neuen Refresh Token als auch einen Access Token für den gegebenen Benutzer. Der Refresh Token kann später für die Erstellung neuer Access Token genutzt werden.",
tags: ["Benutzer"],
summary: "Refresh Token anfragen.",
},
input: z.object({
email: z.string().email(),
passwort: z.string().min(8).max(100),
}),
output: z.object({
uid: z.string().uuid(),
accessToken: z.string(),
refreshToken: z.string(),
refreshTokenBase64: z.string(),
accessTokenBase64: z.string(),
exp: z.number(),
}),
async fetch(input, ctx) {
console.log(input);
// Falls der Nutzer nicht existiert, wird eine Fehlermeldung zurückgegeben.
const user = await prisma.benutzer.findUnique({
where: {
email: input.email,
},
});
if (!user) {
throw new APIError({
code: "BAD_REQUEST",
message: "Benutzer konnte nicht gefunden werden.",
});
}
// Falls das Passwort nicht stimmt, wird eine Fehlermeldung zurückgegeben.
if (!validatePassword(user.passwort, input.passwort)) {
throw new APIError({
code: "BAD_REQUEST",
message: "Benutzer konnte nicht gefunden werden.",
});
}
const refreshTokenExpiry = moment().add(30, "days");
const accessToken = encodeToken({
uid: user.uid,
typ: TokenType.Access,
exp: moment().add(30, "minutes").valueOf(),
});
const refreshToken = encodeToken({
uid: user.uid,
typ: TokenType.Refresh,
exp: refreshTokenExpiry.valueOf(),
});
const { id } = await prisma.refreshTokens.create({
data: {
token: refreshToken,
benutzer_id: user.id,
ip: ctx.clientAddress ?? "",
expiry: refreshTokenExpiry.toDate(),
},
});
return {
uid: user.uid,
accessToken,
refreshToken,
refreshTokenBase64: Buffer.from(refreshToken).toString("base64"),
accessTokenBase64: Buffer.from(accessToken).toString("base64"),
exp: refreshTokenExpiry.valueOf(),
};
},
});

View File

@@ -0,0 +1,46 @@
import { authorizationMiddleware } from "#lib/middleware/authorization.js";
import { prisma } from "@ibcornelsen/database/server";
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
export const PATCH = defineApiRoute({
fetch(input, context) {},
});
export const GET = defineApiRoute({
meta: {
description: "Gibt ein spezifisches Gebäude des Benutzers zurück.",
tags: ["Gebäude"],
headers: {
"Authorization": {
description: "Ein gültiger Authentifizierungstoken",
required: true,
allowEmptyValue: false,
examples: {
Bearer: {
value: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
}
}
},
middleware: authorizationMiddleware,
async fetch(input, context, user) {
const { uid } = context.params;
const objekt = await prisma.objekt.findUnique({
where: {
uid,
benutzer_id: user.id
},
});
if (!objekt) {
throw new APIError({
code: "NOT_FOUND",
message: "Objekt mit dieser UID existiert nicht oder gehört nicht dem aktuellen Benutzer."
})
}
return objekt
},
});

View File

@@ -0,0 +1,30 @@
import { authorizationMiddleware } from "#lib/middleware/authorization.js";
import { ObjektSchema, prisma } from "@ibcornelsen/database/server";
import { defineApiRoute } from "astro-typesafe-api/server";
import { z } from "zod";
export const POST = defineApiRoute({
fetch(input, context) {
},
})
export const GET = defineApiRoute({
input: z.object({
limit: z.number()
}),
output: z.array(ObjektSchema),
middleware: authorizationMiddleware,
async fetch(input, context, transfer) {
const objekte = await prisma.objekt.findMany({
take: input.limit,
where: {
benutzer: {
id: transfer.id
}
}
})
return objekte
},
})

View File

@@ -0,0 +1,23 @@
import { BenutzerSchema } from "@ibcornelsen/database/server";
import { z } from "zod";
import { defineApiRoute } from "astro-typesafe-api/server";
import { authorizationMiddleware } from "#lib/middleware/authorization.js";
export const GET = defineApiRoute({
meta: {
description:
"Gibt die Daten des momentan eingeloggten Benutzers zurück. Falls der Authorization Key invalid ist wird stattdessen null zurückgegeben.",
summary: "Gibt die Daten des eingeloggten Benutzers zurück.",
tags: ["Benutzer"],
},
input: z.void(),
output: BenutzerSchema.omit({
passwort: true,
id: true,
}).or(z.null()),
middleware: authorizationMiddleware,
async fetch(input, ctx, transfer) {
return transfer;
},
});

View File

@@ -0,0 +1,71 @@
import { authorizationMiddleware } from "#lib/middleware/authorization.js";
import { prisma } from "@ibcornelsen/database/server";
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
export const PATCH = defineApiRoute({
fetch(input, context) {},
});
export const GET = defineApiRoute({
meta: {
description: "Gibt ein spezifisches Gebäude des Benutzers zurück.",
tags: ["Gebäude"],
headers: {
"Authorization": {
description: "Ein gültiger Authentifizierungstoken",
required: true,
allowEmptyValue: false,
examples: {
Bearer: {
value: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
}
}
},
middleware: authorizationMiddleware,
async fetch(input, context, user) {
const { uid } = context.params;
const ausweis = await prisma.verbrauchsausweisWohnen.findUnique({
where: {
uid: input.uid,
},
include: {
benutzer: true,
aufnahme: {
include: {
objekt: {
include: {
gebaeude_bilder: true
},
},
rechnungen: true,
events: {
include: {
benutzer: {
select: {
uid: true
}
}
},
orderBy: {
date: "asc"
}
}
},
}
},
});
if (!ausweis || (ausweis.benutzer_id !== null && ausweis.benutzer_id !== user.id)) {
// Falls wir den Ausweis nicht finden können, werfen wir einen Fehler
throw new APIError({
code: "NOT_FOUND",
message: "Ausweis konnte nicht gefunden werden.",
});
}
return ausweis
},
});

View File

@@ -0,0 +1,71 @@
import { authorizationMiddleware } from "#lib/middleware/authorization.js";
import { prisma } from "@ibcornelsen/database/server";
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
export const PATCH = defineApiRoute({
fetch(input, context) {},
});
export const GET = defineApiRoute({
meta: {
description: "Gibt ein spezifisches Gebäude des Benutzers zurück.",
tags: ["Gebäude"],
headers: {
"Authorization": {
description: "Ein gültiger Authentifizierungstoken",
required: true,
allowEmptyValue: false,
examples: {
Bearer: {
value: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
}
}
},
middleware: authorizationMiddleware,
async fetch(input, context, user) {
const { uid } = context.params;
const ausweis = await prisma.verbrauchsausweisWohnen.findUnique({
where: {
uid,
},
include: {
benutzer: true,
aufnahme: {
include: {
objekt: {
include: {
gebaeude_bilder: true
},
},
rechnungen: true,
events: {
include: {
benutzer: {
select: {
uid: true
}
}
},
orderBy: {
date: "asc"
}
}
},
}
},
});
if (!ausweis || (ausweis.benutzer_id !== null && ausweis.benutzer_id !== user.id)) {
// Falls wir den Ausweis nicht finden können, werfen wir einen Fehler
throw new APIError({
code: "NOT_FOUND",
message: "Ausweis konnte nicht gefunden werden.",
});
}
return ausweis
},
});

View File

@@ -1,8 +1,9 @@
---
import { createCaller } from "#lib/caller";
import { createCaller } from "../../astro-typesafe-api-caller.js";
import UserLayout from "../../layouts/UserLayout.astro";
import { validateAccessTokenServer } from "#server/lib/validateAccessToken";
import DashboardModule from "#modules/Dashboard/DashboardModule.svelte";
import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants";
const accessTokenValid = await validateAccessTokenServer(Astro);
@@ -12,8 +13,16 @@ if (!accessTokenValid) {
const caller = createCaller(Astro);
const user = await caller.v1.benutzer.self(undefined);
const gebaeudeArray = await caller.v1.gebaeude.getMany({ limit: 5 });
const user = await caller.user.self.GET.fetch(undefined, {
headers: {
"Authorization": `Bearer ${Astro.cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)?.value}`
}
});
const gebaeudeArray = await caller.objekt.GET.fetch({ limit: 5 }, {
headers: {
"Authorization": `Bearer ${Astro.cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)?.value}`
}
});
---
<UserLayout title="Dashboard">

View File

@@ -2,23 +2,34 @@
import AusweisLayout from "#layouts/AusweisLayoutDaten.astro";
import VerbrauchsausweisWohnenModule from "#modules/VerbrauchsausweisWohnen/VerbrauchsausweisWohnenModule.svelte";
import { VerbrauchsausweisWohnenClient } from "#components/Ausweis/types";
import { createCaller } from "#lib/caller";
import { createCaller } from "../../../astro-typesafe-api-caller.js";
const uid = Astro.url.searchParams.get("uid");
let ausweis: VerbrauchsausweisWohnenClient = {
gebaeude_aufnahme_allgemein: { gebaeude_stammdaten: {} },
aufnahme: { objekt: {} },
} as VerbrauchsausweisWohnenClient;
const caller = createCaller(Astro);
const caller = await createCaller(Astro);
if (uid) {
ausweis = await caller.v1.verbrauchsausweisWohnen.get({
uid: uid,
});
try {
ausweis = await caller["verbrauchsausweis-wohnen"]._id.GET.fetch(null, {
headers: {
authorization: "Basic "
},
params: {
id: "123"
}
});
if (!ausweis) {
// Der Ausweis scheint nicht zu existieren.
// Wir leiten auf die generische Ausweisseite ohne UID weiter.
if (!ausweis) {
// Der Ausweis scheint nicht zu existieren.
// Wir leiten auf die generische Ausweisseite ohne UID weiter.
return Astro.redirect(
"/energieausweis-erstellen/verbrauchsausweis-wohnen"
);
}
} catch(e) {
return Astro.redirect(
"/energieausweis-erstellen/verbrauchsausweis-wohnen"
);

View File

@@ -1,8 +1,8 @@
---
import Layout from "#layouts/Layout.astro";
import ProduktVergleich from "#content/ProduktVergleich.svelte";
import ProduktVergleichGewerbe from "#content/ProduktVergleichGewerbe.svelte";
import WelcherAusweisWidget from "#widgets/WelcherAusweisWidget.svelte";
import ProduktVergleich from "#components/design/content/ProduktVergleich.svelte";
import ProduktVergleichGewerbe from "#components/design/content/ProduktVergleichGewerbe.svelte";
import WelcherAusweisWidget from "#components/widgets/WelcherAusweisWidget.svelte";
import TextboxCardTemplate from "#components/design/content/TextboxCardTemplate.svelte";
---

View File

@@ -1,7 +1,7 @@
---
import { BenutzerClient, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types";
import { createCaller } from "#lib/caller";
import { pdfDatenblatt } from "#lib/pdf/pdfDatenblatt";
import { pdfDatenblattVerbrauchsausweisWohnen } from "#lib/pdf/pdfDatenblattVerbrauchsausweisWohnen";
const base64 = Astro.url.searchParams.get("base64");
let ausweis: VerbrauchsausweisWohnenClient | null = null;
@@ -23,7 +23,7 @@ if (base64) {
});
}
const pdf = await pdfDatenblatt(ausweis);
const pdf = await pdfDatenblattVerbrauchsausweisWohnen(ausweis);
return new Response(pdf, {

View File

@@ -1,6 +1,6 @@
---
import Layout from "#layouts/Layout.astro";
import WelcherAusweisWidget from "#widgets/WelcherAusweisWidget.svelte";
import WelcherAusweisWidget from "#components/widgets/WelcherAusweisWidget.svelte";
import TextboxCardTemplate from "#components/design/content/TextboxCardTemplate.svelte";
---

View File

@@ -1,7 +1,6 @@
---
import Layout from "#layouts/WidgetLayout_1.astro";
import WelcherAusweisWidget from "#widgets/WelcherAusweisWidget_1.svelte";
import WelcherAusweisWidget from "#components/widgets/WelcherAusweisWidget.svelte";
const { vermittler } = Astro.params;

View File

@@ -27,20 +27,9 @@
"#modules/*": ["./src/modules/*"],
"#client/*": ["./src/client/*"],
"#server/*": ["./src/server/*"],
"#style/*": ["./src/style/*"],
"#footer/*": ["./src/components/design/footer/*"],
"#header/*": ["./src/components/design/header/*"],
"#content/*": ["./src/components/design/content/*"],
"#sidebarCards/*": ["./src/components/design/sidebars/cards/*"],
"#sidebarLeft/*": ["./src/components/design/sidebars/left/*"],
"#sidebarRight/*": ["./src/components/design/sidebars/right/*"],
"#ausweise/*": ["./src/pages/energieausweis-erstellen/*"],
"#labels/*": ["./src/components/labels/*"],
"#widgets/*": ["./src/components/widgets/*"]
"#style/*": ["./src/style/*"]
},
"types": ["cypress", "cypress-file-upload", "bun-types", "svelte"]
},
"exclude": ["node_modules"]
"include": ["src/**/*", "tests/**/*"]
}