Using next-auth

This commit is contained in:
aronmal 2023-04-23 18:21:18 +02:00
parent b9e72b2a32
commit dab3abdda2
Signed by: aronmal
GPG key ID: 816B7707426FC612
50 changed files with 656 additions and 1525 deletions

View file

@ -28,15 +28,15 @@ function LobbyFrame({ openSettings }: { openSettings: () => void }) {
<div className="flex items-center justify-around">
<Player
src="player_blue.png"
text={gameProps.player?.username ?? "Spieler 1 (Du)"}
text={gameProps.player?.name ?? "Spieler 1 (Du)"}
primary={true}
edit={true}
/>
<p className="font-farro m-4 text-6xl font-semibold">VS</p>
{enemy ? (
<Player src="player_red.png" text={enemy.username ?? "Spieler 2"} />
<Player src="player_red.png" text={enemy.name ?? "Spieler 2"} />
) : (
<p className="font-farro w-96 text-center text-5xl font-medium">
<p className="font-farro w-96 text-center text-4xl font-medium">
Warte auf Spieler 2 {Array.from(Array(dots), () => ".").join("")}
{Array.from(Array(3 - dots), (_, i) => (
<Fragment key={i}>&nbsp;</Fragment>

View file

@ -2,21 +2,32 @@ import {
FontAwesomeIcon,
FontAwesomeIconProps,
} from "@fortawesome/react-fontawesome"
import classNames from "classnames"
import { ReactNode } from "react"
function OptionButton({
icon,
action,
children,
disabled,
}: {
icon: FontAwesomeIconProps["icon"]
action?: () => void
children: ReactNode
disabled?: boolean
}) {
return (
<button
className="flex w-full flex-row items-center justify-between rounded-xl border-b-4 border-shield-gray bg-voidDark py-2 pl-8 pr-4 text-lg text-grayish duration-100 first:mt-4 last:mt-4 active:border-b-0 active:border-t-4 sm:py-4 sm:pl-16 sm:pr-8 sm:text-4xl sm:first:mt-8 sm:last:mt-8"
className={classNames(
"flex w-full flex-row items-center justify-between rounded-xl py-2 pl-8 pr-4 text-lg text-grayish duration-100 first:mt-4 last:mt-4 sm:py-4 sm:pl-16 sm:pr-8 sm:text-4xl sm:first:mt-8 sm:last:mt-8",
{
"bg-voidDark border-shield-gray border-b-4 active:border-b-0 active:border-t-4":
!disabled,
"bg-red-950 border-slate-600 border-4 border-dashed": disabled,
}
)}
onClick={() => action && setTimeout(action, 200)}
disabled={disabled}
>
<span className="mx-auto">{children}</span>
<FontAwesomeIcon

View file

@ -1,16 +0,0 @@
import { rejectionErrors } from "../errors"
import sendError, { API } from "./sendError"
import type { Player } from "@prisma/client"
import bcrypt from "bcrypt"
export default async function checkPasswordIsValid<T>(
context: API<T>,
player: Player,
password: string
) {
// Validate for correct password
const result = await bcrypt.compare(password, player.passwordHash ?? "")
if (!result) throw sendError(context, rejectionErrors.wrongPassword)
return
}

View file

@ -1,25 +0,0 @@
import { rejectionErrorFns } from "../errors"
import jwtVerifyCatch from "../jwtVerifyCatch"
import type { IdToken, RawToken } from "./createTokenDB"
import sendError, { API } from "./sendError"
import jwt from "jsonwebtoken"
async function checkTokenIsValid<T>(context: API<T>, rawToken: RawToken) {
const { value, type } = rawToken
// Verify the token and get the payload
let data: string | jwt.JwtPayload
try {
data = jwt.verify(value, process.env.TOKEN_SECRET as string)
} catch (err: any) {
// Deal with the problem in more detail
throw sendError(context, jwtVerifyCatch(type, err))
}
// Making sure the token data is not a string (because it should be an object)
if (typeof data === "string")
throw sendError(context, rejectionErrorFns.tokenWasString(type, value))
return { id: data.id, type }
}
export default checkTokenIsValid

View file

@ -1,12 +0,0 @@
import prisma from "../../prisma"
import logging from "../logging"
import type { Player } from "@prisma/client"
async function createPlayerDB() {
const player = await prisma.player.create({ data: {} })
logging("Anonymous player created: " + player.id, ["debug"])
return player
}
export default createPlayerDB

View file

@ -1,44 +0,0 @@
import prisma from "../../prisma"
import type { Player, Token, TokenType } from "@prisma/client"
import jwt from "jsonwebtoken"
export interface RawToken {
value: string
type: TokenType
}
export interface IdToken {
id: string
type: TokenType
}
export const tokenLifetime = {
REFRESH: 172800,
ACCESS: 15,
}
export default async function createTokenDB(
player: Player,
newTokenType: TokenType
) {
// Create token entry in DB
const newTokenDB = await prisma.token.create({
data: {
type: newTokenType,
// expires: new Date(Date.now() + tokenLifetime[newTokenType] + "000"),
owner: {
connect: {
id: player.id,
},
},
},
})
// Sign a new access token
const newToken = jwt.sign(
{ id: newTokenDB.id },
process.env.TOKEN_SECRET as string,
{ expiresIn: tokenLifetime[newTokenType] }
)
return { newToken: { value: newToken, type: newTokenType }, newTokenDB }
}

View file

@ -1,31 +0,0 @@
import { rejectionErrorFns } from "../errors"
import jwtVerifyCatch from "../jwtVerifyCatch"
import logging from "../logging"
import type { IdToken, RawToken } from "./createTokenDB"
import sendError, { API } from "./sendError"
import jwt from "jsonwebtoken"
async function decodeToken<T>(context: API<T>, rawToken: RawToken) {
const { value, type } = rawToken
// Verify the token and get the payload
let data: string | jwt.JwtPayload
try {
data = jwt.verify(value, process.env.TOKEN_SECRET as string)
} catch (err: any) {
// Deal with the problem in more detail
logging(jwtVerifyCatch(type, err).message, ["error"])
const fallbackData = jwt.decode(value)
// Making sure the token data is not a string (because it should be an object)
if (typeof fallbackData === "string")
throw sendError(context, rejectionErrorFns.tokenWasString(type, value))
return { id: fallbackData?.id, type }
}
// Making sure the token data is not a string (because it should be an object)
if (typeof data === "string")
throw sendError(context, rejectionErrorFns.tokenWasString(type, value))
return { id: data.id, type }
}
export default decodeToken

View file

@ -1,15 +0,0 @@
import checkTokenIsValid from "./checkTokenIsValid"
import getPlayerByIdDB from "./getPlayerByIdDB"
import getTokenDB from "./getTokenDB"
import getTokenFromBody from "./getTokenFromBody"
import { API } from "./sendError"
async function getPlayer<T>(context: API<T>) {
const accessToken = await getTokenFromBody(context)
const token = await checkTokenIsValid(context, accessToken)
const tokenDB = await getTokenDB(context, token)
const player = await getPlayerByIdDB(context, tokenDB)
return { player, tokenDB }
}
export default getPlayer

View file

@ -1,21 +0,0 @@
import prisma from "../../prisma"
import { rejectionErrors } from "../errors"
import sendError, { API } from "./sendError"
import type { Player, Token } from "@prisma/client"
export default async function getPlayerByIdDB<T>(
context: API<T>,
tokenDB: Token
) {
// Find Host in DB if it still exists (just to make sure)
const player = await prisma.player.findUnique({
where: {
id: tokenDB.ownerId,
},
})
if (!player) {
throw sendError(context, rejectionErrors.playerNotFound)
}
return player
}

View file

@ -1,26 +0,0 @@
import prisma from "../../prisma"
import { rejectionErrors } from "../errors"
import sendError, { API } from "./sendError"
import type { Player } from "@prisma/client"
export default async function getPlayerByNameDB<T>(
context: API<T>,
username: string
) {
// Find Player in DB if it still exists (just to make sure)
const player = await Promise.any([
prisma.player.findUniqueOrThrow({
where: {
username: username,
},
}),
prisma.player.findUniqueOrThrow({
where: {
email: username,
},
}),
]).catch(() => null)
if (player === null) throw sendError(context, rejectionErrors.playerNotFound)
return player
}

View file

@ -1,42 +0,0 @@
import prisma from "../../prisma"
import { rejectionErrorFns, rejectionErrors } from "../errors"
import type { IdToken } from "./createTokenDB"
import sendError, { API } from "./sendError"
import { deleteCookie } from "cookies-next"
async function getTokenDB<T>(
context: API<T>,
token: IdToken,
ignoreChecks?: boolean
) {
const { id, type } = token
// Find refresh token in DB
const tokenDB = await prisma.token.findUnique({
where: {
id,
},
})
if (!tokenDB) {
deleteCookie("token", { ...context, path: "/api" })
throw sendError(context, rejectionErrorFns.tokenNotFound(type))
}
if (tokenDB.used && !ignoreChecks)
throw sendError(context, rejectionErrors.tokenUsed)
await prisma.token.update({
where: {
id,
},
data: {
used: type === "ACCESS",
},
})
// await logging('Old token has been invalidated.', ['debug'], req)
return tokenDB
}
export default getTokenDB

View file

@ -1,23 +0,0 @@
import { rejectionErrorFns } from "../errors"
import type { RawToken } from "./createTokenDB"
import sendError, { API } from "./sendError"
async function getTokenFromBody<T>(context: API<T>): Promise<RawToken> {
const type = "ACCESS"
const body = JSON.parse(context.req.body)
// Checking for cookie presens, because it is necessary
if (
typeof body === "object" &&
body &&
"token" in body &&
typeof body.token === "string"
) {
const value = body.token
return { value, type }
}
console.log(body)
throw sendError(context, rejectionErrorFns.noToken(type))
}
export default getTokenFromBody

View file

@ -1,31 +0,0 @@
import createPlayerDB from "./createPlayerDB"
import createTokenDB, { RawToken } from "./createTokenDB"
import type { API } from "./sendError"
import { Player, Token } from "@prisma/client"
interface Returning {
refreshToken: RawToken
newPlayer?: { player: Player; newToken: RawToken; newTokenDB: Token }
}
async function getTokenFromCookie<T>(context: API<T>): Promise<Returning> {
const type = "REFRESH"
const value = context.req.cookies.token
// Checking for cookie presens, because it is necessary
if (!value) {
const player = await createPlayerDB()
const { newToken, newTokenDB } = await createTokenDB(player, type)
return {
refreshToken: newToken,
newPlayer: {
player,
newToken,
newTokenDB,
},
}
}
return { refreshToken: { value, type } }
}
export default getTokenFromCookie

View file

@ -1,21 +0,0 @@
import { rejectionErrors } from "../errors"
import sendError, { API } from "./sendError"
async function getUserFromBody<T>(context: API<T>) {
const body = JSON.parse(context.req.body)
if (
typeof body !== "object" ||
!body ||
!("username" in body) ||
typeof body.username !== "string"
)
throw sendError(context, rejectionErrors.noUsername)
const { username } = body
if (!("password" in body) || typeof body.password !== "string")
throw sendError(context, rejectionErrors.noPassword)
const { password } = body
return { username, password }
}
export default getUserFromBody

View file

@ -1,41 +0,0 @@
import logging, { Logging } from "../logging"
import { RawToken, tokenLifetime } from "./createTokenDB"
import type { API } from "./sendError"
import { deleteCookie, setCookie } from "cookies-next"
export interface Result<T> {
message: string
statusCode?: number
body?: T
type?: Logging[]
cookie?: string
}
export default function sendResponse<T>(context: API<T>, result: Result<T>) {
const { req, res } = context
if (typeof result.cookie === "string") {
if (result.cookie) {
console.log(1)
setCookie("token", result.cookie, {
req,
res,
maxAge: tokenLifetime.REFRESH,
httpOnly: true,
sameSite: true,
secure: true,
path: "/api",
})
} else {
console.log(2)
deleteCookie("token", {
req,
res,
path: "/api",
})
}
}
res.status(result.statusCode ?? 200)
result.body ? res.json(result.body) : res.end()
logging(result.message, result.type ?? ["debug"], req)
return "done" as const
}

View file

@ -1,20 +0,0 @@
import prisma from "../../prisma"
import { rejectionErrors } from "../errors"
import sendError, { API } from "./sendError"
import type { Player, Prisma } from "@prisma/client"
async function updatePlayerDB<T>(
context: API<T>,
player: Player,
data: Prisma.PlayerUpdateInput
) {
if (!player.anonymous) throw sendError(context, rejectionErrors.registered)
const updatedPlayer = await prisma.player.update({
where: { id: player.id },
data,
})
return updatedPlayer
}
export default updatePlayerDB

View file

@ -1,106 +1,71 @@
import { Logging } from "./logging"
import { TokenType } from "@prisma/client"
export interface rejectionError {
rejected?: boolean
message: string
statusCode: number
solved: boolean
type?: Logging
type?: Logging[]
}
interface rejectionErrors {
[key: string]: rejectionError
}
interface rejectionErrorFns {
[key: string]: (...args: any[]) => rejectionError
}
export const rejectionErrors: rejectionErrors = {
noCookie: {
rejected: true,
message: "Unauthorized. No cookie.",
statusCode: 401,
solved: true,
},
noBody: {
rejected: true,
message: "Unauthorized. No Body.",
statusCode: 401,
solved: true,
type: "warn",
type: ["warn"],
},
noUsername: {
rejected: true,
message: "No username in request body!",
statusCode: 401,
solved: true,
type: "warn",
type: ["warn"],
},
noPassword: {
rejected: true,
message: "No password in request body!",
statusCode: 401,
solved: true,
type: "warn",
type: ["warn"],
},
wrongPassword: {
message: "Passwords do not match!",
statusCode: 401,
solved: true,
type: "warn",
type: ["warn"],
},
playerNotFound: {
message: "Player name not found in DB!",
statusCode: 401,
solved: false,
type: "warn",
type: ["warn"],
},
tokenUsed: {
rejected: true,
message: "DBToken was already used!",
statusCode: 401,
solved: true,
type: "warn",
type: ["warn"],
},
registered: {
rejected: true,
message: "Player is already registered!",
statusCode: 403,
solved: true,
type: "warn",
type: ["warn"],
},
gameNotFound: {
rejected: true,
message: "Game not found!",
statusCode: 403,
solved: true,
},
}
export const rejectionErrorFns: rejectionErrorFns = {
tokenWasString(tokenType: TokenType, tokenValue: string) {
return {
rejected: true,
message: `${tokenType}-Token data was a string. Token: ${tokenValue}`,
statusCode: 401,
solved: false,
}
},
tokenNotFound(tokenType: TokenType) {
return {
rejected: true,
message: `${tokenType}-Token not found in DB!`,
statusCode: 401,
solved: true,
type: "warn",
}
},
noToken(tokenType: TokenType) {
return {
rejected: true,
message: `Unauthorized. No ${tokenType}-Token.`,
statusCode: 401,
solved: true,
}
unauthorized: {
message: "Unauthorized",
statusCode: 401,
solved: true,
},
}

View file

@ -1,14 +1,15 @@
import sendError, { API } from "./sendError"
import sendError from "./sendError"
import { NextApiRequest, NextApiResponse } from "next"
async function getPinFromBody<T>(context: API<T>) {
const body = JSON.parse(context.req.body)
async function getPinFromBody<T>(req: NextApiRequest, res: NextApiResponse<T>) {
const body = JSON.parse(req.body)
if (
typeof body !== "object" ||
!body ||
!("pin" in body) ||
typeof body.pin !== "string"
)
throw sendError(context, {
throw sendError(req, res, {
rejected: true,
message: "No pin in request body!",
statusCode: 401,

View file

@ -2,16 +2,11 @@ import type { rejectionError } from "../errors"
import logging from "../logging"
import type { NextApiRequest, NextApiResponse } from "next"
export interface API<T> {
req: NextApiRequest
res: NextApiResponse<T>
}
export default function sendError<T>(
context: API<T>,
req: NextApiRequest,
res: NextApiResponse<T>,
err: rejectionError | Error
) {
const { res, req } = context
// If something went wrong, let the client know with status 500
res.status("statusCode" in err ? err.statusCode : 500).end()
logging(

View file

@ -0,0 +1,20 @@
import logging, { Logging } from "../logging"
import { NextApiRequest, NextApiResponse } from "next"
export interface Result<T> {
message: string
statusCode?: number
body?: T
type?: Logging[]
}
export default function sendResponse<T>(
req: NextApiRequest,
res: NextApiResponse<T>,
result: Result<T>
) {
res.status(result.statusCode ?? 200)
result.body ? res.json(result.body) : res.end()
logging(result.message, result.type ?? ["debug"], req)
return "done" as const
}

View file

@ -1,42 +0,0 @@
import status from "http-status"
import { toast } from "react-toastify"
import { ZodError, z } from "zod"
const tokenSchema = z.object({
token: z.string(),
})
export function successfulResponse(res: Response) {
if (status[`${res.status}_CLASS`] === status.classes.SUCCESSFUL)
return res.json()
const resStatus = status[`${res.status}_CLASS`]
if (typeof resStatus !== "string") return
toast(status.classes[resStatus] + ": " + status[res.status], {
position: "top-center",
type: "error",
theme: "colored",
})
return Promise.reject()
}
async function getAccessToken() {
return fetch("/api/user/auth", {
method: "GET",
})
.then(successfulResponse)
.then((response) => {
try {
const { token } = tokenSchema.parse(response)
return token
} catch (err: any) {
const error = err as ZodError
toast(JSON.stringify(error))
return Promise.reject()
}
})
}
export default getAccessToken

View file

@ -0,0 +1,30 @@
import { gameContext } from "../../pages/_app"
import { useSession } from "next-auth/react"
import { useContext, useEffect } from "react"
import { toast } from "react-toastify"
function useGameState() {
const [gameProps, setGameProps] = useContext(gameContext)
const { data: session, status } = useSession()
useEffect(() => {
if (!session) return
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}
/>
) : undefined,
})
}, [session])
return {
gameProps,
setGameProps,
session,
status,
}
}
export default useGameState

View file

@ -0,0 +1,23 @@
import { z } from "zod"
export const createSchema = z.object({
game: z.object({
id: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
running: z.boolean(),
}),
pin: z.string().optional(),
player: z.object({
id: z.string(),
name: z.string().optional(),
isOwner: z.boolean().optional(),
}),
enemy: z
.object({
id: z.string(),
username: z.string().optional(),
isOwner: z.boolean().optional(),
})
.optional(),
})

View file

@ -17,17 +17,17 @@
"@fortawesome/pro-thin-svg-icons": "^6.4.0",
"@fortawesome/react-fontawesome": "^0.2.0",
"@fortawesome/sharp-solid-svg-icons": "^6.4.0",
"@next-auth/prisma-adapter": "^1.0.6",
"@next/font": "13.1.1",
"@prisma/client": "^4.12.0",
"bcrypt": "^5.1.0",
"classnames": "^2.3.2",
"colors": "^1.4.0",
"cookies-next": "^2.1.1",
"eslint": "8.31.0",
"eslint-config-next": "13.1.1",
"http-status": "^1.6.2",
"jsonwebtoken": "^9.0.0",
"next": "13.1.1",
"next-auth": "^4.22.1",
"nodemailer": "^6.9.1",
"prisma": "^4.12.0",
"react": "18.2.0",
"react-dom": "18.2.0",
@ -36,13 +36,12 @@
"socket.io": "^4.6.1",
"socket.io-client": "^4.6.1",
"typescript": "4.9.4",
"unique-names-generator": "^4.7.1",
"zod": "^3.21.4"
},
"devDependencies": {
"@total-typescript/ts-reset": "^0.3.7",
"@trivago/prettier-plugin-sort-imports": "^4.1.1",
"@types/bcrypt": "^5.0.0",
"@types/jsonwebtoken": "^9.0.1",
"@types/node": "^18.15.11",
"@types/react": "^18.0.33",
"@types/react-dom": "^18.0.11",

View file

@ -2,6 +2,7 @@ import "../styles/App.scss"
import "../styles/globals.css"
import "../styles/grid2.scss"
import "../styles/grid.scss"
import { SessionProvider } from "next-auth/react"
import type { AppProps } from "next/app"
import { Dispatch, SetStateAction, createContext, useState } from "react"
import { ToastContainer } from "react-toastify"
@ -14,12 +15,12 @@ interface gameContext {
}
player?: {
id: string
username?: string
name?: string
isOwner?: boolean
}
enemy?: {
id: string
username?: string
name?: string
}
}
@ -27,12 +28,17 @@ export const gameContext = createContext<
[gameContext, Dispatch<SetStateAction<gameContext>>]
>([{}, () => {}])
export default function App({ Component, pageProps }: AppProps) {
export default function App({
Component,
pageProps: { session, ...pageProps },
}: AppProps) {
const gameProps = useState<gameContext>({})
return (
<gameContext.Provider value={gameProps}>
<Component {...pageProps} />
<ToastContainer />
</gameContext.Provider>
<SessionProvider session={session}>
<gameContext.Provider value={gameProps}>
<Component {...pageProps} />
<ToastContainer />
</gameContext.Provider>
</SessionProvider>
)
}

View file

@ -0,0 +1,59 @@
import prisma from "@lib/prisma"
import { PrismaAdapter } from "@next-auth/prisma-adapter"
import { NextApiHandler } from "next"
import NextAuth, { NextAuthOptions } from "next-auth"
import AzureADProvider from "next-auth/providers/azure-ad"
import EmailProvider from "next-auth/providers/email"
import {
uniqueNamesGenerator,
Config,
adjectives,
colors,
animals,
NumberDictionary,
} from "unique-names-generator"
const numberDictionary = NumberDictionary.generate({ min: 0, max: 999 })
const customConfig: Config = {
dictionaries: [adjectives, animals, numberDictionary],
separator: " ",
style: "capital",
length: 3,
}
const options: NextAuthOptions = {
debug: true,
providers: [
EmailProvider({
server: process.env.EMAIL_SERVER,
from: process.env.EMAIL_FROM,
}),
AzureADProvider({
clientId: process.env.AZURE_AD_CLIENT_ID ?? "",
clientSecret: process.env.AZURE_AD_CLIENT_SECRET ?? "",
tenantId: process.env.AZURE_AD_TENANT_ID,
}),
],
adapter: PrismaAdapter(prisma),
secret: process.env.SECRET,
callbacks: {
signIn: ({ user, account }) => {
// Custom signIn callback to add username to email provider
if (account && account.provider === "email") {
user.name = uniqueNamesGenerator(customConfig) // Replace with your desired username
}
return true
},
session: ({ session, user }) => {
if (session?.user) {
session.user.id = user.id
}
return session
},
},
}
export { options as authOptions }
const authHandler: NextApiHandler = (req, res) => NextAuth(req, res, options)
export default authHandler

View file

@ -1,40 +1,36 @@
import sendResponse from "@backend/components/sendResponse"
import getPlayer from "@lib/backend/components/getPlayer"
import { authOptions } from "../auth/[...nextauth]"
import sendResponse from "@backend/sendResponse"
import prisma from "@lib/prisma"
import { createSchema } from "@lib/zodSchemas"
import type { NextApiRequest, NextApiResponse } from "next"
import { getServerSession } from "next-auth"
import { z } from "zod"
export const createSchema = z.object({
game: z.object({
id: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
running: z.boolean(),
}),
pin: z.string().optional(),
player: z.object({
id: z.string(),
username: z.string().optional(),
isOwner: z.boolean().optional(),
}),
enemy: z
.object({
id: z.string(),
username: z.string().optional(),
isOwner: z.boolean().optional(),
})
.optional(),
})
type Data = z.infer<typeof createSchema>
export default async function create(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
const context = { req, res }
const session = await getServerSession(req, res, authOptions)
if (!session) {
return sendResponse(req, res, {
message: "Unauthorized",
statusCode: 401,
type: ["error"],
})
}
if (!session.user) {
return sendResponse(req, res, {
message: "Unauthorized - No User",
statusCode: 401,
type: ["error"],
})
}
const { email, id, name } = session.user
const { player } = await getPlayer(context)
// Generate a random 4-digit code
const pin = Math.floor(Math.random() * 10000)
.toString()
@ -45,49 +41,49 @@ export default async function create(
let game = await prisma.game.findFirst({
where: {
running: true,
players: {
users: {
some: {
playerId: player.id,
userId: id,
},
},
},
include: {
pin: true,
players: true,
gamePin: true,
users: true,
},
})
if (!game) {
created = true
game = await prisma.game.create({
data: {
pin: {
gamePin: {
create: {
pin,
},
},
players: {
users: {
create: {
isOwner: true,
playerId: player.id,
userId: id,
},
},
},
include: {
pin: true,
players: true,
gamePin: true,
users: true,
},
})
}
return sendResponse(context, {
message: `Player: ${player.id} created game: ${game.id}`,
return sendResponse(req, res, {
message: `User <${email}> created game: ${game.id}`,
statusCode: created ? 201 : 200,
body: {
game,
pin: game.pin?.pin,
pin: game.gamePin?.pin,
player: {
id: player.id,
username: player.username ?? undefined,
id,
name: name ?? undefined,
isOwner: true,
},
},

View file

@ -1,11 +1,12 @@
import sendError from "@backend/components/sendError"
import sendResponse from "@backend/components/sendResponse"
import getPinFromBody from "@lib/backend/components/getPinFromBody"
import getPlayer from "@lib/backend/components/getPlayer"
import { authOptions } from "../auth/[...nextauth]"
import sendError from "@backend/sendError"
import sendResponse from "@backend/sendResponse"
import { rejectionErrors } from "@lib/backend/errors"
import getPinFromBody from "@lib/backend/getPinFromBody"
import prisma from "@lib/prisma"
import type { Game } from "@prisma/client"
import type { NextApiRequest, NextApiResponse } from "next"
import { getServerSession } from "next-auth"
interface Data {
game: Game
@ -15,56 +16,64 @@ export default async function join(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
const context = { req, res }
const session = await getServerSession(req, res, authOptions)
const pin = await getPinFromBody(req, res)
if (!session?.user) {
return sendResponse(req, res, rejectionErrors.unauthorized)
}
const { name, email, id } = session.user
const pin = await getPinFromBody(context)
const { player } = await getPlayer(context)
try {
const pinDB = await prisma.gamepin.update({
const game = await prisma.game.findFirst({
where: {
pin,
},
data: {
game: {
update: {
players: {
create: {
isOwner: false,
playerId: player.id,
},
},
},
gamePin: {
pin,
},
},
include: {
game: {
users: {
include: {
players: {
include: {
player: true,
},
},
user: true,
},
},
},
})
if (!game) {
return sendResponse(req, res, {
message: "Spiel existiert nicht",
statusCode: 404,
type: ["infoCyan"],
})
}
const enemy = pinDB.game.players.find(
(enemy) => enemy.player.id !== player.id
)
return sendResponse(context, {
message: `Player: ${player.id} joined game: ${pinDB.game.id}`,
const player = game.users.find(({ user }) => user.id === id)?.user
const enemy = game.users.find(({ user }) => user.id !== id)?.user
if (!player) {
await prisma.user_Game.create({
data: {
isOwner: false,
gameId: game.id,
userId: id,
},
})
}
return sendResponse(req, res, {
message: `User <${email}> joined game: ${game.id}`,
body: {
game: pinDB.game,
pin: pinDB.pin,
game,
pin,
player: {
id: player.id,
username: player.username ?? undefined,
id,
name,
isOwner: true,
},
enemy: {
id: enemy?.player.id,
username: enemy?.player.username ?? undefined,
id: enemy?.id,
name: enemy?.name,
isOwner: false,
},
},
@ -72,6 +81,6 @@ export default async function join(
})
} catch (err: any) {
console.log("HERE".red, err.code, err.meta, err.message)
throw sendError(context, rejectionErrors.gameNotFound)
throw sendError(req, res, rejectionErrors.gameNotFound)
}
}

View file

@ -1,43 +0,0 @@
import checkTokenIsValid from "@backend/components/checkTokenIsValid"
import createTokenDB from "@backend/components/createTokenDB"
import getPlayerByIdDB from "@backend/components/getPlayerByIdDB"
import getTokenDB from "@backend/components/getTokenDB"
import getTokenFromCookie from "@backend/components/getTokenFromCookie"
import sendError from "@backend/components/sendError"
import sendResponse from "@backend/components/sendResponse"
import { Player, Token } from "@prisma/client"
import type { NextApiRequest, NextApiResponse } from "next"
interface Data {
token: string
}
export default async function auth(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
const context = { req, res }
const type = "ACCESS"
const { refreshToken, newPlayer } = await getTokenFromCookie(context)
let player: Player, tokenDB: Token, cookie: string | undefined
if (!newPlayer) {
const token = await checkTokenIsValid(context, refreshToken)
tokenDB = await getTokenDB(context, token)
player = await getPlayerByIdDB(context, tokenDB)
} else {
player = newPlayer.player
tokenDB = newPlayer.newTokenDB
cookie = newPlayer.newToken.value
}
const { newToken, newTokenDB } = await createTokenDB(player, type)
return sendResponse(context, {
message: `Access-Token generated: ${newTokenDB.id} with Refreshtoken-Token: ${tokenDB.id}`,
body: { token: newToken.value },
type: ["debug", "infoCyan"],
cookie,
})
}

View file

@ -1,23 +0,0 @@
import sendResponse from "@backend/components/sendResponse"
import getPlayer from "@lib/backend/components/getPlayer"
import type { Game } from "@prisma/client"
import type { NextApiRequest, NextApiResponse } from "next"
interface Data {
games: Game[]
}
export default async function data(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
const context = { req, res }
const { player, tokenDB } = await getPlayer(context)
const games: any = {}
return sendResponse(context, {
message: `Requested data of user: ${player.id} with Access-Token: ${tokenDB.id}`,
body: { games },
type: ["debug", "infoCyan"],
})
}

View file

@ -1,78 +0,0 @@
import checkPasswordIsValid from "@backend/components/checkPasswordIsValid"
import createTokenDB from "@backend/components/createTokenDB"
import getPlayerByNameDB from "@backend/components/getPlayerByNameDB"
import getUserFromBody from "@backend/components/getUserFromBody"
import sendError, { API } from "@backend/components/sendError"
import sendResponse from "@backend/components/sendResponse"
import logging from "@backend/logging"
import { rejectionErrors } from "@lib/backend/errors"
import prisma from "@lib/prisma"
import jwt from "jsonwebtoken"
import type { NextApiRequest, NextApiResponse } from "next"
interface Data {
loggedIn: boolean
}
export default async function login(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
const context = { req, res }
await preCheck(context)
const { username, password } = await getUserFromBody(context)
const player = await getPlayerByNameDB(context, username)
await checkPasswordIsValid(context, player, password)
const { newToken, newTokenDB } = await createTokenDB(player, "REFRESH")
return sendResponse(context, {
message:
"User " +
player.id +
" logged in and generated Refresh-Token: " +
newTokenDB.id,
body: { loggedIn: true },
type: ["debug", "infoCyan"],
cookie: newToken.value,
})
}
async function preCheck<T>(context: API<T>) {
const { req } = context
const oldRefreshToken = req.cookies.token
try {
if (!oldRefreshToken) throw sendError(context, rejectionErrors.noCookie)
// Check for old cookie, if unused invalidate it
const tokenData = jwt.verify(
oldRefreshToken,
process.env.TOKEN_SECRET as string
)
if (!tokenData || typeof tokenData === "string") return
const oldDBToken = await prisma.token.findUniqueOrThrow({
where: {
id: tokenData.id,
},
})
if (oldDBToken.used)
logging("Old login token was used: " + oldDBToken.id, ["debug"], req)
else {
await prisma.token.update({
where: {
id: tokenData.id,
},
data: {
used: true,
},
})
await logging("Old login token has been invalidated.", ["debug"], req)
}
} catch (err) {
// err is expected if no correct cookie, just continue, otherwise it has been invalidated above
}
return
}

View file

@ -1,26 +0,0 @@
import checkTokenIsValid from "@backend/components/checkTokenIsValid"
import getPlayerByIdDB from "@backend/components/getPlayerByIdDB"
import getTokenDB from "@backend/components/getTokenDB"
import getTokenFromCookie from "@backend/components/getTokenFromCookie"
import sendResponse from "@backend/components/sendResponse"
import type { NextApiRequest, NextApiResponse } from "next"
interface Data {
token: string
}
export default async function auth(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
const context = { req, res }
const { refreshToken } = await getTokenFromCookie(context)
const token = await checkTokenIsValid(context, refreshToken)
const tokenDB = await getTokenDB(context, token)
const player = await getPlayerByIdDB(context, tokenDB)
return sendResponse(context, {
message: "loginCheck -> true : " + player.id,
type: ["debug", "infoCyan"],
})
}

View file

@ -1,28 +0,0 @@
import getTokenDB from "@backend/components/getTokenDB"
import getTokenFromCookie from "@backend/components/getTokenFromCookie"
import sendError from "@backend/components/sendError"
import sendResponse from "@backend/components/sendResponse"
import decodeToken from "@lib/backend/components/decodeToken"
import type { NextApiRequest, NextApiResponse } from "next"
interface Data {
loggedOut: boolean
}
export default async function logout(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
const context = { req, res }
const { refreshToken } = await getTokenFromCookie(context)
const token = await decodeToken(context, refreshToken)
const tokenDB = await getTokenDB(context, token, true)
return sendResponse(context, {
message: "User of Token " + tokenDB.id + " logged out.",
body: { loggedOut: true },
type: ["debug", "infoCyan"],
cookie: "",
})
}

View file

@ -1,40 +0,0 @@
import checkTokenIsValid from "@backend/components/checkTokenIsValid"
import getPlayerByIdDB from "@backend/components/getPlayerByIdDB"
import getTokenDB from "@backend/components/getTokenDB"
import getTokenFromCookie from "@backend/components/getTokenFromCookie"
import getUserFromBody from "@backend/components/getUserFromBody"
import sendError from "@backend/components/sendError"
import sendResponse from "@backend/components/sendResponse"
import updatePlayerDB from "@backend/components/updatePlayerDB"
import bcrypt from "bcrypt"
import type { NextApiRequest, NextApiResponse } from "next"
interface Data {
registered: boolean
}
export default async function register(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
const context = { req, res }
const { refreshToken } = await getTokenFromCookie(context)
const token = await checkTokenIsValid(context, refreshToken)
const tokenDB = await getTokenDB(context, token)
const { username, password } = await getUserFromBody(context)
let player = await getPlayerByIdDB(context, tokenDB)
player = await updatePlayerDB(context, player, {
username,
email: "arst",
passwordHash: await bcrypt.hash(password, 10),
anonymous: false,
})
return sendResponse(context, {
message: "Player registered : " + player.id,
statusCode: 201,
body: { registered: true },
type: ["debug", "infoCyan"],
})
}

View file

@ -1,38 +0,0 @@
import checkPasswordIsValid from "@backend/components/checkPasswordIsValid"
import getPlayerByNameDB from "@backend/components/getPlayerByNameDB"
import getUserFromBody from "@backend/components/getUserFromBody"
import sendError from "@backend/components/sendError"
import sendResponse from "@backend/components/sendResponse"
import prisma from "@lib/prisma"
import { NextApiRequest, NextApiResponse } from "next"
interface Data {
loggedIn: boolean
}
export default async function remove(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
const context = { req, res }
const { username, password } = await getUserFromBody(context)
const player = await getPlayerByNameDB(context, username)
await checkPasswordIsValid(context, player, password)
prisma.player.update({
where: {
id: player.id,
},
data: {
deleted: true,
username: null,
email: null,
passwordHash: null,
},
})
return sendResponse(context, {
message: "User successfully deleted: " + player.id,
type: ["debug", "infoCyan"],
})
}

View file

@ -1,6 +1,5 @@
import type { NextApiResponseWithSocket } from "../../interfaces/NextApiSocket"
import prisma from "@lib/prisma"
import jwt from "jsonwebtoken"
import type { NextApiRequest } from "next"
import { Server } from "socket.io"
@ -37,21 +36,6 @@ interface SocketData {
//
// }
async function checkTokenIsValid(rawToken: string) {
// Verify the token and get the payload
let tokenData: string | jwt.JwtPayload
try {
tokenData = jwt.verify(rawToken, process.env.TOKEN_SECRET as string)
} catch (err: any) {
// Deal with the problem in more detail
return Promise.reject()
}
// Making sure the token data is not a string (because it should be an object)
if (typeof tokenData === "string") return Promise.reject()
return tokenData
}
const SocketHandler = (req: NextApiRequest, res: NextApiResponseWithSocket) => {
if (res.socket.server.io) {
console.log("Socket is already running " + req.url)
@ -73,22 +57,6 @@ const SocketHandler = (req: NextApiRequest, res: NextApiResponseWithSocket) => {
io.emit("update-input", msg)
})
socket.on("authenticate", (payload) => {
checkTokenIsValid(payload.token)
.then(async ({ tokenBody }) => {
const token = await prisma.token.findUnique({
where: { id: tokenBody.id },
})
if (!token || token.used) {
socket.emit("unauthenticated")
} else {
socket.emit("authenticated")
socket.data.isAuthenticated = true
}
})
.catch((err) => console.log("cat", err.message))
})
socket.on("test", (payload) => {
console.log("Got test:", payload)
// ...

View file

@ -1,230 +0,0 @@
import BurgerMenu from "../../components/BurgerMenu"
import Logo from "../../components/Logo"
import OptionButton from "../../components/OptionButton"
import { gameContext } from "../_app"
import { faEye, faLeftLong } from "@fortawesome/pro-regular-svg-icons"
import { faPlus, faUserPlus } from "@fortawesome/pro-solid-svg-icons"
import { faCirclePlay } from "@fortawesome/pro-thin-svg-icons"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import getAccessToken, {
successfulResponse,
} from "@lib/frontend/getAccessToken"
import { GetServerSideProps } from "next"
import { useRouter } from "next/router"
import { useCallback, useContext, useEffect, useState } from "react"
import OtpInput from "react-otp-input"
import { toast } from "react-toastify"
import { z } from "zod"
interface Props {
q: string | string[] | undefined
}
function isInputOnlyNumbers(input: string) {
return /^\d+$/.test(input)
}
export default function Home({ q }: Props) {
const [otp, setOtp] = useState("")
const [gameProps, setGameProps] = useContext(gameContext)
const router = useRouter()
const gameFetch = useCallback(
async (pin?: string) => {
const createSchema = z.object({
game: z.object({
id: z.string(),
}),
pin: z.string().optional(),
player: z.object({
id: z.string(),
username: z.string().optional(),
isOwner: z.boolean().optional(),
}),
enemy: z
.object({
id: z.string(),
username: z.string().optional(),
isOwner: z.boolean().optional(),
})
.optional(),
})
const gamePromise = getAccessToken().then((token) =>
fetch("/api/game/" + (!pin ? "create" : "join"), {
method: "POST",
body: JSON.stringify({ token, pin }),
})
.then(successfulResponse)
.then((game) => createSchema.parse(game))
)
const res = await toast.promise(gamePromise, {
pending: {
render: "Raum wird " + (!pin ? "erstellt" : "angefragt"),
},
success: {
render: "Raum " + (!pin ? "erstellt" : "angefragt") + " 👌",
type: "info",
theme: "colored",
},
error: {
render: "Es ist ein Fehler aufgetreten 🤯",
type: "error",
theme: "colored",
},
})
setGameProps(res)
await toast.promise(router.push("/dev/lobby"), {
pending: {
render: "Raum wird beigetreten",
},
success: {
render: "Raum begetreten 👌",
type: "info",
},
error: {
render: "Es ist ein Fehler aufgetreten 🤯",
type: "error",
},
})
},
[router, setGameProps]
)
useEffect(() => {
if (otp.length !== 4) return
if (!isInputOnlyNumbers(otp)) {
toast("Der Code darf nur Zahlen beinhalten!", {
type: "warning",
theme: "dark",
})
return
}
gameFetch(otp)
}, [otp, gameFetch])
return (
<div className="h-full bg-theme">
<div className="mx-auto flex h-full max-w-screen-md flex-col items-center justify-evenly">
<Logo />
<BurgerMenu />
{(() => {
switch (q) {
case "join":
return (
<div className="flex flex-col items-center rounded-xl border-4 border-black bg-grayish px-4 py-6 shadow-lg sm:mx-8 sm:p-12 md:w-full">
<button
className="-mt-2 w-20 self-start rounded-xl border-b-4 border-shield-gray bg-voidDark text-2xl text-grayish duration-100 active:border-b-0 active:border-t-4 sm:-mt-6 sm:w-40 sm:px-2 sm:text-5xl"
onClick={() =>
setTimeout(() => {
// Navigate to the same page with the `start` query parameter set to `false`
router.push({
pathname: router.pathname,
query: null,
})
}, 200)
}
>
<FontAwesomeIcon icon={faLeftLong} />
</button>
<div className="flex flex-col items-center gap-6 sm:gap-12">
<OptionButton icon={faPlus}>Raum erstellen</OptionButton>
<OptionButton
action={() => {
router.push({
pathname: router.pathname,
query: { q: "join" },
})
}}
icon={faUserPlus}
>
<OtpInput
containerStyle={{ color: "initial" }}
value={otp}
onChange={setOtp}
numInputs={4}
placeholder="0000"
renderSeparator={<span>-</span>}
renderInput={(props) => <input {...props} />}
/>
</OptionButton>
<OptionButton icon={faEye}>Zuschauen</OptionButton>
</div>
</div>
)
case "start":
return (
<div className="flex flex-col items-center rounded-xl border-4 border-black bg-grayish px-4 py-6 shadow-lg sm:mx-8 sm:p-12 md:w-full">
<button
className="-mt-2 w-20 self-start rounded-xl border-b-4 border-shield-gray bg-voidDark text-2xl text-grayish duration-100 active:border-b-0 active:border-t-4 sm:-mt-6 sm:w-40 sm:px-2 sm:text-5xl"
onClick={() =>
setTimeout(() => {
// Navigate to the same page with the `start` query parameter set to `false`
router.push({
pathname: router.pathname,
query: null,
})
}, 200)
}
>
<FontAwesomeIcon icon={faLeftLong} />
</button>
<div className="flex flex-col items-center gap-6 sm:gap-12">
<OptionButton action={() => gameFetch()} icon={faPlus}>
Raum erstellen
</OptionButton>
<OptionButton
action={() => {
router.push({
pathname: router.pathname,
query: { q: "join" },
})
}}
icon={faUserPlus}
>
Raum beitreten
</OptionButton>
<OptionButton icon={faEye}>Zuschauen</OptionButton>
</div>
</div>
)
default:
return (
<>
<div className="flex h-36 w-64 items-center justify-center rounded-xl border-4 border-black bg-[#2227] sm:h-48 sm:w-96 md:h-72 md:w-[32rem] md:border-[6px] xl:h-[26rem] xl:w-[48rem]">
<FontAwesomeIcon
className="text-6xl sm:text-7xl md:text-8xl"
icon={faCirclePlay}
/>
</div>
<button
className="font-farro rounded-lg border-b-4 border-orange-400 bg-warn px-12 pb-4 pt-5 text-2xl font-bold duration-100 active:border-b-0 active:border-t-4 sm:rounded-xl sm:border-b-[6px] sm:px-14 sm:pb-5 sm:pt-6 sm:text-3xl sm:active:border-t-[6px] md:rounded-2xl md:border-b-8 md:px-20 md:pb-6 md:pt-7 md:text-4xl md:active:border-t-8 xl:px-24 xl:pb-8 xl:pt-10 xl:text-5xl"
onClick={() =>
setTimeout(() => {
// Navigate to the same page with the `start` query parameter set to `true`
router.push({
pathname: router.pathname,
query: { q: "start" },
})
}, 200)
}
>
START
</button>
</>
)
}
})()}
</div>
</div>
)
}
export const getServerSideProps: GetServerSideProps<Props> = async (
context
) => {
const { q } = context.query
return { props: { q: q ? q : "" } }
}

View file

@ -1,62 +1,34 @@
import Head from "next/head"
import Link from "next/link"
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"
export default function Home() {
const router = useRouter()
return (
<>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main>
<p>
<Link href="/dev/gamefield" target="_blank">
Gamefield
</Link>
</p>
<p>
<Link href="/dev" target="_blank">
Homepage
</Link>
</p>
<p>
<Link href="/dev/lobby" target="_blank">
Lobby
</Link>
</p>
<p>
<Link href="/dev/login" target="_blank">
Login
</Link>
</p>
<p>
<Link href="/dev/logout" target="_blank">
Logout
</Link>
</p>
<p>
<Link href="/dev/grid" target="_blank">
Grid Effect
</Link>
</p>
<p>
<Link href="/dev/grid2" target="_blank">
Grid Effect with Content
</Link>
</p>
<p>
<Link href="/dev/socket" target="_blank">
Socket
</Link>
</p>
<p>
<Link href="/dev/socketio" target="_blank">
SocketIO
</Link>
</p>
</main>
</>
<div className="h-full bg-theme">
<div className="mx-auto flex h-full max-w-screen-md flex-col items-center justify-evenly">
<Logo />
<BurgerMenu />
<div className="flex h-36 w-64 items-center justify-center rounded-xl border-4 border-black bg-[#2227] sm:h-48 sm:w-96 md:h-72 md:w-[32rem] md:border-[6px] xl:h-[26rem] xl:w-[48rem]">
<FontAwesomeIcon
className="text-6xl sm:text-7xl md:text-8xl"
icon={faCirclePlay}
/>
</div>
<button
className="font-farro rounded-lg border-b-4 border-orange-400 bg-warn px-12 pb-4 pt-5 text-2xl font-bold duration-100 active:border-b-0 active:border-t-4 sm:rounded-xl sm:border-b-[6px] sm:px-14 sm:pb-5 sm:pt-6 sm:text-3xl sm:active:border-t-[6px] md:rounded-2xl md:border-b-8 md:px-20 md:pb-6 md:pt-7 md:text-4xl md:active:border-t-8 xl:px-24 xl:pb-8 xl:pt-10 xl:text-5xl"
onClick={() =>
setTimeout(() => {
router.push("/start")
}, 200)
}
>
START
</button>
</div>
</div>
)
}

View file

@ -1,4 +1,4 @@
import SocketIO from "../../components/SocketIO"
import SocketIO from "../components/SocketIO"
export default function Home() {
return (

168
leaky-ships/pages/start.tsx Normal file
View file

@ -0,0 +1,168 @@
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 { createSchema } from "@lib/zodSchemas"
import status from "http-status"
import { GetServerSideProps } from "next"
import { useRouter } from "next/router"
import { useCallback, useEffect, useState } from "react"
import OtpInput from "react-otp-input"
import { toast } from "react-toastify"
interface Props {
q: string | string[] | undefined
}
function isInputOnlyNumbers(input: string) {
return /^\d+$/.test(input)
}
export function isAuthenticated(res: Response) {
if (status[`${res.status}_CLASS`] === status.classes.SUCCESSFUL)
return res.json()
const resStatus = status[`${res.status}_CLASS`]
if (typeof resStatus !== "string") return
toast(status[res.status], {
position: "top-center",
type: "error",
theme: "colored",
})
return Promise.reject()
}
export default function Home({ q }: Props) {
const [otp, setOtp] = useState("")
const { gameProps, setGameProps } = useGameState()
const router = useRouter()
const { session } = useGameState()
const gameFetch = useCallback(
async (pin?: string) => {
const gamePromise = fetch("/api/game/" + (!pin ? "create" : "join"), {
method: "POST",
body: JSON.stringify({ pin }),
})
.then(isAuthenticated)
.then((game) => createSchema.parse(game))
const res = await toast.promise(gamePromise, {
pending: {
render: "Raum wird " + (!pin ? "erstellt" : "angefragt"),
},
success: {
render: "Raum " + (!pin ? "erstellt" : "angefragt") + " 👌",
type: "info",
theme: "colored",
},
error: {
render: "Es ist ein Fehler aufgetreten 🤯",
type: "error",
theme: "colored",
},
})
console.log(res)
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, setGameProps]
)
useEffect(() => {
if (otp.length !== 4) return
if (!isInputOnlyNumbers(otp)) {
toast("Der Code darf nur Zahlen beinhalten!", {
type: "warning",
theme: "dark",
})
return
}
gameFetch(otp)
}, [otp, gameFetch])
return (
<div className="h-full bg-theme">
<div className="mx-auto flex h-full max-w-screen-md flex-col items-center justify-evenly">
<Logo />
<BurgerMenu />
<div className="flex flex-col items-center rounded-xl border-4 border-black bg-grayish px-4 py-6 shadow-lg sm:mx-8 sm:p-12 md:w-full">
<button
className="-mt-2 w-20 self-start rounded-xl border-b-4 border-shield-gray bg-voidDark text-2xl text-grayish duration-100 active:border-b-0 active:border-t-4 sm:-mt-6 sm:w-40 sm:px-2 sm:text-5xl"
onClick={() =>
setTimeout(() => {
// Navigate to the same page with the `start` query parameter set to `false`
router.push({
pathname: router.pathname,
query: null,
})
}, 200)
}
>
<FontAwesomeIcon icon={faLeftLong} />
</button>
<div className="flex flex-col items-center gap-6 sm:gap-12">
<OptionButton
action={() => gameFetch()}
icon={faPlus}
disabled={!session}
>
Raum erstellen
</OptionButton>
<OptionButton
action={() => {
router.push({
pathname: router.pathname,
query: { q: "join" },
})
}}
icon={faUserPlus}
disabled={!session}
>
{q === "join" ? (
"Raum beitreten"
) : (
<OtpInput
shouldAutoFocus
containerStyle={{ color: "initial" }}
value={otp}
onChange={setOtp}
numInputs={4}
placeholder="0000"
renderSeparator={<span>-</span>}
renderInput={(props) => <input {...props} />}
/>
)}
</OptionButton>
<OptionButton icon={faEye}>Zuschauen</OptionButton>
</div>
</div>
</div>
</div>
)
}
export const getServerSideProps: GetServerSideProps<Props> = async (
context
) => {
const { q } = context.query
return { props: { q: q ? q : "" } }
}

View file

@ -25,24 +25,21 @@ dependencies:
'@fortawesome/sharp-solid-svg-icons':
specifier: ^6.4.0
version: 6.4.0
'@next-auth/prisma-adapter':
specifier: ^1.0.6
version: 1.0.6(@prisma/client@4.12.0)(next-auth@4.22.1)
'@next/font':
specifier: 13.1.1
version: 13.1.1
'@prisma/client':
specifier: ^4.12.0
version: 4.12.0(prisma@4.12.0)
bcrypt:
specifier: ^5.1.0
version: 5.1.0
classnames:
specifier: ^2.3.2
version: 2.3.2
colors:
specifier: ^1.4.0
version: 1.4.0
cookies-next:
specifier: ^2.1.1
version: 2.1.1
eslint:
specifier: 8.31.0
version: 8.31.0
@ -52,12 +49,15 @@ dependencies:
http-status:
specifier: ^1.6.2
version: 1.6.2
jsonwebtoken:
specifier: ^9.0.0
version: 9.0.0
next:
specifier: 13.1.1
version: 13.1.1(react-dom@18.2.0)(react@18.2.0)(sass@1.61.0)
next-auth:
specifier: ^4.22.1
version: 4.22.1(next@13.1.1)(nodemailer@6.9.1)(react-dom@18.2.0)(react@18.2.0)
nodemailer:
specifier: ^6.9.1
version: 6.9.1
prisma:
specifier: ^4.12.0
version: 4.12.0
@ -82,6 +82,9 @@ dependencies:
typescript:
specifier: 4.9.4
version: 4.9.4
unique-names-generator:
specifier: ^4.7.1
version: 4.7.1
zod:
specifier: ^3.21.4
version: 3.21.4
@ -93,12 +96,6 @@ devDependencies:
'@trivago/prettier-plugin-sort-imports':
specifier: ^4.1.1
version: 4.1.1(prettier@2.8.7)
'@types/bcrypt':
specifier: ^5.0.0
version: 5.0.0
'@types/jsonwebtoken':
specifier: ^9.0.1
version: 9.0.1
'@types/node':
specifier: ^18.15.11
version: 18.15.11
@ -362,22 +359,14 @@ packages:
/@humanwhocodes/object-schema@1.2.1:
resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==}
/@mapbox/node-pre-gyp@1.0.10:
resolution: {integrity: sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==}
hasBin: true
/@next-auth/prisma-adapter@1.0.6(@prisma/client@4.12.0)(next-auth@4.22.1):
resolution: {integrity: sha512-Z7agwfSZEeEcqKqrnisBun7VndRPshd6vyDsoRU68MXbkui8storkHgvN2hnNDrqr/hSCF9aRn56a1qpihaB4A==}
peerDependencies:
'@prisma/client': '>=2.26.0 || >=3'
next-auth: ^4
dependencies:
detect-libc: 2.0.1
https-proxy-agent: 5.0.1
make-dir: 3.1.0
node-fetch: 2.6.9
nopt: 5.0.0
npmlog: 5.0.1
rimraf: 3.0.2
semver: 7.3.8
tar: 6.1.13
transitivePeerDependencies:
- encoding
- supports-color
'@prisma/client': 4.12.0(prisma@4.12.0)
next-auth: 4.22.1(next@13.1.1)(nodemailer@6.9.1)(react-dom@18.2.0)(react@18.2.0)
dev: false
/@next/env@13.1.1:
@ -529,6 +518,10 @@ packages:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.15.0
/@panva/hkdf@1.0.4:
resolution: {integrity: sha512-003xWiCuvePbLaPHT+CRuaV4GlyCAVm6XYSbBZDHoWZGn1mNkVKFaDbGJjjxmEFvizUwlCoM6O18FCBMMky2zQ==}
dev: false
/@pkgr/utils@2.3.1:
resolution: {integrity: sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
@ -602,12 +595,6 @@ packages:
- supports-color
dev: true
/@types/bcrypt@5.0.0:
resolution: {integrity: sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw==}
dependencies:
'@types/node': 18.15.11
dev: true
/@types/cookie@0.4.1:
resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==}
dev: false
@ -622,16 +609,6 @@ packages:
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
dev: false
/@types/jsonwebtoken@9.0.1:
resolution: {integrity: sha512-c5ltxazpWabia/4UzhIoaDcIza4KViOQhdbjRlfcIGVnsE3c3brkz9Z+F/EeJIECOQP7W7US2hNE930cWWkPiw==}
dependencies:
'@types/node': 18.15.11
dev: true
/@types/node@16.18.23:
resolution: {integrity: sha512-XAMpaw1s1+6zM+jn2tmw8MyaRDIJfXxqmIQIS0HfoGYPuf7dUWeiUKopwq13KFX9lEp1+THGtlaaYx39Nxr58g==}
dev: false
/@types/node@18.15.11:
resolution: {integrity: sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==}
@ -723,10 +700,6 @@ packages:
eslint-visitor-keys: 3.4.0
dev: false
/abbrev@1.1.1:
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
dev: false
/accepts@1.3.8:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
@ -747,15 +720,6 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
/agent-base@6.0.2:
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
engines: {node: '>= 6.0.0'}
dependencies:
debug: 4.3.4
transitivePeerDependencies:
- supports-color
dev: false
/ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
dependencies:
@ -792,18 +756,6 @@ packages:
normalize-path: 3.0.0
picomatch: 2.3.1
/aproba@2.0.0:
resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==}
dev: false
/are-we-there-yet@2.0.0:
resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==}
engines: {node: '>=10'}
dependencies:
delegates: 1.0.0
readable-stream: 3.6.2
dev: false
/arg@5.0.2:
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
dev: true
@ -914,18 +866,6 @@ packages:
engines: {node: ^4.5.0 || >= 5.9}
dev: false
/bcrypt@5.1.0:
resolution: {integrity: sha512-RHBS7HI5N5tEnGTmtR/pppX0mmDSBpQ4aCBsj7CEQfYXDcO74A8sIBYcJMuCsis2E81zDxeENYhv66oZwLiA+Q==}
engines: {node: '>= 10.0.0'}
requiresBuild: true
dependencies:
'@mapbox/node-pre-gyp': 1.0.10
node-addon-api: 5.1.0
transitivePeerDependencies:
- encoding
- supports-color
dev: false
/binary-extensions@2.2.0:
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
engines: {node: '>=8'}
@ -953,10 +893,6 @@ packages:
update-browserslist-db: 1.0.10(browserslist@4.21.5)
dev: true
/buffer-equal-constant-time@1.0.1:
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
dev: false
/call-bind@1.0.2:
resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==}
dependencies:
@ -1006,11 +942,6 @@ packages:
optionalDependencies:
fsevents: 2.3.2
/chownr@2.0.0:
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
engines: {node: '>=10'}
dev: false
/classnames@2.3.2:
resolution: {integrity: sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==}
dev: false
@ -1043,11 +974,6 @@ packages:
/color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
/color-support@1.1.3:
resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==}
hasBin: true
dev: false
/colors@1.4.0:
resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==}
engines: {node: '>=0.1.90'}
@ -1061,21 +987,14 @@ packages:
/concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
/console-control-strings@1.1.0:
resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}
dev: false
/cookie@0.4.2:
resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==}
engines: {node: '>= 0.6'}
dev: false
/cookies-next@2.1.1:
resolution: {integrity: sha512-AZGZPdL1hU3jCjN2UMJTGhLOYzNUN9Gm+v8BdptYIHUdwz397Et1p+sZRfvAl8pKnnmMdX2Pk9xDRKCGBum6GA==}
dependencies:
'@types/cookie': 0.4.1
'@types/node': 16.18.23
cookie: 0.4.2
/cookie@0.5.0:
resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
engines: {node: '>= 0.6'}
dev: false
/cors@2.8.5:
@ -1168,15 +1087,6 @@ packages:
object-keys: 1.1.1
dev: false
/delegates@1.0.0:
resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
dev: false
/detect-libc@2.0.1:
resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==}
engines: {node: '>=8'}
dev: false
/didyoumean@1.2.2:
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
dev: true
@ -1205,20 +1115,10 @@ packages:
dependencies:
esutils: 2.0.3
/ecdsa-sig-formatter@1.0.11:
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
dependencies:
safe-buffer: 5.2.1
dev: false
/electron-to-chromium@1.4.356:
resolution: {integrity: sha512-nEftV1dRX3omlxAj42FwqRZT0i4xd2dIg39sog/CnCJeCcL1TRd2Uh0i9Oebgv8Ou0vzTPw++xc+Z20jzS2B6A==}
dev: true
/emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
dev: false
/emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
dev: false
@ -1713,13 +1613,6 @@ packages:
resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==}
dev: true
/fs-minipass@2.1.0:
resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==}
engines: {node: '>= 8'}
dependencies:
minipass: 3.3.6
dev: false
/fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
@ -1747,21 +1640,6 @@ packages:
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
dev: false
/gauge@3.0.2:
resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==}
engines: {node: '>=10'}
dependencies:
aproba: 2.0.0
color-support: 1.1.3
console-control-strings: 1.1.0
has-unicode: 2.0.1
object-assign: 4.1.1
signal-exit: 3.0.7
string-width: 4.2.3
strip-ansi: 6.0.1
wide-align: 1.1.5
dev: false
/get-intrinsic@1.2.0:
resolution: {integrity: sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==}
dependencies:
@ -1924,10 +1802,6 @@ packages:
has-symbols: 1.0.3
dev: false
/has-unicode@2.0.1:
resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==}
dev: false
/has@1.0.3:
resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
engines: {node: '>= 0.4.0'}
@ -1939,16 +1813,6 @@ packages:
engines: {node: '>= 0.4.0'}
dev: false
/https-proxy-agent@5.0.1:
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
engines: {node: '>= 6'}
dependencies:
agent-base: 6.0.2
debug: 4.3.4
transitivePeerDependencies:
- supports-color
dev: false
/ignore@5.2.4:
resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==}
engines: {node: '>= 4'}
@ -2048,11 +1912,6 @@ packages:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
/is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
dev: false
/is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
@ -2166,6 +2025,10 @@ packages:
hasBin: true
dev: true
/jose@4.14.1:
resolution: {integrity: sha512-SgjXLpP7jhQkUNKL6RpowoR/IF4QKE+WjLDMpNnh2vmhiFs67NftrNpvFtgbwpvRdtueFliahYYWz9E+XZZQlg==}
dev: false
/js-sdsl@4.4.0:
resolution: {integrity: sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==}
@ -2197,16 +2060,6 @@ packages:
minimist: 1.2.8
dev: false
/jsonwebtoken@9.0.0:
resolution: {integrity: sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==}
engines: {node: '>=12', npm: '>=6'}
dependencies:
jws: 3.2.2
lodash: 4.17.21
ms: 2.1.3
semver: 7.3.8
dev: false
/jsx-ast-utils@3.3.3:
resolution: {integrity: sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==}
engines: {node: '>=4.0'}
@ -2215,21 +2068,6 @@ packages:
object.assign: 4.1.4
dev: false
/jwa@1.4.1:
resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==}
dependencies:
buffer-equal-constant-time: 1.0.1
ecdsa-sig-formatter: 1.0.11
safe-buffer: 5.2.1
dev: false
/jws@3.2.2:
resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==}
dependencies:
jwa: 1.4.1
safe-buffer: 5.2.1
dev: false
/language-subtag-registry@0.3.22:
resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==}
dev: false
@ -2267,6 +2105,7 @@ packages:
/lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
dev: true
/loose-envify@1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
@ -2282,13 +2121,6 @@ packages:
yallist: 4.0.0
dev: false
/make-dir@3.1.0:
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
engines: {node: '>=8'}
dependencies:
semver: 6.3.0
dev: false
/merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
@ -2321,32 +2153,6 @@ packages:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
dev: false
/minipass@3.3.6:
resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==}
engines: {node: '>=8'}
dependencies:
yallist: 4.0.0
dev: false
/minipass@4.2.5:
resolution: {integrity: sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==}
engines: {node: '>=8'}
dev: false
/minizlib@2.1.2:
resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
engines: {node: '>= 8'}
dependencies:
minipass: 3.3.6
yallist: 4.0.0
dev: false
/mkdirp@1.0.4:
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
engines: {node: '>=10'}
hasBin: true
dev: false
/ms@2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
@ -2375,6 +2181,32 @@ packages:
engines: {node: '>= 0.6'}
dev: false
/next-auth@4.22.1(next@13.1.1)(nodemailer@6.9.1)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-NTR3f6W7/AWXKw8GSsgSyQcDW6jkslZLH8AiZa5PQ09w1kR8uHtR9rez/E9gAq/o17+p0JYHE8QjF3RoniiObA==}
peerDependencies:
next: ^12.2.5 || ^13
nodemailer: ^6.6.5
react: ^17.0.2 || ^18
react-dom: ^17.0.2 || ^18
peerDependenciesMeta:
nodemailer:
optional: true
dependencies:
'@babel/runtime': 7.21.0
'@panva/hkdf': 1.0.4
cookie: 0.5.0
jose: 4.14.1
next: 13.1.1(react-dom@18.2.0)(react@18.2.0)(sass@1.61.0)
nodemailer: 6.9.1
oauth: 0.9.15
openid-client: 5.4.1
preact: 10.13.2
preact-render-to-string: 5.2.6(preact@10.13.2)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
uuid: 8.3.2
dev: false
/next@13.1.1(react-dom@18.2.0)(react@18.2.0)(sass@1.61.0):
resolution: {integrity: sha512-R5eBAaIa3X7LJeYvv1bMdGnAVF4fVToEjim7MkflceFPuANY3YyvFxXee/A+acrSYwYPvOvf7f6v/BM/48ea5w==}
engines: {node: '>=14.6.0'}
@ -2420,32 +2252,13 @@ packages:
- babel-plugin-macros
dev: false
/node-addon-api@5.1.0:
resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==}
dev: false
/node-fetch@2.6.9:
resolution: {integrity: sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==}
engines: {node: 4.x || >=6.0.0}
peerDependencies:
encoding: ^0.1.0
peerDependenciesMeta:
encoding:
optional: true
dependencies:
whatwg-url: 5.0.0
dev: false
/node-releases@2.0.10:
resolution: {integrity: sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==}
dev: true
/nopt@5.0.0:
resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}
engines: {node: '>=6'}
hasBin: true
dependencies:
abbrev: 1.1.1
/nodemailer@6.9.1:
resolution: {integrity: sha512-qHw7dOiU5UKNnQpXktdgQ1d3OFgRAekuvbJLcdG5dnEo/GtcTHRYM7+UfJARdOFU9WUQO8OiIamgWPmiSFHYAA==}
engines: {node: '>=6.0.0'}
dev: false
/normalize-path@3.0.0:
@ -2457,19 +2270,19 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/npmlog@5.0.1:
resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==}
dependencies:
are-we-there-yet: 2.0.0
console-control-strings: 1.1.0
gauge: 3.0.2
set-blocking: 2.0.0
/oauth@0.9.15:
resolution: {integrity: sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==}
dev: false
/object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
/object-hash@2.2.0:
resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==}
engines: {node: '>= 6'}
dev: false
/object-hash@3.0.0:
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
engines: {node: '>= 6'}
@ -2536,6 +2349,11 @@ packages:
es-abstract: 1.21.2
dev: false
/oidc-token-hash@5.0.2:
resolution: {integrity: sha512-U91Ba78GtVBxcExLI7U+hC2AwJQqXQEW/D3fjmJC4hhSVIgdl954KO4Gu95WqAlgDKJdLATxkmuxraWLT0fVRQ==}
engines: {node: ^10.13.0 || >=12.0.0}
dev: false
/once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies:
@ -2550,6 +2368,15 @@ packages:
is-wsl: 2.2.0
dev: false
/openid-client@5.4.1:
resolution: {integrity: sha512-z19tZRY1CvpDf9L8nFyv5LW3CWoRDuz8EdmUFyouUrf5FDZitaJ8C+tIHF6WnjA2DPG4Fu2JWkNjexNzLWjh3Q==}
dependencies:
jose: 4.14.1
lru-cache: 6.0.0
object-hash: 2.2.0
oidc-token-hash: 5.0.2
dev: false
/optionator@0.9.1:
resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==}
engines: {node: '>= 0.8.0'}
@ -2695,6 +2522,19 @@ packages:
source-map-js: 1.0.2
dev: true
/preact-render-to-string@5.2.6(preact@10.13.2):
resolution: {integrity: sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==}
peerDependencies:
preact: '>=10'
dependencies:
preact: 10.13.2
pretty-format: 3.8.0
dev: false
/preact@10.13.2:
resolution: {integrity: sha512-q44QFLhOhty2Bd0Y46fnYW0gD/cbVM9dUVtNTDKPcdXSMA7jfY+Jpd6rk3GB0lcQss0z5s/6CmVP0Z/hV+g6pw==}
dev: false
/prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
@ -2761,6 +2601,10 @@ packages:
hasBin: true
dev: true
/pretty-format@3.8.0:
resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==}
dev: false
/prisma@4.12.0:
resolution: {integrity: sha512-xqVper4mbwl32BWzLpdznHAYvYDWQQWK2tBfXjdUD397XaveRyAP7SkBZ6kFlIg8kKayF4hvuaVtYwXd9BodAg==}
engines: {node: '>=14.17'}
@ -2838,15 +2682,6 @@ packages:
pify: 2.3.0
dev: true
/readable-stream@3.6.2:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'}
dependencies:
inherits: 2.0.4
string_decoder: 1.3.0
util-deprecate: 1.0.2
dev: false
/readdirp@3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
@ -2906,10 +2741,6 @@ packages:
dependencies:
queue-microtask: 1.2.3
/safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
dev: false
/safe-regex-test@1.0.0:
resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==}
dependencies:
@ -2946,10 +2777,6 @@ packages:
lru-cache: 6.0.0
dev: false
/set-blocking@2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
dev: false
/shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
@ -2968,10 +2795,6 @@ packages:
object-inspect: 1.12.3
dev: false
/signal-exit@3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
dev: false
/slash@3.0.0:
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
engines: {node: '>=8'}
@ -3047,15 +2870,6 @@ packages:
internal-slot: 1.0.5
dev: false
/string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
dev: false
/string.prototype.matchall@4.0.8:
resolution: {integrity: sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==}
dependencies:
@ -3094,12 +2908,6 @@ packages:
es-abstract: 1.21.2
dev: false
/string_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
dependencies:
safe-buffer: 5.2.1
dev: false
/strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
@ -3210,18 +3018,6 @@ packages:
engines: {node: '>=6'}
dev: false
/tar@6.1.13:
resolution: {integrity: sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==}
engines: {node: '>=10'}
dependencies:
chownr: 2.0.0
fs-minipass: 2.1.0
minipass: 4.2.5
minizlib: 2.1.2
mkdirp: 1.0.4
yallist: 4.0.0
dev: false
/text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
@ -3256,10 +3052,6 @@ packages:
dependencies:
is-number: 7.0.0
/tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
dev: false
/ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
dev: true
@ -3324,6 +3116,11 @@ packages:
which-boxed-primitive: 1.0.2
dev: false
/unique-names-generator@4.7.1:
resolution: {integrity: sha512-lMx9dX+KRmG8sq6gulYYpKWZc9RlGsgBR6aoO8Qsm3qvkSJ+3rAymr+TnV8EDMrIrwuFJ4kruzMWM/OpYzPoow==}
engines: {node: '>=8'}
dev: false
/update-browserslist-db@1.0.10(browserslist@4.21.5):
resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==}
hasBin: true
@ -3342,23 +3139,18 @@ packages:
/util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: true
/uuid@8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
dev: false
/vary@1.1.2:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
dev: false
/webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
dev: false
/whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
dependencies:
tr46: 0.0.3
webidl-conversions: 3.0.1
dev: false
/which-boxed-primitive@1.0.2:
resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
dependencies:
@ -3397,12 +3189,6 @@ packages:
dependencies:
isexe: 2.0.0
/wide-align@1.1.5:
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
dependencies:
string-width: 4.2.3
dev: false
/word-wrap@1.2.3:
resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==}
engines: {node: '>=0.10.0'}

View file

@ -7,17 +7,61 @@ datasource db {
url = env("DATABASE_URL")
}
model Player {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deleted Boolean @default(false)
anonymous Boolean @default(true)
username String? @unique
email String? @unique
passwordHash String? @unique
tokens Token[]
games Player_Game[]
model Account {
id String @id @default(cuid())
userId String @map("user_id")
type String
provider String
providerAccountId String @map("provider_account_id")
refresh_token String? @db.Text
access_token String? @db.Text
expires_at Int?
ext_expires_in Int?
token_type String?
scope String?
id_token String? @db.Text
session_state String?
oauth_token_secret String?
oauth_token String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
@@map("accounts")
}
model Session {
id String @id @default(cuid())
sessionToken String @unique @map("session_token")
userId String @map("user_id")
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("sessions")
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime? @map("email_verified")
image String? @db.Text
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
games User_Game[]
accounts Account[]
sessions Session[]
@@map(name: "users")
}
model VerificationToken {
identifier String
token String @unique
expires DateTime
@@unique([identifier, token])
@@map("verificationtokens")
}
enum TokenType {
@ -25,45 +69,35 @@ enum TokenType {
ACCESS
}
model Token {
id String @id @default(cuid())
createdAt DateTime @default(now())
type TokenType
used Boolean @default(false)
ownerId String
owner Player @relation(fields: [ownerId], references: [id])
Socket Socket?
}
model Game {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
running Boolean @default(true)
pin Gamepin?
players Player_Game[]
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
running Boolean @default(true)
gamePin Gamepin?
users User_Game[]
}
model Player_Game {
model User_Game {
id String @id @default(cuid())
createdAt DateTime @default(now())
gameId String
playerId String
userId String
isOwner Boolean
moves Move[]
chats Chat[]
game Game @relation(fields: [gameId], references: [id])
player Player @relation(fields: [playerId], references: [id])
user User @relation(fields: [userId], references: [id])
@@unique([gameId, playerId])
@@unique([gameId, userId])
}
model Move {
id String @id @default(cuid())
createdAt DateTime @default(now())
id String @id @default(cuid())
createdAt DateTime @default(now())
index Int
gameId String
game Player_Game @relation(fields: [gameId], references: [id])
game User_Game @relation(fields: [gameId], references: [id])
}
model Gamepin {
@ -75,17 +109,10 @@ model Gamepin {
}
model Chat {
id String @id @default(cuid())
createdAt DateTime @default(now())
id String @id @default(cuid())
createdAt DateTime @default(now())
message String?
event String?
gameId String
game Player_Game @relation(fields: [gameId], references: [id])
}
model Socket {
sessionId String @id
createdAt DateTime @default(now())
tokenId String @unique
token Token @relation(fields: [tokenId], references: [id])
game User_Game @relation(fields: [gameId], references: [id])
}

12
leaky-ships/types/next-auth.d.ts vendored Normal file
View file

@ -0,0 +1,12 @@
import NextAuth, { DefaultSession } from "next-auth"
declare module "next-auth" {
/**
* Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
*/
interface Session {
user: {
id: string
} & DefaultSession["user"]
}
}