API Vollständig Umgezogen

This commit is contained in:
Moritz Utcke
2025-01-25 09:02:51 +07:00
parent 6f3ddedd96
commit 51fb7ad9b6
18 changed files with 329 additions and 113 deletions

View File

@@ -3,16 +3,17 @@ import { createCallerFactory } from "astro-typesafe-api/server";
export const createCaller = createCallerFactory({
"klimafaktoren": await import("../src/pages/api/klimafaktoren.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": 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/refresh-token": await import("../src/pages/api/auth/refresh-token.ts"),
"bedarfsausweis-wohnen": await import("../src/pages/api/bedarfsausweis-wohnen/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/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": await import("../src/pages/api/verbrauchsausweis-wohnen/index.ts"),
"objekt/[uid]/bilder": await import("../src/pages/api/objekt/[uid]/bilder.ts"),

View File

@@ -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 { 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) {
if (images.length == 0) {
return images;
}
export async function bilderHochladen(
images: (UploadedGebaeudeBild & { base64?: string, update?: boolean })[],
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
// 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 }[];
// 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.
const imagesToUpload = images.filter(
(image) => !image.uid || image.update
) as unknown as {
base64: string;
kategorie: string;
uid?: string;
update: boolean;
}[];
if (imagesToUpload.length == 0) {
return images;
}
if (imagesToUpload.length == 0) {
return images;
}
// Alle Bilder hochladen
const notification = addNotification({
dismissable: false,
message: "Bilder hochladen.",
subtext: `${imagesToUpload.length} Bilder werden hochgeladen, bitte haben sie Geduld.`,
timeout: 0,
type: "info"
})
for (let i = 0; i < imagesToUpload.length; i++) {
const image = imagesToUpload[i];
// Alle Bilder hochladen
const notification = addNotification({
dismissable: false,
message: "Bilder hochladen.",
subtext: `${imagesToUpload.length} Bilder werden hochgeladen, bitte haben sie Geduld.`,
timeout: 0,
type: "info",
});
for (let i = 0; i < imagesToUpload.length; i++) {
const image = imagesToUpload[i];
try {
if (image.update) {
await client.v1.bilder.update.mutate({
uid: image.uid as string,
base64: image.base64,
kategorie: image.kategorie as Enums.BilderKategorie
})
} else {
const response = await client.v1.bilder.upload.mutate({
base64: image.base64,
kategorie: image.kategorie as Enums.BilderKategorie,
gebaeude_uid
})
try {
if (image.update) {
await api.bilder._uid.PATCH.fetch({
base64: image.base64,
kategorie: image.kategorie as Enums.BilderKategorie,
}, {
params: {
uid: image.uid as string,
},
headers: {
"Authorization": `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
});
} 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, {
dismissable: true,
message: "Bild hochgeladen.",
subtext: `${i + 1}/${imagesToUpload.length} Bildern wurden erfolgreich hochgeladen.`,
timeout: 3000
})
} catch (e) {
updateNotification(notification, {
dismissable: true,
message: "Bild konnte nicht hochgeladen werden.",
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"
})
}
}
updateNotification(notification, {
dismissable: true,
message: "Bild hochgeladen.",
subtext: `${i + 1}/${
imagesToUpload.length
} Bildern wurden erfolgreich hochgeladen.`,
timeout: 3000,
});
} catch (e) {
updateNotification(notification, {
dismissable: true,
message: "Bild konnte nicht hochgeladen werden.",
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;
}

View File

@@ -1,6 +1,6 @@
import { dialogs } from "../../../svelte-dialogs.config";
import { addNotification } from "#components/Notifications/shared";
import { client } from "src/trpc";
import { dialogs } from "../../../svelte-dialogs.config.js";
import { addNotification } from "#components/Notifications/shared.js";
import { api } from "astro-typesafe-api/client";
export async function spawnSignupPrompt() {
const result = await dialogs.prompt(
@@ -46,7 +46,7 @@ export async function spawnSignupPrompt() {
const [vorname, name, email, passwort] = result;
try {
const response = await client.v1.benutzer.erstellen.mutate({
const response = await api.user.PUT.fetch({
email,
passwort,
vorname,

View File

@@ -1,7 +1,6 @@
import { AppRouter } from "@ibcornelsen/api";
import { inferProcedureInput } from "@trpc/server";
import { client } from "src/trpc";
import { OmitKeys, TicketClient } from "#components/Ausweis/types.js";
import { api } from "astro-typesafe-api/client";
export async function createTicket(info: inferProcedureInput<AppRouter["v1"]["tickets"]["erstellen"]>) {
return await client.v1.tickets.erstellen.mutate(info)
export async function createTicket(info: OmitKeys<TicketClient, "created_at" | "deleted_at" | "prioritaet" | "updated_at" | "status" | "uid">) {
return await api.ticket.PUT.fetch(info)
}

View File

@@ -5,12 +5,14 @@ import {
Benutzer,
GebaeudeBilder,
Objekt,
Rechnung,
Tickets,
VerbrauchsausweisGewerbe,
VerbrauchsausweisWohnen,
} from "@ibcornelsen/database/client";
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"> & {
base64: string
@@ -84,9 +86,11 @@ export type AufnahmeClient = OmitKeys<
uid_objekt: string
};
export type TicketClient = OmitKeys<Tickets, "bearbeiter_id" | "benutzer_id" | "id">
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 {
return arg;

View File

@@ -1,5 +1,8 @@
<script lang="ts">
import {
AufnahmeClient,
ObjektClient,
UploadedGebaeudeBild,
VerbrauchsausweisWohnenClient,
} from "#components/Ausweis/types.js";
import moment from "moment";
@@ -12,9 +15,14 @@
QuestionMarkCircled,
} from "radix-svelte-icons";
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 aufnahme: AufnahmeClient;
export let bilder: UploadedGebaeudeBild[];
export let objekt: ObjektClient;
export let progress: number;
async function ausweisStornieren() {
@@ -33,11 +41,16 @@
});
if (result === true) {
await client.v1.verbrauchsausweisWohnen.stornieren.mutate({
uid: ausweis.uid
await api["verbrauchsausweis-wohnen"]._uid.DELETE.fetch(undefined, {
params: {
uid: ausweis.uid
},
headers: {
Authorization: `Bearer ${Cookies.get(API_ACCESS_TOKEN_COOKIE_NAME)}`
}
})
ausweis.aufnahme.storniert = true;
aufnahme.storniert = true;
ausweis = ausweis;
dialogs.alert({
@@ -58,14 +71,14 @@
</script>
<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">
<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>
{/if}
<figure class="lg:w-1/2">
<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"
alt="Gebäudebild"
/>
@@ -95,25 +108,25 @@
</ul>
</div>
<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">
Verbrauchsausweis Wohnen
</div>
{:else if ausweis.aufnahme.ausweisart == "BedarfsausweisWohnen"}
{:else if aufnahme.ausweisart == "BedarfsausweisWohnen"}
<div class="badge badge-accent font-semibold">
Bedarfsausweis Wohnen
</div>
{:else if ausweis.aufnahme.ausweisart == "VerbrauchsausweisGewerbe"}
{:else if aufnahme.ausweisart == "VerbrauchsausweisGewerbe"}
<div class="badge badge-accent font-semibold">
Verbrauchsausweis Gewerbe
</div>
{/if}
{#if ausweis.erledigt}
{#if aufnahme.erledigt}
<div class="badge badge-success font-semibold">Ausgestellt</div>
{/if}
</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">
<progress class="progress w-full" value={progress} max="100"></progress>
<!-- TODO: Metrics für den Fortschritt festlegen -->
@@ -134,7 +147,7 @@
<div class="flex flex-row justify-between">
<span>Erstellungsdatum</span>
<span class="font-bold text-base-content"
>{moment(ausweis.erstellungsdatum).format(
>{moment(aufnahme.erstellungsdatum).format(
"DD.MM.YYYY"
)}</span
>
@@ -144,16 +157,16 @@
<span
class="font-bold text-base-content"
title="Gebäude / Heizung"
>{ausweis.aufnahme.baujahr_gebaeude[0] || "N/A"} /
{ausweis.aufnahme.baujahr_heizung[0] ||
>{aufnahme.baujahr_gebaeude[0] || "N/A"} /
{aufnahme.baujahr_heizung[0] ||
"N/A"}</span
>
</div>
<div class="flex flex-row justify-between">
<span>Wohnfläche</span>
<span class="font-bold text-base-content"
>{ausweis.aufnahme.flaeche
? `${ausweis.aufnahme.flaeche}m²`
>{aufnahme.flaeche
? `${aufnahme.flaeche}m²`
: "N/A"}</span
>
</div>

View File

@@ -1,5 +1,4 @@
<script lang="ts">
import type { inferProcedureOutput } from "@trpc/server";
import { API, api, inferOutput } from "astro-typesafe-api/client";
export let name: string;

View File

@@ -1,6 +1,5 @@
<script lang="ts">
import { addNotification } from "#components/Notifications/shared";
import { client } from "src/trpc";
import { api } from "astro-typesafe-api/client";
import { getClose } from "svelte-dialogs";
const close = getClose();
@@ -8,7 +7,7 @@
async function createTicket(e: SubmitEvent) {
e.preventDefault();
try {
await client.v1.tickets.erstellen.mutate({
await api.ticket.PUT.fetch({
beschreibung: description,
email: email,
metadata: {

View File

@@ -1,6 +1,5 @@
import moment from "moment";
import { memoize } from "./Memoization.js";
import { client } from "src/trpc.js";
import { api } from "astro-typesafe-api/client"
export const getKlimafaktoren = memoize(async (date: Date, plz: string) => {

View File

@@ -1,9 +1,8 @@
<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 DashboardAusweisSkeleton from "#components/Dashboard/DashboardAusweisSkeleton.svelte";
import { verbrauchsausweisWohnenCalculateFormProgress } from "#lib/VerbrauchsausweisWohnen/calculateFormProgress";
import { client } from "src/trpc";
import { onMount } from "svelte";
export let user: BenutzerClient;

View File

@@ -8,7 +8,7 @@
import { Tabs, Tab, TabList, TabPanel } from "../../components/Tabs";
import { dialogs } from "../../../svelte-dialogs.config";
import { BenutzerClient } from "#components/Ausweis/types";
import { client } from "src/trpc";
// import { client } from "src/trpc";
import { exclude } from "#lib/exclude";
export let benutzer: BenutzerClient;

View File

@@ -1,6 +1,6 @@
<script lang="ts">
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 onRegister: (response: { email: string, name: string, vorname: string }) => void;
@@ -12,7 +12,7 @@
async function signUp(e: SubmitEvent) {
e.preventDefault()
try {
const response = await client.v1.benutzer.erstellen.mutate({
const response = await api.user.PUT.fetch({
email,
passwort,
vorname,

View File

@@ -6,20 +6,16 @@
} from "@ibcornelsen/database/client";
import { Enums } from "@ibcornelsen/database/client";
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 { BenutzerClient, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types";
import { PRICES } from "#lib/constants";
import { BenutzerClient, VerbrauchsausweisWohnenClient } from "#components/Ausweis/types.js";
import { PRICES } from "#lib/constants.js";
import { RechnungClient } from "#components/Ausweis/types.js";
export let user: BenutzerClient;
export let ausweis:
| VerbrauchsausweisWohnenClient;
// TODO: überarbeiten und zu inferProcedureOutput machen
let rechnung: inferProcedureInput<
AppRouter["v1"]["rechnungen"]["erstellen"]
> = {
let rechnung: RechnungClient = {
email: user.email,
empfaenger: user.vorname + " " + user.name,
strasse: user.adresse,
@@ -65,6 +61,7 @@
async function createPayment(e: SubmitEvent) {
e.preventDefault();
// TODO
const response = await client.v1.rechnungen.erstellen.mutate({
...rechnung,
ausweisart: Enums.Ausweisart.VerbrauchsausweisWohnen,

View File

@@ -2,8 +2,7 @@ import { z } from "zod";
import moment from "moment";
import { prisma } from "@ibcornelsen/database/server";
import { encodeToken } from "../../../lib/auth/token.js";
import { TRPCError } from "@trpc/server";
import { defineApiRoute } from "astro-typesafe-api/server";
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
import { TokenType } from "#lib/auth/types.js";
export const GET = defineApiRoute({
@@ -35,7 +34,7 @@ export const GET = defineApiRoute({
});
if (!response) {
throw new TRPCError({
throw new APIError({
code: "BAD_REQUEST",
message: "Der gegebene refresh token ist nicht gültig.",
});
@@ -48,7 +47,7 @@ export const GET = defineApiRoute({
id: response.id,
},
});
throw new TRPCError({
throw new APIError({
code: "BAD_REQUEST",
message: "Der gegebene refresh token ist nicht gültig.",
});
@@ -62,7 +61,7 @@ export const GET = defineApiRoute({
// 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({
@@ -78,7 +77,7 @@ export const GET = defineApiRoute({
id: response.id,
},
});
throw new TRPCError({
throw new APIError({
code: "BAD_REQUEST",
message: "Der gegebene refresh token ist nicht mehr gültig.",
});

View 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.",
});
}
},
})

View File

@@ -1,6 +1,6 @@
import { OptionalNullable, UUidWithPrefix, VerbrauchsausweisWohnenClient, ZodOverlap } from "#components/Ausweis/types.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 { APIError, defineApiRoute } from "astro-typesafe-api/server";
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({
meta: {
description: "Gibt ein spezifisches Gebäude des Benutzers zurück.",

View File

@@ -1,9 +1,7 @@
import { Enums } from "@ibcornelsen/database/client";
import { test, describe, expect } from "bun:test";
import {faker} from "@faker-js/faker";
import { client } from "src/trpc";
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from '@ibcornelsen/api';
// import { client } from "src/trpc";
import { Buffer } from 'buffer';
describe("Bilder hochladen", async () => {