api: add score history endpoints (per leaderboard and last 30 day improvements (all maps))
Some checks failed
Deploy API / docker (17, 3.8.5) (push) Failing after 47s

This commit is contained in:
Lee 2024-08-04 00:32:39 +01:00
parent 6fda81e81a
commit 199ee50534
5 changed files with 146 additions and 21 deletions

@ -37,4 +37,14 @@ public class DateUtils {
public static Date alignToCurrentHour(Date date) { public static Date alignToCurrentHour(Date date) {
return Date.from(Instant.ofEpochMilli(date.getTime()).truncatedTo(ChronoUnit.HOURS)); return Date.from(Instant.ofEpochMilli(date.getTime()).truncatedTo(ChronoUnit.HOURS));
} }
/**
* Gets the date from an amount of days ago.
*
* @param days The amount to go back.
* @return The date.
*/
public static Date getDaysAgo(int days) {
return Date.from(Instant.now().minus(days, ChronoUnit.DAYS));
}
} }

@ -0,0 +1,21 @@
package cc.fascinated.common;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author Fascinated (fascinated7)
*/
@AllArgsConstructor
@Getter
public class Tuple<L, R> {
/**
* The left value of the tuple.
*/
private final L left;
/**
* The right value of the tuple.
*/
private final R right;
}

@ -3,6 +3,7 @@ package cc.fascinated.controller;
import cc.fascinated.exception.impl.BadRequestException; import cc.fascinated.exception.impl.BadRequestException;
import cc.fascinated.platform.Platform; import cc.fascinated.platform.Platform;
import cc.fascinated.services.ScoreService; import cc.fascinated.services.ScoreService;
import cc.fascinated.services.UserService;
import lombok.NonNull; import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@ -22,9 +23,16 @@ public class ScoresController {
@NonNull @NonNull
private final ScoreService scoreService; private final ScoreService scoreService;
/**
* The user service to use
*/
@NonNull
private final UserService userService;
@Autowired @Autowired
public ScoresController(@NonNull ScoreService scoreService) { public ScoresController(@NonNull ScoreService scoreService, @NonNull UserService userService) {
this.scoreService = scoreService; this.scoreService = scoreService;
this.userService = userService;
} }
/** /**
@ -54,4 +62,40 @@ public class ScoresController {
public ResponseEntity<?> getScoresCount(@PathVariable String platform) { public ResponseEntity<?> getScoresCount(@PathVariable String platform) {
return ResponseEntity.ok(scoreService.getTotalScores(Platform.Platforms.getPlatform(platform))); return ResponseEntity.ok(scoreService.getTotalScores(Platform.Platforms.getPlatform(platform)));
} }
/**
* A GET mapping to retrieve the score
* history for a leaderboard
*
* @param platform the platform to get the history from
* @return the score history
* @throws BadRequestException if there were no history found
*/
@ResponseBody
@GetMapping(value = "/history/{platform}/{playerId}/{leaderboardId}")
public ResponseEntity<?> getScoreHistory(@PathVariable String platform, @PathVariable String playerId, @PathVariable String leaderboardId) {
return ResponseEntity.ok(scoreService.getPreviousScore(
Platform.Platforms.getPlatform(platform),
userService.getUser(playerId),
leaderboardId
).getRight());
}
/**
* A GET mapping to retrieve the score
* history for a leaderboard
*
* @param platform the platform to get the history from
* @return the score history
* @throws BadRequestException if there were no history found
*/
@ResponseBody
@GetMapping(value = "/history/{platform}/{playerId}")
public ResponseEntity<?> getScoreHistory(@PathVariable String platform, @PathVariable String playerId) {
return ResponseEntity.ok(scoreService.getImprovedScores(
Platform.Platforms.getPlatform(platform),
userService.getUser(playerId),
30
));
}
} }

@ -7,6 +7,7 @@ import org.springframework.data.mongodb.repository.Aggregation;
import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import java.util.Date;
import java.util.List; import java.util.List;
/** /**
@ -39,6 +40,21 @@ public interface ScoreRepository extends MongoRepository<Score, String> {
}) })
List<Score> getRankedScores(@NonNull Platform.Platforms platform); List<Score> getRankedScores(@NonNull Platform.Platforms platform);
/**
* Gets the improved scores from the platform.
*
* @param platform the platform to get the scores from
* @param playerId the player id to get the scores from
* @param after the date to get the scores after
* @return the scores
*/
@Aggregation(pipeline = {
"{ $match: { platform: ?0, playerId: ?1, timestamp: { $gt: ?2 } } }",
"{ $match: { 'previousScores.0': { $exists: true } } }",
"{ $sort: { pp: -1 } }"
})
List<Score> getImprovedScores(@NonNull Platform.Platforms platform, @NonNull String playerId, @NonNull Date after);
/** /**
* Gets a score from a platform and leaderboard id. * Gets a score from a platform and leaderboard id.
* *

@ -3,6 +3,7 @@ package cc.fascinated.services;
import cc.fascinated.common.DateUtils; import cc.fascinated.common.DateUtils;
import cc.fascinated.common.EnumUtils; import cc.fascinated.common.EnumUtils;
import cc.fascinated.common.MathUtils; import cc.fascinated.common.MathUtils;
import cc.fascinated.common.Tuple;
import cc.fascinated.model.leaderboard.Leaderboard; import cc.fascinated.model.leaderboard.Leaderboard;
import cc.fascinated.model.score.Score; import cc.fascinated.model.score.Score;
import cc.fascinated.model.score.TotalScoresResponse; import cc.fascinated.model.score.TotalScoresResponse;
@ -146,23 +147,15 @@ public class ScoreService {
ScoreSaberScoreToken score = token.getScore(); ScoreSaberScoreToken score = token.getScore();
User user = userService.getUser(score.getLeaderboardPlayerInfo().getId()); User user = userService.getUser(score.getLeaderboardPlayerInfo().getId());
Score previousScore = this.scoreRepository.findScore(Platform.Platforms.SCORESABER, user.getSteamId(), leaderboard.getId()); Tuple<Score, List<Score>> previousScoreTuple = this.getPreviousScore(Platform.Platforms.SCORESABER, user, leaderboard.getId());
List<Score> previousScores = new ArrayList<>(); List<Score> previousScores = previousScoreTuple.getRight();
if (previousScore != null) { // There is a previous score Score previousScore = previousScoreTuple.getLeft();
if (previousScore.getPreviousScores() != null) { boolean previousScoreExists = previousScore != null && (previousScores != null && !previousScores.isEmpty());
previousScores.addAll(previousScore.getPreviousScores()); // Add the previous scores if (previousScoreExists) { // There is a previous score
}
previousScore.setPreviousScores(null); // Clear the previous scores
previousScores.add(previousScore); // Add the previous score
// Sort previous scores by timestamp (newest -> oldest)
previousScores.sort(Comparator.comparing(Score::getTimestamp).reversed());
// Delete the previous score // Delete the previous score
scoreRepository.delete(previousScore); scoreRepository.delete(previousScore);
} } else {
// There are no previous scores, so set it to null to save data // There are no previous scores, so set it to null to save data
if (previousScores.isEmpty()) {
previousScores = null; previousScores = null;
} }
@ -193,8 +186,46 @@ public class ScoreService {
score.getMaxCombo() score.getMaxCombo()
); );
scoreRepository.save(scoreSaberScore); scoreRepository.save(scoreSaberScore);
this.logScore(Platform.Platforms.SCORESABER, scoreSaberScore, user, this.logScore(Platform.Platforms.SCORESABER, Leaderboard.getFromScoreSaberToken(leaderboard), scoreSaberScore, user,
previousScore != null && previousScore.getScore() < scoreSaberScore.getScore()); previousScoreExists && previousScore.getScore() < scoreSaberScore.getScore());
}
/**
* Gets the previous score of a user.
*
* @param platform The platform to get the score from.
* @param user The user to get the score from.
* @param leaderboardId The leaderboard id to get the score from.
* @return The previous score.
*/
public Tuple<Score, List<Score>> getPreviousScore(@NonNull Platform.Platforms platform, @NonNull User user, @NonNull String leaderboardId) {
Score score = this.scoreRepository.findScore(platform, user.getSteamId(), leaderboardId);
List<Score> previousScores = new ArrayList<>();
if (score == null) { // There is a previous score
return new Tuple<>(null, previousScores);
}
if (score.getPreviousScores() != null) {
previousScores.addAll(score.getPreviousScores()); // Add the previous scores
}
score.setPreviousScores(null); // Clear the previous scores
previousScores.add(score); // Add the previous score
// Sort previous scores by timestamp (newest -> oldest)
previousScores.sort(Comparator.comparing(Score::getTimestamp).reversed());
return new Tuple<>(score, previousScores);
}
/**
* Gets the improved scores of a user.
*
* @param platform The platform to get the scores from.
* @param user The user to get the scores from.
* @param days The amount of days in the past to get the scores from.
* @return The improved scores.
*/
public List<Score> getImprovedScores(@NonNull Platform.Platforms platform, @NonNull User user, int days) {
return scoreRepository.getImprovedScores(platform, user.getSteamId(), DateUtils.getDaysAgo(days));
} }
/** /**
@ -204,10 +235,11 @@ public class ScoreService {
* @param score The score to log. * @param score The score to log.
* @param user The user who set the score. * @param user The user who set the score.
*/ */
private void logScore(@NonNull Platform.Platforms platform, @NonNull Score score, @NonNull User user, boolean improvedScore) { private void logScore(@NonNull Platform.Platforms platform, @NonNull Leaderboard leaderboard, @NonNull Score score,
@NonNull User user, boolean improvedScore) {
String platformName = EnumUtils.getEnumName(platform); String platformName = EnumUtils.getEnumName(platform);
boolean isRanked = score.getPp() != 0; boolean isRanked = score.getPp() != 0;
log.info("[{}] {}Tracked{} Score! id: {}, acc: {}%, {} score id: {},{} leaderboard: {}, player: {}", log.info("[{}] {}Tracked{} Score! id: {}, acc: {}%, {} score id: {},{} leaderboard: {}, difficulty: {}, player: {} ({})",
platformName, platformName,
improvedScore ? "Improved " : "", improvedScore ? "Improved " : "",
isRanked ? " Ranked" : "", isRanked ? " Ranked" : "",
@ -216,7 +248,9 @@ public class ScoreService {
platformName.toLowerCase(), score.getPlatformScoreId(), platformName.toLowerCase(), score.getPlatformScoreId(),
isRanked ? " pp: %s,".formatted(score.getPp()) : "", isRanked ? " pp: %s,".formatted(score.getPp()) : "",
score.getLeaderboardId(), score.getLeaderboardId(),
user.getUsername() == null ? user.getSteamId() : user.getUsername() leaderboard.getDifficulty().getDifficulty(),
user.getUsername() == null ? user.getSteamId() : user.getUsername(),
user.getSteamId()
); );
} }
} }