diff --git a/bun.lockb b/bun.lockb index c31ceb1..13feb71 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/projects/website/package.json b/projects/website/package.json index 9f0b0b0..f6ba20c 100644 --- a/projects/website/package.json +++ b/projects/website/package.json @@ -9,7 +9,6 @@ "lint": "next lint" }, "dependencies": { - "@ssr/common": "workspace:*", "@formkit/tempo": "^0.1.2", "@heroicons/react": "^2.1.5", "@hookform/resolvers": "^3.9.0", @@ -21,6 +20,7 @@ "@radix-ui/react-toast": "^1.2.1", "@radix-ui/react-tooltip": "^1.1.2", "@sentry/nextjs": "8", + "@ssr/common": "workspace:*", "@tanstack/react-query": "^5.55.4", "@uidotdev/usehooks": "^2.4.1", "chart.js": "^4.4.4", @@ -36,6 +36,7 @@ "next": "15.0.0-rc.0", "next-build-id": "^3.0.0", "next-themes": "^0.3.0", + "node-cache": "^5.1.2", "react": "19.0.0-rc-d5bba18b-20241009", "react-chartjs-2": "^5.2.0", "react-dom": "19.0.0-rc-d5bba18b-20241009", diff --git a/projects/website/src/app/(pages)/leaderboard/[...slug]/page.tsx b/projects/website/src/app/(pages)/leaderboard/[...slug]/page.tsx index 3c2d7cb..03fedda 100644 --- a/projects/website/src/app/(pages)/leaderboard/[...slug]/page.tsx +++ b/projects/website/src/app/(pages)/leaderboard/[...slug]/page.tsx @@ -2,10 +2,11 @@ import { Metadata, Viewport } from "next"; import { redirect } from "next/navigation"; import { Colors } from "@/common/colors"; import { getAverageColor } from "@/common/image-utils"; -import { cache } from "react"; import { LeaderboardData } from "@/components/leaderboard/leaderboard-data"; import { scoresaberService } from "@ssr/common/service/impl/scoresaber"; import ScoreSaberLeaderboardScoresPageToken from "@ssr/common/types/token/scoresaber/score-saber-leaderboard-scores-page-token"; +import NodeCache from "node-cache"; +import ScoreSaberLeaderboardToken from "@ssr/common/types/token/scoresaber/score-saber-leaderboard-token"; const UNKNOWN_LEADERBOARD = { title: "ScoreSaber Reloaded - Unknown Leaderboard", @@ -21,6 +22,14 @@ type Props = { }>; }; +type LeaderboardData = { + leaderboard: ScoreSaberLeaderboardToken | undefined; + scores: ScoreSaberLeaderboardScoresPageToken | undefined; + page: number; +}; + +const leaderboardCache = new NodeCache({ stdTTL: 60, checkperiod: 120 }); + /** * Gets the leaderboard data and scores * @@ -28,23 +37,30 @@ type Props = { * @param fetchScores whether to fetch the scores * @returns the leaderboard data and scores */ -const getLeaderboardData = cache(async ({ params }: Props, fetchScores: boolean = true) => { +const getLeaderboardData = async ({ params }: Props, fetchScores: boolean = true) => { const { slug } = await params; const id = slug[0]; // The leaderboard id const page = parseInt(slug[1]) || 1; // The page number + const cacheId = `${id}-${page}`; + if (leaderboardCache.has(cacheId)) { + return leaderboardCache.get(cacheId) as LeaderboardData; + } + const leaderboard = await scoresaberService.lookupLeaderboard(id); let scores: ScoreSaberLeaderboardScoresPageToken | undefined; if (fetchScores) { scores = await scoresaberService.lookupLeaderboardScores(id + "", page); } - return { + const leaderboardData = { page: page, leaderboard: leaderboard, scores: scores, }; -}); + leaderboardCache.set(cacheId, leaderboardData); + return leaderboardData; +}; export async function generateMetadata(props: Props): Promise { const { leaderboard } = await getLeaderboardData(props, false); diff --git a/projects/website/src/app/(pages)/player/[...slug]/page.tsx b/projects/website/src/app/(pages)/player/[...slug]/page.tsx index 731837b..f19057c 100644 --- a/projects/website/src/app/(pages)/player/[...slug]/page.tsx +++ b/projects/website/src/app/(pages)/player/[...slug]/page.tsx @@ -5,13 +5,13 @@ import { Metadata, Viewport } from "next"; import { redirect } from "next/navigation"; import { Colors } from "@/common/colors"; import { getAverageColor } from "@/common/image-utils"; -import { cache } from "react"; import { ScoreSort } from "@ssr/common/types/score/score-sort"; import { scoresaberService } from "@ssr/common/service/impl/scoresaber"; import ScoreSaberPlayerScoresPageToken from "@ssr/common/types/token/scoresaber/score-saber-player-scores-page-token"; -import { getScoreSaberPlayerFromToken } from "@ssr/common/types/player/impl/scoresaber-player"; +import ScoreSaberPlayer, { getScoreSaberPlayerFromToken } from "@ssr/common/types/player/impl/scoresaber-player"; import { config } from "../../../../../config"; import { cookies } from "next/headers"; +import NodeCache from "node-cache"; const UNKNOWN_PLAYER = { title: "ScoreSaber Reloaded - Unknown Player", @@ -27,6 +27,16 @@ type Props = { }>; }; +type PlayerData = { + player: ScoreSaberPlayer | undefined; + scores: ScoreSaberPlayerScoresPageToken | undefined; + sort: ScoreSort; + page: number; + search: string; +}; + +const playerCache = new NodeCache({ stdTTL: 60, checkperiod: 120 }); + /** * Gets the player data and scores * @@ -34,13 +44,18 @@ type Props = { * @param fetchScores whether to fetch the scores * @returns the player data and scores */ -const getPlayerData = cache(async ({ params }: Props, fetchScores: boolean = true) => { +const getPlayerData = async ({ params }: Props, fetchScores: boolean = true): Promise => { const { slug } = await params; const id = slug[0]; // The players id const sort: ScoreSort = (slug[1] as ScoreSort) || "recent"; // The sorting method const page = parseInt(slug[2]) || 1; // The page number const search = (slug[3] as string) || ""; // The search query + const cacheId = `${id}-${sort}-${page}-${search}`; + if (playerCache.has(cacheId)) { + return playerCache.get(cacheId) as PlayerData; + } + const playerToken = await scoresaberService.lookupPlayer(id); const player = playerToken && (await getScoreSaberPlayerFromToken(playerToken, config.siteApi, cookies().get("playerId")?.value)); @@ -54,14 +69,16 @@ const getPlayerData = cache(async ({ params }: Props, fetchScores: boolean = tru }); } - return { + const playerData = { sort: sort, page: page, search: search, player: player, scores: scores, }; -}); + playerCache.set(cacheId, playerData); + return playerData; +}; export async function generateMetadata(props: Props): Promise { const { player } = await getPlayerData(props, false); diff --git a/projects/website/src/common/image-utils.ts b/projects/website/src/common/image-utils.ts index ecb4c52..287a030 100644 --- a/projects/website/src/common/image-utils.ts +++ b/projects/website/src/common/image-utils.ts @@ -17,8 +17,8 @@ export function getImageUrl(originalUrl: string) { * @param src the image url * @returns the average color */ -export const getAverageColor = cache(async (src: string) => { +export const getAverageColor = async (src: string) => { return { hex: "#fff", }; -}); +};