From 4a501ea87085b4856d85bf81cf036a3fe47f9e0b Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Wed, 16 Aug 2023 13:36:53 +0200 Subject: [PATCH] Add Backend Validation --- package.json | 11 ++++--- pnpm-lock.yaml | 7 +++++ src/repo/event.ts | 10 ++++--- src/repo/fight.ts | 8 +++-- src/stores/stores.ts | 27 +++++++++-------- src/types/data.ts | 24 +++++++++------ src/types/event.ts | 69 +++++++++++++++++++++++++------------------- src/types/team.ts | 18 +++++++----- 8 files changed, 104 insertions(+), 70 deletions(-) diff --git a/package.json b/package.json index 0859dec..47ff08b 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,10 @@ "@tsconfig/svelte": "^4.0.1", "autoprefixer": "^10.4.14", "flowbite-svelte-icons": "^0.2.5", + "flowbite": "^1.7.0", + "flowbite-svelte": "^0.39.2", + "svelte-awesome": "^3.2.0", + "tailwind-merge": "^1.13.2", "postcss": "^8.4.24", "sass": "^1.62.0", "svelte": "^4.0.1", @@ -25,14 +29,9 @@ "vite": "^4.3.9" }, "dependencies": { - "@popperjs/core": "^2.11.8", "color": "^4.2.3", - "flowbite": "^1.7.0", - "flowbite-svelte": "^0.39.2", - "install": "^0.13.0", "moment": "^2.29.4", - "svelte-awesome": "^3.2.0", "svelte-spa-router": "^3.3.0", - "tailwind-merge": "^1.13.2" + "zod": "^3.21.4" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 34640c4..9748c10 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,6 +28,9 @@ dependencies: tailwind-merge: specifier: ^1.13.2 version: 1.13.2 + zod: + specifier: ^3.21.4 + version: 3.21.4 devDependencies: '@sveltejs/vite-plugin-svelte': @@ -1459,6 +1462,10 @@ packages: engines: {node: '>= 14'} dev: true + /zod@3.21.4: + resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==} + dev: false + settings: autoInstallPeers: true excludeLinksFromLockfile: false diff --git a/src/repo/event.ts b/src/repo/event.ts index 3054749..e14b6b1 100644 --- a/src/repo/event.ts +++ b/src/repo/event.ts @@ -1,6 +1,8 @@ import type {ExtendedEvent, ShortEvent, SWEvent} from "../types/event.js"; import {fetchWithToken} from "./repo.js"; import type {Moment} from "moment"; +import {ExtendedEventSchema, ShortEventSchema, SWEventSchema} from "../types/event.js"; +import {z} from "zod"; export interface CreateEvent { name: string @@ -26,7 +28,7 @@ export class EventRepo { const res = await fetchWithToken(this.token, "/events"); if (res.ok) { - return await res.json() as ShortEvent[]; + return z.array(ShortEventSchema).parse(await res.json()); } else { throw new Error("Could not fetch events: " + res.statusText); } @@ -36,7 +38,7 @@ export class EventRepo { const res = await fetchWithToken(this.token, `/events/${id}`); if (res.ok) { - return await res.json() as ExtendedEvent; + return ExtendedEventSchema.parse(await res.json()); } else { throw new Error("Could not fetch event: " + res.statusText); } @@ -53,7 +55,7 @@ export class EventRepo { }); if (res.ok) { - return await res.json() as SWEvent; + return SWEventSchema.parse(await res.json()); } else { throw new Error("Could not create event: " + res.statusText); } @@ -78,7 +80,7 @@ export class EventRepo { }); if (res.ok) { - return await res.json() as SWEvent; + return SWEventSchema.parse(await res.json()); } else { throw new Error("Could not update event: " + res.statusText); } diff --git a/src/repo/fight.ts b/src/repo/fight.ts index 1fe1d17..1a6cc5e 100644 --- a/src/repo/fight.ts +++ b/src/repo/fight.ts @@ -1,6 +1,8 @@ import type {EventFight} from "../types/event.js"; import {fetchWithToken} from "./repo.js"; import type {Moment} from "moment"; +import {z} from "zod"; +import {EventFightSchema} from "../types/event.js"; export interface CreateFight { spielmodus: string @@ -29,7 +31,7 @@ export class FightRepo { const res = await fetchWithToken(this.token, `/events/${eventId}/fights`); if (res.ok) { - return await res.json() as EventFight[]; + return z.array(EventFightSchema).parse(await res.json()); } else { throw new Error("Could not fetch fights: " + res.statusText); } @@ -51,7 +53,7 @@ export class FightRepo { }) if (res.ok) { - return await res.json() as EventFight; + return EventFightSchema.parse(await res.json()); } else { throw new Error("Could not create fight: " + res.statusText); } @@ -72,7 +74,7 @@ export class FightRepo { }) if (res.ok) { - return await res.json() as EventFight; + return EventFightSchema.parse(await res.json()); } else { throw new Error("Could not update fight: " + res.statusText); } diff --git a/src/stores/stores.ts b/src/stores/stores.ts index 58c35d1..b5bcca5 100644 --- a/src/stores/stores.ts +++ b/src/stores/stores.ts @@ -3,20 +3,23 @@ import {cached, cachedFamily} from "./cached.js"; import type {Team} from "../types/team.js"; import {get, writable} from "svelte/store"; import {tokenStore} from "../repo/repo.js"; +import {PlayerSchema} from "../types/data.js"; +import {z} from "zod"; +import {TeamSchema} from "../types/team.js"; export const schemTypes = cached([], () => { return fetch("https://steamwar.de/eventplanner-api/data/schematicTypes", {headers: {"X-SW-Auth": get(tokenStore)}}) .then(res => res.json()) }) -export const players = cached([], () => { - return fetch("https://steamwar.de/eventplanner-api/data/users", {headers: {"X-SW-Auth": get(tokenStore)}}) - .then(res => res.json()) +export const players = cached([], async () => { + const res = await fetch("https://steamwar.de/eventplanner-api/data/users", {headers: {"X-SW-Auth": get(tokenStore)}}); + return z.array(PlayerSchema).parse(await res.json()); }) -export const gamemodes = cached([], () => { - return fetch("https://steamwar.de/eventplanner-api/data/gamemodes", {headers: {"X-SW-Auth": get(tokenStore)}}) - .then(res => res.json()) +export const gamemodes = cached([], async () => { + const res = await fetch("https://steamwar.de/eventplanner-api/data/gamemodes", {headers: {"X-SW-Auth": get(tokenStore)}}); + return z.array(z.string()).parse(await res.json()); }) export const maps = cachedFamily([], async (gamemode) => { @@ -30,14 +33,14 @@ export const maps = cachedFamily([], async (gamemode) => { } }) -export const groups = cached([], () => { - return fetch("https://steamwar.de/eventplanner-api/data/groups", {headers: {"X-SW-Auth": get(tokenStore)}}) - .then(res => res.json()) +export const groups = cached([], async () => { + const res = await fetch("https://steamwar.de/eventplanner-api/data/groups", {headers: {"X-SW-Auth": get(tokenStore)}}); + return z.array(z.string()).parse(await res.json()); }) -export const teams = cached([], () => { - return fetch("https://steamwar.de/eventplanner-api/team", {headers: {"X-SW-Auth": get(tokenStore)}}) - .then(res => res.json()) +export const teams = cached([], async () => { + const res = await fetch("https://steamwar.de/eventplanner-api/team", {headers: {"X-SW-Auth": get(tokenStore)}}); + return z.array(TeamSchema).parse(await res.json()); }) export const isWide = writable(window.innerWidth >= 640); diff --git a/src/types/data.ts b/src/types/data.ts index 05b8ee7..c6a4456 100644 --- a/src/types/data.ts +++ b/src/types/data.ts @@ -1,10 +1,16 @@ -export interface SchematicType { - name: string; - db: string; -} +import {z} from "zod"; -export interface Player { - id: number; - name: string; - uuid: string; -} +export const SchematicTypeSchema = z.object({ + name: z.string(), + db: z.string(), +}) + +export type SchematicType = z.infer; + +export const PlayerSchema = z.object({ + id: z.number(), + name: z.string(), + uuid: z.string().uuid(), +}) + +export type Player = z.infer; diff --git a/src/types/event.ts b/src/types/event.ts index 9904b8d..523ce98 100644 --- a/src/types/event.ts +++ b/src/types/event.ts @@ -1,35 +1,46 @@ import type {Team} from "./team.js"; import type {Player} from "./data.js"; +import {z} from "zod"; +import {TeamSchema} from "./team.js"; +import {PlayerSchema} from "./data.js"; -export interface ShortEvent { - id: number; - name: string; - start: number; - end: number; -} +export const ShortEventSchema = z.object({ + id: z.number(), + name: z.string(), + start: z.number(), + end: z.number(), +}) -export interface SWEvent extends ShortEvent { - deadline: number; - maxTeamMembers: number; - schemType: string | null; - publicSchemsOnly: boolean; - spectateSystem: boolean; -} +export type ShortEvent = z.infer; -export interface EventFight { - id: number; - spielmodus: string; - map: string; - blueTeam: Team; - redTeam: Team; - kampfleiter: Player | null; - start: number; - ergebnis: number; - group: string | null; -} +export const SWEventSchema = ShortEventSchema.extend({ + deadline: z.number(), + maxTeamMembers: z.number(), + schemType: z.string().nullable(), + publicSchemsOnly: z.boolean(), + spectateSystem: z.boolean(), +}) -export interface ExtendedEvent { - event: SWEvent; - teams: Team[]; - fights: EventFight[]; -} +export type SWEvent = z.infer; + +export const EventFightSchema = z.object({ + id: z.number(), + spielmodus: z.string(), + map: z.string(), + blueTeam: TeamSchema, + redTeam: TeamSchema, + kampfleiter: PlayerSchema.nullable(), + start: z.number(), + ergebnis: z.number(), + group: z.string().nullable(), +}) + +export type EventFight = z.infer; + +export const ExtendedEventSchema = z.object({ + event: SWEventSchema, + teams: z.array(TeamSchema), + fights: z.array(EventFightSchema), +}) + +export type ExtendedEvent = z.infer; diff --git a/src/types/team.ts b/src/types/team.ts index efd40dc..ee2b4ab 100644 --- a/src/types/team.ts +++ b/src/types/team.ts @@ -1,8 +1,12 @@ -import Color from "color"; +import {z} from "zod"; + +export const TeamSchema = z.object({ + id: z.number(), + name: z.string(), + kuerzel: z.string().min(1).max(4), + color: z.string().max(1), +}) + +export type Team = z.infer; + -export interface Team { - id: number; - name: string; - kuerzel: string; - color: string; -}