cleanup top scores and add timeframes to them
This commit is contained in:
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user