Game settings and better socket performance

- Added game settings
- Reworked GamePropsSchema for only relevant information
-> Prisma schema and zod object
- Imporved toast notifications
- No duplicate socket connections anymore
- Using now Zustand for the gameProps instead of React Context & State
This commit is contained in:
aronmal 2023-05-10 20:54:52 +02:00
parent 12295b316f
commit 61ae4b901d
Signed by: aronmal
GPG key ID: 816B7707426FC612
31 changed files with 652 additions and 350 deletions

View file

@ -2,7 +2,7 @@
// import FogImages from './FogImages'
import Labeling from "./Labeling"
import Ships from "./Ships"
import useGameEvent from "@lib/hooks/useGameEvent"
import useGameEvent from "@hooks/useGameEvent"
import { CSSProperties } from "react"
function Gamefield() {

View file

@ -2,32 +2,17 @@ import Icon from "./Icon"
import Player from "./Player"
import { faSpinnerThird } from "@fortawesome/pro-solid-svg-icons"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import useGameState from "@lib/hooks/useGameState"
import useSocket from "@lib/hooks/useSocket"
import { useGameProps } from "@hooks/useGameProps"
import useSocket from "@hooks/useSocket"
import { useRouter } from "next/router"
import { Fragment, useEffect, useState } from "react"
import { toast } from "react-toastify"
function LobbyFrame({ openSettings }: { openSettings: () => void }) {
const { payload } = useGameState().gameProps
const { isConnected, hasJoined } = useSocket()
const { payload } = useGameProps()
const [dots, setDots] = useState(3)
const { isConnected } = useSocket()
const router = useRouter()
useEffect(() => {
if (isConnected && hasJoined) return
const timeout = setTimeout(() => {
toast("Es konnte keine Echtzeitverbindung aufgebaute werden.", {
toastId: "connection_error",
type: "error",
autoClose: 10000,
})
router.push("/start")
}, 15000)
return () => clearTimeout(timeout)
}, [])
useEffect(() => {
if (payload?.player2) return
const interval = setInterval(() => setDots((e) => (e % 3) + 1), 1000)
@ -41,7 +26,7 @@ function LobbyFrame({ openSettings }: { openSettings: () => void }) {
<h1 className="font-farro text-5xl font-medium">
Game-PIN:{" "}
{isConnected ? (
<span className="underline">{payload?.gamePin?.pin ?? "---"}</span>
<span className="underline">{payload?.gamePin ?? "----"}</span>
) : (
<FontAwesomeIcon icon={faSpinnerThird} spin={true} />
)}
@ -65,7 +50,7 @@ function LobbyFrame({ openSettings }: { openSettings: () => void }) {
/>
) : (
<p className="font-farro w-96 text-center text-4xl font-medium">
Warte auf {isConnected && hasJoined ? "Spieler 2" : "Verbindung"}{" "}
Warte auf {isConnected ? "Spieler 2" : "Verbindung"}{" "}
{Array.from(Array(dots), () => ".").join("")}
{Array.from(Array(3 - dots), (_, i) => (
<Fragment key={i}>&nbsp;</Fragment>
@ -74,7 +59,10 @@ function LobbyFrame({ openSettings }: { openSettings: () => void }) {
)}
</div>
<div className="flex items-center justify-center border-t-2 border-slate-900">
<button className="font-farro m-4 rounded-xl border-b-4 border-orange-400 bg-warn px-12 py-4 text-5xl font-medium duration-100 active:border-t-4 active:border-b-0">
<button
className="font-farro m-4 rounded-xl border-b-4 border-orange-400 bg-warn px-12 py-4 text-5xl font-medium duration-100 active:border-t-4 active:border-b-0"
onClick={() => router.push("/")}
>
START
</button>
</div>

View file

@ -3,38 +3,69 @@ import {
faToggleLargeOn,
} from "@fortawesome/pro-solid-svg-icons"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { useGameProps } from "@hooks/useGameProps"
import { socket } from "@lib/socket"
import classNames from "classnames"
import { ReactNode, useMemo } from "react"
import { toast } from "react-toastify"
type GameSettingKeys =
| "allowSpectators"
| "allowSpecials"
| "allowChat"
| "allowMarkDraw"
export type GameSettings = { [key in GameSettingKeys]?: boolean }
export const gameSetting = (payload: GameSettings) => {
socket.emit("gameSetting", payload, ({ ack }) => {
if (ack) return
toast.warn("Something is wrong... ", {
toastId: "st_wrong",
theme: "colored",
})
})
}
function Setting({
props: { key, state, onClick },
children,
prop,
}: {
props: {
key: string
state: boolean
onClick: () => void
}
children: ReactNode
prop: GameSettingKeys
}) {
const { payload, setSetting } = useGameProps()
const state = useMemo(() => payload?.game?.[prop], [payload?.game, prop])
return (
<label className="flex items-center justify-between" htmlFor={key}>
<label className="flex items-center justify-between" htmlFor={prop}>
<span className="col-span-2 w-96 select-none text-5xl text-white drop-shadow-md">
{key}
{children}
</span>
<FontAwesomeIcon
className={classNames(
"text-md mx-auto w-24 rounded-full px-4 drop-shadow-md transition-all",
"text-md mx-auto rounded-full px-4 drop-shadow-md transition-all",
state ? "text-blue-500" : "text-gray-800",
{
"bg-gray-300 ": state,
}
)}
size="3x"
icon={state ? faToggleLargeOn : faToggleLargeOff}
/>
<input
className="bg-none"
checked={state}
type="checkbox"
id={key}
onChange={onClick}
id={prop}
onChange={() => {
const payload = {
[prop]: !state,
}
setSetting(payload)
gameSetting(payload)
}}
hidden={true}
/>
</label>

View file

@ -1,18 +1,12 @@
import Setting from "./Setting"
import Setting, { gameSetting } from "./Setting"
import { faRotateLeft } from "@fortawesome/pro-regular-svg-icons"
import { faXmark } from "@fortawesome/pro-solid-svg-icons"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { useState } from "react"
const settingOptionsInit: { [key: string]: boolean } = {
allowSpectators: false,
allowSpecials: false,
allowChat: false,
allowMarkDraw: false,
}
import { useGameProps } from "@hooks/useGameProps"
function Settings({ closeSettings }: { closeSettings: () => void }) {
const [settingOptions, setSettingOptions] = useState(settingOptionsInit)
const { payload, setSetting } = useGameProps()
return (
<div className="absolute inset-0 flex flex-col items-center justify-center bg-black/40">
<div className="w-full max-w-screen-lg">
@ -27,6 +21,7 @@ function Settings({ closeSettings }: { closeSettings: () => void }) {
>
<FontAwesomeIcon
className="h-full w-full text-gray-800 drop-shadow-md"
size="3x"
icon={faXmark}
/>
</button>
@ -35,21 +30,29 @@ function Settings({ closeSettings }: { closeSettings: () => void }) {
<div className="flex items-center justify-end">
<button
className="top-8 right-12 h-14 w-14"
onClick={() => setSettingOptions(settingOptionsInit)}
onClick={() => {
const payload = {
allowSpectators: true,
allowSpecials: true,
allowChat: true,
allowMarkDraw: true,
}
setSetting(payload)
gameSetting(payload)
}}
>
<FontAwesomeIcon
className="h-full w-full text-gray-800 drop-shadow-md"
size="3x"
icon={faRotateLeft}
/>
</button>
</div>
<div className="flex flex-col gap-8">
{Object.keys(settingOptions).map((key) => {
const state = settingOptions[key]
const onClick = () =>
setSettingOptions((e) => ({ ...e, [key]: !e[key] }))
return <Setting key={key} props={{ key, state, onClick }} />
})}
<Setting prop="allowSpectators">Erlaube Zuschauer</Setting>
<Setting prop="allowSpecials">Erlaube spezial Items</Setting>
<Setting prop="allowChat">Erlaube den Chat</Setting>
<Setting prop="allowMarkDraw">Erlaube zeichen/makieren</Setting>
</div>
</div>
</div>

View file

@ -0,0 +1,13 @@
import React from "react"
function profileImg(src: string) {
return (
<img
style={{ transform: "scale(1.5)", borderRadius: "100%" }}
src={src}
alt="profile picture"
/>
)
}
export default profileImg

View file

@ -1,22 +1,22 @@
import BorderTiles from "../../components/Gamefield/BorderTiles"
import EventBar from "../../components/Gamefield/EventBar"
import type { PointerProps } from "../../components/Gamefield/GamefieldPointer"
import HitElems from "../../components/Gamefield/HitElems"
import Targets from "../../components/Gamefield/Targets"
import {
Hit,
Mode,
MouseCursor,
Target,
Position,
} from "../../interfaces/frontend"
} from "../interfaces/frontend"
import BorderTiles from "@components/Gamefield/BorderTiles"
import EventBar from "@components/Gamefield/EventBar"
import type { PointerProps } from "@components/Gamefield/GamefieldPointer"
import HitElems from "@components/Gamefield/HitElems"
import Targets from "@components/Gamefield/Targets"
import {
hitReducer,
initlialLastLeftTile,
initlialTarget,
initlialTargetPreview,
initlialMouseCursor,
} from "../utils/helpers"
} from "@lib/utils/helpers"
import { useCallback, useEffect, useReducer, useState } from "react"
const modes: Mode[] = [

View file

@ -0,0 +1,58 @@
import { GameSettings } from "@components/Lobby/SettingsFrame/Setting"
import { GamePropsSchema } from "@lib/zodSchemas"
import { produce } from "immer"
import { create } from "zustand"
import { devtools } from "zustand/middleware"
const initialState: GamePropsSchema = { payload: null, hash: null }
type State = GamePropsSchema
type Action = {
setSetting: (by: GameSettings) => void
full: (newProps: GamePropsSchema) => void
reset: () => void
}
export const useGameProps = create<State & Action>()(
devtools(
(set) => ({
...initialState,
setSetting: (settings) =>
set(
produce((state: State) => {
if (!state.payload?.game) return
Object.assign(state.payload.game, settings)
})
),
full: (newGameProps) =>
set((state) => {
if (state.hash === newGameProps.hash) {
console.log("Everything up to date.")
} else {
console.log("Update was needed.", state.hash, newGameProps.hash)
if (
state.payload?.game?.id &&
state.payload?.game?.id !== newGameProps.payload?.game?.id
) {
console.warn(
"Different gameId detected on update: ",
state.payload?.game?.id,
newGameProps.payload?.game?.id
)
}
return newGameProps
}
return state
}),
reset: () => {
set(initialState)
},
}),
{
name: "gameState",
}
)
)

View file

@ -0,0 +1,105 @@
import { useGameProps } from "@hooks/useGameProps"
import { socket } from "@lib/socket"
import { useEffect, useState } from "react"
import { toast } from "react-toastify"
/** This function should only be called once per page, otherwise there will be multiple socket connections and duplicate event listeners. */
function useSocket() {
const [isConnected, setIsConnected] = useState(false)
const { full } = useGameProps()
useEffect(() => setIsConnected(socket.connected), [socket.connected])
useEffect(() => {
socket.connect()
socket.on("connect", () => {
console.log("connected")
const toastId = "connect_error"
toast.dismiss(toastId)
})
socket.on("connect_error", () => {
const toastId = "connect_error"
const isActive = toast.isActive(toastId)
if (isActive)
toast.update(toastId, {
autoClose: 5000,
})
else
toast.warn("Es gibt Probleme mit der Echtzeitverbindung.", { toastId })
})
socket.on("disconnect", () => {
console.log("disconnect")
})
socket.on("update", (body) => {
console.log("update")
full(body)
})
return () => {
socket.removeAllListeners()
socket.disconnect()
}
}, [])
// useEffect(() => {
// const toastId = "realtime"
// toast("Echtzeitverbindung wird hergestellt...", {
// icon: Icons.spinner(),
// toastId,
// autoClose: false,
// hideProgressBar: true,
// closeButton: false,
// })
// socket.emit("join", ({ ack }) => {
// if (!ack) {
// toast.update(toastId, {
// render: "Bei der Echtzeitverbindung ist ein Fehler aufgetreten 🤯",
// type: "error",
// icon: Icons.error,
// theme: "colored",
// autoClose: 5000,
// hideProgressBar: false,
// closeButton: true,
// })
// } else {
// setHasJoined(true)
// toast.update(toastId, {
// render: "Echtzeitverbindung hergestellt 👌",
// type: "info",
// icon: Icons.success,
// autoClose: 5000,
// hideProgressBar: false,
// closeButton: true,
// })
// }
// })
// }, [])
// useEffect(() => {
// if (!isConnected) return
// socket.emit("authenticate", { token: `hello from ${session?.user.email}` })
// }, [isConnected, status, session?.user.email])
// useEffect(() => {
// if (!isConnected) return
// let count = 0
// const interval = setInterval(() => {
// const start = Date.now()
// socket.volatile.emit("ping", ++count, (count) => {
// const duration = Date.now() - start
// console.log("ping", count, duration)
// })
// }, 5000)
// return () => clearInterval(interval)
// }, [isConnected])
return { isConnected }
}
export default useSocket

View file

@ -1,3 +1,4 @@
import { GameSettings } from "@components/Lobby/SettingsFrame/Setting"
import { GamePropsSchema } from "@lib/zodSchemas"
import { User } from "@prisma/client"
import type { Server as HTTPServer } from "http"
@ -22,13 +23,16 @@ export interface ServerToClientEvents {
// noArg: () => void
// basicEmit: (a: number, b: string, c: Buffer) => void
// withAck: (d: string, ) => void
forbidden: () => void
update: (game: GamePropsSchema) => void
}
export interface ClientToServerEvents {
join: (userId: string, callback: () => void) => void
authenticate: (payload: { token: string }) => void
ping: (count: number, callback: (count: number) => void) => void
join: (withAck: ({ ack }: { ack: boolean }) => void) => void
gameSetting: (
payload: GameSettings,
withAck: ({ ack }: { ack: boolean }) => void
) => void
}
interface InterServerEvents {
@ -37,7 +41,7 @@ interface InterServerEvents {
interface SocketData {
user: User | null
gameProps: GamePropsSchema
gameId: string | null
}
export type cServer = Server<

View file

@ -1,7 +1,11 @@
import { GamePropsSchema } from "./zodSchemas"
import crypto from "crypto"
export function getObjectChecksum(obj: Object) {
const objString = JSON.stringify(obj)
export function getPayloadwithChecksum(
payload: GamePropsSchema["payload"] | null
): GamePropsSchema {
if (payload === null) return { payload: null, hash: null }
const objString = JSON.stringify(payload)
const hash = crypto.createHash("md5").update(objString).digest("hex")
return hash
return { payload, hash }
}

View file

@ -1,92 +0,0 @@
import { GamePropsSchema } from "@lib/zodSchemas"
import { useSession } from "next-auth/react"
import {
Dispatch,
ReactNode,
createContext,
useContext,
useEffect,
useMemo,
useReducer,
} from "react"
import { toast } from "react-toastify"
const initialValue: GamePropsSchema = { payload: null, hash: null }
interface gameContext {
gameProps: GamePropsSchema
setGameProps: Dispatch<GamePropsSchema>
}
export const gameContext = createContext<gameContext>({
gameProps: initialValue,
setGameProps: () => {},
})
function updateProps(state: GamePropsSchema, body: GamePropsSchema) {
if (state.hash === body.hash) {
console.log("Everything up to date.")
return state
} else {
console.log("Update was needed.", state.hash, body.hash)
if (
state.payload?.game?.id &&
state.payload?.game?.id !== body.payload?.game?.id
) {
console.warn(
"Different gameId detected on update: ",
state.payload?.game?.id,
body.payload?.game?.id
)
}
return body
}
}
export function GameContextProvider({ children }: { children: ReactNode }) {
const [gameProps, setGameProps] = useReducer(updateProps, initialValue)
const value = useMemo<gameContext>(
() => ({ gameProps, setGameProps }),
[gameProps, setGameProps]
)
return <gameContext.Provider value={value}>{children}</gameContext.Provider>
}
function useGameState() {
const { gameProps, setGameProps } = useContext(gameContext)
const { data: session, status } = useSession()
useEffect(() => {
if (status === "loading") return
if (!session)
toast("Nicht angemeldet.", {
toastId: "user",
position: "top-center",
})
else
toast(session.user.email, {
toastId: "user",
position: "top-center",
icon: session.user.image ? (
<img
style={{ transform: "scale(1.5)", borderRadius: "100%" }}
src={session.user.image}
alt="profile picture"
/>
) : undefined,
})
}, [session, status])
return {
gameProps,
setGameProps,
session,
status,
}
}
export default useGameState

View file

@ -1,58 +0,0 @@
import useGameState from "@lib/hooks/useGameState"
import { socket } from "@lib/socket"
import { useEffect, useState } from "react"
function useSocket() {
const [isConnected, setIsConnected] = useState(socket.connected)
const [hasJoined, setHasJoined] = useState(false)
// const [fooEvents, setFooEvents] = useState([])
const { session, status, setGameProps } = useGameState()
useEffect(() => {
if (status === "loading") return
socket.on("connect", () => {
console.log("connected")
setIsConnected(true)
})
socket.on("connect_error", () => {
console.log("connect_error")
})
socket.on("disconnect", () => {
console.log("disconnect")
setIsConnected(false)
})
socket.on("forbidden", () => {
console.log("forbidden")
})
socket.on("update", (body) => {
console.log("update")
setGameProps(body)
})
return () => {
// for all events
socket.removeAllListeners()
}
}, [status, setGameProps])
useEffect(() => {
if (session?.user.id && !hasJoined) {
socket?.emit("join", session?.user.id, () => {
setHasJoined(true)
})
}
}, [session?.user.id, hasJoined])
useEffect(() => {
if (!isConnected) return
socket?.emit("authenticate", { token: `hello from ${session?.user.email}` })
}, [isConnected, status, session?.user.email])
return { isConnected, hasJoined }
}
export default useSocket

View file

@ -3,4 +3,5 @@ import { io } from "socket.io-client"
export const socket: cSocket = io({
path: "/api/ws",
autoConnect: false,
})

View file

@ -1,23 +1,41 @@
import {
GameSchema,
GamepinSchema,
User_GameSchema,
} from "../prisma/generated/zod"
import { GameState, PlayerN } from "@prisma/client"
import { z } from "zod"
export const PlayerSchema = z
.object({
email: z.string().nullable(),
name: z.string().nullable(),
})
.and(User_GameSchema)
export const PlayerSchema = z.object({
id: z.string(),
name: z.string().nullable(),
index: z.nativeEnum(PlayerN),
chats: z
.object({
id: z.string(),
event: z.string().nullable(),
message: z.string().nullable(),
createdAt: z.date(),
})
.array(),
moves: z
.object({
id: z.string(),
index: z.number(),
})
.array(),
})
export const CreateSchema = z
.object({
game: GameSchema.nullish(),
gamePin: GamepinSchema.nullish(),
player1: PlayerSchema.nullish(),
player2: PlayerSchema.nullish(),
game: z
.object({
id: z.string(),
state: z.nativeEnum(GameState),
allowSpectators: z.boolean(),
allowSpecials: z.boolean(),
allowChat: z.boolean(),
allowMarkDraw: z.boolean(),
})
.nullable(),
gamePin: z.string().nullable(),
player1: PlayerSchema.nullable(),
player2: PlayerSchema.nullable(),
})
.strict()

View file

@ -25,6 +25,7 @@
"eslint": "8.31.0",
"eslint-config-next": "13.1.1",
"http-status": "^1.6.2",
"immer": "^10.0.2",
"next": "13.1.1",
"next-auth": "^4.22.1",
"nodemailer": "^6.9.1",
@ -38,7 +39,8 @@
"typescript": "4.9.4",
"unique-names-generator": "^4.7.1",
"zod": "^3.21.1",
"zod-prisma-types": "^2.5.6"
"zod-prisma-types": "^2.5.6",
"zustand": "^4.3.8"
},
"devDependencies": {
"@total-typescript/ts-reset": "^0.3.7",

View file

@ -3,7 +3,6 @@ import "../styles/globals.scss"
import "../styles/grid2.scss"
import "../styles/grid.scss"
import "@fortawesome/fontawesome-svg-core/styles.css"
import { GameContextProvider } from "@lib/hooks/useGameState"
import { SessionProvider } from "next-auth/react"
import type { AppProps } from "next/app"
import { ToastContainer } from "react-toastify"
@ -15,10 +14,8 @@ export default function App({
}: AppProps) {
return (
<SessionProvider session={session}>
<GameContextProvider>
<Component {...pageProps} />
<ToastContainer />
</GameContextProvider>
<Component {...pageProps} />
<ToastContainer />
</SessionProvider>
)
}

View file

@ -1,5 +1,5 @@
import { authOptions } from "../auth/[...nextauth]"
import { composeBody, gameIncludes, getAnyRunningGame } from "./running"
import { composeBody, gameSelects, getAnyRunningGame } from "./running"
import sendResponse from "@backend/sendResponse"
import { rejectionErrors } from "@lib/backend/errors"
import prisma from "@lib/prisma"
@ -46,7 +46,7 @@ export default async function create(
},
},
},
...gameIncludes,
...gameSelects,
})
}

View file

@ -1,5 +1,5 @@
import { authOptions } from "../auth/[...nextauth]"
import { composeBody, gameIncludes } from "./running"
import { composeBody, gameSelects } from "./running"
import sendError from "@backend/sendError"
import sendResponse from "@backend/sendResponse"
import { rejectionErrors } from "@lib/backend/errors"
@ -46,7 +46,7 @@ export default async function join(
index: "player2",
},
include: {
game: gameIncludes,
game: gameSelects,
},
})

View file

@ -1,21 +1,46 @@
import { authOptions } from "../auth/[...nextauth]"
import sendResponse from "@backend/sendResponse"
import { rejectionErrors } from "@lib/backend/errors"
import { getObjectChecksum } from "@lib/getObjectChecksum"
import { getPayloadwithChecksum } from "@lib/getObjectChecksum"
import prisma from "@lib/prisma"
import { GamePropsSchema } from "@lib/zodSchemas"
import type { NextApiRequest, NextApiResponse } from "next"
import { getServerSession } from "next-auth"
export const gameIncludes = {
include: {
gamePin: true,
export const gameSelects = {
select: {
id: true,
allowChat: true,
allowMarkDraw: true,
allowSpecials: true,
allowSpectators: true,
state: true,
gamePin: {
select: {
pin: true,
},
},
users: {
include: {
select: {
id: true,
index: true,
chats: {
select: {
id: true,
event: true,
message: true,
createdAt: true,
},
},
moves: {
select: {
id: true,
index: true,
},
},
user: {
select: {
name: true,
email: true,
},
},
},
@ -31,7 +56,7 @@ export const getAnyGame = (gameId: string) => {
},
id: gameId,
},
...gameIncludes,
...gameSelects,
})
return game
}
@ -48,14 +73,14 @@ export const getAnyRunningGame = (userId: string) => {
},
},
},
...gameIncludes,
...gameSelects,
})
return game
}
export function composeBody(
gameDB: NonNullable<Awaited<ReturnType<typeof getAnyRunningGame>>>
) {
): GamePropsSchema {
const { gamePin, ...game } = gameDB
const users = gameDB.users.map(({ user, ...props }) => ({
...props,
@ -64,13 +89,12 @@ export function composeBody(
const player1 = users.find((user) => user.index === "player1")
const player2 = users.find((user) => user.index === "player2")
const payload = {
game,
gamePin,
player1,
player2,
game: game,
gamePin: gamePin?.pin ?? null,
player1: player1 ?? null,
player2: player2 ?? null,
}
const hash = getObjectChecksum(payload)
return { payload, hash }
return getPayloadwithChecksum(payload)
}
export default async function running(

View file

@ -2,7 +2,12 @@ import {
NextApiResponseWithSocket,
cServer,
} from "../../interfaces/NextApiSocket"
import { composeBody, getAnyRunningGame } from "./game/running"
import {
composeBody,
gameSelects,
getAnyGame,
getAnyRunningGame,
} from "./game/running"
import logging from "@lib/backend/logging"
import prisma from "@lib/prisma"
import colors from "colors"
@ -40,31 +45,9 @@ const SocketHandler = async (
id: session?.user.id,
},
})
next()
} catch (err) {
logging("Unauthorized", ["warn"], socket.request)
next(new Error("Unauthorized"))
}
})
io.on("connection", (socket) => {
logging(
`User connected <${socket.data.user?.email}>`.green +
", " +
socket.id.cyan,
["infoGreen"],
socket.request
)
socket.on("join", async (userId, cb) => {
logging(
"Join: " + JSON.stringify(Array.from(socket.rooms)),
["debug"],
socket.request
)
const game = await getAnyRunningGame(userId ?? "")
const game = await getAnyRunningGame(socket.data.user?.id ?? "")
if (!game) {
socket.emit("forbidden")
logging(
"Forbidden, no game found: " +
JSON.stringify(Array.from(socket.rooms)),
@ -73,11 +56,46 @@ const SocketHandler = async (
)
return
}
cb()
socket.join(userId)
socket.data.gameId = game.id
socket.join(game.id)
next()
} catch (err) {
logging("Unauthorized", ["warn"], socket.request)
next(new Error("Unauthorized"))
}
})
io.on("connection", async (socket) => {
logging(
`User connected <${socket.data.user?.email}>`.green +
", " +
socket.id.cyan,
["infoGreen"],
socket.request
)
const game = await getAnyGame(socket.data.gameId ?? "")
if (!socket.data.user || !socket.data.gameId || !game)
return socket.disconnect()
const body = composeBody(game)
io.to(socket.data.gameId).emit("update", body)
socket.on("gameSetting", async (payload, cb) => {
if (!socket.data.gameId) {
cb({ ack: false })
return
}
const game = await prisma.game.update({
where: { id: socket.data.gameId },
data: payload,
...gameSelects,
})
const body = composeBody(game)
socket.data.gameProps = body
io.to(userId).emit("update", body)
cb({ ack: true })
io.to(game.id).emit("update", body)
})
socket.on("ping", (count, callback) => {
callback(count)
})
socket.on("disconnecting", () => {

View file

@ -1,33 +1,36 @@
import useGameState from "@lib/hooks/useGameState"
import { useGameProps } from "@hooks/useGameProps"
import { useSession } from "next-auth/react"
import { useRouter } from "next/router"
import React, { useEffect } from "react"
import { toast } from "react-toastify"
export default function Game() {
const { gameProps, session } = useGameState()
const { payload } = useGameProps()
const router = useRouter()
const { data: session } = useSession()
useEffect(() => {
const gameId = gameProps.payload?.game?.id
const gameId = payload?.game?.id
const path = gameId ? "/game" : "/start"
toast.promise(router.push(path), {
pending: {
render: "Wird weitergeleitet...",
toastId: "redirect",
toastId: "pageLoad",
},
success: {
render: gameId
? "Spiel gefunden!"
: session?.user
: session?.user.id
? "Kein laufendes Spiel."
: "Kein laufendes Spiel. Bitte anmelden.",
toastId: session?.user ? "postRedirect" : "user",
theme: session?.user ? "dark" : undefined,
toastId: "pageLoad",
theme: session?.user.id ? "dark" : undefined,
type: gameId ? "success" : "info",
},
error: {
render: "Es ist ein Fehler aufgetreten 🤯",
type: "error",
toastId: "pageLoad",
theme: "colored",
},
})

View file

@ -1,4 +1,4 @@
import Gamefield from "../components/Gamefield/Gamefield"
import Gamefield from "@components/Gamefield/Gamefield"
import Head from "next/head"
export default function Home() {

View file

@ -1,4 +1,4 @@
import Grid from "../components/Grid"
import Grid from "@components/Grid"
import Head from "next/head"
export default function Home() {

View file

@ -1,4 +1,4 @@
import Grid2 from "../components/Grid2"
import Grid2 from "@components/Grid2"
import Head from "next/head"
export default function Home() {

View file

@ -1,5 +1,5 @@
import BurgerMenu from "../components/BurgerMenu"
import Logo from "../components/Logo"
import BurgerMenu from "@components/BurgerMenu"
import Logo from "@components/Logo"
import { faCirclePlay } from "@fortawesome/pro-thin-svg-icons"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { useRouter } from "next/router"

View file

@ -1,14 +1,12 @@
import BurgerMenu from "../components/BurgerMenu"
import LobbyFrame from "../components/Lobby/LobbyFrame"
import Settings from "../components/Lobby/SettingsFrame/Settings"
import Logo from "../components/Logo"
import useGameState from "@lib/hooks/useGameState"
import BurgerMenu from "@components/BurgerMenu"
import LobbyFrame from "@components/Lobby/LobbyFrame"
import Settings from "@components/Lobby/SettingsFrame/Settings"
import Logo from "@components/Logo"
import classNames from "classnames"
import Head from "next/head"
import { useState } from "react"
export default function Lobby() {
useGameState()
const [settings, setSettings] = useState(false)
return (

View file

@ -1,16 +1,18 @@
import BurgerMenu from "../components/BurgerMenu"
import Logo from "../components/Logo"
import OptionButton from "../components/OptionButton"
import BurgerMenu from "@components/BurgerMenu"
import Logo from "@components/Logo"
import OptionButton from "@components/OptionButton"
import { faEye, faLeftLong } from "@fortawesome/pro-regular-svg-icons"
import { faPlus, faUserPlus } from "@fortawesome/pro-solid-svg-icons"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import useGameState from "@lib/hooks/useGameState"
import { useGameProps } from "@hooks/useGameProps"
import { GamePropsSchema } from "@lib/zodSchemas"
import status from "http-status"
import { useSession } from "next-auth/react"
import { useRouter } from "next/router"
import { useCallback, useEffect, useMemo, useState } from "react"
import OtpInput from "react-otp-input"
import { toast } from "react-toastify"
import { Icons } from "react-toastify"
function isInputOnlyNumbers(input: string) {
return /^\d+$/.test(input)
@ -45,8 +47,9 @@ const handleConfirmation = () => {
export default function Start() {
const [otp, setOtp] = useState("")
const { session, setGameProps } = useGameState()
const { full } = useGameProps()
const router = useRouter()
const { data: session } = useSession()
const query = useMemo((): { join?: boolean; watch?: boolean } => {
switch (router.query.q) {
@ -68,43 +71,58 @@ export default function Start() {
.then(isAuthenticated)
.then((game) => GamePropsSchema.parse(game))
const toastId = !pin ? "erstellt" : "angefragt"
const res = await toast.promise(gamePromise, {
pending: {
render: "Raum wird " + toastId,
toastId,
},
success: {
render: "Raum " + (!pin ? "erstellt" : "angefragt") + " 👌",
type: "info",
theme: "colored",
toastId,
},
error: {
const action = !pin ? "erstellt" : "angefragt"
const toastId = "pageLoad"
toast("Raum wird " + action, {
icon: Icons.spinner(),
toastId,
autoClose: false,
hideProgressBar: true,
closeButton: false,
})
const res = await gamePromise.catch(() =>
toast.update(toastId, {
render: "Es ist ein Fehler aufgetreten 🤯",
type: "error",
icon: Icons.error,
theme: "colored",
toastId,
},
autoClose: 5000,
hideProgressBar: false,
closeButton: true,
})
)
if (!res) return
full(res)
toast.update(toastId, {
render: "Weiterleitung",
})
setGameProps(res)
await toast.promise(router.push("/lobby"), {
pending: {
render: "Raum wird beigetreten",
},
success: {
render: "Raum begetreten 👌",
type: "info",
},
error: {
render: "Es ist ein Fehler aufgetreten 🤯",
type: "error",
},
})
router
.push("/lobby")
.then(() =>
toast.update(toastId, {
render: "Raum begetreten 👌",
type: "info",
icon: Icons.success,
autoClose: 5000,
hideProgressBar: false,
closeButton: true,
})
)
.catch(() =>
toast.update(toastId, {
render: "Es ist ein Fehler aufgetreten 🤯",
type: "error",
icon: Icons.error,
theme: "colored",
autoClose: 5000,
hideProgressBar: false,
closeButton: true,
})
)
},
[router, setGameProps]
[router, full]
)
useEffect(() => {
@ -151,7 +169,7 @@ export default function Start() {
icon={faUserPlus}
disabled={!session}
>
{query.join && session?.user ? (
{query.join && session ? (
<OtpInput
shouldAutoFocus
containerStyle={{ color: "initial" }}
@ -167,7 +185,7 @@ export default function Start() {
)}
</OptionButton>
<OptionButton icon={faEye}>
{query.watch && session?.user ? (
{query.watch ? (
<OtpInput
shouldAutoFocus
containerStyle={{ color: "initial" }}

View file

@ -49,6 +49,9 @@ dependencies:
http-status:
specifier: ^1.6.2
version: 1.6.2
immer:
specifier: ^10.0.2
version: 10.0.2
next:
specifier: 13.1.1
version: 13.1.1(react-dom@18.2.0)(react@18.2.0)(sass@1.61.0)
@ -91,6 +94,9 @@ dependencies:
zod-prisma-types:
specifier: ^2.5.6
version: 2.5.6
zustand:
specifier: ^4.3.8
version: 4.3.8(immer@10.0.2)(react@18.2.0)
devDependencies:
'@total-typescript/ts-reset':
@ -1861,6 +1867,10 @@ packages:
resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==}
engines: {node: '>= 4'}
/immer@10.0.2:
resolution: {integrity: sha512-Rx3CqeqQ19sxUtYV9CU911Vhy8/721wRFnJv3REVGWUmoAcIwzifTsdmJte/MV+0/XpM35LZdQMBGkRIoLPwQA==}
dev: false
/immutable@4.3.0:
resolution: {integrity: sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==}
@ -3180,6 +3190,14 @@ packages:
dependencies:
punycode: 2.3.0
/use-sync-external-store@1.2.0(react@18.2.0):
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
react: 18.2.0
dev: false
/util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: true
@ -3289,3 +3307,20 @@ packages:
/zod@3.21.4:
resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==}
dev: false
/zustand@4.3.8(immer@10.0.2)(react@18.2.0):
resolution: {integrity: sha512-4h28KCkHg5ii/wcFFJ5Fp+k1J3gJoasaIbppdgZFO4BPJnsNxL0mQXBSFgOgAdCdBj35aDTPvdAJReTMntFPGg==}
engines: {node: '>=12.7.0'}
peerDependencies:
immer: '>=9.0'
react: '>=16.8'
peerDependenciesMeta:
immer:
optional: true
react:
optional: true
dependencies:
immer: 10.0.2
react: 18.2.0
use-sync-external-store: 1.2.0(react@18.2.0)
dev: false

View file

@ -14,7 +14,7 @@ export const AccountScalarFieldEnumSchema = z.enum(['id','userId','type','provid
export const ChatScalarFieldEnumSchema = z.enum(['id','createdAt','message','event','user_game_id']);
export const GameScalarFieldEnumSchema = z.enum(['id','createdAt','updatedAt','state']);
export const GameScalarFieldEnumSchema = z.enum(['id','createdAt','updatedAt','state','allowSpectators','allowSpecials','allowChat','allowMarkDraw']);
export const GamepinScalarFieldEnumSchema = z.enum(['id','createdAt','pin','gameId']);
@ -118,6 +118,10 @@ export const GameSchema = z.object({
id: z.string().cuid(),
createdAt: z.coerce.date(),
updatedAt: z.coerce.date(),
allowSpectators: z.boolean(),
allowSpecials: z.boolean(),
allowChat: z.boolean(),
allowMarkDraw: z.boolean(),
})
export type Game = z.infer<typeof GameSchema>
@ -306,6 +310,10 @@ export const GameSelectSchema: z.ZodType<Prisma.GameSelect> = z.object({
createdAt: z.boolean().optional(),
updatedAt: z.boolean().optional(),
state: z.boolean().optional(),
allowSpectators: z.boolean().optional(),
allowSpecials: z.boolean().optional(),
allowChat: z.boolean().optional(),
allowMarkDraw: z.boolean().optional(),
gamePin: z.union([z.boolean(),z.lazy(() => GamepinArgsSchema)]).optional(),
users: z.union([z.boolean(),z.lazy(() => User_GameFindManyArgsSchema)]).optional(),
_count: z.union([z.boolean(),z.lazy(() => GameCountOutputTypeArgsSchema)]).optional(),
@ -655,6 +663,10 @@ export const GameWhereInputSchema: z.ZodType<Prisma.GameWhereInput> = z.object({
createdAt: z.union([ z.lazy(() => DateTimeFilterSchema),z.coerce.date() ]).optional(),
updatedAt: z.union([ z.lazy(() => DateTimeFilterSchema),z.coerce.date() ]).optional(),
state: z.union([ z.lazy(() => EnumGameStateFilterSchema),z.lazy(() => GameStateSchema) ]).optional(),
allowSpectators: z.union([ z.lazy(() => BoolFilterSchema),z.boolean() ]).optional(),
allowSpecials: z.union([ z.lazy(() => BoolFilterSchema),z.boolean() ]).optional(),
allowChat: z.union([ z.lazy(() => BoolFilterSchema),z.boolean() ]).optional(),
allowMarkDraw: z.union([ z.lazy(() => BoolFilterSchema),z.boolean() ]).optional(),
gamePin: z.union([ z.lazy(() => GamepinRelationFilterSchema),z.lazy(() => GamepinWhereInputSchema) ]).optional().nullable(),
users: z.lazy(() => User_GameListRelationFilterSchema).optional()
}).strict();
@ -664,6 +676,10 @@ export const GameOrderByWithRelationInputSchema: z.ZodType<Prisma.GameOrderByWit
createdAt: z.lazy(() => SortOrderSchema).optional(),
updatedAt: z.lazy(() => SortOrderSchema).optional(),
state: z.lazy(() => SortOrderSchema).optional(),
allowSpectators: z.lazy(() => SortOrderSchema).optional(),
allowSpecials: z.lazy(() => SortOrderSchema).optional(),
allowChat: z.lazy(() => SortOrderSchema).optional(),
allowMarkDraw: z.lazy(() => SortOrderSchema).optional(),
gamePin: z.lazy(() => GamepinOrderByWithRelationInputSchema).optional(),
users: z.lazy(() => User_GameOrderByRelationAggregateInputSchema).optional()
}).strict();
@ -677,6 +693,10 @@ export const GameOrderByWithAggregationInputSchema: z.ZodType<Prisma.GameOrderBy
createdAt: z.lazy(() => SortOrderSchema).optional(),
updatedAt: z.lazy(() => SortOrderSchema).optional(),
state: z.lazy(() => SortOrderSchema).optional(),
allowSpectators: z.lazy(() => SortOrderSchema).optional(),
allowSpecials: z.lazy(() => SortOrderSchema).optional(),
allowChat: z.lazy(() => SortOrderSchema).optional(),
allowMarkDraw: z.lazy(() => SortOrderSchema).optional(),
_count: z.lazy(() => GameCountOrderByAggregateInputSchema).optional(),
_max: z.lazy(() => GameMaxOrderByAggregateInputSchema).optional(),
_min: z.lazy(() => GameMinOrderByAggregateInputSchema).optional()
@ -690,6 +710,10 @@ export const GameScalarWhereWithAggregatesInputSchema: z.ZodType<Prisma.GameScal
createdAt: z.union([ z.lazy(() => DateTimeWithAggregatesFilterSchema),z.coerce.date() ]).optional(),
updatedAt: z.union([ z.lazy(() => DateTimeWithAggregatesFilterSchema),z.coerce.date() ]).optional(),
state: z.union([ z.lazy(() => EnumGameStateWithAggregatesFilterSchema),z.lazy(() => GameStateSchema) ]).optional(),
allowSpectators: z.union([ z.lazy(() => BoolWithAggregatesFilterSchema),z.boolean() ]).optional(),
allowSpecials: z.union([ z.lazy(() => BoolWithAggregatesFilterSchema),z.boolean() ]).optional(),
allowChat: z.union([ z.lazy(() => BoolWithAggregatesFilterSchema),z.boolean() ]).optional(),
allowMarkDraw: z.union([ z.lazy(() => BoolWithAggregatesFilterSchema),z.boolean() ]).optional(),
}).strict();
export const GamepinWhereInputSchema: z.ZodType<Prisma.GamepinWhereInput> = z.object({
@ -1187,6 +1211,10 @@ export const GameCreateInputSchema: z.ZodType<Prisma.GameCreateInput> = z.object
createdAt: z.coerce.date().optional(),
updatedAt: z.coerce.date().optional(),
state: z.lazy(() => GameStateSchema).optional(),
allowSpectators: z.boolean().optional(),
allowSpecials: z.boolean().optional(),
allowChat: z.boolean().optional(),
allowMarkDraw: z.boolean().optional(),
gamePin: z.lazy(() => GamepinCreateNestedOneWithoutGameInputSchema).optional(),
users: z.lazy(() => User_GameCreateNestedManyWithoutGameInputSchema).optional()
}).strict();
@ -1196,6 +1224,10 @@ export const GameUncheckedCreateInputSchema: z.ZodType<Prisma.GameUncheckedCreat
createdAt: z.coerce.date().optional(),
updatedAt: z.coerce.date().optional(),
state: z.lazy(() => GameStateSchema).optional(),
allowSpectators: z.boolean().optional(),
allowSpecials: z.boolean().optional(),
allowChat: z.boolean().optional(),
allowMarkDraw: z.boolean().optional(),
gamePin: z.lazy(() => GamepinUncheckedCreateNestedOneWithoutGameInputSchema).optional(),
users: z.lazy(() => User_GameUncheckedCreateNestedManyWithoutGameInputSchema).optional()
}).strict();
@ -1205,6 +1237,10 @@ export const GameUpdateInputSchema: z.ZodType<Prisma.GameUpdateInput> = z.object
createdAt: z.union([ z.coerce.date(),z.lazy(() => DateTimeFieldUpdateOperationsInputSchema) ]).optional(),
updatedAt: z.union([ z.coerce.date(),z.lazy(() => DateTimeFieldUpdateOperationsInputSchema) ]).optional(),
state: z.union([ z.lazy(() => GameStateSchema),z.lazy(() => EnumGameStateFieldUpdateOperationsInputSchema) ]).optional(),
allowSpectators: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
allowSpecials: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
allowChat: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
allowMarkDraw: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
gamePin: z.lazy(() => GamepinUpdateOneWithoutGameNestedInputSchema).optional(),
users: z.lazy(() => User_GameUpdateManyWithoutGameNestedInputSchema).optional()
}).strict();
@ -1214,6 +1250,10 @@ export const GameUncheckedUpdateInputSchema: z.ZodType<Prisma.GameUncheckedUpdat
createdAt: z.union([ z.coerce.date(),z.lazy(() => DateTimeFieldUpdateOperationsInputSchema) ]).optional(),
updatedAt: z.union([ z.coerce.date(),z.lazy(() => DateTimeFieldUpdateOperationsInputSchema) ]).optional(),
state: z.union([ z.lazy(() => GameStateSchema),z.lazy(() => EnumGameStateFieldUpdateOperationsInputSchema) ]).optional(),
allowSpectators: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
allowSpecials: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
allowChat: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
allowMarkDraw: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
gamePin: z.lazy(() => GamepinUncheckedUpdateOneWithoutGameNestedInputSchema).optional(),
users: z.lazy(() => User_GameUncheckedUpdateManyWithoutGameNestedInputSchema).optional()
}).strict();
@ -1222,7 +1262,11 @@ export const GameCreateManyInputSchema: z.ZodType<Prisma.GameCreateManyInput> =
id: z.string().cuid().optional(),
createdAt: z.coerce.date().optional(),
updatedAt: z.coerce.date().optional(),
state: z.lazy(() => GameStateSchema).optional()
state: z.lazy(() => GameStateSchema).optional(),
allowSpectators: z.boolean().optional(),
allowSpecials: z.boolean().optional(),
allowChat: z.boolean().optional(),
allowMarkDraw: z.boolean().optional()
}).strict();
export const GameUpdateManyMutationInputSchema: z.ZodType<Prisma.GameUpdateManyMutationInput> = z.object({
@ -1230,6 +1274,10 @@ export const GameUpdateManyMutationInputSchema: z.ZodType<Prisma.GameUpdateManyM
createdAt: z.union([ z.coerce.date(),z.lazy(() => DateTimeFieldUpdateOperationsInputSchema) ]).optional(),
updatedAt: z.union([ z.coerce.date(),z.lazy(() => DateTimeFieldUpdateOperationsInputSchema) ]).optional(),
state: z.union([ z.lazy(() => GameStateSchema),z.lazy(() => EnumGameStateFieldUpdateOperationsInputSchema) ]).optional(),
allowSpectators: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
allowSpecials: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
allowChat: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
allowMarkDraw: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
}).strict();
export const GameUncheckedUpdateManyInputSchema: z.ZodType<Prisma.GameUncheckedUpdateManyInput> = z.object({
@ -1237,6 +1285,10 @@ export const GameUncheckedUpdateManyInputSchema: z.ZodType<Prisma.GameUncheckedU
createdAt: z.union([ z.coerce.date(),z.lazy(() => DateTimeFieldUpdateOperationsInputSchema) ]).optional(),
updatedAt: z.union([ z.coerce.date(),z.lazy(() => DateTimeFieldUpdateOperationsInputSchema) ]).optional(),
state: z.union([ z.lazy(() => GameStateSchema),z.lazy(() => EnumGameStateFieldUpdateOperationsInputSchema) ]).optional(),
allowSpectators: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
allowSpecials: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
allowChat: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
allowMarkDraw: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
}).strict();
export const GamepinCreateInputSchema: z.ZodType<Prisma.GamepinCreateInput> = z.object({
@ -1776,6 +1828,11 @@ export const EnumGameStateFilterSchema: z.ZodType<Prisma.EnumGameStateFilter> =
not: z.union([ z.lazy(() => GameStateSchema),z.lazy(() => NestedEnumGameStateFilterSchema) ]).optional(),
}).strict();
export const BoolFilterSchema: z.ZodType<Prisma.BoolFilter> = z.object({
equals: z.boolean().optional(),
not: z.union([ z.boolean(),z.lazy(() => NestedBoolFilterSchema) ]).optional(),
}).strict();
export const GamepinRelationFilterSchema: z.ZodType<Prisma.GamepinRelationFilter> = z.object({
is: z.lazy(() => GamepinWhereInputSchema).optional().nullable(),
isNot: z.lazy(() => GamepinWhereInputSchema).optional().nullable()
@ -1785,21 +1842,33 @@ export const GameCountOrderByAggregateInputSchema: z.ZodType<Prisma.GameCountOrd
id: z.lazy(() => SortOrderSchema).optional(),
createdAt: z.lazy(() => SortOrderSchema).optional(),
updatedAt: z.lazy(() => SortOrderSchema).optional(),
state: z.lazy(() => SortOrderSchema).optional()
state: z.lazy(() => SortOrderSchema).optional(),
allowSpectators: z.lazy(() => SortOrderSchema).optional(),
allowSpecials: z.lazy(() => SortOrderSchema).optional(),
allowChat: z.lazy(() => SortOrderSchema).optional(),
allowMarkDraw: z.lazy(() => SortOrderSchema).optional()
}).strict();
export const GameMaxOrderByAggregateInputSchema: z.ZodType<Prisma.GameMaxOrderByAggregateInput> = z.object({
id: z.lazy(() => SortOrderSchema).optional(),
createdAt: z.lazy(() => SortOrderSchema).optional(),
updatedAt: z.lazy(() => SortOrderSchema).optional(),
state: z.lazy(() => SortOrderSchema).optional()
state: z.lazy(() => SortOrderSchema).optional(),
allowSpectators: z.lazy(() => SortOrderSchema).optional(),
allowSpecials: z.lazy(() => SortOrderSchema).optional(),
allowChat: z.lazy(() => SortOrderSchema).optional(),
allowMarkDraw: z.lazy(() => SortOrderSchema).optional()
}).strict();
export const GameMinOrderByAggregateInputSchema: z.ZodType<Prisma.GameMinOrderByAggregateInput> = z.object({
id: z.lazy(() => SortOrderSchema).optional(),
createdAt: z.lazy(() => SortOrderSchema).optional(),
updatedAt: z.lazy(() => SortOrderSchema).optional(),
state: z.lazy(() => SortOrderSchema).optional()
state: z.lazy(() => SortOrderSchema).optional(),
allowSpectators: z.lazy(() => SortOrderSchema).optional(),
allowSpecials: z.lazy(() => SortOrderSchema).optional(),
allowChat: z.lazy(() => SortOrderSchema).optional(),
allowMarkDraw: z.lazy(() => SortOrderSchema).optional()
}).strict();
export const EnumGameStateWithAggregatesFilterSchema: z.ZodType<Prisma.EnumGameStateWithAggregatesFilter> = z.object({
@ -1812,6 +1881,14 @@ export const EnumGameStateWithAggregatesFilterSchema: z.ZodType<Prisma.EnumGameS
_max: z.lazy(() => NestedEnumGameStateFilterSchema).optional()
}).strict();
export const BoolWithAggregatesFilterSchema: z.ZodType<Prisma.BoolWithAggregatesFilter> = z.object({
equals: z.boolean().optional(),
not: z.union([ z.boolean(),z.lazy(() => NestedBoolWithAggregatesFilterSchema) ]).optional(),
_count: z.lazy(() => NestedIntFilterSchema).optional(),
_min: z.lazy(() => NestedBoolFilterSchema).optional(),
_max: z.lazy(() => NestedBoolFilterSchema).optional()
}).strict();
export const GameRelationFilterSchema: z.ZodType<Prisma.GameRelationFilter> = z.object({
is: z.lazy(() => GameWhereInputSchema).optional(),
isNot: z.lazy(() => GameWhereInputSchema).optional()
@ -2207,6 +2284,10 @@ export const EnumGameStateFieldUpdateOperationsInputSchema: z.ZodType<Prisma.Enu
set: z.lazy(() => GameStateSchema).optional()
}).strict();
export const BoolFieldUpdateOperationsInputSchema: z.ZodType<Prisma.BoolFieldUpdateOperationsInput> = z.object({
set: z.boolean().optional()
}).strict();
export const GamepinUpdateOneWithoutGameNestedInputSchema: z.ZodType<Prisma.GamepinUpdateOneWithoutGameNestedInput> = z.object({
create: z.union([ z.lazy(() => GamepinCreateWithoutGameInputSchema),z.lazy(() => GamepinUncheckedCreateWithoutGameInputSchema) ]).optional(),
connectOrCreate: z.lazy(() => GamepinCreateOrConnectWithoutGameInputSchema).optional(),
@ -2589,6 +2670,11 @@ export const NestedEnumGameStateFilterSchema: z.ZodType<Prisma.NestedEnumGameSta
not: z.union([ z.lazy(() => GameStateSchema),z.lazy(() => NestedEnumGameStateFilterSchema) ]).optional(),
}).strict();
export const NestedBoolFilterSchema: z.ZodType<Prisma.NestedBoolFilter> = z.object({
equals: z.boolean().optional(),
not: z.union([ z.boolean(),z.lazy(() => NestedBoolFilterSchema) ]).optional(),
}).strict();
export const NestedEnumGameStateWithAggregatesFilterSchema: z.ZodType<Prisma.NestedEnumGameStateWithAggregatesFilter> = z.object({
equals: z.lazy(() => GameStateSchema).optional(),
in: z.lazy(() => GameStateSchema).array().optional(),
@ -2599,6 +2685,14 @@ export const NestedEnumGameStateWithAggregatesFilterSchema: z.ZodType<Prisma.Nes
_max: z.lazy(() => NestedEnumGameStateFilterSchema).optional()
}).strict();
export const NestedBoolWithAggregatesFilterSchema: z.ZodType<Prisma.NestedBoolWithAggregatesFilter> = z.object({
equals: z.boolean().optional(),
not: z.union([ z.boolean(),z.lazy(() => NestedBoolWithAggregatesFilterSchema) ]).optional(),
_count: z.lazy(() => NestedIntFilterSchema).optional(),
_min: z.lazy(() => NestedBoolFilterSchema).optional(),
_max: z.lazy(() => NestedBoolFilterSchema).optional()
}).strict();
export const NestedEnumPlayerNFilterSchema: z.ZodType<Prisma.NestedEnumPlayerNFilter> = z.object({
equals: z.lazy(() => PlayerNSchema).optional(),
in: z.lazy(() => PlayerNSchema).array().optional(),
@ -3026,6 +3120,10 @@ export const GameCreateWithoutGamePinInputSchema: z.ZodType<Prisma.GameCreateWit
createdAt: z.coerce.date().optional(),
updatedAt: z.coerce.date().optional(),
state: z.lazy(() => GameStateSchema).optional(),
allowSpectators: z.boolean().optional(),
allowSpecials: z.boolean().optional(),
allowChat: z.boolean().optional(),
allowMarkDraw: z.boolean().optional(),
users: z.lazy(() => User_GameCreateNestedManyWithoutGameInputSchema).optional()
}).strict();
@ -3034,6 +3132,10 @@ export const GameUncheckedCreateWithoutGamePinInputSchema: z.ZodType<Prisma.Game
createdAt: z.coerce.date().optional(),
updatedAt: z.coerce.date().optional(),
state: z.lazy(() => GameStateSchema).optional(),
allowSpectators: z.boolean().optional(),
allowSpecials: z.boolean().optional(),
allowChat: z.boolean().optional(),
allowMarkDraw: z.boolean().optional(),
users: z.lazy(() => User_GameUncheckedCreateNestedManyWithoutGameInputSchema).optional()
}).strict();
@ -3052,6 +3154,10 @@ export const GameUpdateWithoutGamePinInputSchema: z.ZodType<Prisma.GameUpdateWit
createdAt: z.union([ z.coerce.date(),z.lazy(() => DateTimeFieldUpdateOperationsInputSchema) ]).optional(),
updatedAt: z.union([ z.coerce.date(),z.lazy(() => DateTimeFieldUpdateOperationsInputSchema) ]).optional(),
state: z.union([ z.lazy(() => GameStateSchema),z.lazy(() => EnumGameStateFieldUpdateOperationsInputSchema) ]).optional(),
allowSpectators: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
allowSpecials: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
allowChat: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
allowMarkDraw: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
users: z.lazy(() => User_GameUpdateManyWithoutGameNestedInputSchema).optional()
}).strict();
@ -3060,6 +3166,10 @@ export const GameUncheckedUpdateWithoutGamePinInputSchema: z.ZodType<Prisma.Game
createdAt: z.union([ z.coerce.date(),z.lazy(() => DateTimeFieldUpdateOperationsInputSchema) ]).optional(),
updatedAt: z.union([ z.coerce.date(),z.lazy(() => DateTimeFieldUpdateOperationsInputSchema) ]).optional(),
state: z.union([ z.lazy(() => GameStateSchema),z.lazy(() => EnumGameStateFieldUpdateOperationsInputSchema) ]).optional(),
allowSpectators: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
allowSpecials: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
allowChat: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
allowMarkDraw: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
users: z.lazy(() => User_GameUncheckedUpdateManyWithoutGameNestedInputSchema).optional()
}).strict();
@ -3114,6 +3224,10 @@ export const GameCreateWithoutUsersInputSchema: z.ZodType<Prisma.GameCreateWitho
createdAt: z.coerce.date().optional(),
updatedAt: z.coerce.date().optional(),
state: z.lazy(() => GameStateSchema).optional(),
allowSpectators: z.boolean().optional(),
allowSpecials: z.boolean().optional(),
allowChat: z.boolean().optional(),
allowMarkDraw: z.boolean().optional(),
gamePin: z.lazy(() => GamepinCreateNestedOneWithoutGameInputSchema).optional()
}).strict();
@ -3122,6 +3236,10 @@ export const GameUncheckedCreateWithoutUsersInputSchema: z.ZodType<Prisma.GameUn
createdAt: z.coerce.date().optional(),
updatedAt: z.coerce.date().optional(),
state: z.lazy(() => GameStateSchema).optional(),
allowSpectators: z.boolean().optional(),
allowSpecials: z.boolean().optional(),
allowChat: z.boolean().optional(),
allowMarkDraw: z.boolean().optional(),
gamePin: z.lazy(() => GamepinUncheckedCreateNestedOneWithoutGameInputSchema).optional()
}).strict();
@ -3222,6 +3340,10 @@ export const GameUpdateWithoutUsersInputSchema: z.ZodType<Prisma.GameUpdateWitho
createdAt: z.union([ z.coerce.date(),z.lazy(() => DateTimeFieldUpdateOperationsInputSchema) ]).optional(),
updatedAt: z.union([ z.coerce.date(),z.lazy(() => DateTimeFieldUpdateOperationsInputSchema) ]).optional(),
state: z.union([ z.lazy(() => GameStateSchema),z.lazy(() => EnumGameStateFieldUpdateOperationsInputSchema) ]).optional(),
allowSpectators: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
allowSpecials: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
allowChat: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
allowMarkDraw: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
gamePin: z.lazy(() => GamepinUpdateOneWithoutGameNestedInputSchema).optional()
}).strict();
@ -3230,6 +3352,10 @@ export const GameUncheckedUpdateWithoutUsersInputSchema: z.ZodType<Prisma.GameUn
createdAt: z.union([ z.coerce.date(),z.lazy(() => DateTimeFieldUpdateOperationsInputSchema) ]).optional(),
updatedAt: z.union([ z.coerce.date(),z.lazy(() => DateTimeFieldUpdateOperationsInputSchema) ]).optional(),
state: z.union([ z.lazy(() => GameStateSchema),z.lazy(() => EnumGameStateFieldUpdateOperationsInputSchema) ]).optional(),
allowSpectators: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
allowSpecials: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
allowChat: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
allowMarkDraw: z.union([ z.boolean(),z.lazy(() => BoolFieldUpdateOperationsInputSchema) ]).optional(),
gamePin: z.lazy(() => GamepinUncheckedUpdateOneWithoutGameNestedInputSchema).optional()
}).strict();

View file

@ -75,12 +75,16 @@ enum GameState {
}
model Game {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
state GameState @default(launching)
gamePin Gamepin?
users User_Game[]
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
state GameState @default(launching)
allowSpectators Boolean @default(true)
allowSpecials Boolean @default(true)
allowChat Boolean @default(true)
allowMarkDraw Boolean @default(true)
gamePin Gamepin?
users User_Game[]
}
model Gamepin {

View file

@ -16,6 +16,8 @@
"incremental": true,
"paths": {
"@lib/*": ["./lib/*"],
"@hooks/*": ["./hooks/*"],
"@components/*": ["./components/*"],
"@backend/*": ["./lib/backend/*"]
}
},