From ff16c3b54722a90baea2568f06191c0d358b1a40 Mon Sep 17 00:00:00 2001 From: Moritz Utcke Date: Sun, 7 Jan 2024 22:58:12 +0700 Subject: [PATCH] =?UTF-8?q?tRPC=20Hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 + .../Ausweis/PerformanceScore.svelte | 10 +- .../audits/BedarfsausweisBenoetigt.ts | 2 +- src/components/ZIPSearch.svelte | 3 +- src/lib/Ausweis/Ausweis.ts | 37 -- src/lib/Ausweis/Bedarfsausweis.ts | 437 ------------------ src/lib/Ausweis/Verbrauchsausweis.ts | 154 ------ src/lib/Ausweis/VerbrauchsausweisGewerbe.ts | 237 ---------- src/lib/Ausweis/types.ts | 25 - .../VerbrauchsausweisWohnen_2016.ts | 20 +- src/lib/Klimafaktoren.ts | 60 +-- src/lib/Klimafaktoren/getClimateFactor.ts | 33 -- src/lib/Klimafaktoren/index.ts | 0 src/lib/XML/AusweisBerechnungen2016.ts | 347 ++++++++++++++ .../xmlVerbrauchsausweisWohnen_2016.ts | 350 ++++++++++++++ src/lib/XML/getEmpfehlungen.ts | 125 +++++ src/lib/ZIPInformation/index.ts | 47 -- src/lib/client/Ausweis/Verbrauchsausweis.ts | 145 ------ src/lib/client/Ausweis/index.ts | 1 - src/lib/client/Gebaeude/Gebaeude.ts | 36 -- src/lib/client/Gebaeude/index.ts | 0 src/lib/client/fetch.ts | 9 - ...e => VerbrauchsausweisWohnenModule.svelte} | 68 +-- src/pages/api/ausweis/bedarfsausweis.ts | 137 ------ src/pages/api/ausweis/verbrauchsausweis.ts | 149 ------ src/pages/api/building/images.json.ts | 39 -- src/pages/api/klimafaktor/index.ts | 63 --- src/pages/api/trpc/[trpc].ts | 31 ++ src/pages/api/trpc/context.ts | 34 ++ src/pages/api/trpc/procedures/v1/index.ts | 42 ++ .../api/trpc/procedures/v1/klimafaktoren.ts | 78 ++++ .../2016/erstellen.ts | 127 +++++ .../2016/zusatzdaten-erfassung.xml.ts | 41 ++ src/pages/api/user.ts | 4 +- src/pages/api/zip.ts | 22 +- src/pages/verbrauchsausweis/erstellen.astro | 8 - src/pages/verbrauchsausweis/index.astro | 4 +- src/trpc.ts | 16 + test.ts | 10 + 39 files changed, 1302 insertions(+), 1652 deletions(-) delete mode 100644 src/lib/Ausweis/Ausweis.ts delete mode 100644 src/lib/Ausweis/Bedarfsausweis.ts delete mode 100644 src/lib/Ausweis/Verbrauchsausweis.ts delete mode 100644 src/lib/Ausweis/VerbrauchsausweisGewerbe.ts delete mode 100644 src/lib/Ausweis/types.ts delete mode 100644 src/lib/Klimafaktoren/getClimateFactor.ts delete mode 100644 src/lib/Klimafaktoren/index.ts create mode 100644 src/lib/XML/AusweisBerechnungen2016.ts create mode 100644 src/lib/XML/VerbrauchsausweisWohnen/xmlVerbrauchsausweisWohnen_2016.ts create mode 100644 src/lib/XML/getEmpfehlungen.ts delete mode 100644 src/lib/ZIPInformation/index.ts delete mode 100644 src/lib/client/Ausweis/Verbrauchsausweis.ts delete mode 100644 src/lib/client/Ausweis/index.ts delete mode 100644 src/lib/client/Gebaeude/Gebaeude.ts delete mode 100644 src/lib/client/Gebaeude/index.ts delete mode 100644 src/lib/client/fetch.ts rename src/modules/Ausweise/{Verbrauchsausweis.svelte => VerbrauchsausweisWohnenModule.svelte} (91%) delete mode 100644 src/pages/api/ausweis/bedarfsausweis.ts delete mode 100644 src/pages/api/ausweis/verbrauchsausweis.ts delete mode 100644 src/pages/api/building/images.json.ts delete mode 100644 src/pages/api/klimafaktor/index.ts create mode 100644 src/pages/api/trpc/[trpc].ts create mode 100644 src/pages/api/trpc/context.ts create mode 100644 src/pages/api/trpc/procedures/v1/index.ts create mode 100644 src/pages/api/trpc/procedures/v1/klimafaktoren.ts create mode 100644 src/pages/api/trpc/procedures/v1/verbrauchsausweis-wohnen/2016/erstellen.ts create mode 100644 src/pages/api/trpc/procedures/v1/verbrauchsausweis-wohnen/2016/zusatzdaten-erfassung.xml.ts delete mode 100644 src/pages/verbrauchsausweis/erstellen.astro create mode 100644 src/trpc.ts create mode 100644 test.ts diff --git a/package.json b/package.json index 91179b97..fe53573f 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,8 @@ "@ibcornelsen/database": "link:@ibcornelsen/database", "@ibcornelsen/ui": "^0.0.2", "@mollie/api-client": "^3.7.0", + "@trpc/client": "^10.45.0", + "@trpc/server": "^10.45.0", "astro": "^2.5.1", "astro-i18next": "1.0.0-beta.21", "bun": "^1.0.2", @@ -46,6 +48,7 @@ "svelte": "^3.59.1", "svelte-preprocess": "^5.0.3", "tailwindcss": "^3.3.2", + "trpc-openapi": "^1.2.0", "uuid": "^9.0.0", "vite-tsconfig-paths": "^4.2.0", "zod": "^3.21.4" diff --git a/src/components/Ausweis/PerformanceScore.svelte b/src/components/Ausweis/PerformanceScore.svelte index ed82e312..54b5cc2f 100644 --- a/src/components/Ausweis/PerformanceScore.svelte +++ b/src/components/Ausweis/PerformanceScore.svelte @@ -1,5 +1,5 @@ + +

Verbrauchsausweis erstellen - 45€

@@ -69,8 +86,8 @@
-
-
+
@@ -107,8 +124,7 @@
-
Ort des Gebäudes wird automatisch ermittelt.
Keller *
+ Warmwasser im Verbrauch enthalten HeizungWarmwasserLüftungKühlung
-
-
+
+ diff --git a/src/pages/api/ausweis/bedarfsausweis.ts b/src/pages/api/ausweis/bedarfsausweis.ts deleted file mode 100644 index b9d551f3..00000000 --- a/src/pages/api/ausweis/bedarfsausweis.ts +++ /dev/null @@ -1,137 +0,0 @@ -import type { APIRoute } from "astro"; -import { error, success } from "src/lib/APIResponse"; -import { z } from "zod"; - -const AusweisUploadChecker = z.object({ - typ: z.enum(["VA", "BA", "VANW"]), - - objekt: z.object({ - typ: z.string(), - plz: z.string().min(4).max(5), - ort: z.string(), - strasse: z.string(), - baujahr: z.number(), - saniert: z.boolean(), - gebaeudeteil: z.enum(["Gesamtgebäude", "Wohnen"]), - einheiten: z.number(), - }), - - heizquellen: z - .array( - z.object({ - verbrauch: z.array(z.number()).max(3).min(3), - einheit: z.string(), - brennstoff: z.string(), - anteil_warmwasser: z.number(), - }) - ) - .max(2) - .min(1), - - energieverbrauch_zeitraum: z.date(), - wohnflaeche: z.number(), - - keller_beheizt: z.boolean(), - dachgeschoss: z.number(), - zusaetzliche_heizquelle: z.boolean(), - warmwasser_enthalten: z.boolean(), - lueftungskonzept: z.enum([ - "Fensterlüftung", - "Schachtlüftung", - "Lüftungsanlage ohne Wärmerückgewinnung", - "Lüftungsanlage mit Wärmerückgewinnung", - ]), - wird_gekuehlt: z.boolean(), - leerstand: z.number(), - images: z.array(z.string()), - - versorgungssysteme: z.number(), - fenster_dach: z.number(), - energiequelle_2_nutzung: z.number(), - daemmung: z.number(), - - /** - * Bedarfsausweis spezifische Eigenschaften - */ - anzahl_vollgeschosse: z.number(), - geschosshoehe: z.number(), - anzahl_gauben: z.number(), - breite_gauben: z.number(), - masse_a: z.number(), - masse_b: z.number(), - masse_c: z.number(), - masse_d: z.number(), - masse_e: z.number(), - masse_f: z.number(), - fensterflaeche_so_sw: z.number(), - fensterflaeche_nw_no: z.number(), - aussenwandflaeche_unbeheizt: z.number(), - dachflaeche: z.number(), - dach_u_wert: z.number(), - deckenflaeche: z.number(), - decke_u_wert: z.number(), - aussenwand_flaeche: z.number(), - aussenwand_u_wert: z.number(), - fussboden_flaeche: z.number(), - fussboden_u_wert: z.number(), - volumen: z.number(), - dicht: z.boolean(), - fenster_flaeche_1: z.number(), - fenster_art_1: z.number(), - fenster_flaeche_2: z.number(), - fenster_art_2: z.number(), - dachfenster_flaeche: z.number(), - dachfenster_art: z.number(), - haustuer_flaeche: z.number(), - haustuer_art: z.number(), - dach_bauart: z.string(), - dach_daemmung: z.number(), - decke_bauart: z.string(), - decke_daemmung: z.number(), - aussenwand_bauart: z.string(), - aussenwand_daemmung: z.number(), - boden_bauart: z.string(), - boden_daemmung: z.number(), - warmwasser_verteilung: z.string(), - warmwasser_speicherung: z.string(), - warmwasser_erzeugung: z.string(), - heizung_zentral: z.boolean(), - heizung_verteilung: z.string(), - heizung_speicherung: z.string(), - waerme_erzeugung_heizung: z.string(), - anteil_zusatzheizung: z.number(), - kollektor_flaeche: z.number(), - - // VANW - - vanw_stromverbrauch_enthalten: z.number(), - vanw_stromverbrauch_sonstige: z.string(), - vanw_strom_1: z.number(), - vanw_strom_2: z.number(), - vanw_strom_3: z.number(), - - erledigt: z.boolean(), - - anrede: z.string(), - name: z.string(), - vorname: z.string(), - email: z.string(), - telefonnummer: z.string(), -}); - -/** - * Erstellt einen Verbrauchsausweis anhand der gegebenen Daten und trägt ihn in die Datenbank ein. - * @param param0 - * @returns - */ -export const post: APIRoute = async ({ request }) => { - const body = await request.json(); - - const result = AusweisUploadChecker.safeParse(body); - - if (!result.success) { - return error(result.error.issues); - } - - return success({}); -}; diff --git a/src/pages/api/ausweis/verbrauchsausweis.ts b/src/pages/api/ausweis/verbrauchsausweis.ts deleted file mode 100644 index 74950c8a..00000000 --- a/src/pages/api/ausweis/verbrauchsausweis.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { prisma } from "@ibcornelsen/database"; -import type { APIRoute } from "astro"; -import { ActionFailedError, MissingEntityError, error, success } from "src/lib/APIResponse"; -import { Ausweis } from "src/lib/Ausweis/Ausweis"; -import { Gebaeude } from "src/lib/Gebaeude"; -import { z } from "zod"; - -const AusweisUploadChecker = z.object({ - ausweis: z.object({ - ausweisart: z.enum(["VA", "BA", "VANW"]), - ausstellgrund: z.enum([ - "Vermietung", - "Neubau", - "Verkauf", - "Modernisierung", - "Sonstiges", - ]), - }), - - gebaeude: z.object({ - typ: z.string(), - plz: z.string(), - ort: z.string(), - strasse: z.string(), - gebaeudeteil: z.string(), - saniert: z.boolean(), - baujahr: z.number(), - einheiten: z.number(), - wohnflaeche: z.number(), - keller_beheizt: z.boolean(), - dachgeschoss_beheizt: z.number(), - lueftungskonzept: z.enum([ - "Fensterlüftung", - "Schachtlüftung", - "Lüftungsanlage ohne Wärmerückgewinnung", - "Lüftungsanlage mit Wärmerückgewinnung", - ]), - wird_gekuehlt: z.boolean(), - leerstand: z.number(), - versorgungssysteme: z.number(), - fenster_dach: z.number(), - energiequelle_2_nutzung: z.number(), - daemmung: z.number(), - }), - - kennwerte: z.object({ - zeitraum: z.string(), - verbrauch_1: z.number(), - verbrauch_2: z.number(), - verbrauch_3: z.number(), - verbrauch_4: z.number(), - verbrauch_5: z.number(), - verbrauch_6: z.number(), - einheit_1: z.string(), - einheit_2: z.string(), - energietraeger_1: z.string(), - energietraeger_2: z.string(), - anteil_warmwasser_1: z.number(), - anteil_warmwasser_2: z.number(), - }), - - gebaeude_uid: z.string().optional(), - kennwerte_uid: z.string().optional(), - ausweis_uid: z.string().optional(), -}); - -const AusweisDownloadChecker = z.object({ - uid: z.string() -}) - -/** - * Erstellt einen Verbrauchsausweis anhand der gegebenen Daten und trägt ihn in die Datenbank ein. - * @param param0 - * @returns - */ -export const post: APIRoute = async ({ request }) => { - const body: z.infer = await request.json(); - - const validation = AusweisUploadChecker.safeParse(body); - - if (!validation.success) { - return error(validation.error.issues); - } - - let gebaeude, ausweis; - if (body.gebaeude_uid) { - gebaeude = await prisma.gebaeudeStammdaten.update({ - where: { - uid: body.gebaeude_uid, - }, - data: body.gebaeude, - }) - } else { - gebaeude = await prisma.gebaeudeStammdaten.create({ - data: body.gebaeude, - }) - } - - if (!gebaeude) { - return ActionFailedError(); - } - - if (body.ausweis_uid) { - ausweis = await prisma.verbrauchsausweisWohnen.update({ - where: { - uid: body.ausweis_uid, - }, - data: body.ausweis, - }) - } else { - ausweis = await prisma.verbrauchsausweisWohnen.create({ - data: body.ausweis, - }) - } - - if (!ausweis) { - return ActionFailedError(); - } - - return success({ - ausweis: ausweis, - gebaeude: gebaeude, - }); -}; - -export const get: APIRoute = async ({ request }) => { - const body: z.infer = await request.json(); - - const validation = AusweisDownloadChecker.safeParse(body); - - if (!validation.success) { - return error(validation.error.issues); - } - - const ausweis = await prisma.verbrauchsausweisWohnen.findUnique({ - where: { - uid: body.uid, - }, - include: { - gebaeude_stammdaten: true, - } - }) - - if (!ausweis) { - return MissingEntityError("gebäude"); - } - - return success(ausweis); -}; \ No newline at end of file diff --git a/src/pages/api/building/images.json.ts b/src/pages/api/building/images.json.ts deleted file mode 100644 index 788957fa..00000000 --- a/src/pages/api/building/images.json.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { APIRoute } from "astro"; -import { - MissingEntityError, - error, - success, -} from "src/lib/APIResponse"; -import { prisma } from "@ibcornelsen/database"; - -export const get: APIRoute = async ({ url }) => { - const body = url.searchParams - const uid = body.get("uid") - - if (!body.has("uid") || !uid) { - return error(["Missing 'uid' in request body."]) - } - - - const gebaeude = await prisma.gebaeudeStammdaten.findUnique({ - where: { - uid: uid - } - }) - - if (!gebaeude) { - return MissingEntityError("gebaeude") - } - - const images = await prisma.gebaeudeBilder.findMany({ - where: { - gebaeude_stammdaten_id: gebaeude.id - }, - select: { - uid: true, - kategorie: true - } - }) - - return success(images); -}; \ No newline at end of file diff --git a/src/pages/api/klimafaktor/index.ts b/src/pages/api/klimafaktor/index.ts deleted file mode 100644 index 46e07e06..00000000 --- a/src/pages/api/klimafaktor/index.ts +++ /dev/null @@ -1,63 +0,0 @@ -import type { APIRoute } from "astro"; -import moment from "moment"; -import { ActionFailedError, MissingPropertyError, error, success } from "src/lib/APIResponse"; -import { getClimateFactor } from "src/lib/Klimafaktoren/getClimateFactor"; - -export const get: APIRoute = async function({ url }) { - const body = url.searchParams; - let zip = body.get("zip"); - - if (!body.get("date")) { - return MissingPropertyError(["date"]); - } - - let accuracy = body.get("accuracy") || "months"; - - if (accuracy !== "months" && accuracy !== "years") { - return error(["Accuracy must be either 'months' or 'years'."]) - } - - if (!zip) { - return error(["Invalid ZIP Code, must be 4 or 5 characters long."]) - } - - let start = moment(body.get("date")); - let end = moment(body.get("date")).add("2", "years"); - - if (!start.isValid()) { - return error(["Invalid start date given."]); - } - - if (!end.isValid()) { - return error(["Invalid end date given."]); - } - - if (start.isSameOrAfter(end)) { - return error(["Start date not before end date."]) - } - - const intervals = []; - - let currentDate = start.clone(); - while (currentDate.isSameOrBefore(end)) { - let copy = currentDate.clone(); - intervals.push(copy); - currentDate.add(1, accuracy); - } - - const klimafaktoren = await getClimateFactor(intervals, zip); - - if (!klimafaktoren) { - return ActionFailedError(); - } - - if (klimafaktoren.length !== intervals.length) { - return error(["Not all dates could be found."]); - } - - return success(klimafaktoren.map(klimafaktor => ({ - month: klimafaktor.month, - year: klimafaktor.year, - klimafaktor: klimafaktor.klimafaktor, - }))); -} \ No newline at end of file diff --git a/src/pages/api/trpc/[trpc].ts b/src/pages/api/trpc/[trpc].ts new file mode 100644 index 00000000..382a2b22 --- /dev/null +++ b/src/pages/api/trpc/[trpc].ts @@ -0,0 +1,31 @@ +// NOTE: Öffentliche API benötigt OpenApiMeta. Das Package bräuchte momentan noch einen extra Server, deshalb nehmen wir es momentan noch nicht mit rein. +//import { OpenApiMeta } from "trpc-openapi"; +import { fetchRequestHandler } from "@trpc/server/adapters/fetch"; +import { APIRoute } from "astro"; +import { t } from "./context"; +import { v1Router } from "./procedures/v1"; + +export const AppRouter = t.router({ + v1: v1Router, +}) + +export const all: APIRoute = ({ request }) => { + console.log(request); + + return fetchRequestHandler({ + req: request, + endpoint: "/api/trpc", + router: AppRouter, + createContext: async ({ req }) => { + return { uid: req.headers.get("X-Session") ?? undefined }; + }, + }); +}; + +export function tRPCCaller(request: Request) { + const { uid } = { uid: request.headers.get("Authorization") || "" }; + const createCaller = t.createCallerFactory(AppRouter); + return createCaller({ uid }); +} + +export type AppRouter = typeof AppRouter; diff --git a/src/pages/api/trpc/context.ts b/src/pages/api/trpc/context.ts new file mode 100644 index 00000000..a3fce6bf --- /dev/null +++ b/src/pages/api/trpc/context.ts @@ -0,0 +1,34 @@ +import { TRPCError, initTRPC } from "@trpc/server"; +import { ZodError } from "zod"; +import { OpenApiMeta } from "trpc-openapi"; + +type Context = { uid?: string }; + +export const t = initTRPC.context().meta().create({ + errorFormatter(opts) { + const { shape, error } = opts; + return { + success: false, + ...shape, + data: { + zodError: + error.code === 'BAD_REQUEST' && error.cause instanceof ZodError + ? error.cause.flatten() + : null, + }, + }; + } +}); + +export const publicProcedure = t.procedure + +export const privateProcedure = t.procedure.use((opts) => { + if (!opts.ctx.uid) { + throw new TRPCError({ + code: 'FORBIDDEN', + message: "Diese Ressource benötigt eine UID, welche im 'Authorization' Header gegeben sein muss.", + }); + } + + return opts.next(); +}); \ No newline at end of file diff --git a/src/pages/api/trpc/procedures/v1/index.ts b/src/pages/api/trpc/procedures/v1/index.ts new file mode 100644 index 00000000..71f573b9 --- /dev/null +++ b/src/pages/api/trpc/procedures/v1/index.ts @@ -0,0 +1,42 @@ +import { z } from "zod"; +import { t } from "../../context"; +import { tRPCKlimafaktorenProcedure } from "./klimafaktoren"; +import { VerbrauchsausweisWohnen2016Erstellen } from "./verbrauchsausweis-wohnen/2016/erstellen"; + +const router = t.router; + +export const v1Router = router({ + verbrauchsausweisWohnen: router({ + 2016: router({ + erstellen: VerbrauchsausweisWohnen2016Erstellen + }), + 2023: router({ + + }) + }), + verbrauchsausweisGewerbe: router({ + 2016: router({ + + }), + 2023: router({ + + }) + }), + bedarfsausweisWohen: router({ + 2016: router({ + + }), + 2023: router({ + + }) + }), + klimafaktoren: tRPCKlimafaktorenProcedure, + test: t.procedure.meta({ + openapi: { + method: "GET", + path: "/v1/test", + } + }).input(z.void({})).output(z.string()).query(async (opts) => { + return "Hello World!"; + }) +}) \ No newline at end of file diff --git a/src/pages/api/trpc/procedures/v1/klimafaktoren.ts b/src/pages/api/trpc/procedures/v1/klimafaktoren.ts new file mode 100644 index 00000000..0007cbe2 --- /dev/null +++ b/src/pages/api/trpc/procedures/v1/klimafaktoren.ts @@ -0,0 +1,78 @@ +import { z } from "zod"; +import { t } from "../../context"; +import moment from "moment"; +import { TRPCError } from "@trpc/server"; +import { prisma } from "@ibcornelsen/database"; + +export const tRPCKlimafaktorenProcedure = t.procedure + .input( + z.object({ + plz: z.string().min(4).max(5), + startdatum: z.coerce.date(), + enddatum: z.coerce.date(), + genauigkeit: z.enum(["months", "years"]), + }) + ) + .output( + z.array( + z.object({ + month: z.number(), + year: z.number(), + klimafaktor: z.number(), + }) + ) + ) + .query(async (opts) => { + const start = moment(opts.input.startdatum); + const end = moment(opts.input.enddatum); + + if (start.isSameOrAfter(end)) { + throw new TRPCError({ + code: "PRECONDITION_FAILED", + message: "Das Startdatum kann nicht vor dem Enddatum liegen.", + }); + } + + const intervals = []; + + let currentDate = start.clone(); + while (currentDate.isSameOrBefore(end)) { + let copy = currentDate.clone(); + intervals.push(copy); + currentDate.add(1, opts.input.genauigkeit); + } + + let klimafaktoren = await prisma.klimafaktoren.findMany({ + where: { + plz: opts.input.plz, + month: intervals[0].month(), + OR: intervals.map((date) => { + return { + year: date.year(), + }; + }), + }, + }); + + if (!klimafaktoren) { + throw new TRPCError({ + code: "NOT_FOUND", + message: + "Die Klimafaktoren konnten nicht geladen werden. Das kann daran liegen, dass sie für diesen Zeitraum oder Ort nicht verfügbar sind.", + }); + } + + if (klimafaktoren.length !== intervals.length) { + throw new TRPCError({ + code: "NOT_FOUND", + message: + "Für diesen Zeitraum konnten nicht alle Klimafaktoren gefunden werden.", + }); + } + + return klimafaktoren.map((klimafaktor) => ({ + month: klimafaktor.month, + year: klimafaktor.year, + klimafaktor: klimafaktor.klimafaktor, + })); + }); diff --git a/src/pages/api/trpc/procedures/v1/verbrauchsausweis-wohnen/2016/erstellen.ts b/src/pages/api/trpc/procedures/v1/verbrauchsausweis-wohnen/2016/erstellen.ts new file mode 100644 index 00000000..96c2f764 --- /dev/null +++ b/src/pages/api/trpc/procedures/v1/verbrauchsausweis-wohnen/2016/erstellen.ts @@ -0,0 +1,127 @@ +import { ZodSchema, z } from "zod"; +import { publicProcedure } from "../../../../context"; +import { GebaeudeStammdaten, VerbrauchsausweisWohnen, prisma } from "@ibcornelsen/database"; + +export const VerbrauchsausweisWohnen2016Erstellen = publicProcedure + .meta({ + openapi: { + method: "POST", + path: "/v1/verbrauchsausweis-wohnen/2016/erstellen", + contentTypes: ["application/json"], + description: "Erstellt einen neuen Verbrauchsausweis für Wohngebäude nach dem Schema der EnEV von 2016.", + tags: ["Verbrauchsausweis Wohnen"], + } + }) + .input(z.object({ + baujahr_heizung: z.array(z.number()).optional(), + zusaetzliche_heizquelle: z.boolean().optional(), + brennstoff_1: z.string().max(50).optional(), + einheit_1: z.string().max(10).optional(), + brennstoff_2: z.string().max(50).optional(), + einheit_2: z.string().max(10).optional(), + startdatum: z.date().optional(), + enddatum: z.date().optional(), + verbrauch_1: z.number().optional(), + verbrauch_2: z.number().optional(), + verbrauch_3: z.number().optional(), + verbrauch_4: z.number().optional(), + verbrauch_5: z.number().optional(), + verbrauch_6: z.number().optional(), + warmwasser_enthalten: z.boolean().optional(), + warmwasser_anteil_bekannt: z.boolean().optional(), + wird_gekuehlt: z.boolean().optional(), + keller_beheizt: z.boolean().optional(), + alternative_heizung: z.boolean().optional(), + alternative_warmwasser: z.boolean().optional(), + alternative_lueftung: z.boolean().optional(), + alternative_kuehlung: z.boolean().optional(), + anteil_warmwasser_1: z.number().optional(), + anteil_warmwasser_2: z.number().optional(), + + gebaeude_stammdaten: z.string().uuid().or(z.object({ + gebaeudetyp: z.string().max(255).optional(), // Adjust max length as needed + gebaeudeteil: z.string().max(255).optional(), // Adjust max length as needed + baujahr_gebaeude: z.array(z.number()).optional(), + baujahr_heizung: z.array(z.number()).optional(), + baujahr_klima: z.array(z.number()).optional(), + einheiten: z.number().optional(), + flaeche: z.number().optional(), + saniert: z.boolean().optional(), + keller: z.number().optional(), + dachgeschoss: z.number().optional(), + lueftung: z.string().max(50).optional(), // Adjust max length as needed + kuehlung: z.string().max(50).optional(), // Adjust max length as needed + leerstand: z.number().optional(), + plz: z.string().max(5).optional(), // Adjust max length as needed + ort: z.string().max(50).optional(), // Adjust max length as needed + adresse: z.string().max(100).optional(), // Adjust max length as needed + + zentralheizung: z.boolean().optional(), + solarsystem_warmwasser: z.boolean().optional(), + warmwasser_rohre_gedaemmt: z.boolean().optional(), + niedertemperatur_kessel: z.boolean().optional(), + brennwert_kessel: z.boolean().optional(), + heizungsrohre_gedaemmt: z.boolean().optional(), + standard_kessel: z.boolean().optional(), + waermepumpe: z.boolean().optional(), + raum_temperatur_regler: z.boolean().optional(), + photovoltaik: z.boolean().optional(), + durchlauf_erhitzer: z.boolean().optional(), + einzelofen: z.boolean().optional(), + zirkulation: z.boolean().optional(), + einfach_verglasung: z.boolean().optional(), + dreifach_verglasung: z.boolean().optional(), + fenster_teilweise_undicht: z.boolean().optional(), + doppel_verglasung: z.boolean().optional(), + fenster_dicht: z.boolean().optional(), + rolllaeden_kaesten_gedaemmt: z.boolean().optional(), + isolier_verglasung: z.boolean().optional(), + tueren_undicht: z.boolean().optional(), + tueren_dicht: z.boolean().optional(), + dachgeschoss_gedaemmt: z.boolean().optional(), + keller_decke_gedaemmt: z.boolean().optional(), + keller_wand_gedaemmt: z.boolean().optional(), + aussenwand_gedaemmt: z.boolean().optional(), + oberste_geschossdecke_gedaemmt: z.boolean().optional(), + aussenwand_min_12cm_gedaemmt: z.boolean().optional(), + dachgeschoss_min_12cm_gedaemmt: z.boolean().optional(), + oberste_geschossdecke_min_12cm_gedaemmt: z.boolean().optional(), + } satisfies ZodSchema)) + } satisfies ZodSchema)) + .output( + z.object({ + uid: z.string().uuid(), + }) + ) + .mutation(async (opts) => { + + // Es kann sein, dass ein Gebäude bereits existiert. In diesem Fall wird es nicht neu erstellt, sondern nur der Verbrauchsausweis. + // Das können wir ganz einfach überprüfen, indem wir schauen, ob eine UUID für das Gebäude übergeben wurde. + if (typeof opts.input.gebaeude_stammdaten === "string") { + // Gebäude existiert bereits + const gebaeude = await prisma.gebaeudeStammdaten.findUnique({ + where: { + uid: opts.input.gebaeude_stammdaten + } + }); + + if (!gebaeude) { + throw new Error("Das Gebäude mit der übergebenen UUID existiert nicht."); + } + + const verbrauchsausweis = await prisma.verbrauchsausweisWohnen.create({ + data: { + ...opts.input, + gebaeude_stammdaten: { + connect: { + uid: opts.input.gebaeude_stammdaten + } + } + } + }); + + return { uid: verbrauchsausweis.uid }; + } + + return { uid: "" }; + }); diff --git a/src/pages/api/trpc/procedures/v1/verbrauchsausweis-wohnen/2016/zusatzdaten-erfassung.xml.ts b/src/pages/api/trpc/procedures/v1/verbrauchsausweis-wohnen/2016/zusatzdaten-erfassung.xml.ts new file mode 100644 index 00000000..f2e50a4b --- /dev/null +++ b/src/pages/api/trpc/procedures/v1/verbrauchsausweis-wohnen/2016/zusatzdaten-erfassung.xml.ts @@ -0,0 +1,41 @@ +import type { APIRoute } from "astro"; +import { MissingEntityError, error } from "src/lib/APIResponse"; +import { prisma } from "@ibcornelsen/database"; +import { xmlVerbrauchsausweisWohnen_2016 } from "#lib/XML/VerbrauchsausweisWohnen/xmlVerbrauchsausweisWohnen_2016"; +import uuid from "uuid"; + +export const get: APIRoute = async ({ url }) => { + const body = url.searchParams; + const uid = body.get("uid"); + + if (!body.has("uid") || !uid) { + return error(["Missing 'uid' in request body."]); + } + + if (!uuid.validate(uid)) { + return error(["'uid' in request body must follow the UUID v4 format."]); + } + + const ausweis = await prisma.verbrauchsausweisWohnen.findUnique({ + where: { + uid, + }, + include: { + gebaeude_stammdaten: true, + }, + }); + + if (!ausweis) { + return MissingEntityError(uid); + } + + const xml = await xmlVerbrauchsausweisWohnen_2016(ausweis); + + const response = new Response(xml, { + headers: { + "Content-Type": "application/xml", + }, + }); + + return response; +}; diff --git a/src/pages/api/user.ts b/src/pages/api/user.ts index 8fad996b..181b8f9d 100644 --- a/src/pages/api/user.ts +++ b/src/pages/api/user.ts @@ -1,7 +1,7 @@ import type { APIRoute } from "astro"; import { success, MissingPropertyError, MissingEntityError, ActionFailedError, InvalidDataError, error } from "../../lib/APIResponse"; import { User } from "../../lib/User"; -import { UserRegisterValidator, UserType, UserTypeValidator } from "../../lib/User/type"; +import { UserRegisterValidator, UserType } from "../../lib/User/type"; import { z } from "zod"; /** @@ -36,7 +36,7 @@ export const put: APIRoute = async ({ request }) => { const user = await User.fromEmail(body.email); if (user) { - return error(["Email address is already being used."]); + return error(["Diese Email Adresse wird bereits verwendet."]); } const result = await User.create(body as UserType); diff --git a/src/pages/api/zip.ts b/src/pages/api/zip.ts index c6c2af53..52838a33 100644 --- a/src/pages/api/zip.ts +++ b/src/pages/api/zip.ts @@ -1,7 +1,7 @@ import type { APIRoute } from "astro"; -import { success, MissingPropertyError, MissingEntityError, InvalidDataError, error } from "../../lib/APIResponse"; -import { ZIPInformation } from "src/lib/ZIPInformation"; +import { success, MissingPropertyError, MissingEntityError, error } from "../../lib/APIResponse"; import { validateAuthorizationHeader } from "src/lib/server/Authorization"; +import { prisma } from "@ibcornelsen/database"; /** * Ruft einen Nutzer anhand seiner uid aus der Datenbank ab. @@ -18,11 +18,23 @@ export const get: APIRoute = async ({ request }) => { let result; if (body.zip) { - result = await ZIPInformation.fromZipCode(body.zip) + result = await prisma.postleitzahlen.findUnique({ + where: { + plz: body.zip, + }, + }) } else if (body.city) { - result = await ZIPInformation.fromCity(body.city) + result = await prisma.postleitzahlen.findMany({ + where: { + stadt: body.city, + }, + }) } else if (body.state) { - result = await ZIPInformation.fromState(body.state) + result = await prisma.postleitzahlen.findMany({ + where: { + bundesland: body.state, + }, + }) } else { return MissingPropertyError(["Either 'state', 'city' or 'zip' have to exist in request body."]) } diff --git a/src/pages/verbrauchsausweis/erstellen.astro b/src/pages/verbrauchsausweis/erstellen.astro deleted file mode 100644 index bf30fcc0..00000000 --- a/src/pages/verbrauchsausweis/erstellen.astro +++ /dev/null @@ -1,8 +0,0 @@ ---- -import { changeLanguage } from "i18next"; - -changeLanguage("de"); - -console.log(Object.fromEntries(new URLSearchParams(await Astro.request.text()))); -return Astro.redirect("/kundendaten"); ---- \ No newline at end of file diff --git a/src/pages/verbrauchsausweis/index.astro b/src/pages/verbrauchsausweis/index.astro index 0e70dc71..3beb7b94 100644 --- a/src/pages/verbrauchsausweis/index.astro +++ b/src/pages/verbrauchsausweis/index.astro @@ -1,7 +1,7 @@ --- import { changeLanguage } from "i18next"; import AusweisLayout from "#layouts/AusweisLayout.astro"; -import Verbrauchsausweis from "#modules/Ausweise/Verbrauchsausweis.svelte"; +import VerbrauchsausweisWohnenModule from "#modules/Ausweise/VerbrauchsausweisWohnenModule.svelte"; changeLanguage("de"); @@ -9,5 +9,5 @@ const uid = Astro.cookies.get("ausweis_uid").value; --- - + diff --git a/src/trpc.ts b/src/trpc.ts new file mode 100644 index 00000000..fd7aa221 --- /dev/null +++ b/src/trpc.ts @@ -0,0 +1,16 @@ +import { createTRPCProxyClient, httpBatchLink } from '@trpc/client'; +import cookies from 'js-cookie'; +import type { AppRouter } from 'src/pages/api/trpc/[trpc]'; + +export default createTRPCProxyClient({ + links: [ + httpBatchLink({ + url: 'http://localhost:3000/api/trpc', + headers() { + return { + 'Authorization': `Bearer ${cookies.get('uid')}`, + }; + }, + }), + ], +}); \ No newline at end of file diff --git a/test.ts b/test.ts new file mode 100644 index 00000000..930ceea3 --- /dev/null +++ b/test.ts @@ -0,0 +1,10 @@ +import { AppRouter } from "src/pages/api/trpc/[trpc]" +import { createOpenApiExpressMiddleware } from "trpc-openapi" +import express from 'express'; + +const app = express(); +app.use(createOpenApiExpressMiddleware({ router: AppRouter })); + +app.listen(5555); + +console.log("Server listening on port 80"); \ No newline at end of file