API Vollständig Umgezogen
This commit is contained in:
@@ -3,16 +3,17 @@ import { createCallerFactory } from "astro-typesafe-api/server";
|
|||||||
export const createCaller = createCallerFactory({
|
export const createCaller = createCallerFactory({
|
||||||
"klimafaktoren": await import("../src/pages/api/klimafaktoren.ts"),
|
"klimafaktoren": await import("../src/pages/api/klimafaktoren.ts"),
|
||||||
"postleitzahlen": await import("../src/pages/api/postleitzahlen.ts"),
|
"postleitzahlen": await import("../src/pages/api/postleitzahlen.ts"),
|
||||||
"ticket": await import("../src/pages/api/ticket.ts"),
|
|
||||||
"aufnahme/[uid]": await import("../src/pages/api/aufnahme/[uid].ts"),
|
"aufnahme/[uid]": await import("../src/pages/api/aufnahme/[uid].ts"),
|
||||||
"aufnahme": await import("../src/pages/api/aufnahme/index.ts"),
|
"aufnahme": await import("../src/pages/api/aufnahme/index.ts"),
|
||||||
|
"bilder/[uid]": await import("../src/pages/api/bilder/[uid].ts"),
|
||||||
"auth/access-token": await import("../src/pages/api/auth/access-token.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"),
|
"auth/refresh-token": await import("../src/pages/api/auth/refresh-token.ts"),
|
||||||
"bedarfsausweis-wohnen": await import("../src/pages/api/bedarfsausweis-wohnen/index.ts"),
|
"bedarfsausweis-wohnen": await import("../src/pages/api/bedarfsausweis-wohnen/index.ts"),
|
||||||
"objekt": await import("../src/pages/api/objekt/index.ts"),
|
"objekt": await import("../src/pages/api/objekt/index.ts"),
|
||||||
"verbrauchsausweis-gewerbe": await import("../src/pages/api/verbrauchsausweis-gewerbe/index.ts"),
|
"ticket": await import("../src/pages/api/ticket/index.ts"),
|
||||||
"user": await import("../src/pages/api/user/index.ts"),
|
"user": await import("../src/pages/api/user/index.ts"),
|
||||||
"user/self": await import("../src/pages/api/user/self.ts"),
|
"user/self": await import("../src/pages/api/user/self.ts"),
|
||||||
|
"verbrauchsausweis-gewerbe": await import("../src/pages/api/verbrauchsausweis-gewerbe/index.ts"),
|
||||||
"verbrauchsausweis-wohnen/[uid]": await import("../src/pages/api/verbrauchsausweis-wohnen/[uid].ts"),
|
"verbrauchsausweis-wohnen/[uid]": await import("../src/pages/api/verbrauchsausweis-wohnen/[uid].ts"),
|
||||||
"verbrauchsausweis-wohnen": await import("../src/pages/api/verbrauchsausweis-wohnen/index.ts"),
|
"verbrauchsausweis-wohnen": await import("../src/pages/api/verbrauchsausweis-wohnen/index.ts"),
|
||||||
"objekt/[uid]/bilder": await import("../src/pages/api/objekt/[uid]/bilder.ts"),
|
"objekt/[uid]/bilder": await import("../src/pages/api/objekt/[uid]/bilder.ts"),
|
||||||
|
|||||||
@@ -1,65 +1,95 @@
|
|||||||
import { GebaeudeClient, UploadedGebaeudeBild, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types";
|
import {
|
||||||
|
ObjektClient,
|
||||||
|
UploadedGebaeudeBild,
|
||||||
|
VerbrauchsausweisWohnenClient,
|
||||||
|
} from "#components/Ausweis/types.js";
|
||||||
|
import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js";
|
||||||
import { Enums } from "@ibcornelsen/database/client";
|
import { Enums } from "@ibcornelsen/database/client";
|
||||||
import { addNotification, updateNotification } from "@ibcornelsen/ui";
|
import { addNotification, updateNotification } from "@ibcornelsen/ui";
|
||||||
import { client } from "src/trpc";
|
import { api } from "astro-typesafe-api/client";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
export async function bilderHochladen(images: (UploadedGebaeudeBild & { base64?: string })[], gebaeude_uid: string) {
|
export async function bilderHochladen(
|
||||||
if (images.length == 0) {
|
images: (UploadedGebaeudeBild & { base64?: string, update?: boolean })[],
|
||||||
return images;
|
gebaeude_uid: string
|
||||||
}
|
) {
|
||||||
|
if (images.length == 0) {
|
||||||
|
return images;
|
||||||
|
}
|
||||||
|
|
||||||
// Wenn Bilder hochgeladen werden konvertieren wir sie zu base64, das heißt, dass die base64 Eigenschaft bei diesen Bildern
|
// Wenn Bilder hochgeladen werden konvertieren wir sie zu base64, das heißt, dass die base64 Eigenschaft bei diesen Bildern
|
||||||
// existiert. Das müssen wir TypeScript nur wissen lassen, damit es uns in Ruhe lässt.
|
// existiert. Das müssen wir TypeScript nur wissen lassen, damit es uns in Ruhe lässt.
|
||||||
const imagesToUpload = images.filter(image => !image.uid || image.update) as unknown as { base64: string, kategorie: string, uid?: string, update: boolean }[];
|
const imagesToUpload = images.filter(
|
||||||
|
(image) => !image.uid || image.update
|
||||||
|
) as unknown as {
|
||||||
|
base64: string;
|
||||||
|
kategorie: string;
|
||||||
|
uid?: string;
|
||||||
|
update: boolean;
|
||||||
|
}[];
|
||||||
|
|
||||||
if (imagesToUpload.length == 0) {
|
if (imagesToUpload.length == 0) {
|
||||||
return images;
|
return images;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alle Bilder hochladen
|
// Alle Bilder hochladen
|
||||||
const notification = addNotification({
|
const notification = addNotification({
|
||||||
dismissable: false,
|
dismissable: false,
|
||||||
message: "Bilder hochladen.",
|
message: "Bilder hochladen.",
|
||||||
subtext: `${imagesToUpload.length} Bilder werden hochgeladen, bitte haben sie Geduld.`,
|
subtext: `${imagesToUpload.length} Bilder werden hochgeladen, bitte haben sie Geduld.`,
|
||||||
timeout: 0,
|
timeout: 0,
|
||||||
type: "info"
|
type: "info",
|
||||||
})
|
});
|
||||||
for (let i = 0; i < imagesToUpload.length; i++) {
|
for (let i = 0; i < imagesToUpload.length; i++) {
|
||||||
const image = imagesToUpload[i];
|
const image = imagesToUpload[i];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (image.update) {
|
if (image.update) {
|
||||||
await client.v1.bilder.update.mutate({
|
await api.bilder._uid.PATCH.fetch({
|
||||||
uid: image.uid as string,
|
base64: image.base64,
|
||||||
base64: image.base64,
|
kategorie: image.kategorie as Enums.BilderKategorie,
|
||||||
kategorie: image.kategorie as Enums.BilderKategorie
|
}, {
|
||||||
})
|
params: {
|
||||||
} else {
|
uid: image.uid as string,
|
||||||
const response = await client.v1.bilder.upload.mutate({
|
},
|
||||||
base64: image.base64,
|
headers: {
|
||||||
kategorie: image.kategorie as Enums.BilderKategorie,
|
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
|
||||||
gebaeude_uid
|
}
|
||||||
})
|
});
|
||||||
|
} else {
|
||||||
|
const response = await api.objekt._uid.bilder.PUT.fetch({
|
||||||
|
base64: image.base64,
|
||||||
|
kategorie: image.kategorie as Enums.BilderKategorie
|
||||||
|
}, {
|
||||||
|
params: {
|
||||||
|
uid: gebaeude_uid
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
image.uid = response.uid
|
image.uid = response.uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateNotification(notification, {
|
updateNotification(notification, {
|
||||||
dismissable: true,
|
dismissable: true,
|
||||||
message: "Bild hochgeladen.",
|
message: "Bild hochgeladen.",
|
||||||
subtext: `${i + 1}/${imagesToUpload.length} Bildern wurden erfolgreich hochgeladen.`,
|
subtext: `${i + 1}/${
|
||||||
timeout: 3000
|
imagesToUpload.length
|
||||||
})
|
} Bildern wurden erfolgreich hochgeladen.`,
|
||||||
} catch (e) {
|
timeout: 3000,
|
||||||
updateNotification(notification, {
|
});
|
||||||
dismissable: true,
|
} catch (e) {
|
||||||
message: "Bild konnte nicht hochgeladen werden.",
|
updateNotification(notification, {
|
||||||
subtext: `Eines ihrer Bilder konnte nicht hochgeladen werden. Wir haben bereits ein Ticket erstellt und melden uns so schnell wie möglich bei ihnen.`,
|
dismissable: true,
|
||||||
timeout: 15000,
|
message: "Bild konnte nicht hochgeladen werden.",
|
||||||
type: "error"
|
subtext: `Eines ihrer Bilder konnte nicht hochgeladen werden. Wir haben bereits ein Ticket erstellt und melden uns so schnell wie möglich bei ihnen.`,
|
||||||
})
|
timeout: 15000,
|
||||||
}
|
type: "error",
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return images;
|
return images;
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { dialogs } from "../../../svelte-dialogs.config";
|
import { dialogs } from "../../../svelte-dialogs.config.js";
|
||||||
import { addNotification } from "#components/Notifications/shared";
|
import { addNotification } from "#components/Notifications/shared.js";
|
||||||
import { client } from "src/trpc";
|
import { api } from "astro-typesafe-api/client";
|
||||||
|
|
||||||
export async function spawnSignupPrompt() {
|
export async function spawnSignupPrompt() {
|
||||||
const result = await dialogs.prompt(
|
const result = await dialogs.prompt(
|
||||||
@@ -46,7 +46,7 @@ export async function spawnSignupPrompt() {
|
|||||||
const [vorname, name, email, passwort] = result;
|
const [vorname, name, email, passwort] = result;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await client.v1.benutzer.erstellen.mutate({
|
const response = await api.user.PUT.fetch({
|
||||||
email,
|
email,
|
||||||
passwort,
|
passwort,
|
||||||
vorname,
|
vorname,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { AppRouter } from "@ibcornelsen/api";
|
import { OmitKeys, TicketClient } from "#components/Ausweis/types.js";
|
||||||
import { inferProcedureInput } from "@trpc/server";
|
import { api } from "astro-typesafe-api/client";
|
||||||
import { client } from "src/trpc";
|
|
||||||
|
|
||||||
export async function createTicket(info: inferProcedureInput<AppRouter["v1"]["tickets"]["erstellen"]>) {
|
export async function createTicket(info: OmitKeys<TicketClient, "created_at" | "deleted_at" | "prioritaet" | "updated_at" | "status" | "uid">) {
|
||||||
return await client.v1.tickets.erstellen.mutate(info)
|
return await api.ticket.PUT.fetch(info)
|
||||||
}
|
}
|
||||||
@@ -5,12 +5,14 @@ import {
|
|||||||
Benutzer,
|
Benutzer,
|
||||||
GebaeudeBilder,
|
GebaeudeBilder,
|
||||||
Objekt,
|
Objekt,
|
||||||
|
Rechnung,
|
||||||
|
Tickets,
|
||||||
VerbrauchsausweisGewerbe,
|
VerbrauchsausweisGewerbe,
|
||||||
VerbrauchsausweisWohnen,
|
VerbrauchsausweisWohnen,
|
||||||
} from "@ibcornelsen/database/client";
|
} from "@ibcornelsen/database/client";
|
||||||
import { z, ZodSchema } from "zod";
|
import { z, ZodSchema } from "zod";
|
||||||
|
|
||||||
type OmitKeys<T, K extends keyof T> = Omit<T, K>;
|
export type OmitKeys<T, K extends keyof T> = Omit<T, K>;
|
||||||
|
|
||||||
export type UploadedGebaeudeBild = OmitKeys<GebaeudeBilder, "id" | "objekt_id"> & {
|
export type UploadedGebaeudeBild = OmitKeys<GebaeudeBilder, "id" | "objekt_id"> & {
|
||||||
base64: string
|
base64: string
|
||||||
@@ -84,9 +86,11 @@ export type AufnahmeClient = OmitKeys<
|
|||||||
uid_objekt: string
|
uid_objekt: string
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TicketClient = OmitKeys<Tickets, "bearbeiter_id" | "benutzer_id" | "id">
|
||||||
|
|
||||||
export type BenutzerClient = OmitKeys<Benutzer, "id" | "passwort">;
|
export type BenutzerClient = OmitKeys<Benutzer, "id" | "passwort">;
|
||||||
|
|
||||||
type ZodOverlapType<T> = z.ZodType<T, z.ZodTypeDef, T>;
|
export type RechnungClient = OmitKeys<Rechnung, "aufnahme_id" | "benutzer_id" | "id">
|
||||||
|
|
||||||
export function ZodOverlap<T, S = z.ZodType<T, z.ZodTypeDef, T>>(arg: S): S {
|
export function ZodOverlap<T, S = z.ZodType<T, z.ZodTypeDef, T>>(arg: S): S {
|
||||||
return arg;
|
return arg;
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {
|
import {
|
||||||
|
AufnahmeClient,
|
||||||
|
ObjektClient,
|
||||||
|
UploadedGebaeudeBild,
|
||||||
VerbrauchsausweisWohnenClient,
|
VerbrauchsausweisWohnenClient,
|
||||||
} from "#components/Ausweis/types.js";
|
} from "#components/Ausweis/types.js";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
@@ -12,9 +15,14 @@
|
|||||||
QuestionMarkCircled,
|
QuestionMarkCircled,
|
||||||
} from "radix-svelte-icons";
|
} from "radix-svelte-icons";
|
||||||
import { endEnergieVerbrauchVerbrauchsausweis_2016 } from "#lib/Berechnungen/VerbrauchsausweisWohnen/VerbrauchsausweisWohnen_2016.js";
|
import { endEnergieVerbrauchVerbrauchsausweis_2016 } from "#lib/Berechnungen/VerbrauchsausweisWohnen/VerbrauchsausweisWohnen_2016.js";
|
||||||
import { client } from "src/trpc.js";
|
import { api } from "astro-typesafe-api/client";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
import { API_ACCESS_TOKEN_COOKIE_NAME } from "#lib/constants.js";
|
||||||
|
|
||||||
export let ausweis: VerbrauchsausweisWohnenClient;
|
export let ausweis: VerbrauchsausweisWohnenClient;
|
||||||
|
export let aufnahme: AufnahmeClient;
|
||||||
|
export let bilder: UploadedGebaeudeBild[];
|
||||||
|
export let objekt: ObjektClient;
|
||||||
export let progress: number;
|
export let progress: number;
|
||||||
|
|
||||||
async function ausweisStornieren() {
|
async function ausweisStornieren() {
|
||||||
@@ -33,11 +41,16 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (result === true) {
|
if (result === true) {
|
||||||
await client.v1.verbrauchsausweisWohnen.stornieren.mutate({
|
await api["verbrauchsausweis-wohnen"]._uid.DELETE.fetch(undefined, {
|
||||||
uid: ausweis.uid
|
params: {
|
||||||
|
uid: ausweis.uid
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ausweis.aufnahme.storniert = true;
|
aufnahme.storniert = true;
|
||||||
ausweis = ausweis;
|
ausweis = ausweis;
|
||||||
|
|
||||||
dialogs.alert({
|
dialogs.alert({
|
||||||
@@ -58,14 +71,14 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="card lg:card-side bg-base-200 card-bordered border-base-300">
|
<div class="card lg:card-side bg-base-200 card-bordered border-base-300">
|
||||||
{#if ausweis.aufnahme.storniert}
|
{#if aufnahme.storniert}
|
||||||
<div class="absolute top-0 left-0 w-full h-full bg-[rgba(0,0,0,0.7)] z-[5] rounded-lg select-none">
|
<div class="absolute top-0 left-0 w-full h-full bg-[rgba(0,0,0,0.7)] z-[5] rounded-lg select-none">
|
||||||
<h1 class="absolute -rotate-[25deg] text-5xl md:text-7xl tracking-wide uppercase text-red-500 border-4 border-red-500 rounded-lg top-[50%] translate-y-[-50%] left-[50%] translate-x-[-50%]">Storniert</h1>
|
<h1 class="absolute -rotate-[25deg] text-5xl md:text-7xl tracking-wide uppercase text-red-500 border-4 border-red-500 rounded-lg top-[50%] translate-y-[-50%] left-[50%] translate-x-[-50%]">Storniert</h1>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<figure class="lg:w-1/2">
|
<figure class="lg:w-1/2">
|
||||||
<img
|
<img
|
||||||
src={(ausweis.aufnahme.objekt.gebaeude_bilder && `/bilder/${ausweis.aufnahme.objekt.gebaeude_bilder[0]?.uid}.webp`) || "/images/placeholder.jpg"}
|
src={(bilder.length > 0 && `/bilder/${bilder[0].uid}.webp`) || "/images/placeholder.jpg"}
|
||||||
class="object-cover w-full h-full"
|
class="object-cover w-full h-full"
|
||||||
alt="Gebäudebild"
|
alt="Gebäudebild"
|
||||||
/>
|
/>
|
||||||
@@ -95,25 +108,25 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row flex-wrap gap-2">
|
<div class="flex flex-row flex-wrap gap-2">
|
||||||
{#if ausweis.aufnahme.ausweisart == "VerbrauchsausweisWohnen"}
|
{#if aufnahme.ausweisart == "VerbrauchsausweisWohnen"}
|
||||||
<div class="badge badge-accent font-semibold">
|
<div class="badge badge-accent font-semibold">
|
||||||
Verbrauchsausweis Wohnen
|
Verbrauchsausweis Wohnen
|
||||||
</div>
|
</div>
|
||||||
{:else if ausweis.aufnahme.ausweisart == "BedarfsausweisWohnen"}
|
{:else if aufnahme.ausweisart == "BedarfsausweisWohnen"}
|
||||||
<div class="badge badge-accent font-semibold">
|
<div class="badge badge-accent font-semibold">
|
||||||
Bedarfsausweis Wohnen
|
Bedarfsausweis Wohnen
|
||||||
</div>
|
</div>
|
||||||
{:else if ausweis.aufnahme.ausweisart == "VerbrauchsausweisGewerbe"}
|
{:else if aufnahme.ausweisart == "VerbrauchsausweisGewerbe"}
|
||||||
<div class="badge badge-accent font-semibold">
|
<div class="badge badge-accent font-semibold">
|
||||||
Verbrauchsausweis Gewerbe
|
Verbrauchsausweis Gewerbe
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if ausweis.erledigt}
|
{#if aufnahme.erledigt}
|
||||||
<div class="badge badge-success font-semibold">Ausgestellt</div>
|
<div class="badge badge-success font-semibold">Ausgestellt</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<h2 class="card-title">{ausweis.aufnahme.objekt.adresse}</h2>
|
<h2 class="card-title">{objekt.adresse}</h2>
|
||||||
<div class="mb-4 flex flex-row items-center gap-4">
|
<div class="mb-4 flex flex-row items-center gap-4">
|
||||||
<progress class="progress w-full" value={progress} max="100"></progress>
|
<progress class="progress w-full" value={progress} max="100"></progress>
|
||||||
<!-- TODO: Metrics für den Fortschritt festlegen -->
|
<!-- TODO: Metrics für den Fortschritt festlegen -->
|
||||||
@@ -134,7 +147,7 @@
|
|||||||
<div class="flex flex-row justify-between">
|
<div class="flex flex-row justify-between">
|
||||||
<span>Erstellungsdatum</span>
|
<span>Erstellungsdatum</span>
|
||||||
<span class="font-bold text-base-content"
|
<span class="font-bold text-base-content"
|
||||||
>{moment(ausweis.erstellungsdatum).format(
|
>{moment(aufnahme.erstellungsdatum).format(
|
||||||
"DD.MM.YYYY"
|
"DD.MM.YYYY"
|
||||||
)}</span
|
)}</span
|
||||||
>
|
>
|
||||||
@@ -144,16 +157,16 @@
|
|||||||
<span
|
<span
|
||||||
class="font-bold text-base-content"
|
class="font-bold text-base-content"
|
||||||
title="Gebäude / Heizung"
|
title="Gebäude / Heizung"
|
||||||
>{ausweis.aufnahme.baujahr_gebaeude[0] || "N/A"} /
|
>{aufnahme.baujahr_gebaeude[0] || "N/A"} /
|
||||||
{ausweis.aufnahme.baujahr_heizung[0] ||
|
{aufnahme.baujahr_heizung[0] ||
|
||||||
"N/A"}</span
|
"N/A"}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row justify-between">
|
<div class="flex flex-row justify-between">
|
||||||
<span>Wohnfläche</span>
|
<span>Wohnfläche</span>
|
||||||
<span class="font-bold text-base-content"
|
<span class="font-bold text-base-content"
|
||||||
>{ausweis.aufnahme.flaeche
|
>{aufnahme.flaeche
|
||||||
? `${ausweis.aufnahme.flaeche}m²`
|
? `${aufnahme.flaeche}m²`
|
||||||
: "N/A"}</span
|
: "N/A"}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { inferProcedureOutput } from "@trpc/server";
|
|
||||||
import { API, api, inferOutput } from "astro-typesafe-api/client";
|
import { API, api, inferOutput } from "astro-typesafe-api/client";
|
||||||
|
|
||||||
export let name: string;
|
export let name: string;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { addNotification } from "#components/Notifications/shared";
|
import { api } from "astro-typesafe-api/client";
|
||||||
import { client } from "src/trpc";
|
|
||||||
import { getClose } from "svelte-dialogs";
|
import { getClose } from "svelte-dialogs";
|
||||||
|
|
||||||
const close = getClose();
|
const close = getClose();
|
||||||
@@ -8,7 +7,7 @@
|
|||||||
async function createTicket(e: SubmitEvent) {
|
async function createTicket(e: SubmitEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
try {
|
try {
|
||||||
await client.v1.tickets.erstellen.mutate({
|
await api.ticket.PUT.fetch({
|
||||||
beschreibung: description,
|
beschreibung: description,
|
||||||
email: email,
|
email: email,
|
||||||
metadata: {
|
metadata: {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { memoize } from "./Memoization.js";
|
import { memoize } from "./Memoization.js";
|
||||||
import { client } from "src/trpc.js";
|
|
||||||
import { api } from "astro-typesafe-api/client"
|
import { api } from "astro-typesafe-api/client"
|
||||||
|
|
||||||
export const getKlimafaktoren = memoize(async (date: Date, plz: string) => {
|
export const getKlimafaktoren = memoize(async (date: Date, plz: string) => {
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { BenutzerClient, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types";
|
import { BenutzerClient, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types.js";
|
||||||
import DashboardAusweis from "#components/Dashboard/DashboardAusweis.svelte";
|
import DashboardAusweis from "#components/Dashboard/DashboardAusweis.svelte";
|
||||||
import DashboardAusweisSkeleton from "#components/Dashboard/DashboardAusweisSkeleton.svelte";
|
import DashboardAusweisSkeleton from "#components/Dashboard/DashboardAusweisSkeleton.svelte";
|
||||||
import { verbrauchsausweisWohnenCalculateFormProgress } from "#lib/VerbrauchsausweisWohnen/calculateFormProgress";
|
import { verbrauchsausweisWohnenCalculateFormProgress } from "#lib/VerbrauchsausweisWohnen/calculateFormProgress";
|
||||||
import { client } from "src/trpc";
|
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
export let user: BenutzerClient;
|
export let user: BenutzerClient;
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import { Tabs, Tab, TabList, TabPanel } from "../../components/Tabs";
|
import { Tabs, Tab, TabList, TabPanel } from "../../components/Tabs";
|
||||||
import { dialogs } from "../../../svelte-dialogs.config";
|
import { dialogs } from "../../../svelte-dialogs.config";
|
||||||
import { BenutzerClient } from "#components/Ausweis/types";
|
import { BenutzerClient } from "#components/Ausweis/types";
|
||||||
import { client } from "src/trpc";
|
// import { client } from "src/trpc";
|
||||||
import { exclude } from "#lib/exclude";
|
import { exclude } from "#lib/exclude";
|
||||||
|
|
||||||
export let benutzer: BenutzerClient;
|
export let benutzer: BenutzerClient;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { addNotification } from "@ibcornelsen/ui";
|
import { addNotification } from "@ibcornelsen/ui";
|
||||||
import { client } from "src/trpc";
|
import { api } from "astro-typesafe-api/client";
|
||||||
|
|
||||||
export let navigate: (target: string) => void;
|
export let navigate: (target: string) => void;
|
||||||
export let onRegister: (response: { email: string, name: string, vorname: string }) => void;
|
export let onRegister: (response: { email: string, name: string, vorname: string }) => void;
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
async function signUp(e: SubmitEvent) {
|
async function signUp(e: SubmitEvent) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
try {
|
try {
|
||||||
const response = await client.v1.benutzer.erstellen.mutate({
|
const response = await api.user.PUT.fetch({
|
||||||
email,
|
email,
|
||||||
passwort,
|
passwort,
|
||||||
vorname,
|
vorname,
|
||||||
|
|||||||
@@ -6,20 +6,16 @@
|
|||||||
} from "@ibcornelsen/database/client";
|
} from "@ibcornelsen/database/client";
|
||||||
import { Enums } from "@ibcornelsen/database/client";
|
import { Enums } from "@ibcornelsen/database/client";
|
||||||
import PaymentOption from "#components/PaymentOption.svelte";
|
import PaymentOption from "#components/PaymentOption.svelte";
|
||||||
import { client } from "src/trpc";
|
|
||||||
import type { inferProcedureInput } from "@trpc/server";
|
|
||||||
import type { AppRouter } from "@ibcornelsen/api";
|
|
||||||
import CheckoutItem from "#components/CheckoutItem.svelte";
|
import CheckoutItem from "#components/CheckoutItem.svelte";
|
||||||
import { BenutzerClient, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types";
|
import { BenutzerClient, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types.js";
|
||||||
import { PRICES } from "#lib/constants";
|
import { PRICES } from "#lib/constants.js";
|
||||||
|
import { RechnungClient } from "#components/Ausweis/types.js";
|
||||||
|
|
||||||
export let user: BenutzerClient;
|
export let user: BenutzerClient;
|
||||||
export let ausweis:
|
export let ausweis:
|
||||||
| VerbrauchsausweisWohnenClient;
|
| VerbrauchsausweisWohnenClient;
|
||||||
// TODO: überarbeiten und zu inferProcedureOutput machen
|
// TODO: überarbeiten und zu inferProcedureOutput machen
|
||||||
let rechnung: inferProcedureInput<
|
let rechnung: RechnungClient = {
|
||||||
AppRouter["v1"]["rechnungen"]["erstellen"]
|
|
||||||
> = {
|
|
||||||
email: user.email,
|
email: user.email,
|
||||||
empfaenger: user.vorname + " " + user.name,
|
empfaenger: user.vorname + " " + user.name,
|
||||||
strasse: user.adresse,
|
strasse: user.adresse,
|
||||||
@@ -65,6 +61,7 @@
|
|||||||
async function createPayment(e: SubmitEvent) {
|
async function createPayment(e: SubmitEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
// TODO
|
||||||
const response = await client.v1.rechnungen.erstellen.mutate({
|
const response = await client.v1.rechnungen.erstellen.mutate({
|
||||||
...rechnung,
|
...rechnung,
|
||||||
ausweisart: Enums.Ausweisart.VerbrauchsausweisWohnen,
|
ausweisart: Enums.Ausweisart.VerbrauchsausweisWohnen,
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ import { z } from "zod";
|
|||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { prisma } from "@ibcornelsen/database/server";
|
import { prisma } from "@ibcornelsen/database/server";
|
||||||
import { encodeToken } from "../../../lib/auth/token.js";
|
import { encodeToken } from "../../../lib/auth/token.js";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
|
||||||
import { defineApiRoute } from "astro-typesafe-api/server";
|
|
||||||
import { TokenType } from "#lib/auth/types.js";
|
import { TokenType } from "#lib/auth/types.js";
|
||||||
|
|
||||||
export const GET = defineApiRoute({
|
export const GET = defineApiRoute({
|
||||||
@@ -35,7 +34,7 @@ export const GET = defineApiRoute({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!response) {
|
if (!response) {
|
||||||
throw new TRPCError({
|
throw new APIError({
|
||||||
code: "BAD_REQUEST",
|
code: "BAD_REQUEST",
|
||||||
message: "Der gegebene refresh token ist nicht gültig.",
|
message: "Der gegebene refresh token ist nicht gültig.",
|
||||||
});
|
});
|
||||||
@@ -48,7 +47,7 @@ export const GET = defineApiRoute({
|
|||||||
id: response.id,
|
id: response.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
throw new TRPCError({
|
throw new APIError({
|
||||||
code: "BAD_REQUEST",
|
code: "BAD_REQUEST",
|
||||||
message: "Der gegebene refresh token ist nicht gültig.",
|
message: "Der gegebene refresh token ist nicht gültig.",
|
||||||
});
|
});
|
||||||
@@ -62,7 +61,7 @@ export const GET = defineApiRoute({
|
|||||||
// id: response.id
|
// 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." });
|
// throw new APIError({ 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({
|
const user = await prisma.benutzer.findUnique({
|
||||||
@@ -78,7 +77,7 @@ export const GET = defineApiRoute({
|
|||||||
id: response.id,
|
id: response.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
throw new TRPCError({
|
throw new APIError({
|
||||||
code: "BAD_REQUEST",
|
code: "BAD_REQUEST",
|
||||||
message: "Der gegebene refresh token ist nicht mehr gültig.",
|
message: "Der gegebene refresh token ist nicht mehr gültig.",
|
||||||
});
|
});
|
||||||
|
|||||||
84
src/pages/api/bilder/[uid].ts
Normal file
84
src/pages/api/bilder/[uid].ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { authorizationMiddleware } from "#lib/middleware/authorization.js";
|
||||||
|
import { GebaeudeBilderSchema, prisma } from "@ibcornelsen/database/server";
|
||||||
|
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
|
||||||
|
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 PATCH = defineApiRoute({
|
||||||
|
input: GebaeudeBilderSchema.pick({
|
||||||
|
kategorie: true,
|
||||||
|
}).merge(z.object({
|
||||||
|
base64: z.string()
|
||||||
|
})),
|
||||||
|
output: z.void(),
|
||||||
|
middleware: authorizationMiddleware,
|
||||||
|
async fetch(input, ctx, user) {
|
||||||
|
if (!UUidWithPrefix.safeParse(ctx.params.uid).success) {
|
||||||
|
throw new APIError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "UID konnte nicht verifiziert werden."
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const image = await prisma.gebaeudeBilder.findUnique({
|
||||||
|
where: {
|
||||||
|
uid: ctx.params.uid,
|
||||||
|
objekt: {
|
||||||
|
benutzer_id: user.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!image) {
|
||||||
|
throw new APIError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Bild existiert nicht oder gehört einem anderen Benutzer."
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const base64 = input.base64;
|
||||||
|
|
||||||
|
if (!isBase64(base64, { mimeRequired: true })) {
|
||||||
|
throw new APIError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Das Bild ist nicht base64.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataWithoutPrefix = base64.replace(
|
||||||
|
/^data:image\/\w+;base64,/,
|
||||||
|
""
|
||||||
|
);
|
||||||
|
const buffer = Buffer.from(dataWithoutPrefix, "base64");
|
||||||
|
|
||||||
|
if (input.kategorie !== image.kategorie) {
|
||||||
|
await prisma.gebaeudeBilder.update({
|
||||||
|
where: {
|
||||||
|
id: image.id
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
kategorie: input.kategorie
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = fileURLToPath(new URL(`../../../../../persistent/images/${image.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) {
|
||||||
|
// Und geben einen Fehler zurück
|
||||||
|
throw new APIError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
message: "Bild konnte nicht gespeichert werden.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { OptionalNullable, UUidWithPrefix, VerbrauchsausweisWohnenClient, ZodOverlap } from "#components/Ausweis/types.js";
|
import { OptionalNullable, UUidWithPrefix, VerbrauchsausweisWohnenClient, ZodOverlap } from "#components/Ausweis/types.js";
|
||||||
import { exclude } from "#lib/exclude.js";
|
import { exclude } from "#lib/exclude.js";
|
||||||
import { authorizationMiddleware } from "#lib/middleware/authorization.js";
|
import { authorizationHeaders, authorizationMiddleware } from "#lib/middleware/authorization.js";
|
||||||
import { prisma, VerbrauchsausweisWohnenSchema } from "@ibcornelsen/database/server";
|
import { prisma, VerbrauchsausweisWohnenSchema } from "@ibcornelsen/database/server";
|
||||||
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
|
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -43,6 +43,101 @@ export const PATCH = defineApiRoute({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const DELETE = defineApiRoute({
|
||||||
|
meta: {
|
||||||
|
description: "Storniert einen Ausweis"
|
||||||
|
},
|
||||||
|
headers: authorizationHeaders,
|
||||||
|
middleware: authorizationMiddleware,
|
||||||
|
async fetch(input, ctx, user) {
|
||||||
|
const { uid } = ctx.params;
|
||||||
|
|
||||||
|
if (!UUidWithPrefix.safeParse(uid).success) {
|
||||||
|
throw new APIError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "UID konnte nicht verifiziert werden."
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wir holen uns den Verbrauchsausweis
|
||||||
|
// Dieser MUSS mit dem Nutzer verknüpft sein.
|
||||||
|
const ausweis = await prisma.verbrauchsausweisWohnen.findUnique({
|
||||||
|
where: {
|
||||||
|
uid,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
aufnahme: {
|
||||||
|
select: {
|
||||||
|
storniert: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!ausweis) {
|
||||||
|
// Falls wir den Ausweis nicht finden können, werfen wir einen Fehler
|
||||||
|
throw new APIError({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: "Ausweis konnte nicht gefunden werden.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wir dürfen den Ausweis nur stornieren, wenn er noch nicht ausgestellt wurde
|
||||||
|
// Außerdem müssen wir schauen, ob wir Admin oder der Besitzer des Ausweises sind.
|
||||||
|
if ((ausweis.benutzer_id !== user.id) && user.rolle !== "ADMIN") {
|
||||||
|
// Falls der Ausweis nicht dem Nutzer gehört, werfen wir einen Fehler
|
||||||
|
throw new APIError({
|
||||||
|
code: "FORBIDDEN",
|
||||||
|
message: "Ausweis gehört nicht dem Nutzer.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (ausweis.erledigt) {
|
||||||
|
// // Falls der Ausweis bereits ausgestellt wurde, werfen wir einen Fehler
|
||||||
|
// throw new TRPCError({
|
||||||
|
// code: "BAD_REQUEST",
|
||||||
|
// message: "Ausweis wurde bereits ausgestellt.",
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (ausweis.aufnahme.storniert) {
|
||||||
|
// Falls der Ausweis bereits storniert ist, werfen wir einen Fehler
|
||||||
|
throw new APIError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Ausweis wurde bereits storniert.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.aufnahme.update({
|
||||||
|
where: {
|
||||||
|
id: ausweis.aufnahme_id
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
storniert: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Wir erstellen ein Event, dass der Ausweis storniert wurde
|
||||||
|
// Dann können wir das in der Historie anzeigen
|
||||||
|
await prisma.event.create({
|
||||||
|
data: {
|
||||||
|
title: "Ausweis storniert",
|
||||||
|
description: ((user.rolle === "ADMIN") && (ausweis.benutzer_id !== user.id)) ? "Ausweis wurde von einem Administrator storniert." : "Ausweis wurde vom Besitzer storniert.",
|
||||||
|
benutzer: {
|
||||||
|
connect: {
|
||||||
|
id: user.id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
aufnahme: {
|
||||||
|
connect: {
|
||||||
|
id: ausweis.aufnahme_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
export const GET = defineApiRoute({
|
export const GET = defineApiRoute({
|
||||||
meta: {
|
meta: {
|
||||||
description: "Gibt ein spezifisches Gebäude des Benutzers zurück.",
|
description: "Gibt ein spezifisches Gebäude des Benutzers zurück.",
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { Enums } from "@ibcornelsen/database/client";
|
import { Enums } from "@ibcornelsen/database/client";
|
||||||
import { test, describe, expect } from "bun:test";
|
import { test, describe, expect } from "bun:test";
|
||||||
import {faker} from "@faker-js/faker";
|
import {faker} from "@faker-js/faker";
|
||||||
import { client } from "src/trpc";
|
// import { client } from "src/trpc";
|
||||||
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
|
|
||||||
import type { AppRouter } from '@ibcornelsen/api';
|
|
||||||
import { Buffer } from 'buffer';
|
import { Buffer } from 'buffer';
|
||||||
|
|
||||||
describe("Bilder hochladen", async () => {
|
describe("Bilder hochladen", async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user