move score page fetching to the backend
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { PlayerModel } from "../model/player";
|
||||
import { PlayerModel } from "@ssr/common/model/player";
|
||||
import { AppStatistics } from "@ssr/common/types/backend/app-statistics";
|
||||
|
||||
export class AppService {
|
||||
|
30
projects/backend/src/service/beatsaver.service.ts
Normal file
30
projects/backend/src/service/beatsaver.service.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { beatsaverService } from "@ssr/common/service/impl/beatsaver";
|
||||
import { BeatSaverMap, BeatSaverMapModel } from "@ssr/common/model/beatsaver/beatsaver-map";
|
||||
|
||||
export default class BeatSaverService {
|
||||
/**
|
||||
* Gets a map by its hash.
|
||||
*
|
||||
* @param hash the hash of the map
|
||||
* @returns the beatsaver map
|
||||
*/
|
||||
public static async getMap(hash: string): Promise<BeatSaverMap | undefined> {
|
||||
let map = await BeatSaverMapModel.findById(hash);
|
||||
if (map != undefined) {
|
||||
return map.toObject() as BeatSaverMap;
|
||||
}
|
||||
|
||||
const token = await beatsaverService.lookupMap(hash);
|
||||
if (token == undefined) {
|
||||
return undefined;
|
||||
}
|
||||
map = await BeatSaverMapModel.create({
|
||||
_id: hash,
|
||||
bsr: token.id,
|
||||
author: {
|
||||
id: token.uploader.id,
|
||||
},
|
||||
});
|
||||
return map.toObject() as BeatSaverMap;
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ import { StarIcon } from "../../components/star-icon";
|
||||
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 ScoreSaberPlayer, { getScoreSaberPlayerFromToken } from "@ssr/common/player/impl/scoresaber-player";
|
||||
import { Jimp } from "jimp";
|
||||
import { extractColors } from "extract-colors";
|
||||
import { Config } from "@ssr/common/config";
|
||||
|
78
projects/backend/src/service/leaderboard.service.ts
Normal file
78
projects/backend/src/service/leaderboard.service.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import { Leaderboards } from "@ssr/common/leaderboard";
|
||||
import { scoresaberService } from "@ssr/common/service/impl/scoresaber";
|
||||
import { SSRCache } from "@ssr/common/cache";
|
||||
import { LeaderboardResponse } from "@ssr/common/response/leaderboard-response";
|
||||
import Leaderboard from "@ssr/common/leaderboard/leaderboard";
|
||||
import ScoreSaberLeaderboardToken from "@ssr/common/types/token/scoresaber/score-saber-leaderboard-token";
|
||||
import { NotFoundError } from "elysia";
|
||||
import { getScoreSaberLeaderboardFromToken } from "@ssr/common/leaderboard/impl/scoresaber-leaderboard";
|
||||
import BeatSaverService from "./beatsaver.service";
|
||||
import { BeatSaverMap } from "@ssr/common/model/beatsaver/beatsaver-map";
|
||||
|
||||
const leaderboardCache = new SSRCache({
|
||||
ttl: 1000 * 60 * 60 * 24,
|
||||
});
|
||||
|
||||
export default class LeaderboardService {
|
||||
/**
|
||||
* Gets the leaderboard.
|
||||
*
|
||||
* @param leaderboard the leaderboard
|
||||
* @param id the id
|
||||
*/
|
||||
private static async getLeaderboardToken<T>(leaderboard: Leaderboards, id: string): Promise<T | undefined> {
|
||||
const cacheKey = `${leaderboard}-${id}`;
|
||||
if (leaderboardCache.has(cacheKey)) {
|
||||
return leaderboardCache.get(cacheKey) as T;
|
||||
}
|
||||
|
||||
switch (leaderboard) {
|
||||
case "scoresaber": {
|
||||
const leaderboard = (await scoresaberService.lookupLeaderboard(id)) as T;
|
||||
leaderboardCache.set(cacheKey, leaderboard);
|
||||
return leaderboard;
|
||||
}
|
||||
default: {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a leaderboard.
|
||||
*
|
||||
* @param leaderboardName the leaderboard to get
|
||||
* @param id the players id
|
||||
* @returns the scores
|
||||
*/
|
||||
public static async getLeaderboard(
|
||||
leaderboardName: Leaderboards,
|
||||
id: string
|
||||
): Promise<LeaderboardResponse<Leaderboard>> {
|
||||
let leaderboard: Leaderboard | undefined;
|
||||
let beatSaverMap: BeatSaverMap | undefined;
|
||||
|
||||
switch (leaderboardName) {
|
||||
case "scoresaber": {
|
||||
const leaderboardToken = await LeaderboardService.getLeaderboardToken<ScoreSaberLeaderboardToken>(
|
||||
leaderboardName,
|
||||
id
|
||||
);
|
||||
if (leaderboardToken == undefined) {
|
||||
throw new NotFoundError(`Leaderboard not found for "${id}"`);
|
||||
}
|
||||
leaderboard = getScoreSaberLeaderboardFromToken(leaderboardToken);
|
||||
beatSaverMap = await BeatSaverService.getMap(leaderboard.songHash);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new NotFoundError(`Leaderboard "${leaderboardName}" not found`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
leaderboard: leaderboard,
|
||||
beatsaver: beatSaverMap,
|
||||
};
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { PlayerDocument, PlayerModel } from "../model/player";
|
||||
import { PlayerDocument, PlayerModel } from "@ssr/common/model/player";
|
||||
import { NotFoundError } from "../error/not-found-error";
|
||||
import { getDaysAgoDate, getMidnightAlignedDate } from "@ssr/common/utils/time-utils";
|
||||
import { scoresaberService } from "@ssr/common/service/impl/scoresaber";
|
||||
|
@ -5,6 +5,21 @@ import { MessageBuilder, Webhook } from "discord-webhook-node";
|
||||
import { formatPp } from "@ssr/common/utils/number-utils";
|
||||
import { isProduction } from "@ssr/common/utils/utils";
|
||||
import { Config } from "@ssr/common/config";
|
||||
import { Metadata } from "@ssr/common/types/metadata";
|
||||
import { NotFoundError } from "elysia";
|
||||
import BeatSaverService from "./beatsaver.service";
|
||||
import { getScoreSaberLeaderboardFromToken } from "@ssr/common/leaderboard/impl/scoresaber-leaderboard";
|
||||
import { getScoreSaberScoreFromToken } from "@ssr/common/score/impl/scoresaber-score";
|
||||
import { scoresaberService } from "@ssr/common/service/impl/scoresaber";
|
||||
import { ScoreSort } from "@ssr/common/score/score-sort";
|
||||
import { Leaderboards } from "@ssr/common/leaderboard";
|
||||
import Leaderboard from "@ssr/common/leaderboard/leaderboard";
|
||||
import LeaderboardService from "./leaderboard.service";
|
||||
import { BeatSaverMap } from "@ssr/common/model/beatsaver/beatsaver-map";
|
||||
import { PlayerScore } from "@ssr/common/score/player-score";
|
||||
import LeaderboardScoresResponse from "@ssr/common/response/leaderboard-scores-response";
|
||||
import Score from "@ssr/common/score/score";
|
||||
import PlayerScoresResponse from "@ssr/common/response/player-scores-response";
|
||||
|
||||
export class ScoreService {
|
||||
/**
|
||||
@ -45,4 +60,137 @@ export class ScoreService {
|
||||
embed.setColor("#00ff00");
|
||||
await hook.send(embed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets scores for a player.
|
||||
*
|
||||
* @param leaderboardName the leaderboard to get the scores from
|
||||
* @param id the players id
|
||||
* @param page the page to get
|
||||
* @param sort the sort to use
|
||||
* @param search the search to use
|
||||
* @returns the scores
|
||||
*/
|
||||
public static async getPlayerScores(
|
||||
leaderboardName: Leaderboards,
|
||||
id: string,
|
||||
page: number,
|
||||
sort: string,
|
||||
search?: string
|
||||
): Promise<PlayerScoresResponse<unknown, unknown>> {
|
||||
const scores: PlayerScore<unknown, unknown>[] | undefined = [];
|
||||
let beatSaverMap: BeatSaverMap | undefined;
|
||||
let metadata: Metadata = new Metadata(0, 0, 0, 0); // Default values
|
||||
|
||||
switch (leaderboardName) {
|
||||
case "scoresaber": {
|
||||
const leaderboardScores = await scoresaberService.lookupPlayerScores({
|
||||
playerId: id,
|
||||
page: page,
|
||||
sort: sort as ScoreSort,
|
||||
search: search,
|
||||
});
|
||||
if (leaderboardScores == undefined) {
|
||||
throw new NotFoundError(
|
||||
`No scores found for "${id}", leaderboard "${leaderboardName}", page "${page}", sort "${sort}", search "${search}"`
|
||||
);
|
||||
}
|
||||
|
||||
for (const token of leaderboardScores.playerScores) {
|
||||
const score = getScoreSaberScoreFromToken(token.score);
|
||||
if (score == undefined) {
|
||||
continue;
|
||||
}
|
||||
const tokenLeaderboard = getScoreSaberLeaderboardFromToken(token.leaderboard);
|
||||
if (tokenLeaderboard == undefined) {
|
||||
continue;
|
||||
}
|
||||
beatSaverMap = await BeatSaverService.getMap(tokenLeaderboard.songHash);
|
||||
|
||||
scores.push({
|
||||
score: score,
|
||||
leaderboard: tokenLeaderboard,
|
||||
beatSaver: beatSaverMap,
|
||||
});
|
||||
}
|
||||
|
||||
metadata = new Metadata(
|
||||
Math.ceil(leaderboardScores.metadata.total / leaderboardScores.metadata.itemsPerPage),
|
||||
leaderboardScores.metadata.total,
|
||||
leaderboardScores.metadata.page,
|
||||
leaderboardScores.metadata.itemsPerPage
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new NotFoundError(`Leaderboard "${leaderboardName}" not found`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
scores: scores,
|
||||
metadata: metadata,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets scores for a leaderboard.
|
||||
*
|
||||
* @param leaderboardName the leaderboard to get the scores from
|
||||
* @param id the leaderboard id
|
||||
* @param page the page to get
|
||||
* @returns the scores
|
||||
*/
|
||||
public static async getLeaderboardScores(
|
||||
leaderboardName: Leaderboards,
|
||||
id: string,
|
||||
page: number
|
||||
): Promise<LeaderboardScoresResponse<unknown>> {
|
||||
const scores: Score[] = [];
|
||||
let leaderboard: Leaderboard | undefined;
|
||||
let beatSaverMap: BeatSaverMap | undefined;
|
||||
let metadata: Metadata = new Metadata(0, 0, 0, 0); // Default values
|
||||
|
||||
switch (leaderboardName) {
|
||||
case "scoresaber": {
|
||||
const leaderboardScores = await scoresaberService.lookupLeaderboardScores(id, page);
|
||||
if (leaderboardScores == undefined) {
|
||||
throw new NotFoundError(`No scores found for "${id}", leaderboard "${leaderboardName}", page "${page}""`);
|
||||
}
|
||||
|
||||
const leaderboardResponse = await LeaderboardService.getLeaderboard(leaderboardName, id);
|
||||
if (leaderboardResponse == undefined) {
|
||||
throw new NotFoundError(`Leaderboard "${leaderboardName}" not found`);
|
||||
}
|
||||
leaderboard = leaderboardResponse.leaderboard;
|
||||
beatSaverMap = leaderboardResponse.beatsaver;
|
||||
|
||||
for (const token of leaderboardScores.scores) {
|
||||
const score = getScoreSaberScoreFromToken(token);
|
||||
if (score == undefined) {
|
||||
continue;
|
||||
}
|
||||
scores.push(score);
|
||||
}
|
||||
|
||||
metadata = new Metadata(
|
||||
Math.ceil(leaderboardScores.metadata.total / leaderboardScores.metadata.itemsPerPage),
|
||||
leaderboardScores.metadata.total,
|
||||
leaderboardScores.metadata.page,
|
||||
leaderboardScores.metadata.itemsPerPage
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new NotFoundError(`Leaderboard "${leaderboardName}" not found`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
scores: scores,
|
||||
leaderboard: leaderboard,
|
||||
beatSaver: beatSaverMap,
|
||||
metadata: metadata,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user