diff --git a/projects/backend/src/common/config.ts b/projects/backend/src/common/config.ts deleted file mode 100644 index aeca6ca..0000000 --- a/projects/backend/src/common/config.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const Config = { - mongoUri: process.env.MONGO_URI, - apiUrl: process.env.API_URL || "https://ssr.fascinated.cc/api", - trackedPlayerWebhook: process.env.TRACKED_PLAYERS_WEBHOOK, - numberOneWebhook: process.env.NUMBER_ONE_WEBHOOK, -}; diff --git a/projects/backend/src/index.ts b/projects/backend/src/index.ts index 44d5587..4dd20a0 100644 --- a/projects/backend/src/index.ts +++ b/projects/backend/src/index.ts @@ -10,7 +10,6 @@ import { etag } from "@bogeychan/elysia-etag"; import AppController from "./controller/app.controller"; import * as dotenv from "@dotenvx/dotenvx"; import mongoose from "mongoose"; -import { Config } from "./common/config"; import { setLogLevel } from "@typegoose/typegoose"; import PlayerController from "./controller/player.controller"; import { PlayerService } from "./service/player.service"; @@ -22,6 +21,7 @@ import { connectScoreSaberWebSocket } from "@ssr/common/websocket/scoresaber-web import ImageController from "./controller/image.controller"; import ReplayController from "./controller/replay.controller"; import { ScoreService } from "./service/score.service"; +import { Config } from "@ssr/common/config"; // Load .env file dotenv.config({ diff --git a/projects/backend/src/service/image.service.tsx b/projects/backend/src/service/image.service.tsx index 9249386..83b63e9 100644 --- a/projects/backend/src/service/image.service.tsx +++ b/projects/backend/src/service/image.service.tsx @@ -8,9 +8,9 @@ import { GlobeIcon } from "../../components/globe-icon"; import NodeCache from "node-cache"; import ScoreSaberLeaderboardToken from "@ssr/common/types/token/scoresaber/score-saber-leaderboard-token"; import ScoreSaberPlayer, { getScoreSaberPlayerFromToken } from "@ssr/common/types/player/impl/scoresaber-player"; -import { Config } from "../common/config"; import { Jimp } from "jimp"; import { extractColors } from "extract-colors"; +import { Config } from "@ssr/common/config"; const cache = new NodeCache({ stdTTL: 60 * 60, checkperiod: 120 }); const imageOptions = { width: 1200, height: 630 }; diff --git a/projects/backend/src/service/player.service.ts b/projects/backend/src/service/player.service.ts index 2e0d57e..dc442b2 100644 --- a/projects/backend/src/service/player.service.ts +++ b/projects/backend/src/service/player.service.ts @@ -8,9 +8,9 @@ import ScoreSaberPlayerScoreToken from "@ssr/common/types/token/scoresaber/score // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import { MessageBuilder, Webhook } from "discord-webhook-node"; -import { Config } from "../common/config"; import { formatPp } from "@ssr/common/utils/number-utils"; import { isProduction } from "@ssr/common/utils/utils"; +import { Config } from "@ssr/common/config"; export class PlayerService { /** diff --git a/projects/backend/src/service/score.service.ts b/projects/backend/src/service/score.service.ts index 34e2442..1043b78 100644 --- a/projects/backend/src/service/score.service.ts +++ b/projects/backend/src/service/score.service.ts @@ -2,9 +2,9 @@ import ScoreSaberPlayerScoreToken from "@ssr/common/types/token/scoresaber/score // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import { MessageBuilder, Webhook } from "discord-webhook-node"; -import { Config } from "../common/config"; import { formatPp } from "@ssr/common/utils/number-utils"; import { isProduction } from "@ssr/common/utils/utils"; +import { Config } from "@ssr/common/config"; export class ScoreService { /** diff --git a/projects/common/src/config.ts b/projects/common/src/config.ts new file mode 100644 index 0000000..02a1b56 --- /dev/null +++ b/projects/common/src/config.ts @@ -0,0 +1,14 @@ +export const Config = { + /** + * All projects + */ + websiteUrl: process.env.NEXT_PUBLIC_SITE_URL || "https://ssr.fascinated.cc", + apiUrl: process.env.NEXT_PUBLIC_SITE_API || "https://ssr.fascinated.cc/api", + + /** + * Backend + */ + trackedPlayerWebhook: process.env.TRACKED_PLAYERS_WEBHOOK, + numberOneWebhook: process.env.NUMBER_ONE_WEBHOOK, + mongoUri: process.env.MONGO_URI, +} as const; diff --git a/projects/common/src/types/player/impl/scoresaber-player.ts b/projects/common/src/types/player/impl/scoresaber-player.ts index 5ac4169..fd28cb0 100644 --- a/projects/common/src/types/player/impl/scoresaber-player.ts +++ b/projects/common/src/types/player/impl/scoresaber-player.ts @@ -4,6 +4,7 @@ import { PlayerHistory } from "../player-history"; import ScoreSaberPlayerToken from "../../token/scoresaber/score-saber-player-token"; import { formatDateMinimal, getDaysAgoDate, getMidnightAlignedDate } from "../../../utils/time-utils"; import { getPageFromRank } from "../../../utils/utils"; +import { Config } from "../../../config"; /** * A ScoreSaber player. @@ -75,12 +76,10 @@ export default interface ScoreSaberPlayer extends Player { * Gets the ScoreSaber Player from an {@link ScoreSaberPlayerToken}. * * @param token the player token - * @param apiUrl the api url for SSR * @param playerIdCookie the id of the claimed player */ export async function getScoreSaberPlayerFromToken( token: ScoreSaberPlayerToken, - apiUrl: string, playerIdCookie?: string ): Promise { const bio: ScoreSaberBio = { @@ -105,7 +104,7 @@ export async function getScoreSaberPlayerFromToken( .get<{ statistics: { [key: string]: PlayerHistory }; }>( - `${apiUrl}/player/history/${token.id}${playerIdCookie && playerIdCookie == token.id ? "?createIfMissing=true" : ""}` + `${Config.apiUrl}/player/history/${token.id}${playerIdCookie && playerIdCookie == token.id ? "?createIfMissing=true" : ""}` ) .json(); if (history) { diff --git a/projects/common/src/utils/player-utils.ts b/projects/common/src/utils/player-utils.ts index 32d1b6e..8b13d0a 100644 --- a/projects/common/src/utils/player-utils.ts +++ b/projects/common/src/utils/player-utils.ts @@ -1,4 +1,6 @@ import { PlayerHistory } from "../types/player/player-history"; +import { kyFetch } from "./utils"; +import { Config } from "../config"; /** * Sorts the player history based on date, @@ -11,3 +13,12 @@ export function sortPlayerHistory(history: Map) { (a, b) => Date.parse(b[0]) - Date.parse(a[0]) // Sort in descending order ); } + +/** + * Ensure the player is being tracked. + * + * @param id the player id + */ +export async function trackPlayer(id: string) { + await kyFetch(`${Config.apiUrl}/player/history/${id}?createIfMissing=true`); +} diff --git a/projects/website/config.ts b/projects/website/config.ts deleted file mode 100644 index b46cc87..0000000 --- a/projects/website/config.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const config = { - siteUrl: process.env.NEXT_PUBLIC_SITE_URL || "https://ssr.fascinated.cc", - siteApi: process.env.NEXT_PUBLIC_SITE_API || "https://ssr.fascinated.cc/api", -}; diff --git a/projects/website/src/app/(pages)/leaderboard/[...slug]/page.tsx b/projects/website/src/app/(pages)/leaderboard/[...slug]/page.tsx index 5922992..a32ba67 100644 --- a/projects/website/src/app/(pages)/leaderboard/[...slug]/page.tsx +++ b/projects/website/src/app/(pages)/leaderboard/[...slug]/page.tsx @@ -7,7 +7,7 @@ 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"; -import { config } from "../../../../../config"; +import { Config } from "@ssr/common/config"; const UNKNOWN_LEADERBOARD = { title: "ScoreSaber Reloaded - Unknown Leaderboard", @@ -84,7 +84,7 @@ export async function generateMetadata(props: Props): Promise { description: `View the scores for ${leaderboard.songName} by ${leaderboard.songAuthorName}!`, images: [ { - url: `${config.siteApi}/image/leaderboard/${leaderboard.id}`, + url: `${Config.apiUrl}/image/leaderboard/${leaderboard.id}`, }, ], }, diff --git a/projects/website/src/app/(pages)/page.tsx b/projects/website/src/app/(pages)/page.tsx index d53bea8..23d7597 100644 --- a/projects/website/src/app/(pages)/page.tsx +++ b/projects/website/src/app/(pages)/page.tsx @@ -1,14 +1,14 @@ import { Button } from "@/components/ui/button"; import Link from "next/link"; -import { config } from "../../../config"; import { AppStatistics } from "@ssr/common/types/backend/app-statistics"; import Statistic from "@/components/home/statistic"; import { kyFetch } from "@ssr/common/utils/utils"; +import { Config } from "@ssr/common/config"; export const dynamic = "force-dynamic"; // Always generate the page on load export default async function HomePage() { - const statistics = await kyFetch(config.siteApi + "/statistics"); + const statistics = await kyFetch(Config.apiUrl + "/statistics"); return (
diff --git a/projects/website/src/app/(pages)/player/[...slug]/page.tsx b/projects/website/src/app/(pages)/player/[...slug]/page.tsx index d24821c..86161ca 100644 --- a/projects/website/src/app/(pages)/player/[...slug]/page.tsx +++ b/projects/website/src/app/(pages)/player/[...slug]/page.tsx @@ -7,9 +7,9 @@ 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 ScoreSaberPlayer, { getScoreSaberPlayerFromToken } from "@ssr/common/types/player/impl/scoresaber-player"; -import { config } from "../../../../../config"; import NodeCache from "node-cache"; import { getCookieValue } from "@ssr/common/utils/cookie-utils"; +import { Config } from "@ssr/common/config"; const UNKNOWN_PLAYER = { title: "ScoreSaber Reloaded - Unknown Player", @@ -55,8 +55,7 @@ const getPlayerData = async ({ params }: Props, fetchScores: boolean = true): Pr } const playerToken = await scoresaberService.lookupPlayer(id); - const player = - playerToken && (await getScoreSaberPlayerFromToken(playerToken, config.siteApi, await getCookieValue("playerId"))); + const player = playerToken && (await getScoreSaberPlayerFromToken(playerToken, await getCookieValue("playerId"))); let scores: ScoreSaberPlayerScoresPageToken | undefined; if (fetchScores) { scores = await scoresaberService.lookupPlayerScores({ @@ -98,7 +97,7 @@ export async function generateMetadata(props: Props): Promise { description: `Click here to view the scores for ${player.name}!`, images: [ { - url: `${config.siteApi}/image/player/${player.id}`, + url: `${Config.apiUrl}/image/player/${player.id}`, }, ], }, diff --git a/projects/website/src/common/database/database.ts b/projects/website/src/common/database/database.ts index a5e34d9..2f630ad 100644 --- a/projects/website/src/common/database/database.ts +++ b/projects/website/src/common/database/database.ts @@ -5,6 +5,7 @@ import { Friend } from "@/common/database/types/friends"; import ScoreSaberPlayerToken from "@ssr/common/types/token/scoresaber/score-saber-player-token"; import { scoresaberService } from "@ssr/common/service/impl/scoresaber"; import { setCookieValue } from "@ssr/common/utils/cookie-utils"; +import { trackPlayer } from "@ssr/common/utils/player-utils"; const SETTINGS_ID = "SSR"; // DO NOT CHANGE @@ -114,7 +115,12 @@ export default class Database extends Dexie { const friends = await this.friends.toArray(); const players = await Promise.all( friends.map(async ({ id }) => { - return await scoresaberService.lookupPlayer(id, true); + const token = await scoresaberService.lookupPlayer(id, true); + if (token == undefined) { + return undefined; + } + await trackPlayer(id); // Track the player + return token; }) ); return players.filter(player => player !== undefined) as ScoreSaberPlayerToken[]; diff --git a/projects/website/src/common/image-utils.ts b/projects/website/src/common/image-utils.ts index b07329d..0d47863 100644 --- a/projects/website/src/common/image-utils.ts +++ b/projects/website/src/common/image-utils.ts @@ -1,6 +1,6 @@ -import { config } from "../../config"; import ky from "ky"; import { Colors } from "@/common/colors"; +import { Config } from "@ssr/common/config"; /** * Proxies all non-localhost images to make them load faster. @@ -9,7 +9,7 @@ import { Colors } from "@/common/colors"; * @returns the new image url */ export function getImageUrl(originalUrl: string) { - return `${!config.siteUrl.includes("localhost") ? "https://img.fascinated.cc/upload/q_70/" : ""}${originalUrl}`; + return `${!Config.websiteUrl.includes("localhost") ? "https://img.fascinated.cc/upload/q_70/" : ""}${originalUrl}`; } /** @@ -20,7 +20,7 @@ export function getImageUrl(originalUrl: string) { */ export const getAverageColor = async (src: string) => { try { - return await ky.get<{ color: string }>(`${config.siteApi}/image/averagecolor/${encodeURIComponent(src)}`).json(); + return await ky.get<{ color: string }>(`${Config.apiUrl}/image/averagecolor/${encodeURIComponent(src)}`).json(); } catch { return { color: Colors.primary, diff --git a/projects/website/src/components/api/api-health.tsx b/projects/website/src/components/api/api-health.tsx index c84bc92..15f59ec 100644 --- a/projects/website/src/components/api/api-health.tsx +++ b/projects/website/src/components/api/api-health.tsx @@ -3,9 +3,9 @@ import { useQuery } from "@tanstack/react-query"; import { useEffect, useRef, useState } from "react"; import { getApiHealth } from "@ssr/common/utils/api-utils"; -import { config } from "../../../config"; import { useToast } from "@/hooks/use-toast"; import { useIsFirstRender } from "@uidotdev/usehooks"; +import { Config } from "@ssr/common/config"; export function ApiHealth() { const { toast } = useToast(); @@ -16,7 +16,7 @@ export function ApiHealth() { useQuery({ queryKey: ["api-health"], queryFn: async () => { - const status = (await getApiHealth(config.siteApi + "/health")).online; + const status = (await getApiHealth(Config.apiUrl + "/health")).online; setOnline(status); return status; }, diff --git a/projects/website/src/components/background-cover.tsx b/projects/website/src/components/background-cover.tsx index 87591fa..b28736f 100644 --- a/projects/website/src/components/background-cover.tsx +++ b/projects/website/src/components/background-cover.tsx @@ -1,9 +1,9 @@ "use client"; import { useLiveQuery } from "dexie-react-hooks"; -import { config } from "../../config"; import { getImageUrl } from "@/common/image-utils"; import useDatabase from "../hooks/use-database"; +import { Config } from "@ssr/common/config"; export default function BackgroundCover() { const database = useDatabase(); @@ -22,7 +22,7 @@ export default function BackgroundCover() { backgroundCover = backgroundCover.substring(1); } if (prependWebsiteUrl) { - backgroundCover = config.siteUrl + "/" + backgroundCover; + backgroundCover = Config.websiteUrl + "/" + backgroundCover; } // Static background color diff --git a/projects/website/src/components/friend/add-friend.tsx b/projects/website/src/components/friend/add-friend.tsx index a0e08d0..0cc4d9b 100644 --- a/projects/website/src/components/friend/add-friend.tsx +++ b/projects/website/src/components/friend/add-friend.tsx @@ -7,6 +7,7 @@ import Tooltip from "../tooltip"; import { Button } from "../ui/button"; import { PersonIcon } from "@radix-ui/react-icons"; import ScoreSaberPlayer from "@ssr/common/types/player/impl/scoresaber-player"; +import { trackPlayer } from "@ssr/common/utils/player-utils"; type Props = { /** @@ -27,6 +28,7 @@ export default function AddFriend({ player }: Props) { * Adds this player as a friend */ async function addFriend() { + await trackPlayer(id); await database.addFriend(id); toast({ title: "Friend Added", diff --git a/projects/website/src/components/player/player-data.tsx b/projects/website/src/components/player/player-data.tsx index 293193b..29fda09 100644 --- a/projects/website/src/components/player/player-data.tsx +++ b/projects/website/src/components/player/player-data.tsx @@ -14,7 +14,6 @@ import ScoreSaberPlayer, { getScoreSaberPlayerFromToken } from "@ssr/common/type import ScoreSaberPlayerScoresPageToken from "@ssr/common/types/token/scoresaber/score-saber-player-scores-page-token"; import { ScoreSort } from "@ssr/common/types/score/score-sort"; import { scoresaberService } from "@ssr/common/service/impl/scoresaber"; -import { config } from "../../../config"; import useDatabase from "@/hooks/use-database"; import { useLiveQuery } from "dexie-react-hooks"; @@ -40,16 +39,17 @@ export default function PlayerData({ const isMiniRankingsVisible = useIsVisible(miniRankingsRef); const database = useDatabase(); const settings = useLiveQuery(() => database.getSettings()); + const isFriend = useLiveQuery(() => database.isFriend(initialPlayerData.id)); let player = initialPlayerData; const { data, isLoading, isError } = useQuery({ - queryKey: ["playerData", player.id, settings?.playerId], + queryKey: ["playerData", player.id, settings?.playerId, isFriend], queryFn: async (): Promise => { const playerResponse = await scoresaberService.lookupPlayer(player.id); if (playerResponse == undefined) { return undefined; } - return await getScoreSaberPlayerFromToken(playerResponse, config.siteApi, settings?.playerId); + return await getScoreSaberPlayerFromToken(playerResponse, settings?.playerId); }, refetchInterval: REFRESH_INTERVAL, refetchIntervalInBackground: false, diff --git a/projects/website/src/components/player/player-tracked-status.tsx b/projects/website/src/components/player/player-tracked-status.tsx index 1cba40c..5854861 100644 --- a/projects/website/src/components/player/player-tracked-status.tsx +++ b/projects/website/src/components/player/player-tracked-status.tsx @@ -2,12 +2,12 @@ import { useQuery } from "@tanstack/react-query"; import ky from "ky"; -import { config } from "../../../config"; import Tooltip from "@/components/tooltip"; import { InformationCircleIcon } from "@heroicons/react/16/solid"; import { formatNumberWithCommas } from "@ssr/common/utils/number-utils"; import { PlayerTrackedSince } from "@ssr/common/types/player/player-tracked-since"; import ScoreSaberPlayer from "@ssr/common/types/player/impl/scoresaber-player"; +import { Config } from "@ssr/common/config"; type Props = { player: ScoreSaberPlayer; @@ -16,7 +16,7 @@ type Props = { export default function PlayerTrackedStatus({ player }: Props) { const { data, isLoading, isError } = useQuery({ queryKey: ["playerIsBeingTracked", player.id], - queryFn: () => ky.get(`${config.siteApi}/player/tracked/${player.id}`).json(), + queryFn: () => ky.get(`${Config.apiUrl}/player/tracked/${player.id}`).json(), }); if (isLoading || isError || !data?.tracked) {