cleanup top scores and add timeframes to them
Some checks failed
Deploy Backend / docker (ubuntu-latest) (push) Failing after 45s
Deploy Website / docker (ubuntu-latest) (push) Failing after 32s

This commit is contained in:
Lee
2024-10-29 18:34:58 +00:00
parent 9e96d2f0ba
commit b68de0552f
14 changed files with 244 additions and 145 deletions

View File

@ -38,6 +38,9 @@ import { MapCharacteristic } from "@ssr/common/types/map-characteristic";
import { Page, Pagination } from "@ssr/common/pagination";
import ScoreSaberLeaderboard from "@ssr/common/model/leaderboard/impl/scoresaber-leaderboard";
import Leaderboard from "@ssr/common/model/leaderboard/leaderboard";
import { Timeframe } from "@ssr/common/timeframe";
import { getDaysAgoDate } from "@ssr/common/utils/time-utils";
import { PlayerService } from "./player.service";
const playerScoresCache = new SSRCache({
ttl: 1000 * 60, // 1 minute
@ -305,97 +308,67 @@ export class ScoreService {
* Gets the top tracked scores.
*
* @param amount the amount of scores to get
* @param timeframe the timeframe to filter by
* @returns the top scores
*/
public static async getTopScores(amount: number = 100) {
public static async getTopScores(amount: number = 100, timeframe: Timeframe) {
console.log(`Getting top scores for timeframe: ${timeframe}, limit: ${amount}...`);
const before = Date.now();
let daysAgo = -1;
if (timeframe === "daily") {
daysAgo = 1;
} else if (timeframe === "weekly") {
daysAgo = 8;
} else if (timeframe === "monthly") {
daysAgo = 31;
}
const date: Date = daysAgo == -1 ? new Date(0) : getDaysAgoDate(daysAgo);
const foundScores = await ScoreSaberScoreModel.aggregate([
// Start sorting by timestamp descending using the new compound index
{ $sort: { leaderboardId: 1, playerId: 1, timestamp: -1 } },
{ $match: { timestamp: { $gte: date } } },
{ $sort: { timestamp: -1 } },
{
$group: {
_id: { leaderboardId: "$leaderboardId", playerId: "$playerId" },
latestScore: { $first: "$$ROOT" }, // Retrieve the latest score per group
score: { $first: "$$ROOT" },
},
},
// Sort by pp of the latest scores in descending order
{ $sort: { "latestScore.pp": -1 } },
{ $sort: { "score.pp": -1 } },
{ $limit: amount },
]);
// Collect unique leaderboard IDs
const leaderboardIds = [...new Set(foundScores.map(s => s.latestScore.leaderboardId))];
const leaderboardMap = await this.fetchLeaderboardsInBatch(leaderboardIds);
// Collect player IDs for batch retrieval
const playerIds = foundScores.map(result => result.latestScore.playerId);
const players = await PlayerModel.find({ _id: { $in: playerIds } }).exec();
const playerMap = new Map(players.map(player => [player._id.toString(), player]));
// Prepare to fetch additional data concurrently
const scoreDataPromises = foundScores.map(async result => {
const score: ScoreSaberScore = result.latestScore;
const leaderboardResponse = leaderboardMap[score.leaderboardId];
if (!leaderboardResponse) {
return null; // Skip if leaderboard data is not available
const scores: PlayerScore<ScoreSaberScore, ScoreSaberLeaderboard>[] = [];
for (const { score: scoreData } of foundScores) {
const score = new ScoreSaberScoreModel(scoreData).toObject() as ScoreSaberScore;
const leaderboard = await LeaderboardService.getLeaderboard<ScoreSaberLeaderboard>(
"scoresaber",
score.leaderboardId + ""
);
if (!leaderboard) {
continue;
}
const { leaderboard, beatsaver } = leaderboardResponse;
// Fetch additional data concurrently
const [additionalData, previousScore] = await Promise.all([
this.getAdditionalScoreData(
score.playerId,
leaderboard.songHash,
`${leaderboard.difficulty.difficulty}-${leaderboard.difficulty.characteristic}`,
score.score
),
this.getPreviousScore(score.playerId, leaderboard.id + "", score.timestamp),
]);
// Attach additional and previous score data if available
if (additionalData) score.additionalData = additionalData;
if (previousScore) score.previousScore = previousScore;
// Attach player info if available
const player = playerMap.get(score.playerId.toString());
if (player) {
try {
const player = await PlayerService.getPlayer(score.playerId);
if (player !== undefined) {
score.playerInfo = {
id: player.id,
name: player.name,
};
}
} catch {
score.playerInfo = {
id: player._id,
name: player.name,
id: score.playerId,
};
}
return {
score: score as ScoreSaberScore,
leaderboard: leaderboard,
beatSaver: beatsaver,
};
});
return (await Promise.all(scoreDataPromises)).filter(score => score !== null);
}
/**
* Fetches leaderboards in a batch.
*
* @param leaderboardIds the ids of the leaderboards
* @returns the fetched leaderboards
* @private
*/
private static async fetchLeaderboardsInBatch(leaderboardIds: string[]) {
// Remove duplicates from leaderboardIds
const uniqueLeaderboardIds = Array.from(new Set(leaderboardIds));
const leaderboardResponses = await Promise.all(
uniqueLeaderboardIds.map(id => LeaderboardService.getLeaderboard<ScoreSaberLeaderboard>("scoresaber", id))
);
return leaderboardResponses.reduce(
(map, response) => {
if (response) map[response.leaderboard.id] = response;
return map;
},
{} as Record<string, { leaderboard: ScoreSaberLeaderboard; beatsaver?: BeatSaverMap }>
);
scores.push({
score: score,
leaderboard: leaderboard.leaderboard,
beatSaver: leaderboard.beatsaver,
});
}
console.log(`Got ${scores.length} scores in ${Date.now() - before}ms (timeframe: ${timeframe}, limit: ${amount})`);
return scores;
}
/**