Starting to implement full-stack logic
This commit is contained in:
parent
06f0eacc78
commit
a32b40395e
13 changed files with 211 additions and 57 deletions
|
@ -1,24 +1,20 @@
|
|||
// import Bluetooth from './Bluetooth'
|
||||
import BorderTiles from "./BorderTiles"
|
||||
import EventBar from "./EventBar"
|
||||
// import FogImages from './FogImages'
|
||||
import HitElems from "./HitElems"
|
||||
import Labeling from "./Labeling"
|
||||
import Ships from "./Ships"
|
||||
import Targets from "./Targets"
|
||||
import useGameEvent from "@lib/hooks/useGameEvent"
|
||||
import { CSSProperties } from "react"
|
||||
|
||||
function Gamefield() {
|
||||
const count = 12
|
||||
const { pointersProps, targetsProps, tilesProps, hits } = useGameEvent(count)
|
||||
const { BorderTiles, HitElems, Targets, EventBar } = useGameEvent(count)
|
||||
|
||||
return (
|
||||
<div id="gamefield">
|
||||
{/* <Bluetooth /> */}
|
||||
<div id="game-frame" style={{ "--i": count } as CSSProperties}>
|
||||
{/* Bordes */}
|
||||
<BorderTiles props={tilesProps} />
|
||||
<BorderTiles />
|
||||
|
||||
{/* Collumn lettes and row numbers */}
|
||||
<Labeling count={count} />
|
||||
|
@ -26,14 +22,14 @@ function Gamefield() {
|
|||
{/* Ships */}
|
||||
<Ships />
|
||||
|
||||
<HitElems hits={hits} />
|
||||
<HitElems />
|
||||
|
||||
{/* Fog images */}
|
||||
{/* <FogImages /> */}
|
||||
<Targets props={pointersProps} />
|
||||
<Targets />
|
||||
{/* <span id='dev-debug' style={{gridArea: '1 / 12 / 1 / 15', backgroundColor: 'red', zIndex: 3} as CSSProperties}>Debug</span> */}
|
||||
</div>
|
||||
<EventBar props={targetsProps} />
|
||||
<EventBar />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import { gameContext } from "../../pages/_app"
|
||||
import Icon from "./Icon"
|
||||
import Player from "./Player"
|
||||
import { Fragment, useEffect, useState } from "react"
|
||||
import { Fragment, useContext, useEffect, useState } from "react"
|
||||
|
||||
function LobbyFrame({ openSettings }: { openSettings: () => void }) {
|
||||
const [gameProps, setGameProps] = useContext(gameContext)
|
||||
const [enemy, setEnemy] = useState(false)
|
||||
const [dots, setDots] = useState(1)
|
||||
|
||||
|
@ -17,7 +19,7 @@ function LobbyFrame({ openSettings }: { openSettings: () => void }) {
|
|||
<div className="flex items-center justify-between border-b-2 border-slate-900">
|
||||
<Icon src="speech_bubble.png">Chat</Icon>
|
||||
<h1 className="font-farro text-5xl font-medium">
|
||||
Game-PIN: <span className="underline">3169</span>
|
||||
Game-PIN: <span className="underline">{gameProps.pin}</span>
|
||||
</h1>
|
||||
<Icon src="gear.png" onClick={openSettings}>
|
||||
Settings
|
||||
|
|
35
leaky-ships/lib/backend/components/decodeToken.ts
Normal file
35
leaky-ships/lib/backend/components/decodeToken.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
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,
|
||||
next: (token: IdToken) => void
|
||||
) {
|
||||
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")
|
||||
return sendError(context, rejectionErrorFns.tokenWasString(type, value))
|
||||
return next({ id: fallbackData?.id, type })
|
||||
}
|
||||
// Making sure the token data is not a string (because it should be an object)
|
||||
if (typeof data === "string")
|
||||
return sendError(context, rejectionErrorFns.tokenWasString(type, value))
|
||||
|
||||
return next({ id: data.id, type })
|
||||
}
|
||||
|
||||
export default decodeToken
|
|
@ -7,7 +7,8 @@ import type { Token } from "@prisma/client"
|
|||
async function getTokenDB<T>(
|
||||
context: API<T>,
|
||||
token: IdToken,
|
||||
next: (tokenDB: Token) => void
|
||||
next: (tokenDB: Token) => void,
|
||||
ignoreChecks?: boolean
|
||||
) {
|
||||
const { id, type } = token
|
||||
// Find refresh token in DB
|
||||
|
@ -18,14 +19,15 @@ async function getTokenDB<T>(
|
|||
})
|
||||
if (!tokenDB) return sendError(context, rejectionErrorFns.tokenNotFound(type))
|
||||
|
||||
if (tokenDB.used) return sendError(context, rejectionErrors.tokenUsed)
|
||||
if (tokenDB.used && !ignoreChecks)
|
||||
return sendError(context, rejectionErrors.tokenUsed)
|
||||
|
||||
await prisma.token.update({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
data: {
|
||||
used: true,
|
||||
used: type === "ACCESS",
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
@ -1,18 +1,32 @@
|
|||
import createPlayerDB from "./createPlayerDB"
|
||||
import createTokenDB, { RawToken } from "./createTokenDB"
|
||||
import type { API } from "./sendError"
|
||||
import { setCookie } from "cookies-next"
|
||||
|
||||
async function getTokenFromCookie<T>(
|
||||
context: API<T>,
|
||||
next: (refreshToken: RawToken) => void
|
||||
) {
|
||||
const type = "REFRESH"
|
||||
const value = context.req.cookies.token
|
||||
const { req, res } = context
|
||||
const value = req.cookies.token
|
||||
|
||||
// Checking for cookie presens, because it is necessary
|
||||
if (!value) {
|
||||
return createPlayerDB((player) =>
|
||||
createTokenDB(player, type, (newToken) => next(newToken))
|
||||
createTokenDB(player, type, (newToken) => {
|
||||
// Set login cookie
|
||||
setCookie("token", newToken.value, {
|
||||
req,
|
||||
res,
|
||||
maxAge: 172800,
|
||||
httpOnly: true,
|
||||
sameSite: true,
|
||||
secure: true,
|
||||
path: "/api",
|
||||
})
|
||||
return next(newToken)
|
||||
})
|
||||
)
|
||||
}
|
||||
return next({ value, type })
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
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,
|
||||
|
@ -6,6 +10,7 @@ import {
|
|||
Target,
|
||||
Position,
|
||||
} from "../../interfaces/frontend"
|
||||
import { gameContext } from "../../pages/_app"
|
||||
import {
|
||||
hitReducer,
|
||||
initlialLastLeftTile,
|
||||
|
@ -13,7 +18,7 @@ import {
|
|||
initlialTargetPreview,
|
||||
initlialMouseCursor,
|
||||
} from "../utils/helpers"
|
||||
import { useCallback, useEffect, useReducer, useState } from "react"
|
||||
import { useCallback, useContext, useEffect, useReducer, useState } from "react"
|
||||
|
||||
const modes: Mode[] = [
|
||||
{
|
||||
|
@ -28,10 +33,14 @@ const modes: Mode[] = [
|
|||
pointerGrid: Array.from(Array(1), () => Array.from(Array(3))),
|
||||
type: "vtorpedo",
|
||||
},
|
||||
{ pointerGrid: [[{ x: 0, y: 0 }]], type: "missile" },
|
||||
{
|
||||
pointerGrid: Array.from(Array(1), () => Array.from(Array(1))),
|
||||
type: "missile",
|
||||
},
|
||||
]
|
||||
|
||||
function useGameEvent(count: number) {
|
||||
const [gameProps, setGameProps] = useContext(gameContext)
|
||||
const [lastLeftTile, setLastLeftTile] =
|
||||
useState<Position>(initlialLastLeftTile)
|
||||
const [target, setTarget] = useState<Target>(initlialTarget)
|
||||
|
@ -208,10 +217,16 @@ function useGameEvent(count: number) {
|
|||
}, [targetPreview.show])
|
||||
|
||||
return {
|
||||
tilesProps: { count, settingTarget, setMouseCursor, setLastLeftTile },
|
||||
pointersProps: { composeTargetTiles, target, targetPreview },
|
||||
targetsProps: { setMode, setTarget },
|
||||
hits,
|
||||
BorderTiles: () => (
|
||||
<BorderTiles
|
||||
props={{ count, settingTarget, setMouseCursor, setLastLeftTile }}
|
||||
/>
|
||||
),
|
||||
HitElems: () => <HitElems hits={hits} />,
|
||||
Targets: () => (
|
||||
<Targets props={{ composeTargetTiles, target, targetPreview }} />
|
||||
),
|
||||
EventBar: () => <EventBar props={{ setMode, setTarget }} />,
|
||||
}
|
||||
}
|
||||
|
|
@ -32,7 +32,8 @@
|
|||
"react-dom": "18.2.0",
|
||||
"socket.io": "^4.6.1",
|
||||
"socket.io-client": "^4.6.1",
|
||||
"typescript": "4.9.4"
|
||||
"typescript": "4.9.4",
|
||||
"zod": "^3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@total-typescript/ts-reset": "^0.3.7",
|
||||
|
|
|
@ -3,7 +3,28 @@ import "../styles/globals.css"
|
|||
import "../styles/grid2.scss"
|
||||
import "../styles/grid.scss"
|
||||
import type { AppProps } from "next/app"
|
||||
import { Dispatch, SetStateAction, createContext, useState } from "react"
|
||||
|
||||
interface gameContext {
|
||||
pin?: string
|
||||
game?: {
|
||||
id: ""
|
||||
}
|
||||
enemy?: {
|
||||
id: string
|
||||
username?: string
|
||||
}
|
||||
}
|
||||
|
||||
export const gameContext = createContext<
|
||||
[gameContext, Dispatch<SetStateAction<gameContext>>]
|
||||
>([{}, () => {}])
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
return <Component {...pageProps} />
|
||||
const gameProps = useState<gameContext>({})
|
||||
return (
|
||||
<gameContext.Provider value={gameProps}>
|
||||
<Component {...pageProps} />
|
||||
</gameContext.Provider>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -2,12 +2,18 @@ import sendError from "@backend/components/sendError"
|
|||
import sendResponse from "@backend/components/sendResponse"
|
||||
import getPlayer from "@lib/backend/components/getPlayer"
|
||||
import prisma from "@lib/prisma"
|
||||
import type { Game } from "@prisma/client"
|
||||
import type { NextApiRequest, NextApiResponse } from "next"
|
||||
import { z } from "zod"
|
||||
|
||||
interface Data {
|
||||
game: Game
|
||||
}
|
||||
const returnSchema = z.object({
|
||||
game: z.object({
|
||||
id: z.string(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
running: z.boolean(),
|
||||
}),
|
||||
})
|
||||
type Data = z.infer<typeof returnSchema>
|
||||
|
||||
export default async function create(
|
||||
req: NextApiRequest,
|
||||
|
|
|
@ -18,19 +18,19 @@ export default async function auth(
|
|||
const context = { req, res }
|
||||
const type = "ACCESS"
|
||||
|
||||
getTokenFromCookie(context, (refreshToken) => {
|
||||
checkTokenIsValid(context, refreshToken, (token) => {
|
||||
getTokenDB(context, token, (tokenDB) => {
|
||||
getPlayerByIdDB(context, tokenDB, (player) => {
|
||||
createTokenDB(player, type, (newToken, newTokenDB) => {
|
||||
return getTokenFromCookie(context, (refreshToken) =>
|
||||
checkTokenIsValid(context, refreshToken, (token) =>
|
||||
getTokenDB(context, token, (tokenDB) =>
|
||||
getPlayerByIdDB(context, tokenDB, (player) =>
|
||||
createTokenDB(player, type, (newToken, newTokenDB) =>
|
||||
sendResponse(context, {
|
||||
message: `Access-Token generated: ${newTokenDB.id} with Refreshtoken-Token: ${tokenDB.id}`,
|
||||
body: { token: newToken.value },
|
||||
type: ["debug", "infoCyan"],
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}).catch((err) => sendError(context, err))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
).catch((err) => sendError(context, err))
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import checkTokenIsValid from "@backend/components/checkTokenIsValid"
|
||||
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 { deleteCookie } from "cookies-next"
|
||||
import type { NextApiRequest, NextApiResponse } from "next"
|
||||
|
||||
|
@ -16,18 +16,23 @@ export default async function logout(
|
|||
) {
|
||||
const context = { req, res }
|
||||
|
||||
return getTokenFromCookie(context, (refreshToken) => {
|
||||
checkTokenIsValid(context, refreshToken, (token) => {
|
||||
getTokenDB(context, token, (tokenDB) => {
|
||||
// Set login cookie
|
||||
deleteCookie("token", { req, res })
|
||||
return getTokenFromCookie(context, (refreshToken) =>
|
||||
decodeToken(context, refreshToken, (token) =>
|
||||
getTokenDB(
|
||||
context,
|
||||
token,
|
||||
(tokenDB) => {
|
||||
// Set login cookie
|
||||
deleteCookie("token", { req, res })
|
||||
|
||||
sendResponse(context, {
|
||||
message: "User of Token " + tokenDB.id + " logged out.",
|
||||
body: { loggedOut: true },
|
||||
type: ["debug", "infoCyan"],
|
||||
})
|
||||
})
|
||||
})
|
||||
}).catch((err) => sendError(context, err))
|
||||
return sendResponse(context, {
|
||||
message: "User of Token " + tokenDB.id + " logged out.",
|
||||
body: { loggedOut: true },
|
||||
type: ["debug", "infoCyan"],
|
||||
})
|
||||
},
|
||||
true
|
||||
)
|
||||
)
|
||||
).catch((err) => sendError(context, err))
|
||||
}
|
||||
|
|
|
@ -6,18 +6,23 @@ 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 from "@lib/frontend/getAccessToken"
|
||||
import { GetServerSideProps } from "next"
|
||||
import { useRouter } from "next/router"
|
||||
import { useState } from "react"
|
||||
import { z } from "zod"
|
||||
|
||||
export default function Home() {
|
||||
interface Props {
|
||||
start: boolean
|
||||
}
|
||||
|
||||
export default function Home({ start }: Props) {
|
||||
const router = useRouter()
|
||||
const [heWantsToPlay, setHeWantsToPlay] = useState<boolean | null>(false)
|
||||
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 />
|
||||
{!heWantsToPlay ? (
|
||||
{!start ? (
|
||||
<>
|
||||
<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
|
||||
|
@ -27,7 +32,15 @@ export default function Home() {
|
|||
</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(() => setHeWantsToPlay(true), 200)}
|
||||
onClick={() =>
|
||||
setTimeout(() => {
|
||||
// Navigate to the same page with the `start` query parameter set to `true`
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
query: { start: true },
|
||||
})
|
||||
}, 200)
|
||||
}
|
||||
>
|
||||
START
|
||||
</button>
|
||||
|
@ -36,7 +49,14 @@ export default function Home() {
|
|||
<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(() => setHeWantsToPlay(false), 200)}
|
||||
onClick={() =>
|
||||
setTimeout(() => {
|
||||
// Navigate to the same page with the `start` query parameter set to `false`
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
})
|
||||
}, 200)
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon icon={faLeftLong} />
|
||||
</button>
|
||||
|
@ -48,6 +68,25 @@ export default function Home() {
|
|||
method: "POST",
|
||||
body: JSON.stringify({ token }),
|
||||
})
|
||||
const gameSchema = z.object({
|
||||
game: z.object({
|
||||
id: z.string(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
running: z.boolean(),
|
||||
}),
|
||||
})
|
||||
|
||||
const check = gameSchema.safeParse(game)
|
||||
|
||||
if (check.success) {
|
||||
const gameData = check.data
|
||||
console.log(gameData)
|
||||
} else {
|
||||
console.error(check.error)
|
||||
}
|
||||
|
||||
// const warst = result.game
|
||||
|
||||
router.push("/dev/lobby")
|
||||
}}
|
||||
|
@ -64,3 +103,14 @@ export default function Home() {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const getServerSideProps: GetServerSideProps<Props> = async (
|
||||
context
|
||||
) => {
|
||||
const { start } = context.query
|
||||
|
||||
// Convert the `start` query parameter to a boolean
|
||||
const isStart = start === "true"
|
||||
|
||||
return { props: { start: isStart } }
|
||||
}
|
||||
|
|
7
leaky-ships/pnpm-lock.yaml
generated
7
leaky-ships/pnpm-lock.yaml
generated
|
@ -73,6 +73,9 @@ dependencies:
|
|||
typescript:
|
||||
specifier: 4.9.4
|
||||
version: 4.9.4
|
||||
zod:
|
||||
specifier: ^3.21.4
|
||||
version: 3.21.4
|
||||
|
||||
devDependencies:
|
||||
'@total-typescript/ts-reset':
|
||||
|
@ -3397,3 +3400,7 @@ packages:
|
|||
/yocto-queue@0.1.0:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
/zod@3.21.4:
|
||||
resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==}
|
||||
dev: false
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue