diff --git a/persistent/images/img-694ca166-c339-44df-9240-0bb642291459.webp b/persistent/images/img-694ca166-c339-44df-9240-0bb642291459.webp new file mode 100644 index 00000000..641c1e02 Binary files /dev/null and b/persistent/images/img-694ca166-c339-44df-9240-0bb642291459.webp differ diff --git a/persistent/images/img-85f8a7cd-7351-408f-8576-6d7b9d0ac82b.webp b/persistent/images/img-85f8a7cd-7351-408f-8576-6d7b9d0ac82b.webp new file mode 100644 index 00000000..fffe32ba Binary files /dev/null and b/persistent/images/img-85f8a7cd-7351-408f-8576-6d7b9d0ac82b.webp differ diff --git a/persistent/images/img-a4e04cf7-9443-4462-9582-3c18b33ef711.webp b/persistent/images/img-a4e04cf7-9443-4462-9582-3c18b33ef711.webp new file mode 100644 index 00000000..099f2286 Binary files /dev/null and b/persistent/images/img-a4e04cf7-9443-4462-9582-3c18b33ef711.webp differ diff --git a/persistent/images/img-a50b7f82-0add-4e3a-bb42-3f9b2e49936a.webp b/persistent/images/img-a50b7f82-0add-4e3a-bb42-3f9b2e49936a.webp new file mode 100644 index 00000000..641c1e02 Binary files /dev/null and b/persistent/images/img-a50b7f82-0add-4e3a-bb42-3f9b2e49936a.webp differ diff --git a/persistent/images/img-af39ffd3-389b-43a4-9afb-5e82020dc5b0.webp b/persistent/images/img-af39ffd3-389b-43a4-9afb-5e82020dc5b0.webp new file mode 100644 index 00000000..641c1e02 Binary files /dev/null and b/persistent/images/img-af39ffd3-389b-43a4-9afb-5e82020dc5b0.webp differ diff --git a/persistent/images/img-e7269e2e-de35-491a-b24e-76bde9d88ac0.webp b/persistent/images/img-e7269e2e-de35-491a-b24e-76bde9d88ac0.webp new file mode 100644 index 00000000..641c1e02 Binary files /dev/null and b/persistent/images/img-e7269e2e-de35-491a-b24e-76bde9d88ac0.webp differ diff --git a/src/astro-typesafe-api-caller.ts b/src/astro-typesafe-api-caller.ts index d6ea0c6c..7e7e52d5 100644 --- a/src/astro-typesafe-api-caller.ts +++ b/src/astro-typesafe-api-caller.ts @@ -1,17 +1,18 @@ import { createCallerFactory } from "astro-typesafe-api/server"; export const createCaller = createCallerFactory({ + "bild": await import("../src/pages/api/bild.ts"), "klimafaktoren": await import("../src/pages/api/klimafaktoren.ts"), "postleitzahlen": await import("../src/pages/api/postleitzahlen.ts"), + "auth/access-token": await import("../src/pages/api/auth/access-token.ts"), + "auth/forgot-password": await import("../src/pages/api/auth/forgot-password.ts"), + "auth/refresh-token": await import("../src/pages/api/auth/refresh-token.ts"), "admin/ausstellen": await import("../src/pages/api/admin/ausstellen.ts"), "admin/bestellbestaetigung": await import("../src/pages/api/admin/bestellbestaetigung.ts"), "admin/erinnern": await import("../src/pages/api/admin/erinnern.ts"), "admin/nicht-ausstellen": await import("../src/pages/api/admin/nicht-ausstellen.ts"), "admin/post-ausstellen": await import("../src/pages/api/admin/post-ausstellen.ts"), "admin/registriernummer": await import("../src/pages/api/admin/registriernummer.ts"), - "auth/access-token": await import("../src/pages/api/auth/access-token.ts"), - "auth/forgot-password": await import("../src/pages/api/auth/forgot-password.ts"), - "auth/refresh-token": await import("../src/pages/api/auth/refresh-token.ts"), "aufnahme": await import("../src/pages/api/aufnahme/index.ts"), "bedarfsausweis-wohnen/[uid]": await import("../src/pages/api/bedarfsausweis-wohnen/[uid].ts"), "bedarfsausweis-wohnen": await import("../src/pages/api/bedarfsausweis-wohnen/index.ts"), @@ -26,9 +27,9 @@ export const createCaller = createCallerFactory({ "user/self": await import("../src/pages/api/user/self.ts"), "verbrauchsausweis-gewerbe/[uid]": await import("../src/pages/api/verbrauchsausweis-gewerbe/[uid].ts"), "verbrauchsausweis-gewerbe": await import("../src/pages/api/verbrauchsausweis-gewerbe/index.ts"), + "webhooks/mollie": await import("../src/pages/api/webhooks/mollie.ts"), "verbrauchsausweis-wohnen/[uid]": await import("../src/pages/api/verbrauchsausweis-wohnen/[uid].ts"), "verbrauchsausweis-wohnen": await import("../src/pages/api/verbrauchsausweis-wohnen/index.ts"), - "webhooks/mollie": await import("../src/pages/api/webhooks/mollie.ts"), "aufnahme/[uid]/bilder": await import("../src/pages/api/aufnahme/[uid]/bilder.ts"), "aufnahme/[uid]": await import("../src/pages/api/aufnahme/[uid]/index.ts"), "objekt/[uid]": await import("../src/pages/api/objekt/[uid]/index.ts"), diff --git a/src/client/lib/ausweisSpeichern.ts b/src/client/lib/ausweisSpeichern.ts index f7d409dc..5084df48 100644 --- a/src/client/lib/ausweisSpeichern.ts +++ b/src/client/lib/ausweisSpeichern.ts @@ -3,14 +3,14 @@ import { api } from "astro-typesafe-api/client" import { exclude } from "#lib/exclude.js"; import Cookies from "js-cookie"; import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js"; -import { AufnahmeClient, BedarfsausweisWohnenClient, ObjektClient, UploadedGebaeudeBild, VerbrauchsausweisGewerbeClient, VerbrauchsausweisWohnenClient, } from "#components/Ausweis/types.js"; +import { AufnahmeClient, BedarfsausweisWohnenClient, BildClient, ObjektClient, UploadedGebaeudeBild, VerbrauchsausweisGewerbeClient, VerbrauchsausweisWohnenClient, } from "#components/Ausweis/types.js"; import { Enums } from "@ibcornelsen/database/client"; export async function ausweisSpeichern( ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbeClient | BedarfsausweisWohnenClient, objekt: ObjektClient, aufnahme: AufnahmeClient, - bilder: (UploadedGebaeudeBild & { base64?: string })[], + bilder: BildClient[], ausweisart: Enums.Ausweisart ) { if (objekt.uid) { @@ -99,26 +99,15 @@ export async function ausweisSpeichern( ausweis.uid = uid; } - for (const bild of bilder) { - if (bild.uid) { - continue; + await api.aufnahme._uid.bilder.PUT.fetch(bilder.map(bild => bild.uid), { + params: { + uid: aufnahme.uid + }, + headers: { + "Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}` } - - const response = await api.aufnahme._uid.bilder.PUT.fetch({ - data: bild.data, - kategorie: bild.kategorie - }, { - params: { - uid: aufnahme.uid - }, - headers: { - "Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}` - } - }) - - bild.uid = response.uid - } - + }) + return { uid_ausweis: ausweis.uid, uid_aufnahme: aufnahme.uid, diff --git a/src/client/lib/validateAccessToken.ts b/src/client/lib/validateAccessToken.ts index 1fa7c42e..8c43bdf7 100644 --- a/src/client/lib/validateAccessToken.ts +++ b/src/client/lib/validateAccessToken.ts @@ -38,6 +38,7 @@ export async function validateAccessTokenClient() { const { accessToken: newAccessToken, accessTokenExpiry, refreshToken: newRefreshToken, refreshTokenExpiry } = await api.auth["access-token"].GET.fetch({ refreshToken }) + Cookies.set(API_ACCESS_TOKEN_COOKIE_NAME, newAccessToken, { domain: `.${window.location.hostname}`, diff --git a/src/components/Ausweis/ButtonSpaeterHilfe.svelte b/src/components/Ausweis/ButtonSpaeterHilfe.svelte index 08dfce62..e32be9ac 100644 --- a/src/components/Ausweis/ButtonSpaeterHilfe.svelte +++ b/src/components/Ausweis/ButtonSpaeterHilfe.svelte @@ -1,6 +1,4 @@ @@ -18,13 +16,6 @@ sm:grid-cols-[1fr_min-content_min-content_min-content] sm:justify-self-end sm:mt >Automatisch Ausfüllen - - - - - diff --git a/src/components/Ausweis/ButtonWeiterHilfe.svelte b/src/components/Ausweis/ButtonWeiterHilfe.svelte index e62a1ded..91c10c35 100644 --- a/src/components/Ausweis/ButtonWeiterHilfe.svelte +++ b/src/components/Ausweis/ButtonWeiterHilfe.svelte @@ -1,12 +1,13 @@ @@ -69,7 +74,86 @@ sm:grid-cols-[1fr_min-content_min-content_min-content] sm:justify-self-end sm:mt
- +
+ +
+ + {#if showHelp} +
+
+
+ Gerne helfen wir Ihnen wenn Sie nicht weiterkommen oder Fragen + haben. Kurze Fragen zum Formular oder der Ausweisart werden + kostenfrei telefonisch unter 040/209339850 beantwortet (bis 5min). Sollten Sie Unterstützung bei der Erstellung + benötgen oder lieber die Arbeit von unserem Ingenieurbüro erledigen + lassen, bieten wir Ihnen folgende Hilfen an. Bitte treffen Sie Ihre + Auswahl und klicken auf weiter: +
+ +
+ +
+ + +
+ Verbrauchsausweis online inkl. ausführlicher telefonischer + Beratung +
+ +
+ {PRICES[ausweisart][Enums.AusweisTyp.Beratung]} € inkl. MwSt. +
+
+ +
+ + +
+ Verbrauchsausweis offline (Sie schicken uns 3 + Verbrauchsabrechnungen zu) +
+ +
+ {PRICES[ausweisart][Enums.AusweisTyp.Offline]} € inkl. MwSt. +
+
+ +
+ + + + +
+
+ {/if} + - - -{#if showHelp} -
-
-
- Gerne helfen wir Ihnen wenn Sie nicht weiterkommen oder Fragen - haben. Kurze Fragen zum Formular oder der Ausweisart werden - kostenfrei telefonisch unter 040/209339850 beantwortet (bis 5min). Sollten Sie Unterstützung bei der Erstellung - benötgen oder lieber die Arbeit von unserem Ingenieurbüro erledigen - lassen, bieten wir Ihnen folgende Hilfen an. Bitte treffen Sie Ihre - Auswahl und klicken auf weiter: -
- -
- -
- - -
- Verbrauchsausweis online inkl. ausführlicher telefonischer - Beratung -
- -
- {PRICES.VerbrauchsausweisWohnen[1]} € inkl. MwSt. -
-
- -
- - -
- Verbrauchsausweis offline (Sie schicken uns 3 - Verbrauchsabrechnungen zu) -
- -
- {PRICES.VerbrauchsausweisWohnen[2]} € inkl. MwSt. -
-
- -
- - - - -
-
-{/if} diff --git a/src/components/Ausweis/Rechnungsadresse.svelte b/src/components/Ausweis/Rechnungsadresse.svelte index 8ffa6a69..7ec56e68 100644 --- a/src/components/Ausweis/Rechnungsadresse.svelte +++ b/src/components/Ausweis/Rechnungsadresse.svelte @@ -1,140 +1,142 @@ -
+" +> + - +
+ -
- - - +
- - Bitte geben Sie den Empfänger ein, auf den die Rechnung ausgestellt wird. - -
-
- - - -
- - - - -
- - Bitte geben Sie die Strasse und Hausnummer, so wie Sie auf der Rechnung erscheinen soll, ein. - -
-
- - - -
- -
- - - - -
- -
- - - - -
- - Bitte geben Sie die PLZ des Ortes, so wie Sie auf der Rechnung erscheinen soll, ein. - + + Bitte geben Sie den Empfänger ein, auf den die Rechnung + ausgestellt wird. +
-
+
-
+ - +
+ -
- - - +
+ + Bitte geben Sie die Strasse und Hausnummer, so wie Sie auf der + Rechnung erscheinen soll, ein. + +
+
+ + + +
+
+ + +
+ +
+ + + + +
- Bitte geben Sie, falls erforderlich, zusätzliche nformationen ein. + Bitte geben Sie die PLZ des Ortes, so wie Sie auf der + Rechnung erscheinen soll, ein.
-
+
+
- + -
- +
+ - +
- - Bitte geben Sie die E-Mail Adresse des Rechnungsempfängers ein. - -
-
+ + Bitte geben Sie, falls erforderlich, zusätzliche nformationen + ein. + +
+
- + +
+ + + + +
+ + Bitte geben Sie die E-Mail Adresse des Rechnungsempfängers ein. + +
+
+ + -
+
+
+ -
+ +
+
- + {#if rechnung.abweichende_versand_adresse} + - +
+ -
+ -
- -{#if rechnung.abweichende_versand_adresse} - - - -
- - - - -
+
Bitte geben Sie den Namen des Versand-Empfängers ein.
-
- - - -
- - - - -
- - Bitte geben Sie die Versand-Empfänger Strasse und Hausnummer ein, an die die Rechnung versandt wird. - -
-
- - - -
- -
- - - - -
- -
- - - - -
- - Bitte geben Sie die Versand-Empfänger PLZ des Ortes ein, an den die Rechnung versandt wird. -
-
-
+ - +
+ -
- + - - -
+
- Bitte geben Sie, falls erforderlich, zusätzliche nformationen ein. + Bitte geben Sie die Versand-Empfänger Strasse und Hausnummer + ein, an die die Rechnung versandt wird.
-
+
- + -
- +
+
+ + +
- +
+ -
+ + +
+ + Bitte geben Sie die Versand-Empfänger PLZ des Ortes ein, + an den die Rechnung versandt wird. + +
+
+
+ + + +
+ + + + +
- Bitte geben Sie die E-Mail Adresse des Versand-Empfängers ein. + Bitte geben Sie, falls erforderlich, zusätzliche + nformationen ein.
-
+
- + +
+ + + + +
+ + Bitte geben Sie die E-Mail Adresse des Versand-Empfängers + ein. + +
+
+ + - -{/if } - -
\ No newline at end of file + {/if} +
diff --git a/src/components/Ausweis/types.ts b/src/components/Ausweis/types.ts index e7961347..5884588e 100644 --- a/src/components/Ausweis/types.ts +++ b/src/components/Ausweis/types.ts @@ -153,8 +153,8 @@ export function getAusweisartFromUUID(uid: string): Enums.Ausweisart | null { return null; } -export type UnterlageClient = Omit -export type BildClient = Omit +export type UnterlageClient = Omit +export type BildClient = Omit export type ObjektKomplettClient = ObjektClient & { aufnahmen: AufnahmeKomplettClient[] diff --git a/src/components/ImageGrid.svelte b/src/components/ImageGrid.svelte index eaa5144d..fce64564 100644 --- a/src/components/ImageGrid.svelte +++ b/src/components/ImageGrid.svelte @@ -1,10 +1,10 @@ {#if route == "login"} - + {:else} - { - loginData.email = response.email + { + email = response.email navigate("login") }} {navigate} /> {/if} \ No newline at end of file diff --git a/src/modules/EmbeddedLoginModule.svelte b/src/modules/EmbeddedLoginModule.svelte index e3d24dda..263337a5 100644 --- a/src/modules/EmbeddedLoginModule.svelte +++ b/src/modules/EmbeddedLoginModule.svelte @@ -3,13 +3,14 @@ import { loginClient } from "#lib/login.js"; export let navigate: (target: string) => void; - export let data: { email: string; passwort: string }; - + export let email: string; + export let password: string; + export let onLogin: (response: Awaited>) => any; async function login(e: SubmitEvent) { e.preventDefault() - const response = await loginClient(data.email, data.passwort) + const response = await loginClient(email, password) if (response === null) { addNotification({ @@ -35,7 +36,7 @@ type="email" placeholder="Email" name="email" - bind:value={data.email} + bind:value={email} required />
@@ -46,7 +47,7 @@ type="password" placeholder="********" name="passwort" - bind:value={data.passwort} + bind:value={password} required />
diff --git a/src/modules/EmbeddedRegisterModule.svelte b/src/modules/EmbeddedRegisterModule.svelte index 250f7fac..058f68a8 100644 --- a/src/modules/EmbeddedRegisterModule.svelte +++ b/src/modules/EmbeddedRegisterModule.svelte @@ -4,8 +4,8 @@ export let navigate: (target: string) => void; export let onRegister: (response: { email: string, name: string, vorname: string }) => void; - let passwort: string; - let email: string; + export let password: string; + export let email: string; let vorname: string; let name: string; @@ -14,7 +14,7 @@ try { const response = await api.user.PUT.fetch({ email, - passwort, + passwort: password, vorname, name, }); @@ -82,7 +82,7 @@ placeholder="********" name="passwort" class="px-2.5 py-1.5 rounded-lg border bg-gray-50" - bind:value={passwort} + bind:value={password} required /> diff --git a/src/modules/KundendatenModule.svelte b/src/modules/KundendatenModule.svelte index 0bc2f207..a2a8857e 100644 --- a/src/modules/KundendatenModule.svelte +++ b/src/modules/KundendatenModule.svelte @@ -10,13 +10,13 @@ import LoginDialog from "#components/LoginDialog.svelte"; import { API_ACCESS_TOKEN_COOKIE_NAME, - AusweisTyp, PRICES, } from "#lib/constants.js"; import Cookies from "js-cookie"; import { AufnahmeClient, BenutzerClient, + BildClient, getAusweisartFromUUID, ObjektClient, RechnungClient, @@ -25,14 +25,20 @@ import { validateAccessTokenClient } from "src/client/lib/validateAccessToken.js"; import { api } from "astro-typesafe-api/client"; import PaymentOption from "#components/PaymentOption.svelte"; + import Overlay from "#components/Overlay.svelte"; + import EmbeddedAuthFlowModule from "./EmbeddedAuthFlowModule.svelte"; + import { ausweisSpeichern } from "#client/lib/ausweisSpeichern.js"; + import { addNotification } from "#components/Notifications/shared.js"; + import NotificationWrapper from "#components/Notifications/NotificationWrapper.svelte"; - export let user: BenutzerClient; + export let user: Partial; export let ausweis: VerbrauchsausweisWohnenClient; export let aufnahme: AufnahmeClient; export let objekt: ObjektClient; + export let bilder: BildClient[]; export let ausweisart: Enums.Ausweisart; export let aktiveBezahlmethode: Bezahlmethoden = Enums.Bezahlmethoden.paypal; - export let ausweis_typ: AusweisTyp = AusweisTyp.Standard; + export let ausweistyp: Enums.AusweisTyp = Enums.AusweisTyp.Standard; let rechnung: Partial = { email: user.email, @@ -81,7 +87,7 @@ let prices = PRICES[ausweisart]; - let basePrice: number = prices[0]; + let basePrice: number = prices[ausweistyp]; $: price = basePrice + @@ -135,9 +141,35 @@ } async function bestellen() { - const ausweisart = getAusweisartFromUUID( - ausweis.uid - ) as Enums.Ausweisart; + if (!form.checkValidity()) { + addNotification({ + dismissable: true, + message: "Fehlende Daten.", + subtext: "Nicht alle notwendigen Felder sind ausgefüllt, bitte füllen sie diese aus bevor sie es erneut versuchen.." + }) + + form.reportValidity(); + return; + } + + if (!await validateAccessTokenClient()) { + loginAction = bestellen + rechnung = rechnung + loginOverlayHidden = false; + return + } + + loginOverlayHidden = true + + const result = await ausweisSpeichern(ausweis, objekt, aufnahme, bilder, ausweisart) + + if (result === null) { + addNotification({ + dismissable: true, + message: "Ups... Das hat nicht geklappt.", + subtext: "Der Ausweis konnte nicht gespeichert werden, bitte versuchen sie es erneut oder kontaktieren sie unseren Support." + }) + } try { const { uid, checkout_url } = await api.rechnung.PUT.fetch( @@ -158,7 +190,7 @@ versand_ort: rechnung.versand_ort, telefon: rechnung.telefon, ausweis_uid: ausweis.uid, - ausweis_typ, + ausweistyp, }, { headers: { @@ -209,6 +241,10 @@ ausweisart === Enums.Ausweisart.GEGNachweisWohnen || ausweisart === Enums.Ausweisart.GEGNachweisBedarfsausweis || ausweisart === Enums.Ausweisart.GEGNachweisGewerbe; + + let loginOverlayHidden = true; + let loginAction = () => {}; + let form: HTMLFormElement;
Energiesausweis erstellen

{ausweisart} - {prices[0]} € + {prices[ausweistyp]} €

{#if gegAnfrage}
-
+
- {#if !gegAnfrage} @@ -376,11 +412,12 @@ grid-cols-5 justify-around justify-items-center items-center"
- + {#if gegAnfrage} @@ -388,12 +425,22 @@ grid-cols-5 justify-around justify-items-center items-center" {/if}
- + + +
+ +
+
+ + + + @@ -201,7 +201,7 @@ -

Energieausweis erstellen

-

{ausweisart} {PRICES.VerbrauchsausweisWohnen[0]} €

+

{ausweisart} {PRICES[ausweisart][Enums.AusweisTyp.Standard]} €

@@ -233,10 +184,10 @@ const ausweisart: Enums.Ausweisart = "VerbrauchsausweisWohnen" -
+
- + @@ -346,7 +297,7 @@ const ausweisart: Enums.Ausweisart = "VerbrauchsausweisWohnen" - +
{#each Object.entries($notifications) as [uid, notification] (uid)} diff --git a/src/pages/api/aufnahme/[uid]/bilder.ts b/src/pages/api/aufnahme/[uid]/bilder.ts index 31de4e68..9e304ca8 100644 --- a/src/pages/api/aufnahme/[uid]/bilder.ts +++ b/src/pages/api/aufnahme/[uid]/bilder.ts @@ -5,86 +5,49 @@ import { z } from "zod"; import isBase64 from "is-base64"; import { fileURLToPath } from "url"; import { writeFileSync } from "fs"; +import { UUidWithPrefix } from "#components/Ausweis/types.js"; export const PUT = defineApiRoute({ - input: BildSchema.pick({ - kategorie: true, - }).merge(z.object({ - data: z.string() - })), - output: z.object({ - uid: z.string({ description: "Die UID des Bildes." }) - }), + input: z.array(UUidWithPrefix), + output: z.void(), middleware: authorizationMiddleware, async fetch(input, ctx, user) { - const data = input.data; - - if (!isBase64(data, { mimeRequired: true })) { - throw new APIError({ - code: "BAD_REQUEST", - message: "Das Bild ist nicht base64.", - }); - } - - let aufnahme = await prisma.aufnahme.findUnique({ + const aufnahme = await prisma.aufnahme.findUnique({ where: { - uid: ctx.params.uid, - benutzer_id: user.id - }, - }); + uid: ctx.params.uid + } + }) if (!aufnahme) { throw new APIError({ code: "NOT_FOUND", - message: "Objekt nicht gefunden oder gehört einem anderen Benutzer.", - }); - } - - const dataWithoutPrefix = data.replace( - /^data:image\/\w+;base64,/, - "" - ); - const buffer = Buffer.from(dataWithoutPrefix, "base64"); - - const bild = await prisma.bild.create({ - data: { - kategorie: input.kategorie, - aufnahme: { - connect: { - id: aufnahme.id, - }, - }, - }, - select: { - uid: true, - }, - }); - - const filePath = fileURLToPath(new URL(`../../../../../persistent/images/${bild.uid}.webp`, import.meta.url)); - - try { - // Wir optimieren das Bild und konvertieren es in WebP - // TODO: Sharp scheint nicht zu funktionieren, wir müssen das nochmal testen - // const optimizedBuffer = await sharp(buffer).webp({ quality: 80 }).toArray(); - - writeFileSync(filePath, buffer) - } catch(e) { - // Bild wurde nicht gespeichert, wir löschen den Eintrag wieder - await prisma.bild.delete({ - where: { - uid: bild.uid - } + message: "Aufnahme existiert nicht oder gehört einem anderen Benutzer." }) - // Und geben einen Fehler zurück - throw new APIError({ - code: "INTERNAL_SERVER_ERROR", - message: "Bild konnte nicht gespeichert werden.", - }); } - return { - uid: bild.uid - }; + prisma.$transaction(async tx => { + for (const uid of input) { + const img = await tx.bild.update({ + where: { + uid, + aufnahme_id: null + }, + data: { + aufnahme_id: aufnahme.id + }, + select: { + uid: true + } + }) + + if (!img) { + throw new APIError({ + code: "NOT_FOUND", + message: "Bild existiert nicht oder gehört bereits zu einer anderen Aufnahme." + }) + } + } + }) }, }) @@ -115,8 +78,8 @@ export const GET = defineApiRoute({ if (!aufnahme) { throw new APIError({ - code: "FORBIDDEN", - message: "Objekt existiert nicht oder gehört einem anderen Benutzer." + code: "NOT_FOUND", + message: "Aufnahme existiert nicht oder gehört einem anderen Benutzer." }) } diff --git a/src/pages/api/auth/access-token.ts b/src/pages/api/auth/access-token.ts index 96d77b5b..43057d82 100644 --- a/src/pages/api/auth/access-token.ts +++ b/src/pages/api/auth/access-token.ts @@ -94,7 +94,7 @@ export const GET = defineApiRoute({ const refreshToken = encodeToken({ uid: user.uid, typ: TokenType.Refresh, - exp: refreshTokenExpiry.unix(), + exp: refreshTokenExpiry.valueOf(), }); // Und erstellen einen neuen @@ -107,7 +107,7 @@ export const GET = defineApiRoute({ }, }); - const accessTokenExpiry = moment().add(2, "days").unix(); + const accessTokenExpiry = moment().add(2, "days").valueOf(); const accessToken = encodeToken({ uid: user.uid, typ: TokenType.Access, @@ -118,7 +118,7 @@ export const GET = defineApiRoute({ accessToken, accessTokenExpiry: accessTokenExpiry, refreshToken, - refreshTokenExpiry: refreshTokenExpiry.unix(), + refreshTokenExpiry: refreshTokenExpiry.valueOf(), }; }, }); diff --git a/src/pages/api/bild.ts b/src/pages/api/bild.ts new file mode 100644 index 00000000..1d22aba3 --- /dev/null +++ b/src/pages/api/bild.ts @@ -0,0 +1,106 @@ +import { authorizationMiddleware, maybeAuthorizationMiddleware } from "#lib/middleware/authorization.js"; +import { BildSchema } from "@ibcornelsen/database/client"; +import { prisma } from "@ibcornelsen/database/server"; +import { defineApiRoute, APIError } from "astro-typesafe-api/server"; +import { z } from "astro:content"; +import { fileURLToPath } from "url"; +import isBase64 from "is-base64"; +import { writeFileSync } from "fs" +import { UUidWithPrefix } from "#components/Ausweis/types.js"; + +export const PUT = defineApiRoute({ + input: BildSchema.pick({ + kategorie: true, + }).merge(z.object({ + data: z.string() + })), + output: z.object({ + uid: z.string({ description: "Die UID des Bildes." }) + }), + async fetch(input) { + const data = input.data; + + if (!isBase64(data, { mimeRequired: true })) { + throw new APIError({ + code: "BAD_REQUEST", + message: "Das Bild ist nicht base64.", + }); + } + + const dataWithoutPrefix = data.replace( + /^data:image\/\w+;base64,/, + "" + ); + const buffer = Buffer.from(dataWithoutPrefix, "base64"); + + const bild = await prisma.bild.create({ + data: { + kategorie: input.kategorie + }, + select: { + uid: true, + }, + }); + + const filePath = fileURLToPath(new URL(`../../../persistent/images/${bild.uid}.webp`, import.meta.url)); + + try { + // Wir optimieren das Bild und konvertieren es in WebP + // TODO: Sharp scheint nicht zu funktionieren, wir müssen das nochmal testen + // const optimizedBuffer = await sharp(buffer).webp({ quality: 80 }).toArray(); + + writeFileSync(filePath, buffer) + } catch(e) { + // Bild wurde nicht gespeichert, wir löschen den Eintrag wieder + await prisma.bild.delete({ + where: { + uid: bild.uid + } + }) + // Und geben einen Fehler zurück + throw new APIError({ + code: "INTERNAL_SERVER_ERROR", + message: "Bild konnte nicht gespeichert werden.", + }); + } + + return { + uid: bild.uid + }; + }, +}) + +export const DELETE = defineApiRoute({ + input: z.object({ + uid: UUidWithPrefix + }), + middleware: maybeAuthorizationMiddleware, + async fetch(input, context, user) { + try { + if (user) { + await prisma.bild.delete({ + where: { + uid: input.uid, + aufnahme: { + benutzer: { + id: user.id + } + } + } + }) + } else { + await prisma.bild.delete({ + where: { + uid: input.uid, + aufnahme_id: null + } + }) + } + } catch(e) { + throw new APIError({ + code: "INTERNAL_SERVER_ERROR", + message: "Bild konnte nicht gelöscht werden." + }) + } + }, +}) \ No newline at end of file diff --git a/src/pages/api/rechnung/index.ts b/src/pages/api/rechnung/index.ts index fce17062..c2178ef1 100644 --- a/src/pages/api/rechnung/index.ts +++ b/src/pages/api/rechnung/index.ts @@ -9,7 +9,7 @@ import { } from "#lib/middleware/authorization.js"; import { UUidWithPrefix } from "#components/Ausweis/types.js"; import { getPrismaAusweisAdapter } from "#lib/server/ausweis.js"; -import { AusweisTyp, PRICES, SERVICES } from "#lib/constants.js"; +import { PRICES, SERVICES } from "#lib/constants.js"; export const PUT = defineApiRoute({ meta: { @@ -23,7 +23,7 @@ export const PUT = defineApiRoute({ .object({ ausweisart: z.nativeEnum(Enums.Ausweisart), ausweis_uid: UUidWithPrefix, - ausweis_typ: z.nativeEnum(AusweisTyp) + ausweistyp: z.nativeEnum(Enums.AusweisTyp) }) .merge( RechnungSchema.omit({ @@ -49,8 +49,8 @@ export const PUT = defineApiRoute({ // Wir erstellen eine Mollie Payment Referenz und eine neue Rechnung in unserer Datenbank, daraufhin geben // wir eine Checkout URL zurück auf die der Nutzer weitergeleitet werden kann. - const { ausweis_uid, ausweisart, bezahlmethode, services, ausweis_typ } = input; - let betrag = PRICES[ausweisart][ausweis_typ] + const { ausweis_uid, ausweisart, bezahlmethode, services, ausweistyp } = input; + let betrag = PRICES[ausweisart][ausweistyp] const servicePriceList = SERVICES[ausweisart] for (const service of input.services) { @@ -94,6 +94,8 @@ export const PUT = defineApiRoute({ bezahlmethode: bezahlmethode, status: Enums.Rechnungsstatus.open, aufnahme_id: ausweis.aufnahme_id, + services, + ausweistyp }, select: { uid: true, diff --git a/src/pages/api/verbrauchsausweis-wohnen/index.ts b/src/pages/api/verbrauchsausweis-wohnen/index.ts index 679faab8..caf153b9 100644 --- a/src/pages/api/verbrauchsausweis-wohnen/index.ts +++ b/src/pages/api/verbrauchsausweis-wohnen/index.ts @@ -16,7 +16,9 @@ export const PUT = defineApiRoute({ tags: ["Verbrauchsausweis Wohnen"], }, input: z.object({ - ausweis: VerbrauchsausweisWohnenSchema.omit({ + ausweis: VerbrauchsausweisWohnenSchema.merge(z.object({ + startdatum: z.coerce.date() + })).omit({ id: true, benutzer_id: true, uid: true, diff --git a/src/pages/bilder/[uid].webp.ts b/src/pages/bilder/[uid].webp.ts index 17624334..91da9e3c 100644 --- a/src/pages/bilder/[uid].webp.ts +++ b/src/pages/bilder/[uid].webp.ts @@ -1,29 +1,11 @@ -import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js"; -import { validateAccessTokenServer } from "#server/lib/validateAccessToken.js"; import { prisma } from "@ibcornelsen/database/server"; import { APIRoute } from "astro"; -import { APIError, defineApiRoute } from "astro-typesafe-api/server"; import * as fs from "fs"; import { fileURLToPath } from "url"; export const GET: APIRoute = async (Astro) => { const { uid } = Astro.params - const token = Astro.cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)?.value; - - if (!token) { - return new Response(null, { - status: 400 - }) - } - - const valid = validateAccessTokenServer(Astro); - - if (!valid) { - return new Response(null, { - status: 401 - }) - } const image = await prisma.bild.findUnique({ where: { @@ -38,6 +20,13 @@ export const GET: APIRoute = async (Astro) => { } const path = fileURLToPath(new URL(`../../../persistent/images/${image.uid}.webp`, import.meta.url)) + + if (!fs.existsSync(path)) { + return new Response(null, { + status: 404 + }) + } + const buffer = fs.readFileSync(path) return new Response(buffer, { diff --git a/src/pages/kundendaten.astro b/src/pages/kundendaten.astro index 38b208fa..a0178c50 100644 --- a/src/pages/kundendaten.astro +++ b/src/pages/kundendaten.astro @@ -3,98 +3,37 @@ import KundendatenModule from "#modules/KundendatenModule.svelte"; import AusweisLayout from "#layouts/AusweisLayoutPruefung.astro"; import { Enums } from "@ibcornelsen/database/client"; -import { createCaller } from "../astro-typesafe-api-caller"; -import { API_ACCESS_TOKEN_COOKIE_NAME, API_REFRESH_TOKEN_COOKIE_NAME } from "#lib/constants"; -import { validateAccessTokenServer } from "#server/lib/validateAccessToken"; -import { BedarfsausweisWohnenClient, getAusweisartFromUUID, VerbrauchsausweisGewerbeClient, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types"; +import { getCurrentUser } from "#lib/server/user"; // Man sollte nur auf diese Seite kommen, wenn ein Ausweis bereits vorliegt und in der Datenbank abgespeichert wurde. -const uid = Astro.url.searchParams.get("uid"); -const valid = await validateAccessTokenServer(Astro) -if (!uid || !valid) { - return Astro.redirect("/404"); -} +const user = await getCurrentUser(Astro) || {} +const params = new URLSearchParams(await Astro.request.text()); -const caller = createCaller(Astro) - -const ausweisart = getAusweisartFromUUID(uid); - -let ausweis: VerbrauchsausweisWohnenClient | VerbrauchsausweisGewerbeClient | BedarfsausweisWohnenClient; -if (ausweisart === Enums.Ausweisart.VerbrauchsausweisWohnen) { - ausweis = await caller["verbrauchsausweis-wohnen"]._uid.GET.fetch(undefined, { - headers: { - Authorization: `Bearer ${Astro.cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)?.value}` - }, - params: { - uid - } - }) -} else if (ausweisart === Enums.Ausweisart.VerbrauchsausweisGewerbe) { - ausweis = await caller["verbrauchsausweis-gewerbe"]._uid.GET.fetch(undefined, { - headers: { - Authorization: `Bearer ${Astro.cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)?.value}` - }, - params: { - uid - } - }) -} else if (ausweisart === Enums.Ausweisart.BedarfsausweisWohnen) { - ausweis = await caller["bedarfsausweis-wohnen"]._uid.GET.fetch(undefined, { - headers: { - Authorization: `Bearer ${Astro.cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)?.value}` - }, - params: { - uid - } - }) -} else if (ausweisart === Enums.Ausweisart.GEGNachweisWohnen) { - ausweis = await caller["geg-nachweis-wohnen"]._uid.GET.fetch(undefined, { - headers: { - Authorization: `Bearer ${Astro.cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)?.value}` - }, - params: { - uid - } - }) -} else { +if (!params.has("ausweis") || !params.has("aufnahme") || !params.has("objekt") || !params.has("bilder") || !params.has("ausweisart")) { return Astro.redirect("/404") } -const aufnahme = await caller.aufnahme._uid.GET.fetch(undefined, { - headers: { - Authorization: `Bearer ${Astro.cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)?.value}` - }, - params: { - uid: ausweis.uid_aufnahme +let ausweis, aufnahme, objekt, ausweisart, bilder, ausweistyp; +try { + ausweis = JSON.parse(params.get("ausweis") || "") + aufnahme = JSON.parse(params.get("aufnahme") || "") + objekt = JSON.parse(params.get("objekt") || "") + ausweisart = JSON.parse(params.get("ausweisart") || "") as Enums.Ausweisart; + bilder = JSON.parse(params.get("bilder") || ""); + ausweistyp = JSON.parse(params.get("ausweistyp") || "") as Enums.AusweisTyp; + + if (!ausweisart || !Object.keys(Enums.Ausweisart).includes(ausweisart) || !ausweistyp || !Object.keys(Enums.AusweisTyp).includes(ausweistyp)) { + throw new Error() } -}) - -const objekt = await caller.objekt._uid.GET.fetch(undefined, { - headers: { - Authorization: `Bearer ${Astro.cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)?.value}` - }, - params: { - uid: aufnahme.uid_objekt - } -}) - -const user = await caller.user.self.GET.fetch(undefined, { - headers: { - Authorization: `Bearer ${Astro.cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)?.value}` - } -}); - -aufnahme.ausweisart = "VerbrauchsausweisWohnen" - - -if (!ausweis || !user) { - return Astro.redirect("/404"); +} catch(e){ + return Astro.redirect("/404") } + --- - +