Refactor stores and types for improved data handling and schema definitions
All checks were successful
SteamWarCI Build successful

- Consolidated player fetching logic in stores.ts to utilize dataRepo.
- Introduced teams fetching logic in stores.ts.
- Updated permissions structure in stores.ts for better clarity.
- Enhanced data schemas in data.ts with new ResponseUser and ResponseTeam schemas.
- Expanded event-related schemas in event.ts to include groups, relations, and event creation/update structures.
- Improved code formatting for consistency and readability across files.
This commit is contained in:
2025-05-08 21:47:36 +02:00
parent 6377799e1b
commit 7d67ad0950
11 changed files with 2604 additions and 117 deletions

View File

@ -18,12 +18,12 @@
-->
<script lang="ts">
import type {RouteDefinition} from "svelte-spa-router";
import type { RouteDefinition } from "svelte-spa-router";
import Router from "svelte-spa-router";
import NavLinks from "@components/moderator/layout/NavLinks.svelte";
import {Switch} from "@components/ui/switch";
import {Label} from "@components/ui/label";
import {navigate} from "astro:transitions/client";
import { Switch } from "@components/ui/switch";
import { Label } from "@components/ui/label";
import { navigate } from "astro:transitions/client";
import Players from "@components/moderator/pages/players/Players.svelte";
import Events from "@components/moderator/pages/events/Events.svelte";
import Dashboard from "@components/moderator/pages/dashboard/Dashboard.svelte";
@ -33,19 +33,17 @@
"/": Dashboard,
"/events": Events,
"/players": Players,
"/event/:id": Event
"/event/:id": Event,
};
</script>
<div class="flex flex-col bg-background min-w-full min-h-screen">
<div class="border-b">
<div class="flex h-16 items-center px-4">
<a href="/" class="text-sm font-bold transition-colors text-primary">
SteamWar
</a>
<a href="/" class="text-sm font-bold transition-colors text-primary"> SteamWar </a>
<NavLinks />
<div class="ml-auto flex items-center space-x-4">
<Switch id="new-ui-switch" checked={true} on:click={() => navigate("/admin")} />
<Switch id="new-ui-switch" checked={true} onclick={() => navigate("/admin")} />
<Label for="new-ui-switch">New UI!</Label>
</div>
</div>
@ -53,4 +51,4 @@
<main class="flex flex-col">
<Router {routes} />
</main>
</div>
</div>

View File

@ -18,23 +18,13 @@
-->
<script lang="ts">
import {location} from "svelte-spa-router";
import { location } from "svelte-spa-router";
</script>
<nav class="flex items-center space-x-4 lg:space-x-6 mx-6">
<a href="#/" class="hover:text-primary text-sm font-medium transition-colors" class:text-muted={$location !== "/"}>
Dashboard
</a>
<a href="#/events" class="hover:text-primary text-sm font-medium transition-colors" class:text-muted={$location !== "/events"}>
Events
</a>
<a href="#/players" class="hover:text-primary text-sm font-medium transition-colors" class:text-muted={$location !== "/players"}>
Players
</a>
<a href="#/pages" class="hover:text-primary text-sm font-medium transition-colors" class:text-muted={$location !== "/pages"}>
Pages
</a>
<a href="#/schematics" class="hover:text-primary text-sm font-medium transition-colors" class:text-muted={$location !== "/schematics"}>
Schematics
</a>
</nav>
<a href="#/" class="hover:text-primary text-sm font-medium transition-colors" class:text-muted-foreground={$location !== "/"}> Dashboard </a>
<a href="#/events" class="hover:text-primary text-sm font-medium transition-colors" class:text-muted-foreground={!$location.startsWith("/event")}> Events </a>
<a href="#/players" class="hover:text-primary text-sm font-medium transition-colors" class:text-muted-foreground={$location !== "/players"}> Players </a>
<a href="#/pages" class="hover:text-primary text-sm font-medium transition-colors" class:text-muted-foreground={$location !== "/pages"}> Pages </a>
<a href="#/schematics" class="hover:text-primary text-sm font-medium transition-colors" class:text-muted-foreground={$location !== "/schematics"}> Schematics </a>
</nav>

View File

@ -28,22 +28,16 @@
const { event }: { event: ExtendedEvent } = $props();
let referees = $state(event.event.referees);
let referees = $state(event.referees);
async function addReferee(value: string) {
referees = (
await $eventRepo.updateEvent(event.event.id.toString(), {
addReferee: [value],
})
).referees;
await $eventRepo.updateReferees(event.event.id.toString(), [value]);
referees = await $eventRepo.listReferees(event.event.id.toString());
}
async function removeReferee(value: string) {
referees = (
await $eventRepo.updateEvent(event.event.id.toString(), {
removeReferee: [value],
})
).referees;
await $eventRepo.deleteReferees(event.event.id.toString(), [value]);
referees = await $eventRepo.listReferees(event.event.id.toString());
}
let playerSearch = $state("");

View File

@ -21,14 +21,29 @@
import { Button } from "@components/ui/button";
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell, TableCaption } from "@components/ui/table";
import type { ExtendedEvent } from "@type/event.ts";
import { eventRepo } from "@repo/event";
import { Popover, PopoverContent, PopoverTrigger } from "@components/ui/popover";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@components/ui/command";
import { teams } from "@components/stores/stores";
const { event }: { event: ExtendedEvent } = $props();
let team = $state(event.teams);
async function addTeam(value: number) {
await $eventRepo.updateTeams(event.event.id.toString(), [value]);
team = await $eventRepo.listTeams(event.event.id.toString());
}
async function removeTeam(value: number) {
await $eventRepo.deleteTeams(event.event.id.toString(), [value]);
team = await $eventRepo.listTeams(event.event.id.toString());
}
let teamSearch = $state("");
</script>
<Table>
<TableCaption>
<Button disabled>Add Team</Button>
</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Team</TableHead>
@ -37,19 +52,42 @@
</TableRow>
</TableHeader>
<TableBody>
{#each event.teams as team (team.id)}
{#each team as t (t.id)}
<TableRow>
<TableCell>{team.kuerzel}</TableCell>
<TableCell>{team.name}</TableCell>
<TableCell>{t.kuerzel}</TableCell>
<TableCell>{t.name}</TableCell>
<TableCell>
<Button disabled>Remove</Button>
<Button onclick={() => removeTeam(t.id)}>Remove</Button>
</TableCell>
</TableRow>
{/each}
{#if event.teams.length === 0}
{#if team.length === 0}
<TableRow>
<TableCell class="text-center col-span-3">No teams available</TableCell>
</TableRow>
{/if}
</TableBody>
<Popover>
<TableCaption>
<PopoverTrigger>
<Button>Add Team</Button>
</PopoverTrigger>
</TableCaption>
<PopoverContent class="p-0">
<Command shouldFilter={false}>
<CommandInput bind:value={teamSearch} placeholder="Search teams..." />
<CommandList>
<CommandEmpty>No teams found :(</CommandEmpty>
<CommandGroup heading="Teams">
{#each $teams
.filter((v) => v.name.includes(teamSearch))
.filter((v) => !team.some((k) => k.id === v.id))
.filter((v, i) => i < 50) as t (t.id)}
<CommandItem value={t.id.toString()} onSelect={() => addTeam(t.id)} keywords={[t.name, t.kuerzel]}>{t.name}</CommandItem>
{/each}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</Table>

View File

@ -63,7 +63,7 @@ export const columns: ColumnDef<EventFight> = [
},
{
header: "Gruppe",
accessorKey: "group",
accessorKey: "group.name",
id: "group",
},
{

View File

@ -17,26 +17,38 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import type {Player, Server} from "@type/data.ts";
import {PlayerSchema, ServerSchema} from "@type/data.ts";
import {fetchWithToken, tokenStore} from "./repo.ts";
import {derived, get} from "svelte/store";
import type { Player, Server } from "@type/data.ts";
import { PlayerSchema, ServerSchema } from "@type/data.ts";
import { fetchWithToken, tokenStore } from "./repo.ts";
import { derived, get } from "svelte/store";
import { TeamSchema, type Team } from "@components/types/team.ts";
export class DataRepo {
constructor(private token: string) {
}
constructor(private token: string) {}
public async getServer(): Promise<Server> {
return await fetchWithToken(this.token, "/data/server").then(value => value.json()).then(ServerSchema.parse);
return await fetchWithToken(this.token, "/data/server")
.then((value) => value.json())
.then(ServerSchema.parse);
}
public async getMe(): Promise<Player> {
return await fetchWithToken(this.token, "/data/me").then(value => value.json()).then(PlayerSchema.parse);
return await fetchWithToken(this.token, "/data/me")
.then((value) => value.json())
.then(PlayerSchema.parse);
}
public async getPlayers(): Promise<Player[]> {
return await fetchWithToken(get(tokenStore), "/data/admin/users").then(value => value.json()).then(PlayerSchema.array().parse);
return await fetchWithToken(get(tokenStore), "/data/admin/users")
.then((value) => value.json())
.then(PlayerSchema.array().parse);
}
public async getTeams(): Promise<Team[]> {
return await fetchWithToken(get(tokenStore), "/data/admin/teams")
.then((value) => value.json())
.then(TeamSchema.array().parse);
}
}
export const dataRepo = derived(tokenStore, ($token) => new DataRepo($token));
export const dataRepo = derived(tokenStore, ($token) => new DataRepo($token));

View File

@ -17,12 +17,26 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import type {ExtendedEvent, ShortEvent, SWEvent} from "@type/event";
import {fetchWithToken, tokenStore} from "./repo";
import {ExtendedEventSchema, ShortEventSchema, SWEventSchema} from "@type/event.js";
import {z} from "zod";
import type {Dayjs} from "dayjs";
import {derived} from "svelte/store";
import type { ExtendedEvent, ShortEvent, SWEvent, EventFight, ResponseGroups, ResponseRelation, ResponseTeam } from "@type/event";
import { fetchWithToken, tokenStore } from "./repo";
import {
ExtendedEventSchema,
ShortEventSchema,
SWEventSchema,
EventFightSchema,
ResponseGroupsSchema,
ResponseRelationSchema,
ResponseTeamSchema,
CreateEventGroupSchema,
UpdateEventGroupSchema,
CreateEventRelationSchema,
UpdateEventRelationSchema,
} from "@type/event.js";
import type { CreateEventGroup, UpdateEventGroup, CreateEventRelation, UpdateEventRelation } from "@type/event.js";
import { z } from "zod";
import type { Dayjs } from "dayjs";
import { derived } from "svelte/store";
import { ResponseUserSchema } from "@components/types/data";
export interface CreateEvent {
name: string;
@ -42,19 +56,25 @@ export interface UpdateEvent {
removeReferee?: string[] | null;
}
export interface ResponseUser {
name: string;
uuid: string;
prefix: string;
perms: string[];
}
export class EventRepo {
constructor(private token: string) {
}
constructor(private token: string) {}
public async listEvents(): Promise<ShortEvent[]> {
return await fetchWithToken(this.token, "/events")
.then(value => value.json())
.then(value => z.array(ShortEventSchema).parse(value));
.then((value) => value.json())
.then((value) => z.array(ShortEventSchema).parse(value));
}
public async getEvent(id: string): Promise<ExtendedEvent> {
return await fetchWithToken(this.token, `/events/${id}`)
.then(value => value.json())
.then((value) => value.json())
.then(ExtendedEventSchema.parse);
}
@ -66,7 +86,8 @@ export class EventRepo {
start: +event.start,
end: +event.end,
}),
}).then(value => value.json())
})
.then((value) => value.json())
.then(SWEventSchema.parse);
}
@ -87,7 +108,8 @@ export class EventRepo {
headers: {
"Content-Type": "application/json",
},
}).then(value => value.json())
})
.then((value) => value.json())
.then(SWEventSchema.parse);
}
@ -98,6 +120,150 @@ export class EventRepo {
return res.ok;
}
// Fights
public async listFights(eventId: string): Promise<EventFight[]> {
return await fetchWithToken(this.token, `/events/${eventId}/fights`)
.then((value) => value.json())
.then((value) => z.array(EventFightSchema).parse(value));
}
public async createFight(eventId: string, fight: any): Promise<EventFight> {
return await fetchWithToken(this.token, `/events/${eventId}/fights`, {
method: "POST",
body: JSON.stringify(fight),
headers: { "Content-Type": "application/json" },
})
.then((value) => value.json())
.then(EventFightSchema.parse);
}
public async deleteFight(eventId: string, fightId: string): Promise<boolean> {
const res = await fetchWithToken(this.token, `/events/${eventId}/fights/${fightId}`, {
method: "DELETE",
});
return res.ok;
}
// Groups
public async listGroups(eventId: string): Promise<ResponseGroups[]> {
return await fetchWithToken(this.token, `/events/${eventId}/groups`)
.then((value) => value.json())
.then((value) => z.array(ResponseGroupsSchema).parse(value));
}
public async createGroup(eventId: string, group: CreateEventGroup): Promise<ResponseGroups> {
CreateEventGroupSchema.parse(group);
return await fetchWithToken(this.token, `/events/${eventId}/groups`, {
method: "POST",
body: JSON.stringify(group),
headers: { "Content-Type": "application/json" },
})
.then((value) => value.json())
.then(ResponseGroupsSchema.parse);
}
public async getGroup(eventId: string, groupId: string): Promise<ResponseGroups> {
return await fetchWithToken(this.token, `/events/${eventId}/groups/${groupId}`)
.then((value) => value.json())
.then(ResponseGroupsSchema.parse);
}
public async updateGroup(eventId: string, groupId: string, group: UpdateEventGroup): Promise<ResponseGroups> {
UpdateEventGroupSchema.parse(group);
return await fetchWithToken(this.token, `/events/${eventId}/groups/${groupId}`, {
method: "PUT",
body: JSON.stringify(group),
headers: { "Content-Type": "application/json" },
})
.then((value) => value.json())
.then(ResponseGroupsSchema.parse);
}
public async deleteGroup(eventId: string, groupId: string): Promise<boolean> {
const res = await fetchWithToken(this.token, `/events/${eventId}/groups/${groupId}`, {
method: "DELETE",
});
return res.ok;
}
// Relations
public async listRelations(eventId: string): Promise<ResponseRelation[]> {
return await fetchWithToken(this.token, `/events/${eventId}/relations`)
.then((value) => value.json())
.then((value) => z.array(ResponseRelationSchema).parse(value));
}
public async createRelation(eventId: string, relation: CreateEventRelation): Promise<ResponseRelation> {
CreateEventRelationSchema.parse(relation);
return await fetchWithToken(this.token, `/events/${eventId}/relations`, {
method: "POST",
body: JSON.stringify(relation),
headers: { "Content-Type": "application/json" },
})
.then((value) => value.json())
.then(ResponseRelationSchema.parse);
}
public async getRelation(eventId: string, relationId: string): Promise<ResponseRelation> {
return await fetchWithToken(this.token, `/events/${eventId}/relations/${relationId}`)
.then((value) => value.json())
.then(ResponseRelationSchema.parse);
}
public async updateRelation(eventId: string, relationId: string, relation: UpdateEventRelation): Promise<ResponseRelation> {
UpdateEventRelationSchema.parse(relation);
return await fetchWithToken(this.token, `/events/${eventId}/relations/${relationId}`, {
method: "PUT",
body: JSON.stringify(relation),
headers: { "Content-Type": "application/json" },
})
.then((value) => value.json())
.then(ResponseRelationSchema.parse);
}
public async deleteRelation(eventId: string, relationId: string): Promise<boolean> {
const res = await fetchWithToken(this.token, `/events/${eventId}/relations/${relationId}`, {
method: "DELETE",
});
return res.ok;
}
// Teams
public async listTeams(eventId: string): Promise<ResponseTeam[]> {
return await fetchWithToken(this.token, `/events/${eventId}/teams`)
.then((value) => value.json())
.then((value) => z.array(ResponseTeamSchema).parse(value));
}
public async updateTeams(eventId: string, teams: number[]): Promise<boolean> {
const res = await fetchWithToken(this.token, `/events/${eventId}/teams`, {
method: "PUT",
body: JSON.stringify(teams),
headers: { "Content-Type": "application/json" },
});
return res.ok;
}
public async deleteTeams(eventId: string, teams: number[]): Promise<boolean> {
const res = await fetchWithToken(this.token, `/events/${eventId}/teams`, {
method: "DELETE",
body: JSON.stringify(teams),
headers: { "Content-Type": "application/json" },
});
return res.ok;
}
// Referees
public async listReferees(eventId: string): Promise<ResponseUser[]> {
return await fetchWithToken(this.token, `/events/${eventId}/referees`)
.then((value) => value.json())
.then((value) => z.array(ResponseUserSchema).parse(value));
}
public async updateReferees(eventId: string, refereeUuids: string[]): Promise<boolean> {
const res = await fetchWithToken(this.token, `/events/${eventId}/referees`, {
method: "PUT",
body: JSON.stringify(refereeUuids),
headers: { "Content-Type": "application/json" },
});
return res.status === 204;
}
public async deleteReferees(eventId: string, refereeUuids: string[]): Promise<boolean> {
const res = await fetchWithToken(this.token, `/events/${eventId}/referees`, {
method: "DELETE",
body: JSON.stringify(refereeUuids),
headers: { "Content-Type": "application/json" },
});
return res.status === 204;
}
}
export const eventRepo = derived(tokenStore, ($token) => new EventRepo($token));

File diff suppressed because it is too large Load Diff

View File

@ -17,41 +17,45 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import type {Player, SchematicType} from "@type/data";
import {PlayerSchema} from "@type/data.ts";
import {cached, cachedFamily} from "./cached";
import type {Team} from "@type/team.ts";
import {TeamSchema} from "@type/team";
import {derived, get, writable} from "svelte/store";
import {z} from "zod";
import {fetchWithToken, tokenStore} from "@repo/repo.ts";
import {pageRepo} from "@repo/page.ts";
import {dataRepo} from "@repo/data.ts";
import {permsRepo} from "@repo/perms.ts";
import type { Player, SchematicType } from "@type/data";
import { PlayerSchema } from "@type/data.ts";
import { cached, cachedFamily } from "./cached";
import type { Team } from "@type/team.ts";
import { TeamSchema } from "@type/team";
import { derived, get, writable } from "svelte/store";
import { z } from "zod";
import { fetchWithToken, tokenStore } from "@repo/repo.ts";
import { pageRepo } from "@repo/page.ts";
import { dataRepo } from "@repo/data.ts";
import { permsRepo } from "@repo/perms.ts";
export const schemTypes = cached<SchematicType[]>([], () =>
fetchWithToken(get(tokenStore), "/data/admin/schematicTypes")
.then(res => res.json()));
export const schemTypes = cached<SchematicType[]>([], () => fetchWithToken(get(tokenStore), "/data/admin/schematicTypes").then((res) => res.json()));
export const players = cached<Player[]>([], async () => {
const res = await fetchWithToken(get(tokenStore), "/data/admin/users");
return z.array(PlayerSchema).parse(await res.json());
return get(dataRepo).getPlayers();
});
export const permissions = cached({
perms: [],
prefixes: {},
}, async () => {
return get(permsRepo).listPerms();
export const teams = cached<Team[]>([], async () => {
return get(dataRepo).getTeams();
});
export const permissions = cached(
{
perms: [],
prefixes: {},
},
async () => {
return get(permsRepo).listPerms();
}
);
export const gamemodes = cached<string[]>([], async () => {
const res = await fetchWithToken(get(tokenStore), "/data/admin/gamemodes");
return z.array(z.string()).parse(await res.json());
});
export const maps = cachedFamily<string, string[]>([], async (gamemode) => {
if (get(gamemodes).every(value => value !== gamemode)) return [];
if (get(gamemodes).every((value) => value !== gamemode)) return [];
const res = await fetchWithToken(get(tokenStore), `/data/admin/gamemodes/${gamemode}/maps`);
if (!res.ok) {
@ -66,17 +70,12 @@ export const groups = cached<string[]>([], async () => {
return z.array(z.string()).parse(await res.json());
});
export const teams = cached<Team[]>([], async () => {
const res = await fetchWithToken(get(tokenStore), "/team");
return z.array(TeamSchema).parse(await res.json());
});
export const branches = cached<string[]>([], async () => {
const res = await get(pageRepo).getBranches();
return z.array(z.string()).parse(res);
});
export const server = derived(dataRepo, $dataRepo => $dataRepo.getServer());
export const server = derived(dataRepo, ($dataRepo) => $dataRepo.getServer());
export const isWide = writable(typeof window !== "undefined" && window.innerWidth >= 640);

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {z} from "zod";
import { z } from "zod";
export const SchematicTypeSchema = z.object({
name: z.string(),
@ -57,3 +57,12 @@ export const ResponseErrorSchema = z.object({
});
export type ResponseError = z.infer<typeof ResponseErrorSchema>;
export const ResponseUserSchema = z.object({
name: z.string(),
uuid: z.string(),
prefix: z.string(),
perms: z.array(z.string()),
});
export type ResponseUser = z.infer<typeof ResponseUserSchema>;

View File

@ -17,9 +17,46 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {z} from "zod";
import {TeamSchema} from "./team.js";
import {PlayerSchema} from "./data.js";
import { z } from "zod";
import { TeamSchema } from "./team.js";
import { PlayerSchema, ResponseUserSchema } from "./data.js";
export const ResponseGroupsSchema = z.object({
id: z.number(),
name: z.string(),
pointsPerWin: z.number(),
pointsPerLoss: z.number(),
pointsPerDraw: z.number(),
type: z.enum(["GROUP_STAGE", "ELIMINATION_STAGE"]),
points: z.record(z.string(), z.number()).nullable(),
});
export const EventFightSchema = z.object({
id: z.number(),
spielmodus: z.string(),
map: z.string(),
blueTeam: TeamSchema,
redTeam: TeamSchema,
start: z.number(),
ergebnis: z.number(),
spectatePort: z.number().nullable(),
group: ResponseGroupsSchema.nullable(),
});
export type EventFight = z.infer<typeof EventFightSchema>;
export type ResponseGroups = z.infer<typeof ResponseGroupsSchema>;
export const ResponseRelationSchema = z.object({
id: z.number(),
fight: EventFightSchema,
type: z.enum(["FIGHT", "GROUP"]),
fromFight: EventFightSchema.nullable(),
fromGroup: ResponseGroupsSchema.nullable(),
fromPlace: z.number(),
});
export type ResponseRelation = z.infer<typeof ResponseRelationSchema>;
export const ShortEventSchema = z.object({
id: z.number(),
@ -35,29 +72,63 @@ export const SWEventSchema = ShortEventSchema.extend({
maxTeamMembers: z.number(),
schemType: z.string().nullable(),
publicSchemsOnly: z.boolean(),
referees: z.array(PlayerSchema),
});
export type SWEvent = z.infer<typeof SWEventSchema>;
export const EventFightSchema = z.object({
id: z.number(),
spielmodus: z.string(),
map: z.string(),
blueTeam: TeamSchema,
redTeam: TeamSchema,
start: z.number(),
ergebnis: z.number(),
spectatePort: z.number().nullable(),
group: z.string().nullable(),
});
export type EventFight = z.infer<typeof EventFightSchema>;
export const ExtendedEventSchema = z.object({
event: SWEventSchema,
teams: z.array(TeamSchema),
groups: z.array(ResponseGroupsSchema),
fights: z.array(EventFightSchema),
referees: z.array(ResponseUserSchema),
relations: z.array(ResponseRelationSchema),
});
export type ExtendedEvent = z.infer<typeof ExtendedEventSchema>;
export const ResponseTeamSchema = z.object({
id: z.number(),
name: z.string(),
kuerzel: z.string(),
color: z.string(),
});
export type ResponseTeam = z.infer<typeof ResponseTeamSchema>;
export const CreateEventGroupSchema = z.object({
name: z.string(),
type: z.enum(["GROUP_STAGE", "ELIMINATION_STAGE"]),
});
export type CreateEventGroup = z.infer<typeof CreateEventGroupSchema>;
export const UpdateEventGroupSchema = z.object({
name: z.string().nullable().optional(),
type: z.enum(["GROUP_STAGE", "ELIMINATION_STAGE"]).nullable().optional(),
pointsPerWin: z.number().nullable().optional(),
pointsPerLoss: z.number().nullable().optional(),
pointsPerDraw: z.number().nullable().optional(),
});
export type UpdateEventGroup = z.infer<typeof UpdateEventGroupSchema>;
export const CreateEventRelationSchema = z.object({
fightId: z.number(),
team: z.enum(["RED", "BLUE"]),
fromType: z.enum(["FIGHT", "GROUP"]),
fromId: z.number(),
fromPlace: z.number(),
});
export type CreateEventRelation = z.infer<typeof CreateEventRelationSchema>;
export const UpdateFromRelationSchema = z.object({
fromType: z.enum(["FIGHT", "GROUP"]),
fromId: z.number(),
fromPlace: z.number(),
});
export type UpdateFromRelation = z.infer<typeof UpdateFromRelationSchema>;
export const UpdateEventRelationSchema = z.object({
team: z.enum(["RED", "BLUE"]).nullable().optional(),
from: UpdateFromRelationSchema.nullable().optional(),
});
export type UpdateEventRelation = z.infer<typeof UpdateEventRelationSchema>;