From 14f4e87d9ddd39ed7556444598a0bb47369c82c7 Mon Sep 17 00:00:00 2001 From: Moritz Ruth Date: Sun, 2 Mar 2025 23:22:12 +0100 Subject: [PATCH] Update dependencies and fix issues --- .dockerignore | 9 +- .env.example | 1 - .gitignore | 9 +- .nvmrc | 1 - Dockerfile | 1 - README.md | 7 +- {src/server => backend}/game.ts | 27 +- {src/server => backend}/index.ts | 0 {src/server => backend}/isDev.ts | 0 {src/server => backend}/main.ts | 15 +- {src/server => backend}/trpc/base.ts | 17 +- {src/server => backend}/trpc/game.ts | 0 {src/server => backend}/trpc/index.ts | 50 +- backend/user.ts | 8 + {src => frontend}/App.vue | 17 +- {src => frontend}/assets/noise.png | Bin {src => frontend}/auth.ts | 0 {src => frontend}/clientGame.ts | 6 +- {src => frontend}/components/BigButton.vue | 4 +- {src => frontend}/components/Card.vue | 6 +- {src => frontend}/components/Game.vue | 4 +- {src => frontend}/components/GameEndModal.vue | 8 +- {src => frontend}/components/JoinScreen.vue | 16 +- {src => frontend}/components/LoginScreen.vue | 12 +- {src => frontend}/components/Modal.vue | 0 {src => frontend}/components/NumberCard.vue | 0 {src => frontend}/components/PlayerCards.vue | 6 +- .../components/PreStartScreen.vue | 2 +- {src => frontend}/components/SpecialCard.vue | 4 +- {src => frontend}/icons.ts | 2 +- frontend/index.ts | 8 + {src => frontend}/trpc.ts | 2 +- index.html | 23 +- package.json | 83 +- pnpm-lock.yaml | 5758 ++++++++++------- schema.prisma | 15 - {src/shared => shared}/RemoveKey.ts | 0 {src/shared => shared}/constants.ts | 0 {src/shared => shared}/game/actions.ts | 0 {src/shared => shared}/game/cards.ts | 0 {src/shared => shared}/game/events.ts | 0 {src/shared => shared}/game/state.ts | 0 {src/shared => shared}/util.ts | 0 src/index.ts | 16 - src/server/prisma.ts | 5 - src/types.d.ts | 5 - tsconfig.json | 14 +- windi.config.ts => uno.config.ts | 18 +- vite.config.ts | 10 +- 49 files changed, 3432 insertions(+), 2757 deletions(-) delete mode 100644 .env.example delete mode 100644 .nvmrc rename {src/server => backend}/game.ts (95%) rename {src/server => backend}/index.ts (100%) rename {src/server => backend}/isDev.ts (100%) rename {src/server => backend}/main.ts (86%) rename {src/server => backend}/trpc/base.ts (66%) rename {src/server => backend}/trpc/game.ts (100%) rename {src/server => backend}/trpc/index.ts (73%) create mode 100644 backend/user.ts rename {src => frontend}/App.vue (86%) rename {src => frontend}/assets/noise.png (100%) rename {src => frontend}/auth.ts (100%) rename {src => frontend}/clientGame.ts (95%) rename {src => frontend}/components/BigButton.vue (80%) rename {src => frontend}/components/Card.vue (87%) rename {src => frontend}/components/Game.vue (94%) rename {src => frontend}/components/GameEndModal.vue (86%) rename {src => frontend}/components/JoinScreen.vue (77%) rename {src => frontend}/components/LoginScreen.vue (70%) rename {src => frontend}/components/Modal.vue (100%) rename {src => frontend}/components/NumberCard.vue (100%) rename {src => frontend}/components/PlayerCards.vue (91%) rename {src => frontend}/components/PreStartScreen.vue (91%) rename {src => frontend}/components/SpecialCard.vue (91%) rename {src => frontend}/icons.ts (96%) create mode 100644 frontend/index.ts rename {src => frontend}/trpc.ts (92%) delete mode 100644 schema.prisma rename {src/shared => shared}/RemoveKey.ts (100%) rename {src/shared => shared}/constants.ts (100%) rename {src/shared => shared}/game/actions.ts (100%) rename {src/shared => shared}/game/cards.ts (100%) rename {src/shared => shared}/game/events.ts (100%) rename {src/shared => shared}/game/state.ts (100%) rename {src/shared => shared}/util.ts (100%) delete mode 100644 src/index.ts delete mode 100644 src/server/prisma.ts delete mode 100644 src/types.d.ts rename windi.config.ts => uno.config.ts (54%) diff --git a/.dockerignore b/.dockerignore index f31d82a..1d9dd9d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,3 @@ -/node_modules/ -/dist/ -/run/ -/.idea/ -./src/server/.prisma -.env \ No newline at end of file +/node_modules +/dist +/.idea \ No newline at end of file diff --git a/.env.example b/.env.example deleted file mode 100644 index f1bd19c..0000000 --- a/.env.example +++ /dev/null @@ -1 +0,0 @@ -DATABASE_FILE=file:./run/tapdb.sqlite \ No newline at end of file diff --git a/.gitignore b/.gitignore index f31d82a..1d9dd9d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,3 @@ -/node_modules/ -/dist/ -/run/ -/.idea/ -./src/server/.prisma -.env \ No newline at end of file +/node_modules +/dist +/.idea \ No newline at end of file diff --git a/.nvmrc b/.nvmrc deleted file mode 100644 index 25bf17f..0000000 --- a/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -18 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index fd467af..9257c42 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,6 @@ RUN npm install --global pnpm RUN pnpm install --frozen-lockfile RUN pnpm build:ui RUN mkdir -p /data -RUN pnpm prisma generate EXPOSE 4000 EXPOSE 3000 diff --git a/README.md b/README.md index 569a613..0d2096f 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,8 @@ # Twenty-one -> The game of Twenty-one in the browser with online-multiplayer and special cards. +> The game of Twenty-one, in the browser, with online-multiplayer and special cards -## Known issues - -- The `return-opponent` special card should not be allowed in the first round of a game. - +â–¶ [**twentyone.deltaa.xyz**](https://twentyone.deltaa.xyz) ## License diff --git a/src/server/game.ts b/backend/game.ts similarity index 95% rename from src/server/game.ts rename to backend/game.ts index 027ae33..75a9991 100644 --- a/src/server/game.ts +++ b/backend/game.ts @@ -1,6 +1,5 @@ import EventEmitter from "eventemitter3" import type { GameAction } from "../shared/game/actions" -import { prismaClient } from "./prisma" import { GameState, getNumberCardsSum, getUninitializedGameState, produceNewState } from "../shared/game/state" import type { RemoveKey } from "../shared/RemoveKey" import { random } from "lodash-es" @@ -9,6 +8,7 @@ import { customAlphabet as createNanoIdWithCustomAlphabet } from "nanoid/non-sec import { LOBBY_CODE_LENGTH, LOBBY_SIZE, SPECIAL_CARD_PROBABILITY } from "../shared/constants" import type { SpecialCardId } from "../shared/game/cards" import { specialCardsMeta } from "../shared/game/cards" +import { usersById, usersByToken } from "./user" const generateLobbyCode = createNanoIdWithCustomAlphabet("ABCDEFGHKMNPQRSTUVWXYZ", LOBBY_CODE_LENGTH) @@ -80,18 +80,8 @@ export class Game extends EventEmitter { super() } - private async getPlayers() { - return await prismaClient.user.findMany({ - where: { - id: { - in: [...this.lobbyPlayerIds] - } - }, - select: { - id: true, - name: true - } - }) + private getPlayers() { + return [...this.lobbyPlayerIds.values()].map(id => usersById.get(id)!) } addPlayer(id: string, name: string) { @@ -119,7 +109,7 @@ export class Game extends EventEmitter { async start() { if (this.state.phase !== "pre-start") throw new Error(`Cannot start the game in this phase: ${this.state.phase}`) - const players = await this.getPlayers() + const players = this.getPlayers() if (players.length < 2) throw new Error("At least two players are required for starting the game") this.addAction({ @@ -263,10 +253,17 @@ export class Game extends EventEmitter { } private dealSpecialTo(playerId: string) { + let cardId: SpecialCardId + + // I know. Don't judge me. + do { + cardId = this.state.weightedSpecialCardIdList[random(0, this.state.weightedSpecialCardIdList.length - 1)] + } while (!specialCardsMeta[cardId].isAllowedInFirstRound) + this.addAction({ type: "deal-special", toPlayerId: playerId, - cardId: this.state.weightedSpecialCardIdList[random(0, this.state.weightedSpecialCardIdList.length - 1)] + cardId }) } diff --git a/src/server/index.ts b/backend/index.ts similarity index 100% rename from src/server/index.ts rename to backend/index.ts diff --git a/src/server/isDev.ts b/backend/isDev.ts similarity index 100% rename from src/server/isDev.ts rename to backend/isDev.ts diff --git a/src/server/main.ts b/backend/main.ts similarity index 86% rename from src/server/main.ts rename to backend/main.ts index 53b8630..1ea9179 100644 --- a/src/server/main.ts +++ b/backend/main.ts @@ -8,8 +8,6 @@ import { createContext } from "./trpc/base" import cookieParser from "cookie-parser" import { parse as parseCookie } from "cookie" import { isDev } from "./isDev" -import { prismaClient } from "./prisma" -import * as dateFns from "date-fns" const expressApp = createExpressApp() expressApp.use(cookieParser()) @@ -19,14 +17,6 @@ expressApp.use("/trpc", createTrpcMiddleware({ createContext: ({ req, res }) => createContext(req.cookies.token ?? null, res), })) -await prismaClient.user.deleteMany({ - where: { - lastActivityDate: { - lte: dateFns.subMonths(new Date(), 1) - } - } -}) - const { server } = await listen(expressApp, { isProd: !isDev, autoClose: false }) const wss = new WebSocketServer({ server, path: "/ws" }) @@ -36,6 +26,11 @@ const wssTrpcHandler = applyWSSHandler({ createContext: ({ req }) => { const cookies = parseCookie(req.headers.cookie ?? "") return createContext(cookies.token ?? null, undefined) + }, + keepAlive: { + enabled: true, + pingMs: 30000, + pongWaitMs: 5000, } }) diff --git a/src/server/trpc/base.ts b/backend/trpc/base.ts similarity index 66% rename from src/server/trpc/base.ts rename to backend/trpc/base.ts index 2d65b94..25e2cf7 100644 --- a/src/server/trpc/base.ts +++ b/backend/trpc/base.ts @@ -1,12 +1,9 @@ -import { createInputMiddleware, initTRPC, TRPCError } from "@trpc/server" -import { prismaClient } from "../prisma" +import { initTRPC, TRPCError } from "@trpc/server" import type { Response } from "express" +import { type User, usersByToken } from "../user" export interface Context { - user: { - id: string - name: string - } | null + user: User | null res?: Response } @@ -14,13 +11,7 @@ export async function createContext(authenticationToken: string | null, res: Res let user: Context["user"] = null if (authenticationToken !== null && authenticationToken.length > 0) { - user = await prismaClient.user.findUnique({ - where: { token: authenticationToken }, - select: { - id: true, - name: true - } - }) + user = usersByToken.get(authenticationToken) ?? null } return { diff --git a/src/server/trpc/game.ts b/backend/trpc/game.ts similarity index 100% rename from src/server/trpc/game.ts rename to backend/trpc/game.ts diff --git a/src/server/trpc/index.ts b/backend/trpc/index.ts similarity index 73% rename from src/server/trpc/index.ts rename to backend/trpc/index.ts index 2ea6fac..671ccfb 100644 --- a/src/server/trpc/index.ts +++ b/backend/trpc/index.ts @@ -1,13 +1,13 @@ import { requireAuthentication, t } from "./base" -import { prismaClient } from "../prisma" import z from "zod" import { observable } from "@trpc/server/observable" import type { GameAction } from "../../shared/game/actions" import { isDev } from "../isDev" import { gameRouter } from "./game" -import { nanoid } from "nanoid/async" +import { nanoid } from "nanoid" import { createGame, getGameByLobbyCode } from "../game" import type { GameEvent } from "../../shared/game/events" +import { type User, usersById, usersByToken } from "../user" export const appRouter = t.router({ game: gameRouter, @@ -17,20 +17,21 @@ export const appRouter = t.router({ name: z.string().min(1).max(20) })) .mutation(async ({ input, ctx }) => { - if (ctx.user !== null) await prismaClient.user.delete({ where: { id: ctx.user.id } }) + if (ctx.user !== null) { + usersById.delete(ctx.user.id) + usersByToken.delete(ctx.user.token) + } - const token = await nanoid(60) - const user = await prismaClient.user.create({ - data: { - name: input.name, - token - }, - select: { - id: true - } - }) + const newUser: User = { + id: nanoid(16), + token: nanoid(64), + name: input.name + } - ctx.res!.cookie("token", token, { + usersById.set(newUser.id, newUser) + usersByToken.set(newUser.token, newUser) + + ctx.res!.cookie("token", newUser.token, { maxAge: 60 * 60 * 24 * 365, httpOnly: true, secure: !isDev, @@ -38,7 +39,7 @@ export const appRouter = t.router({ }) return { - id: user.id + id: newUser.id } }), @@ -46,21 +47,8 @@ export const appRouter = t.router({ .query(async ({ ctx }) => { if (ctx.user === null) return { user: null } - await prismaClient.user.update({ - where: { id: ctx.user.id }, - data: { - lastActivityDate: new Date() - } - }) - return { - user: await prismaClient.user.findUnique({ - where: { id: ctx.user.id }, - select: { - id: true, - name: true - } - }) + user: ctx.user } }), @@ -80,10 +68,10 @@ export const appRouter = t.router({ lobbyCode: z.string().nonempty() })) .subscription(async ({ input, ctx }) => { - const game = await getGameByLobbyCode(input.lobbyCode) + const game = getGameByLobbyCode(input.lobbyCode) if (game === null) throw new Error("There is no game with this code.") - await game.addPlayer(ctx.user.id, ctx.user.name) + game.addPlayer(ctx.user.id, ctx.user.name) return observable(emit => { const handleBroadcastAction = (action: GameAction) => emit.next({ type: "action", action }) diff --git a/backend/user.ts b/backend/user.ts new file mode 100644 index 0000000..a2d924a --- /dev/null +++ b/backend/user.ts @@ -0,0 +1,8 @@ +export interface User { + id: string + token: string + name: string +} + +export const usersByToken = new Map() +export const usersById = new Map() \ No newline at end of file diff --git a/src/App.vue b/frontend/App.vue similarity index 86% rename from src/App.vue rename to frontend/App.vue index 90daf48..022685c 100644 --- a/src/App.vue +++ b/frontend/App.vue @@ -1,9 +1,9 @@