diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..11ceb71 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,53 @@ +name: Build +on: + push: + branches: + - main + pull_request: + branches: + - main + types: [opened, synchronize] +jobs: + client: + runs-on: ubuntu-latest + defaults: + run: + working-directory: packages/frontend + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: "pnpm" + - name: Install dependencies + run: pnpm install + - name: Lint & Build Client + run: pnpm build + + server: + runs-on: ubuntu-latest + defaults: + run: + working-directory: packages/backend + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: "pnpm" + - name: Install dependencies + run: pnpm install + - name: Build Server + run: pnpm build diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..f8e5d4b --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,35 @@ +name: Lint +on: + push: + branches: + - main + pull_request: + branches: + - main + types: [opened, synchronize] +jobs: + client: + runs-on: ubuntu-latest + defaults: + run: + working-directory: client + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install dependencies + run: yarn install + - name: Lint Client + run: yarn lint + + server: + runs-on: ubuntu-latest + defaults: + run: + working-directory: server + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install dependencies + run: yarn install + - name: Lint Server + run: yarn lint diff --git a/packages/backend/app.ts b/packages/backend/app.ts index 4ea3b88..614f1d1 100644 --- a/packages/backend/app.ts +++ b/packages/backend/app.ts @@ -1,7 +1,9 @@ import { gameTiersRouter } from "./microservices/game-tiers/game-tiers.routes"; import { gamesRouter } from "./microservices/games/games.routes"; +import { leaderboardRouter } from "./microservices/leaderboard/leaderboard.routes"; +import { playedGamesRouter } from "./microservices/played-games/played-games.routes"; +import { playersRouter } from "./microservices/players/players.routes"; import { seasonsRouter } from "./microservices/seasons/seasons.routes"; -import { usersRouter } from "./microservices/users/users.routes"; import { RedisService, SupabaseService, WSService } from "./services"; import cors from "cors"; import "dotenv/config"; @@ -28,8 +30,10 @@ app.use("/api/v1", v1Router); v1Router.use("/game-tiers", gameTiersRouter); v1Router.use("/games", gamesRouter); -v1Router.use("/users", usersRouter); +v1Router.use("/players", playersRouter); v1Router.use("/seasons", seasonsRouter); +v1Router.use("/played-games", playedGamesRouter); +v1Router.use("/leaderboard", leaderboardRouter); app.use("*", (_req: Request, res: Response) => { res.status(404).json({ diff --git a/packages/backend/microservices/arcade-contract/arcade-game.service.ts b/packages/backend/microservices/arcade-contract/arcade-game.service.ts index 949575c..825a74a 100644 --- a/packages/backend/microservices/arcade-contract/arcade-game.service.ts +++ b/packages/backend/microservices/arcade-contract/arcade-game.service.ts @@ -4,7 +4,10 @@ import { ARCADE_CONTRACT_ADDRESS, } from "../../utils/constants/contracts.constants"; import { NETWORK_RPC_URL } from "../../utils/constants/network-config.constants"; -import { MappedPlayedGame, MappedUser } from "../../utils/types/mappers.types"; +import { + MappedPlayedGame, + MappedPlayer, +} from "../../utils/types/mappers.types"; import { Contract, ethers } from "ethers"; export class ArcadeService { @@ -22,8 +25,8 @@ export class ArcadeService { public static async createGame( game_id: MappedPlayedGame["game_id"], - player_1_address: MappedUser["wallet_address"], - player_2_address: MappedUser["wallet_address"], + player_1_address: MappedPlayer["wallet_address"], + player_2_address: MappedPlayer["wallet_address"], game_tier_id: MappedPlayedGame["game_tier_id"], is_betting_active: boolean, ) { @@ -58,8 +61,8 @@ export class ArcadeService { public static async endGame( game_id: MappedPlayedGame["game_id"], - winner: MappedUser["wallet_address"], - loser: MappedUser["wallet_address"], + winner: MappedPlayer["wallet_address"], + loser: MappedPlayer["wallet_address"], ) { try { const tx = await this.arcade_contract.endGame( @@ -78,7 +81,7 @@ export class ArcadeService { } public static async withdrawUserReward( - user_address: MappedUser["wallet_address"], + user_address: MappedPlayer["wallet_address"], amount: number, ) { try { diff --git a/packages/backend/microservices/connect-four/connect-four.routes.ts b/packages/backend/microservices/connect-four/connect-four.routes.ts index fd6732e..7f1b530 100644 --- a/packages/backend/microservices/connect-four/connect-four.routes.ts +++ b/packages/backend/microservices/connect-four/connect-four.routes.ts @@ -22,11 +22,11 @@ export const ConnectFourRoutes = (socket: Socket, io: Namespace) => { game_id, season_id, tier_id, - user_id, + player_id, }: ConnectFour.JoinEvent["payload"]) => { try { const roomKey: string = `${season_id}::${game_id}::${tier_id}`; - const logId: string = `[${season_id}][${game_id}][${tier_id}][${user_id}]`; + const logId: string = `[${season_id}][${game_id}][${tier_id}][${player_id}]`; let roomId: string | null = (await RedisClient.lpop(roomKey)) || null; @@ -38,7 +38,7 @@ export const ConnectFourRoutes = (socket: Socket, io: Namespace) => { ); const { played_game_id } = await createGame( - user_id, + player_id, season_id, game_id, tier_id, @@ -58,9 +58,9 @@ export const ConnectFourRoutes = (socket: Socket, io: Namespace) => { Omit >({ winner_id: null, - active_player: user_id, + active_player: player_id, player1: { - user_id, + player_id, currentMove: null, }, board: ConnectFour.emptyBoard, @@ -76,7 +76,7 @@ export const ConnectFourRoutes = (socket: Socket, io: Namespace) => { // TODO: fix self joining room - await addPlayer2ToGame(roomId, user_id); + await addPlayer2ToGame(roomId, player_id); await RedisClient.hset( roomId, @@ -84,7 +84,7 @@ export const ConnectFourRoutes = (socket: Socket, io: Namespace) => { Pick >({ player2: { - user_id, + player_id, currentMove: null, }, }), @@ -103,7 +103,7 @@ export const ConnectFourRoutes = (socket: Socket, io: Namespace) => { type: "player-joined", payload: { room_id: roomId, - user_id, + player_id, }, }; io.to(roomId).emit( @@ -124,11 +124,11 @@ export const ConnectFourRoutes = (socket: Socket, io: Namespace) => { payload: { player1: { currentMove: null, - user_id: player1.user_id, + player_id: player1.player_id, }, player2: { currentMove: null, - user_id: player2.user_id, + player_id: player2.player_id, }, active_player, board, @@ -150,7 +150,7 @@ export const ConnectFourRoutes = (socket: Socket, io: Namespace) => { async ({ move, room_id, - user_id, + player_id, }: ConnectFour.MoveEvent["payload"]) => { try { const gameState = @@ -158,7 +158,7 @@ export const ConnectFourRoutes = (socket: Socket, io: Namespace) => { await RedisClient.hgetall(room_id), ); - if (user_id !== gameState.active_player) { + if (player_id !== gameState.active_player) { return; } @@ -166,13 +166,13 @@ export const ConnectFourRoutes = (socket: Socket, io: Namespace) => { return; } - if (user_id === gameState.player1.user_id) { + if (player_id === gameState.player1.player_id) { if (gameState.player1.currentMove === null) { gameState.player1.currentMove = move; } else { return; } - } else if (user_id === gameState.player2.user_id) { + } else if (player_id === gameState.player2.player_id) { if (gameState.player2.currentMove === null) { gameState.player2.currentMove = move; } else { @@ -180,21 +180,21 @@ export const ConnectFourRoutes = (socket: Socket, io: Namespace) => { } } else { throw Error( - `Player ${user_id} does not exist in room ${room_id}`, + `Player ${player_id} does not exist in room ${room_id}`, ); } const activePlayer = - user_id === gameState.player1.user_id && + player_id === gameState.player1.player_id && gameState.player1.currentMove ? gameState.player1 - : gameState.player2.user_id === user_id && + : gameState.player2.player_id === player_id && gameState.player2.currentMove ? gameState.player2 : null; if (!activePlayer?.currentMove) { - throw Error(`no active player for player ${user_id}`); + throw Error(`no active player for player ${player_id}`); } for (let row = ConnectFour.rowCount - 1; row >= 0; row--) { @@ -204,12 +204,12 @@ export const ConnectFourRoutes = (socket: Socket, io: Namespace) => { ] === null ) { gameState.board[row][activePlayer.currentMove.column] = - activePlayer.user_id; + activePlayer.player_id; break; } } - const win = checkWinner(gameState.board, user_id); + const win = checkWinner(gameState.board, player_id); if (!win) { const moveEndEvent: ConnectFour.MoveEndEvent = { @@ -242,7 +242,7 @@ export const ConnectFourRoutes = (socket: Socket, io: Namespace) => { ), ); } else { - gameState.winner_id = activePlayer.user_id; + gameState.winner_id = activePlayer.player_id; await setWinnerToGame(room_id, gameState.winner_id); await RedisClient.del(room_id); diff --git a/packages/backend/microservices/game-history/game-history.routes.ts b/packages/backend/microservices/game-history/game-history.routes.ts new file mode 100644 index 0000000..9fb61dd --- /dev/null +++ b/packages/backend/microservices/game-history/game-history.routes.ts @@ -0,0 +1,31 @@ +import { validateQuery } from "../../middlewares/rest"; +import { MappedSeason } from "../../utils/types/mappers.types"; +import { getSeasonLeaderboardParams } from "./game-history.schema"; +import { fetchPlayerGameHistory } from "./game-history.service"; +import type { NextFunction, Request, Response } from "express"; +import { Router } from "express"; + +export const gameHistoryRouter = Router(); + +const handleGetPlayerGameHistory = async ( + req: Request, + res: Response, + next: NextFunction, +) => { + try { + const { season_id } = req.params as unknown as MappedSeason; + const data = await fetchPlayerGameHistory(season_id); + return res.json({ + success: true, + data, + }); + } catch (error) { + next(error); + } +}; + +gameHistoryRouter.get( + "/:season_id", + validateQuery("params", getSeasonLeaderboardParams), + handleGetPlayerGameHistory, +); diff --git a/packages/backend/microservices/game-history/game-history.schema.ts b/packages/backend/microservices/game-history/game-history.schema.ts new file mode 100644 index 0000000..a786fe1 --- /dev/null +++ b/packages/backend/microservices/game-history/game-history.schema.ts @@ -0,0 +1,12 @@ +import { type MappedSeason } from "../../utils/types/mappers.types"; +import { type PartialYupSchema } from "../../utils/types/shared.types"; +import * as yup from "yup"; + +export const getSeasonLeaderboardParams = yup + .object() + .shape>({ + season_id: yup.string().trim().required(), + }) + .strict() + .noUnknown() + .required(); diff --git a/packages/backend/microservices/game-history/game-history.service.ts b/packages/backend/microservices/game-history/game-history.service.ts new file mode 100644 index 0000000..6c1486b --- /dev/null +++ b/packages/backend/microservices/game-history/game-history.service.ts @@ -0,0 +1,18 @@ +import { SupabaseService } from "../../services"; +import { MappedLeaderboard } from "../../utils/types/mappers.types"; + +export const fetchPlayerGameHistory = async ( + season_id: MappedLeaderboard["season_id"], +) => { + const { data, error } = await SupabaseService.getSupabase() + .from("leaderboard") + .select() + .eq("season_id", season_id!); + + if (error) { + console.error(error); + throw error; + } + + return data; +}; diff --git a/packages/backend/microservices/leaderboard/leaderboard.routes.ts b/packages/backend/microservices/leaderboard/leaderboard.routes.ts new file mode 100644 index 0000000..e729f9e --- /dev/null +++ b/packages/backend/microservices/leaderboard/leaderboard.routes.ts @@ -0,0 +1,31 @@ +import { validateQuery } from "../../middlewares/rest"; +import { MappedSeason } from "../../utils/types/mappers.types"; +import { getSeasonLeaderboardParams } from "./leaderboard.schema"; +import { fetchSeasonLeaderboard } from "./leaderboard.service"; +import type { NextFunction, Request, Response } from "express"; +import { Router } from "express"; + +export const leaderboardRouter = Router(); + +const handleGetSeasonLeaderboard = async ( + req: Request, + res: Response, + next: NextFunction, +) => { + try { + const { season_id } = req.params as unknown as MappedSeason; + const data = await fetchSeasonLeaderboard(season_id); + return res.json({ + success: true, + data, + }); + } catch (error) { + next(error); + } +}; + +leaderboardRouter.get( + "/:season_id", + validateQuery("params", getSeasonLeaderboardParams), + handleGetSeasonLeaderboard, +); diff --git a/packages/backend/microservices/leaderboard/leaderboard.schema.ts b/packages/backend/microservices/leaderboard/leaderboard.schema.ts new file mode 100644 index 0000000..a786fe1 --- /dev/null +++ b/packages/backend/microservices/leaderboard/leaderboard.schema.ts @@ -0,0 +1,12 @@ +import { type MappedSeason } from "../../utils/types/mappers.types"; +import { type PartialYupSchema } from "../../utils/types/shared.types"; +import * as yup from "yup"; + +export const getSeasonLeaderboardParams = yup + .object() + .shape>({ + season_id: yup.string().trim().required(), + }) + .strict() + .noUnknown() + .required(); diff --git a/packages/backend/microservices/leaderboard/leaderboard.service.ts b/packages/backend/microservices/leaderboard/leaderboard.service.ts new file mode 100644 index 0000000..11f76e6 --- /dev/null +++ b/packages/backend/microservices/leaderboard/leaderboard.service.ts @@ -0,0 +1,18 @@ +import { SupabaseService } from "../../services"; +import { MappedLeaderboard } from "../../utils/types/mappers.types"; + +export const fetchSeasonLeaderboard = async ( + season_id: MappedLeaderboard["season_id"], +) => { + const { data, error } = await SupabaseService.getSupabase() + .from("leaderboard") + .select() + .eq("season_id", season_id!); + + if (error) { + console.error(error); + throw error; + } + + return data; +}; diff --git a/packages/backend/microservices/played-games/played-games.routes.ts b/packages/backend/microservices/played-games/played-games.routes.ts new file mode 100644 index 0000000..bafc3d6 --- /dev/null +++ b/packages/backend/microservices/played-games/played-games.routes.ts @@ -0,0 +1,31 @@ +import { validateQuery } from "../../middlewares/rest"; +import { MappedPlayer } from "../../utils/types/mappers.types"; +import { getUserGamesParams } from "./played-games.schema"; +import { fetchAllUserGames } from "./played-games.service"; +import type { NextFunction, Request, Response } from "express"; +import { Router } from "express"; + +export const playedGamesRouter = Router(); + +const handleAllUserGames = async ( + req: Request, + res: Response, + next: NextFunction, +) => { + try { + const { player_id } = req.params as MappedPlayer; + const data = await fetchAllUserGames(player_id); + return res.json({ + success: true, + data, + }); + } catch (error) { + next(error); + } +}; + +playedGamesRouter.get( + "/:player_id", + validateQuery("params", getUserGamesParams), + handleAllUserGames, +); diff --git a/packages/backend/microservices/played-games/played-games.schema.ts b/packages/backend/microservices/played-games/played-games.schema.ts new file mode 100644 index 0000000..0ee1bbf --- /dev/null +++ b/packages/backend/microservices/played-games/played-games.schema.ts @@ -0,0 +1,12 @@ +import { type MappedPlayer } from "../../utils/types/mappers.types"; +import { type PartialYupSchema } from "../../utils/types/shared.types"; +import * as yup from "yup"; + +export const getUserGamesParams = yup + .object() + .shape>({ + player_id: yup.string().trim().required(), + }) + .strict() + .noUnknown() + .required(); diff --git a/packages/backend/microservices/played-games/played-games.service.ts b/packages/backend/microservices/played-games/played-games.service.ts index 7b2d970..cdb3b22 100644 --- a/packages/backend/microservices/played-games/played-games.service.ts +++ b/packages/backend/microservices/played-games/played-games.service.ts @@ -1,5 +1,25 @@ import { SupabaseService } from "../../services"; -import { MappedPlayedGame } from "../../utils/types/mappers.types"; +import { + MappedPlayedGame, + MappedPlayer, +} from "../../utils/types/mappers.types"; + +export const fetchAllUserGames = async ( + player_id: MappedPlayer["player_id"], +) => { + const { data, error } = await SupabaseService.getSupabase() + .from("played_games") + .select() + .or(`player_1_id.eq.${player_id},player_2_id.eq.${player_id}`) + .order("created_at", { ascending: false }); + + if (error) { + console.error(error); + throw error; + } + + return data; +}; export const createGame = async ( player_1_id: MappedPlayedGame["player_1_id"], diff --git a/packages/backend/microservices/users/users.routes.ts b/packages/backend/microservices/players/players.routes.ts similarity index 67% rename from packages/backend/microservices/users/users.routes.ts rename to packages/backend/microservices/players/players.routes.ts index 9a27fea..23b786f 100644 --- a/packages/backend/microservices/users/users.routes.ts +++ b/packages/backend/microservices/players/players.routes.ts @@ -1,11 +1,11 @@ import { validateQuery } from "../../middlewares/rest"; -import { type MappedUser } from "../../utils/types/mappers.types"; -import { getUserParams, userAuthBody } from "./users.schema"; -import { createJWToken, createUser, fetchUserDetails } from "./users.service"; +import { type MappedPlayer } from "../../utils/types/mappers.types"; +import { getUserParams, userAuthBody } from "./players.schema"; +import { createJWToken, createUser, fetchUserDetails } from "./players.service"; import type { NextFunction, Request, Response } from "express"; import { Router } from "express"; -export const usersRouter = Router(); +export const playersRouter = Router(); const handleGetUser = async ( req: Request, @@ -13,8 +13,8 @@ const handleGetUser = async ( next: NextFunction, ) => { try { - const { user_id } = req.params as MappedUser; - const data = await fetchUserDetails(user_id); + const { player_id } = req.params as MappedPlayer; + const data = await fetchUserDetails(player_id); return res.json({ success: true, data, @@ -31,7 +31,7 @@ const handlerUserAuth = async ( ) => { try { const { email_id, name, profile_photo, wallet_address } = - req.body as MappedUser; + req.body as MappedPlayer; let user = await fetchUserDetails(email_id); if (!user) { user = await createUser({ @@ -54,9 +54,13 @@ const handlerUserAuth = async ( } }; -usersRouter.post("/auth", validateQuery("body", userAuthBody), handlerUserAuth); -usersRouter.get( - "/:user_id", +playersRouter.post( + "/auth", + validateQuery("body", userAuthBody), + handlerUserAuth, +); +playersRouter.get( + "/:player_id", validateQuery("params", getUserParams), handleGetUser, ); diff --git a/packages/backend/microservices/users/users.schema.ts b/packages/backend/microservices/players/players.schema.ts similarity index 75% rename from packages/backend/microservices/users/users.schema.ts rename to packages/backend/microservices/players/players.schema.ts index 11df8bc..291e926 100644 --- a/packages/backend/microservices/users/users.schema.ts +++ b/packages/backend/microservices/players/players.schema.ts @@ -1,11 +1,11 @@ -import { type MappedUser } from "../../utils/types/mappers.types"; +import { type MappedPlayer } from "../../utils/types/mappers.types"; import { type PartialYupSchema } from "../../utils/types/shared.types"; import * as yup from "yup"; export const getUserParams = yup .object() - .shape>({ - user_id: yup.string().trim().required(), + .shape>({ + player_id: yup.string().trim().required(), }) .strict() .noUnknown() @@ -13,7 +13,7 @@ export const getUserParams = yup export const userAuthBody = yup .object() - .shape>({ + .shape>({ email_id: yup.string().trim().email().required(), profile_photo: yup.string().trim().url().required(), name: yup.string().trim().required(), diff --git a/packages/backend/microservices/users/users.service.ts b/packages/backend/microservices/players/players.service.ts similarity index 73% rename from packages/backend/microservices/users/users.service.ts rename to packages/backend/microservices/players/players.service.ts index d65066e..03fe48f 100644 --- a/packages/backend/microservices/users/users.service.ts +++ b/packages/backend/microservices/players/players.service.ts @@ -1,8 +1,8 @@ import { SupabaseService } from "../../services"; -import { type MappedUser } from "../../utils/types/mappers.types"; +import { type MappedPlayer } from "../../utils/types/mappers.types"; import { sign } from "jsonwebtoken"; -export const createJWToken = (user: MappedUser) => { +export const createJWToken = (user: MappedPlayer) => { const token = sign(user, process.env.JWT_SECRET_KEY!, { issuer: "dewl$", expiresIn: "24h", @@ -10,9 +10,9 @@ export const createJWToken = (user: MappedUser) => { return token; }; -export const fetchUserDetails = async (email_id: MappedUser["email_id"]) => { +export const fetchUserDetails = async (email_id: MappedPlayer["email_id"]) => { const { data, error } = await SupabaseService.getSupabase() - .from("users") + .from("players") .select() .eq("email_id", email_id) .single(); @@ -30,9 +30,9 @@ export const createUser = async ({ name, profile_photo, wallet_address, -}: Omit) => { +}: Omit) => { const { data, error } = await SupabaseService.getSupabase() - .from("users") + .from("players") .insert({ email_id, name, diff --git a/packages/backend/microservices/rock-paper-scissors/rock-paper-scissors.routes.ts b/packages/backend/microservices/rock-paper-scissors/rock-paper-scissors.routes.ts index c1b8cb2..a1032c5 100644 --- a/packages/backend/microservices/rock-paper-scissors/rock-paper-scissors.routes.ts +++ b/packages/backend/microservices/rock-paper-scissors/rock-paper-scissors.routes.ts @@ -21,11 +21,11 @@ export const RockPaperScissorsRoutes = (socket: Socket, io: Namespace) => { game_id, season_id, tier_id, - user_id, + player_id, }: RockPaperScissors.JoinEvent["payload"]) => { try { const roomKey: string = `${season_id}::${game_id}::${tier_id}`; - const logId: string = `[${season_id}][${game_id}][${tier_id}][${user_id}]`; + const logId: string = `[${season_id}][${game_id}][${tier_id}][${player_id}]`; let roomId: string | null = (await RedisClient.lpop(roomKey)) || null; @@ -37,7 +37,7 @@ export const RockPaperScissorsRoutes = (socket: Socket, io: Namespace) => { ); const { played_game_id } = await createGame( - user_id, + player_id, season_id, game_id, tier_id, @@ -59,7 +59,7 @@ export const RockPaperScissorsRoutes = (socket: Socket, io: Namespace) => { winner_id: null, round: 0, player1: { - user_id, + player_id, currentMove: null, currentScore: 0, }, @@ -75,7 +75,7 @@ export const RockPaperScissorsRoutes = (socket: Socket, io: Namespace) => { // TODO: fix self joining room - await addPlayer2ToGame(roomId, user_id); + await addPlayer2ToGame(roomId, player_id); await RedisClient.hset( roomId, @@ -83,7 +83,7 @@ export const RockPaperScissorsRoutes = (socket: Socket, io: Namespace) => { Pick >({ player2: { - user_id, + player_id, currentMove: null, currentScore: 0, }, @@ -103,7 +103,7 @@ export const RockPaperScissorsRoutes = (socket: Socket, io: Namespace) => { type: "player-joined", payload: { room_id: roomId, - user_id, + player_id, }, }; io.to(roomId).emit( @@ -127,12 +127,12 @@ export const RockPaperScissorsRoutes = (socket: Socket, io: Namespace) => { player1: { currentMove: null, currentScore: 0, - user_id: player1.user_id, + player_id: player1.player_id, }, player2: { currentMove: null, currentScore: 0, - user_id: player2.user_id, + player_id: player2.player_id, }, round: round as 0, }, @@ -153,7 +153,7 @@ export const RockPaperScissorsRoutes = (socket: Socket, io: Namespace) => { async ({ move, room_id, - user_id, + player_id, }: RockPaperScissors.MoveEvent["payload"]) => { try { const gameState = @@ -163,13 +163,13 @@ export const RockPaperScissorsRoutes = (socket: Socket, io: Namespace) => { let updateGameState: boolean = true; - if (gameState.player1.user_id === user_id) { + if (gameState.player1.player_id === player_id) { if (gameState.player1.currentMove === null) { gameState.player1.currentMove = move; } else { return; } - } else if (gameState.player2.user_id === user_id) { + } else if (gameState.player2.player_id === player_id) { if (gameState.player2.currentMove === null) { gameState.player2.currentMove = move; } else { @@ -177,7 +177,7 @@ export const RockPaperScissorsRoutes = (socket: Socket, io: Namespace) => { } } else { throw Error( - `Player ${user_id} does not exist in room ${room_id}`, + `Player ${player_id} does not exist in room ${room_id}`, ); } @@ -202,11 +202,11 @@ export const RockPaperScissorsRoutes = (socket: Socket, io: Namespace) => { (gameState.player1.currentMove === "scissors" && gameState.player2.currentMove === "paper") ) { - gameState.winner_id = gameState.player1.user_id; + gameState.winner_id = gameState.player1.player_id; gameState.player1.currentScore = gameState.player1.currentScore + 1; } else { - gameState.winner_id = gameState.player2.user_id; + gameState.winner_id = gameState.player2.player_id; gameState.player2.currentScore = gameState.player2.currentScore + 1; } diff --git a/packages/backend/microservices/seasons/seasons.routes.ts b/packages/backend/microservices/seasons/seasons.routes.ts index 6557536..155a6bf 100644 --- a/packages/backend/microservices/seasons/seasons.routes.ts +++ b/packages/backend/microservices/seasons/seasons.routes.ts @@ -1,8 +1,4 @@ -import { - fetchAllSeasons, - fetchCurrentLeaderboard, - fetchCurrentSeason, -} from "./seasons.service"; +import { fetchAllSeasons, fetchCurrentSeason } from "./seasons.service"; import type { NextFunction, Request, Response } from "express"; import { Router } from "express"; @@ -40,22 +36,5 @@ const handleGetCurrentSeason = async ( } }; -const handleGetCurrentLeaderboard = async ( - req: Request, - res: Response, - next: NextFunction, -) => { - try { - const data = await fetchCurrentLeaderboard(); - return res.json({ - success: true, - data, - }); - } catch (error) { - next(error); - } -}; - seasonsRouter.get("/", handleGetAllSeasons); seasonsRouter.get("/current", handleGetCurrentSeason); -seasonsRouter.get("/current/leaderboard", handleGetCurrentLeaderboard); diff --git a/packages/backend/microservices/seasons/seasons.service.ts b/packages/backend/microservices/seasons/seasons.service.ts index 7af832c..d625500 100644 --- a/packages/backend/microservices/seasons/seasons.service.ts +++ b/packages/backend/microservices/seasons/seasons.service.ts @@ -29,19 +29,3 @@ export const fetchCurrentSeason = async () => { return data; }; - -export const fetchCurrentLeaderboard = async () => { - const { season_id } = await fetchCurrentSeason(); - - const { data, error } = await SupabaseService.getSupabase() - .from("leaderboard") - .select() - .eq("season_id", season_id); - - if (error) { - console.error(error); - throw error; - } - - return data; -}; diff --git a/packages/backend/package.json b/packages/backend/package.json index cbb790c..5e97b92 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -4,13 +4,12 @@ "description": "", "main": "app.ts", "scripts": { - "dev": "pnpm run types && tsc-watch --onSuccess \"pnpm start\"", + "dev": "pnpm run types:database && tsc-watch --onSuccess \"pnpm start\"", "build": "([ ! -d dist ] || rm -r dist) && tsc", "lint": "eslint .", "pretty": "prettier . --write", "start": "node ./dist/app.js", - "types:database": "node ./scripts/generate-supabase-types.js", - "types": "pnpm run types:database" + "types:database": "node ./scripts/generate-supabase-types.js" }, "engines": { "node": "^20.9.0" diff --git a/packages/backend/utils/types/database.types.ts b/packages/backend/utils/types/database.types.ts index cb914ce..90b45b6 100644 --- a/packages/backend/utils/types/database.types.ts +++ b/packages/backend/utils/types/database.types.ts @@ -123,28 +123,28 @@ export type Database = { columns: ["player_1_id"] isOneToOne: false referencedRelation: "leaderboard" - referencedColumns: ["user_id"] + referencedColumns: ["player_id"] }, { foreignKeyName: "played_games_player_1_id_fkey" columns: ["player_1_id"] isOneToOne: false - referencedRelation: "users" - referencedColumns: ["user_id"] + referencedRelation: "players" + referencedColumns: ["player_id"] }, { foreignKeyName: "played_games_player_2_id_fkey" columns: ["player_2_id"] isOneToOne: false referencedRelation: "leaderboard" - referencedColumns: ["user_id"] + referencedColumns: ["player_id"] }, { foreignKeyName: "played_games_player_2_id_fkey" columns: ["player_2_id"] isOneToOne: false - referencedRelation: "users" - referencedColumns: ["user_id"] + referencedRelation: "players" + referencedColumns: ["player_id"] }, { foreignKeyName: "played_games_season_id_fkey" @@ -158,21 +158,49 @@ export type Database = { columns: ["winner_id"] isOneToOne: false referencedRelation: "leaderboard" - referencedColumns: ["user_id"] + referencedColumns: ["player_id"] }, { foreignKeyName: "played_games_winner_id_fkey" columns: ["winner_id"] isOneToOne: false - referencedRelation: "users" - referencedColumns: ["user_id"] + referencedRelation: "players" + referencedColumns: ["player_id"] }, ] } + players: { + Row: { + created_at: string + email_id: string + name: string + player_id: string + profile_photo: string + wallet_address: string + } + Insert: { + created_at?: string + email_id: string + name: string + player_id?: string + profile_photo: string + wallet_address: string + } + Update: { + created_at?: string + email_id?: string + name?: string + player_id?: string + profile_photo?: string + wallet_address?: string + } + Relationships: [] + } seasons: { Row: { created_at: string ended_on: string + name: string reward_pool_usd: number season_id: string started_on: string @@ -180,6 +208,7 @@ export type Database = { Insert: { created_at?: string ended_on: string + name?: string reward_pool_usd?: number season_id?: string started_on: string @@ -187,52 +216,58 @@ export type Database = { Update: { created_at?: string ended_on?: string + name?: string reward_pool_usd?: number season_id?: string started_on?: string } Relationships: [] } - users: { + } + Views: { + game_history: { Row: { - created_at: string - email_id: string - name: string - profile_photo: string - user_id: string - wallet_address: string - } - Insert: { - created_at?: string - email_id: string - name: string - profile_photo: string - user_id?: string - wallet_address: string - } - Update: { - created_at?: string - email_id?: string - name?: string - profile_photo?: string - user_id?: string - wallet_address?: string + created_at: string | null + game_id: string | null + game_tier_id: string | null + played_game_id: string | null + player_1: Json | null + player_2: Json | null + season_id: string | null } - Relationships: [] + Relationships: [ + { + foreignKeyName: "played_games_game_id_fkey" + columns: ["game_id"] + isOneToOne: false + referencedRelation: "games" + referencedColumns: ["game_id"] + }, + { + foreignKeyName: "played_games_game_tier_id_fkey" + columns: ["game_tier_id"] + isOneToOne: false + referencedRelation: "game_tiers" + referencedColumns: ["tier_id"] + }, + { + foreignKeyName: "played_games_season_id_fkey" + columns: ["season_id"] + isOneToOne: false + referencedRelation: "seasons" + referencedColumns: ["season_id"] + }, + ] } - } - Views: { leaderboard: { Row: { - created_at: string | null - email_id: string | null games_played: number | null games_won: number | null name: string | null + player_id: string | null profile_photo: string | null season_id: string | null total_points: number | null - user_id: string | null wallet_address: string | null } Relationships: [ diff --git a/packages/backend/utils/types/mappers.types.ts b/packages/backend/utils/types/mappers.types.ts index 526a93a..9ceff1a 100644 --- a/packages/backend/utils/types/mappers.types.ts +++ b/packages/backend/utils/types/mappers.types.ts @@ -5,4 +5,6 @@ export type MappedGame = Database["public"]["Tables"]["games"]["Row"]; export type MappedPlayedGame = Database["public"]["Tables"]["played_games"]["Row"]; export type MappedSeason = Database["public"]["Tables"]["seasons"]["Row"]; -export type MappedUser = Database["public"]["Tables"]["users"]["Row"]; +export type MappedPlayer = Database["public"]["Tables"]["players"]["Row"]; +export type MappedLeaderboard = + Database["public"]["Views"]["leaderboard"]["Row"]; diff --git a/packages/common/connect-four/index.ts b/packages/common/connect-four/index.ts index 3d4b616..26f329a 100644 --- a/packages/common/connect-four/index.ts +++ b/packages/common/connect-four/index.ts @@ -12,7 +12,7 @@ export type JoinEvent = { type: "join"; payload: { season_id: string; - user_id: string; + player_id: string; game_id: string; tier_id: string; }; @@ -21,7 +21,7 @@ export type JoinEvent = { export type MoveEvent = { type: "move"; payload: { - user_id: string; + player_id: string; room_id: string; move: Move; }; @@ -32,7 +32,7 @@ export type CLIENT_EVENTS = JoinEvent | MoveEvent; export type PlayerJoinedEvent = { type: "player-joined"; payload: { - user_id: string; + player_id: string; room_id: string; }; }; @@ -106,7 +106,7 @@ export type Board = [ export type PlayerServerState = { currentMove: Move | null; - user_id: string; + player_id: string; }; export const emptyBoard: Board = [ diff --git a/packages/common/rock-paper-scissors/index.ts b/packages/common/rock-paper-scissors/index.ts index dd91065..bb7257c 100644 --- a/packages/common/rock-paper-scissors/index.ts +++ b/packages/common/rock-paper-scissors/index.ts @@ -8,7 +8,7 @@ export type JoinEvent = { type: "join"; payload: { season_id: string; - user_id: string; + player_id: string; game_id: string; tier_id: string; }; @@ -17,7 +17,7 @@ export type JoinEvent = { export type MoveEvent = { type: "move"; payload: { - user_id: string; + player_id: string; room_id: string; move: Move; }; @@ -28,7 +28,7 @@ export type CLIENT_EVENTS = JoinEvent | MoveEvent; export type PlayerJoinedEvent = { type: "player-joined"; payload: { - user_id: string; + player_id: string; room_id: string; }; }; @@ -78,5 +78,5 @@ export type ServerGameState = { export type PlayerServerState = { currentMove: Move | null; currentScore: number; - user_id: string; + player_id: string; }; diff --git a/packages/frontend/src/app/components/leaderboard/LeaderboardTable.tsx b/packages/frontend/src/app/components/leaderboard/LeaderboardTable.tsx index e5f1ba8..a0a4179 100644 --- a/packages/frontend/src/app/components/leaderboard/LeaderboardTable.tsx +++ b/packages/frontend/src/app/components/leaderboard/LeaderboardTable.tsx @@ -1,3 +1,5 @@ +"use client"; + import { useWeb3AuthContext } from "@/utils/context/web3auth.context"; import { truncate } from "@/utils/functions/truncate"; import { MappedLeaderboard } from "@/utils/types"; @@ -5,8 +7,8 @@ import Image from "next/image"; import React from "react"; const LeaderboardTable: React.FC<{ - data: MappedLeaderboard[]; -}> = ({ data }) => { + leaderboard: MappedLeaderboard[]; +}> = ({ leaderboard }) => { const { user } = useWeb3AuthContext(); const headings = ["#", "User", "Won", "Lost", "Score"]; @@ -47,91 +49,99 @@ const LeaderboardTable: React.FC<{
- {data.map( - ( - { - user_id, - profile_photo, - name, - wallet_address, - games_won, - games_played, - total_points, - }, - i - ) => { - const you = user?.data.user_id === user_id; - - return ( -
- {i < 3 ? ( - <> -
- - - - ) : ( -

{i + 1}

- )} - -
- {profile_photo && ( - + {!leaderboard.length ? ( +

+ The first game of this season awaits! +

+ ) : ( + leaderboard.map( + ( + { + player_id, + profile_photo, + name, + wallet_address, + games_won, + games_played, + total_points, + }, + i + ) => { + const you = user?.data.player_id === player_id; + + return ( +
+ {i < 3 ? ( + <> +
+ + + + ) : ( +

{i + 1}

)} -
-

- {name} -

+
+ {profile_photo && ( + + )} -

- {truncate(wallet_address).toUpperCase()} -

+
+

+ {name} +

+ +

+ {truncate(wallet_address).toUpperCase()} +

+
-
-

- {games_won} -

+

+ {games_won} +

-

- {games_played} -

+

+ {games_played} +

-

- {total_points} -

-
- ); - } +

+ {total_points} +

+
+ ); + } + ) )}
diff --git a/packages/frontend/src/app/components/leaderboard/leaderboard.tsx b/packages/frontend/src/app/components/leaderboard/leaderboard.tsx index d5dd2e1..72ad22a 100644 --- a/packages/frontend/src/app/components/leaderboard/leaderboard.tsx +++ b/packages/frontend/src/app/components/leaderboard/leaderboard.tsx @@ -1,22 +1,52 @@ "use client"; -import { MappedLeaderboard, MappedSeason } from "@/utils/types"; +import { + MappedLeaderboard, + MappedSeason, + ResponseWithData, +} from "@/utils/types"; import LeaderboardTable from "./LeaderboardTable"; import Dropdown from "../shared/dropdown"; import { useState } from "react"; import ChevronDown from "@/shared/icons/Chevron-Down"; import coinsLottie from "../../../../public/leaderboard-coins.json"; import Lottie from "react-lottie"; +import { API_BASE_URL } from "@/utils/constants/api.constant"; export const Leaderboard: React.FC<{ seasons: MappedSeason[]; leaderboard: MappedLeaderboard[]; -}> = ({ leaderboard, seasons }) => { +}> = ({ leaderboard: initialLeaderboard, seasons }) => { const [dropdownOpen, setDropdownOpen] = useState(false); const [selectedSeason, setSelectedSeason] = useState( seasons[0] ); - const liveSeason = seasons[0]; + const [loading, setLoading] = useState(false); + const [leaderboard, setLeaderboard] = + useState(initialLeaderboard); + + const updateLeaderboardHandler = async (season_id: string) => { + try { + setLoading(true); + + const leaderboardRes = await fetch( + `${API_BASE_URL}/leaderboard/${season_id}`, + { + cache: "no-cache", + } + ); + const leaderboardResponse = + (await leaderboardRes.json()) as ResponseWithData; + + if (leaderboardResponse.success) { + setLeaderboard(leaderboardResponse.data); + } + } catch (error) { + console.error(error); + } finally { + setLoading(false); + } + }; return (
@@ -35,48 +65,42 @@ export const Leaderboard: React.FC<{ key="seasons-dropdown" className="bg-neutral-600 mt-2 rounded-lg px-4 max-h-96 overflow-auto" > - {seasons.map((season, i) => { - const isActive: boolean = - liveSeason.season_id === season.season_id; - return ( -
{ - setSelectedSeason(season); - setDropdownOpen(false); - }} - > -

- Season {seasons.length - i} -

+ {seasons.map((season, i) => ( +
{ + setSelectedSeason(season); + setDropdownOpen(false); + updateLeaderboardHandler(season.season_id); + }} + > +

{season.name}

- {isActive ? ( - - LIVE - - ) : ( -

- Ended on {new Date(season.ended_on).getDate()} -

- )} -
- ); - })} + {season.ended_on > new Date().toISOString() ? ( + + LIVE + + ) : ( +

+ Ended on {new Date(season.ended_on).getDate()} +

+ )} +
+ ))}
, ]} dropdownClassname="w-full" >
- Season{" "} - {seasons.length - - seasons.findIndex( - ({ season_id }) => selectedSeason.season_id === season_id - )} - {liveSeason.season_id === selectedSeason.season_id && ( + {selectedSeason.name} + + {selectedSeason.ended_on > new Date().toISOString() && ( LIVE @@ -116,7 +140,13 @@ export const Leaderboard: React.FC<{
- + {loading ? ( +

+ Curating the leaderboard... +

+ ) : ( + + )} ); }; diff --git a/packages/frontend/src/app/components/profile/MatchHistory.tsx b/packages/frontend/src/app/components/profile/GameHistory.tsx similarity index 61% rename from packages/frontend/src/app/components/profile/MatchHistory.tsx rename to packages/frontend/src/app/components/profile/GameHistory.tsx index e307ce1..3843208 100644 --- a/packages/frontend/src/app/components/profile/MatchHistory.tsx +++ b/packages/frontend/src/app/components/profile/GameHistory.tsx @@ -1,49 +1,45 @@ "use client"; import BattleIcon from "@/shared/icons/BattleIcon"; -import { MatchPlayer } from "./MatchPlayer"; +import { GamePlayer } from "./GamePlayer"; +import { useWeb3AuthContext } from "@/utils/context/web3auth.context"; +import { MappedPlayedGame } from "@/utils/types"; -export const MatchHistory: React.FC = () => { - // const { user } = useWeb3AuthContext(); - const user = { - email_id: "karanpargal007@gmail.com", - name: "Karan Pargal", - profile_photo: - "https://lh3.googleusercontent.com/a/ACg8ocIJsSTDQXwXlpZTNdu0n6G-EySqxIwKJfUTewSoej7mbMF9ITIH=s96-c", - wallet_address: "0xC1931B33dCb6E64da65D2F3c73bcDc42d2f9Ce98", - won: 10, - played: 20, - score: 1000, - }; +export const GameHistory: React.FC<{ playedGame: MappedPlayedGame }> = async ({ + playedGame, +}) => { + const { user } = useWeb3AuthContext(); if (!user) { return <>; } + const won = playedGame.winner_id === user.data.player_id; + return (

Match History

- Game Name + {playedGame.game_id}

- -
diff --git a/packages/frontend/src/app/components/profile/MatchPlayer.tsx b/packages/frontend/src/app/components/profile/GamePlayer.tsx similarity index 96% rename from packages/frontend/src/app/components/profile/MatchPlayer.tsx rename to packages/frontend/src/app/components/profile/GamePlayer.tsx index 31845be..59929df 100644 --- a/packages/frontend/src/app/components/profile/MatchPlayer.tsx +++ b/packages/frontend/src/app/components/profile/GamePlayer.tsx @@ -2,7 +2,7 @@ import Crown from "@/shared/icons/Crown"; import { truncate } from "@/utils/functions/truncate"; import Image from "next/image"; -export const MatchPlayer: React.FC<{ +export const GamePlayer: React.FC<{ profile_photo: string; won: boolean; name: string; diff --git a/packages/frontend/src/app/components/profile/WalletDetails.tsx b/packages/frontend/src/app/components/profile/WalletDetails.tsx index 99c9fd7..542782e 100644 --- a/packages/frontend/src/app/components/profile/WalletDetails.tsx +++ b/packages/frontend/src/app/components/profile/WalletDetails.tsx @@ -49,7 +49,9 @@ export const WalletDetails: React.FC<{ Connected Wallet

-

{user.wallet_address}

+

+ {user.wallet_address.toUpperCase()} +

{ diff --git a/packages/frontend/src/app/components/profile/hero.tsx b/packages/frontend/src/app/components/profile/hero.tsx index bce6856..833f291 100644 --- a/packages/frontend/src/app/components/profile/hero.tsx +++ b/packages/frontend/src/app/components/profile/hero.tsx @@ -1,19 +1,10 @@ "use client"; +import { useWeb3AuthContext } from "@/utils/context/web3auth.context"; import Image from "next/image"; export const ProfileHero: React.FC = () => { - // const { user } = useWeb3AuthContext(); - const user = { - email_id: "karanpargal007@gmail.com", - name: "Karan Pargal", - profile_photo: - "https://lh3.googleusercontent.com/a/ACg8ocIJsSTDQXwXlpZTNdu0n6G-EySqxIwKJfUTewSoej7mbMF9ITIH=s96-c", - wallet_address: "0xC1931B33dCb6E64da65D2F3c73bcDc42d2f9Ce98", - won: 10, - played: 20, - score: 1000, - }; + const { user } = useWeb3AuthContext(); if (!user) { return <>; @@ -29,31 +20,41 @@ export const ProfileHero: React.FC = () => {
-

{user.name}

-

{user.email_id}

+

{user.data.name}

+

{user.data.email_id}

-
-

Won

-

{user.won}

-
-
-

Played

-

{user.played}

-
-
-

Score

-

{user.score}

-
+ {[ + { + heading: "Won", + value: "user.data.won", + }, + { + heading: "Played", + value: "user.data.played", + }, + { + heading: "Score", + value: "user.data.score", + }, + ].map(({ heading, value }) => ( +
+

{heading}

+

{value}

+
+ ))}
); diff --git a/packages/frontend/src/app/components/profile/index.tsx b/packages/frontend/src/app/components/profile/index.tsx index 1cdf695..a389fca 100644 --- a/packages/frontend/src/app/components/profile/index.tsx +++ b/packages/frontend/src/app/components/profile/index.tsx @@ -1,4 +1,4 @@ export { ProfileHero } from "./hero"; export { WalletDetails } from "./WalletDetails"; export { ChainSelector } from "./ChainSelector"; -export { MatchHistory } from "./MatchHistory"; +export { GameHistory } from "./GameHistory"; diff --git a/packages/frontend/src/app/games/rock-paper-scissors/components/EnemyScreen.tsx b/packages/frontend/src/app/games/rock-paper-scissors/components/EnemyScreen.tsx index 6c6e075..bb45615 100644 --- a/packages/frontend/src/app/games/rock-paper-scissors/components/EnemyScreen.tsx +++ b/packages/frontend/src/app/games/rock-paper-scissors/components/EnemyScreen.tsx @@ -12,7 +12,7 @@ export default function EnemyScreen({ gameState }: EnemyScreenProps) { const moveState = (gameState.state === "roundEnd" || gameState.state === "gameEnd") && gameState.winner_id - ? gameState.winner_id === gameState.player.user_id + ? gameState.winner_id === gameState.player.player_id ? "lose" : "win" : "idle"; diff --git a/packages/frontend/src/app/games/rock-paper-scissors/components/Game.tsx b/packages/frontend/src/app/games/rock-paper-scissors/components/Game.tsx index ee6db9f..8c39982 100644 --- a/packages/frontend/src/app/games/rock-paper-scissors/components/Game.tsx +++ b/packages/frontend/src/app/games/rock-paper-scissors/components/Game.tsx @@ -16,7 +16,7 @@ type Props = { type PlayerState = { currentMove: RockPaperScissors.Move; currentScore: number; - user_id: string; + player_id: string; }; export type GameState = | { state: "initial"; player: PlayerState } @@ -53,7 +53,7 @@ export type GameState = export default function Game({ tier }: Props) { const { user } = useWeb3AuthContext(); - const player_user_id = user!.data.user_id; + const player_user_id = user!.data.player_id; const reducer = ( gameState: GameState, @@ -61,12 +61,16 @@ export default function Game({ tier }: Props) { ): GameState => { switch (action.type) { case "player-joined": { - const { room_id, user_id } = action.payload; - if (user_id === player_user_id) { + const { room_id, player_id } = action.payload; + if (player_id === player_user_id) { return { ...gameState, state: "waiting", - player: { currentMove: "rock", currentScore: 0, user_id }, + player: { + currentMove: "rock", + currentScore: 0, + player_id, + }, room_id, }; } @@ -74,15 +78,15 @@ export default function Game({ tier }: Props) { ...gameState, state: "waiting", room_id, - enemy: { currentMove: "rock", currentScore: 0, user_id }, + enemy: { currentMove: "rock", currentScore: 0, player_id: player_id }, }; } // ? Possible the game start fires before player joined event case "game-start": { const { round, player1, player2 } = action.payload; - const player = player1.user_id === player_user_id ? player1 : player2; - const enemy = player1.user_id !== player_user_id ? player1 : player2; + const player = player1.player_id === player_user_id ? player1 : player2; + const enemy = player1.player_id !== player_user_id ? player1 : player2; if (gameState.state === "waiting") { return { @@ -92,12 +96,12 @@ export default function Game({ tier }: Props) { player: { currentMove: "rock", currentScore: player.currentScore, - user_id: player.user_id, + player_id: player.player_id, }, enemy: { currentMove: "rock", currentScore: enemy.currentScore, - user_id: enemy.user_id, + player_id: enemy.player_id, }, }; } @@ -106,8 +110,8 @@ export default function Game({ tier }: Props) { case "round-end": { const { round, player1, player2, winner_id } = action.payload; - const player = player1.user_id === player_user_id ? player1 : player2; - const enemy = player1.user_id !== player_user_id ? player1 : player2; + const player = player1.player_id === player_user_id ? player1 : player2; + const enemy = player1.player_id !== player_user_id ? player1 : player2; if ( gameState.state === "ongoingRound" && @@ -141,8 +145,8 @@ export default function Game({ tier }: Props) { case "game-end": { const { player1, player2, round, winner_id } = action.payload; - const player = player1.user_id === player_user_id ? player1 : player2; - const enemy = player1.user_id !== player_user_id ? player1 : player2; + const player = player1.player_id === player_user_id ? player1 : player2; + const enemy = player1.player_id !== player_user_id ? player1 : player2; if ( gameState.state === "ongoingRound" && player.currentMove && @@ -165,7 +169,7 @@ export default function Game({ tier }: Props) { }; const [gameState, dispatch] = useReducer(reducer, { state: "initial", - player: { currentMove: "rock", currentScore: 0, user_id: player_user_id }, + player: { currentMove: "rock", currentScore: 0, player_id: player_user_id }, }); const socketRef = useRef( @@ -223,7 +227,7 @@ export default function Game({ tier }: Props) { "join" satisfies RockPaperScissors.JoinEvent["type"], { season_id: "6dd7cc5f-45ab-42d8-84f9-9bc0a5ff2121", - user_id: player_user_id, + player_id: player_user_id, game_id: RockPaperScissors.gameId, tier_id: tier, } satisfies RockPaperScissors.JoinEvent["payload"] @@ -251,7 +255,7 @@ export default function Game({ tier }: Props) { {gameState.state === "roundEnd" && (

{gameState.winner_id - ? gameState.winner_id === gameState.player.user_id + ? gameState.winner_id === gameState.player.player_id ? "You win the round!" : "Enemy wins the round!" : "Draw!"} @@ -259,7 +263,7 @@ export default function Game({ tier }: Props) { )} {gameState.state === "gameEnd" && (

- {gameState.winner_id === gameState.player.user_id + {gameState.winner_id === gameState.player.player_id ? "You win!" : "Enemy wins!"}

diff --git a/packages/frontend/src/app/games/rock-paper-scissors/components/PlayerScreen.tsx b/packages/frontend/src/app/games/rock-paper-scissors/components/PlayerScreen.tsx index 5b30619..e619919 100644 --- a/packages/frontend/src/app/games/rock-paper-scissors/components/PlayerScreen.tsx +++ b/packages/frontend/src/app/games/rock-paper-scissors/components/PlayerScreen.tsx @@ -27,7 +27,7 @@ export default function PlayerScreen({ type: "move", payload: { room_id: gameState.room_id, - user_id: gameState.player.user_id, + player_id: gameState.player.player_id, move, }, }; @@ -43,9 +43,8 @@ export default function PlayerScreen({ const attestation = await signClient.attest({ game_id: RockPaperScissors.gameId, season_id: "6dd7cc5f-45ab-42d8-84f9-9bc0a5ff2121", - user_id: gameState.player.user_id, + player_id: gameState.player.player_id, tier_id: tier, - winner_id: gameState.player.user_id, }); console.log(attestation); }; @@ -55,7 +54,7 @@ export default function PlayerScreen({ const moveState = (gameState.state === "roundEnd" || gameState.state === "gameEnd") && gameState.winner_id - ? gameState.winner_id === gameState.player.user_id + ? gameState.winner_id === gameState.player.player_id ? "win" : "lose" : "idle"; @@ -123,7 +122,7 @@ export default function PlayerScreen({ )} {gameState.state === "gameEnd" && - player?.user_id === gameState.winner_id && ( + player?.player_id === gameState.winner_id && ( )} diff --git a/packages/frontend/src/app/leaderboard/page.tsx b/packages/frontend/src/app/leaderboard/page.tsx index 98e426d..9eb75ea 100644 --- a/packages/frontend/src/app/leaderboard/page.tsx +++ b/packages/frontend/src/app/leaderboard/page.tsx @@ -7,28 +7,29 @@ import { API_BASE_URL } from "@/utils/constants/api.constant"; import { Leaderboard } from "../components/leaderboard/leaderboard"; export default async function LeaderboardPage() { - const leaderboardRes = await fetch( - `${API_BASE_URL}/seasons/current/leaderboard`, - { - cache: "no-cache", - } - ); - const leaderboardResponse = (await leaderboardRes.json()) as ResponseWithData< - MappedLeaderboard[] - >; - const seasonsRes = await fetch(`${API_BASE_URL}/seasons`, { cache: "no-cache", }); const seasonsResponse = (await seasonsRes.json()) as ResponseWithData< MappedSeason[] >; + const seasons = seasonsResponse.success ? seasonsResponse.data : []; - const leaderboard = leaderboardResponse.success - ? leaderboardResponse.data - : []; + let leaderboard: MappedLeaderboard[] = []; - const seasons = seasonsResponse.success ? seasonsResponse.data : []; + if (seasons.length) { + const leaderboardRes = await fetch( + `${API_BASE_URL}/leaderboard/${seasons[0].season_id}`, + { + cache: "no-cache", + } + ); + const leaderboardResponse = + (await leaderboardRes.json()) as ResponseWithData; + if (leaderboardResponse.success) { + leaderboard = leaderboardResponse.data; + } + } return ; } diff --git a/packages/frontend/src/app/profile/page.tsx b/packages/frontend/src/app/profile/page.tsx index 8da8b76..48bd3d0 100644 --- a/packages/frontend/src/app/profile/page.tsx +++ b/packages/frontend/src/app/profile/page.tsx @@ -1,25 +1,62 @@ "use client"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { ProfileHero, WalletDetails, ChainSelector, - MatchHistory, + GameHistory, } from "../components/profile"; import { CustomChainConfig } from "@web3auth/base"; import { CHAINS } from "@/utils/constants/chain-config.constant"; import { web3auth } from "@/utils/service/web3auth.service"; +import { MappedPlayedGame, ResponseWithData } from "@/utils/types"; +import { useWeb3AuthContext } from "@/utils/context/web3auth.context"; +import { API_BASE_URL } from "@/utils/constants/api.constant"; const Profile: React.FC = () => { + const { user } = useWeb3AuthContext(); + + const [loading, setLoading] = useState(false); + const [playedGames, setPlayedGames] = useState([]); const [selectedChain, setSelectedChain] = useState( CHAINS.find( (chain: CustomChainConfig) => - // @ts-ignore + // @ts-expect-error chain.chainId === web3auth.walletAdapters.openlogin?.chainConfig.chainId )! ); + useEffect(() => { + (async () => { + try { + if (!user) { + return; + } + + setLoading(true); + + const gamesRes = await fetch( + `${API_BASE_URL}/played-games/${user.data.player_id}`, + { + cache: "no-cache", + } + ); + const gamesResponse = (await gamesRes.json()) as ResponseWithData< + MappedPlayedGame[] + >; + + if (gamesResponse.success) { + setPlayedGames(gamesResponse.data); + } + } catch (error) { + console.error(error); + } finally { + setLoading(false); + } + })(); + }, []); + return (
@@ -28,7 +65,18 @@ const Profile: React.FC = () => { selectedChain={selectedChain} setSelectedChain={setSelectedChain} /> - + + {loading ? ( +

+ Curating the leaderboard... +

+ ) : ( +
+ {playedGames.map((game) => ( + + ))} +
+ )}
); }; diff --git a/packages/frontend/src/app/wallet/components/ConnectModal.tsx b/packages/frontend/src/app/wallet/components/ConnectModal.tsx index 5ab3425..b6ea7a2 100644 --- a/packages/frontend/src/app/wallet/components/ConnectModal.tsx +++ b/packages/frontend/src/app/wallet/components/ConnectModal.tsx @@ -6,7 +6,7 @@ import { useWeb3AuthContext } from "@/utils/context/web3auth.context"; import { API_BASE_URL } from "@/utils/constants/api.constant"; import { getWalletAddress } from "@/utils/functions/ethers"; import { UserInfo } from "@web3auth/base"; -import { MappedUser, ResponseWithData } from "@/utils/types"; +import { MappedPlayer, ResponseWithData } from "@/utils/types"; export const ConnectModal: React.FC = () => { const [loggedIn, setLoggedIn] = useState(false); @@ -35,7 +35,7 @@ export const ConnectModal: React.FC = () => { user: Partial & { wallet_address: string } ) => { try { - const res = await fetch(`${API_BASE_URL}/users/auth`, { + const res = await fetch(`${API_BASE_URL}/players/auth`, { method: "POST", headers: { "Content-Type": "application/json", @@ -50,7 +50,7 @@ export const ConnectModal: React.FC = () => { }); const userResponse = (await res.json()) as ResponseWithData<{ - user: MappedUser; + user: MappedPlayer; token: string; }>; if (userResponse.success) return userResponse.data; diff --git a/packages/frontend/src/shared/PlayerThumb.tsx b/packages/frontend/src/shared/PlayerThumb.tsx index 06a03a2..57de7de 100644 --- a/packages/frontend/src/shared/PlayerThumb.tsx +++ b/packages/frontend/src/shared/PlayerThumb.tsx @@ -1,6 +1,6 @@ type Props = { - user_id: string; + player_id: string; }; -export default function PlayerThumb({ user_id }: Props) { - return
{user_id}
; +export default function PlayerThumb({ player_id }: Props) { + return
{player_id}
; } diff --git a/packages/frontend/src/shared/Sparkles.tsx b/packages/frontend/src/shared/Sparkles.tsx index 3de621a..b26d565 100644 --- a/packages/frontend/src/shared/Sparkles.tsx +++ b/packages/frontend/src/shared/Sparkles.tsx @@ -41,7 +41,6 @@ export const SparklesCore = (props: ParticlesProps) => { const particlesLoaded = async (container?: Container) => { if (container) { - console.log(container); controls.start({ opacity: 1, transition: { diff --git a/packages/frontend/src/utils/context/web3auth.context.tsx b/packages/frontend/src/utils/context/web3auth.context.tsx index ddf81f9..67971b2 100644 --- a/packages/frontend/src/utils/context/web3auth.context.tsx +++ b/packages/frontend/src/utils/context/web3auth.context.tsx @@ -2,9 +2,9 @@ "use client"; import { IProvider } from "@web3auth/base"; import React, { createContext, ReactNode, useContext, useState } from "react"; -import { MappedUser } from "../types"; +import { MappedPlayer } from "../types"; -export type AuthUser = { data: MappedUser; token: string }; +export type AuthUser = { data: MappedPlayer; token: string }; interface Web3AuthContextProps { provider: IProvider | null; diff --git a/packages/frontend/src/utils/service/sign-protocol.service.ts b/packages/frontend/src/utils/service/sign-protocol.service.ts index 3bf0c36..c32339c 100644 --- a/packages/frontend/src/utils/service/sign-protocol.service.ts +++ b/packages/frontend/src/utils/service/sign-protocol.service.ts @@ -23,9 +23,8 @@ export default class SignClient { data: [ { name: "game_id", type: "string" }, { name: "season_id", type: "string" }, - { name: "user_id", type: "string" }, + { name: "player_id", type: "string" }, { name: "tier_id", type: "string" }, - { name: "winner_id", type: "string" }, ], }); @@ -37,24 +36,27 @@ export default class SignClient { } } - async attest(data: { + async attest({ + game_id, + player_id, + season_id, + tier_id, + }: { game_id: string; season_id: string; - user_id: string; + player_id: string; tier_id: string; - winner_id: string; }): Promise { try { const response = await this.signClient.createAttestation({ schemaId: "0x92", data: { - game_id: data.game_id, - season_id: data.season_id, - user_id: data.user_id, - tier_id: data.tier_id, - winner_id: data.winner_id, + game_id, + season_id, + player_id, + tier_id, }, - indexingValue: data.game_id, + indexingValue: game_id, }); return response; diff --git a/packages/frontend/src/utils/types/database.types.ts b/packages/frontend/src/utils/types/database.types.ts index cb914ce..90b45b6 100644 --- a/packages/frontend/src/utils/types/database.types.ts +++ b/packages/frontend/src/utils/types/database.types.ts @@ -123,28 +123,28 @@ export type Database = { columns: ["player_1_id"] isOneToOne: false referencedRelation: "leaderboard" - referencedColumns: ["user_id"] + referencedColumns: ["player_id"] }, { foreignKeyName: "played_games_player_1_id_fkey" columns: ["player_1_id"] isOneToOne: false - referencedRelation: "users" - referencedColumns: ["user_id"] + referencedRelation: "players" + referencedColumns: ["player_id"] }, { foreignKeyName: "played_games_player_2_id_fkey" columns: ["player_2_id"] isOneToOne: false referencedRelation: "leaderboard" - referencedColumns: ["user_id"] + referencedColumns: ["player_id"] }, { foreignKeyName: "played_games_player_2_id_fkey" columns: ["player_2_id"] isOneToOne: false - referencedRelation: "users" - referencedColumns: ["user_id"] + referencedRelation: "players" + referencedColumns: ["player_id"] }, { foreignKeyName: "played_games_season_id_fkey" @@ -158,21 +158,49 @@ export type Database = { columns: ["winner_id"] isOneToOne: false referencedRelation: "leaderboard" - referencedColumns: ["user_id"] + referencedColumns: ["player_id"] }, { foreignKeyName: "played_games_winner_id_fkey" columns: ["winner_id"] isOneToOne: false - referencedRelation: "users" - referencedColumns: ["user_id"] + referencedRelation: "players" + referencedColumns: ["player_id"] }, ] } + players: { + Row: { + created_at: string + email_id: string + name: string + player_id: string + profile_photo: string + wallet_address: string + } + Insert: { + created_at?: string + email_id: string + name: string + player_id?: string + profile_photo: string + wallet_address: string + } + Update: { + created_at?: string + email_id?: string + name?: string + player_id?: string + profile_photo?: string + wallet_address?: string + } + Relationships: [] + } seasons: { Row: { created_at: string ended_on: string + name: string reward_pool_usd: number season_id: string started_on: string @@ -180,6 +208,7 @@ export type Database = { Insert: { created_at?: string ended_on: string + name?: string reward_pool_usd?: number season_id?: string started_on: string @@ -187,52 +216,58 @@ export type Database = { Update: { created_at?: string ended_on?: string + name?: string reward_pool_usd?: number season_id?: string started_on?: string } Relationships: [] } - users: { + } + Views: { + game_history: { Row: { - created_at: string - email_id: string - name: string - profile_photo: string - user_id: string - wallet_address: string - } - Insert: { - created_at?: string - email_id: string - name: string - profile_photo: string - user_id?: string - wallet_address: string - } - Update: { - created_at?: string - email_id?: string - name?: string - profile_photo?: string - user_id?: string - wallet_address?: string + created_at: string | null + game_id: string | null + game_tier_id: string | null + played_game_id: string | null + player_1: Json | null + player_2: Json | null + season_id: string | null } - Relationships: [] + Relationships: [ + { + foreignKeyName: "played_games_game_id_fkey" + columns: ["game_id"] + isOneToOne: false + referencedRelation: "games" + referencedColumns: ["game_id"] + }, + { + foreignKeyName: "played_games_game_tier_id_fkey" + columns: ["game_tier_id"] + isOneToOne: false + referencedRelation: "game_tiers" + referencedColumns: ["tier_id"] + }, + { + foreignKeyName: "played_games_season_id_fkey" + columns: ["season_id"] + isOneToOne: false + referencedRelation: "seasons" + referencedColumns: ["season_id"] + }, + ] } - } - Views: { leaderboard: { Row: { - created_at: string | null - email_id: string | null games_played: number | null games_won: number | null name: string | null + player_id: string | null profile_photo: string | null season_id: string | null total_points: number | null - user_id: string | null wallet_address: string | null } Relationships: [ diff --git a/packages/frontend/src/utils/types/index.ts b/packages/frontend/src/utils/types/index.ts index 1b4b2cf..4846514 100644 --- a/packages/frontend/src/utils/types/index.ts +++ b/packages/frontend/src/utils/types/index.ts @@ -5,7 +5,7 @@ export type MappedGame = Database["public"]["Tables"]["games"]["Row"]; export type MappedPlayedGame = Database["public"]["Tables"]["played_games"]["Row"]; export type MappedSeason = Database["public"]["Tables"]["seasons"]["Row"]; -export type MappedUser = Database["public"]["Tables"]["users"]["Row"]; +export type MappedPlayer = Database["public"]["Tables"]["players"]["Row"]; export type MappedLeaderboard = Database["public"]["Views"]["leaderboard"]["Row"]; @@ -16,5 +16,5 @@ export type ResponseWithData = } | { success: false; - data: unknown; + message: string; };