diff --git a/API/src/main/java/cc/fascinated/common/DateUtils.java b/API/src/main/java/cc/fascinated/common/DateUtils.java index f5e2aa2..3976426 100644 --- a/API/src/main/java/cc/fascinated/common/DateUtils.java +++ b/API/src/main/java/cc/fascinated/common/DateUtils.java @@ -37,4 +37,14 @@ public class DateUtils { public static Date alignToCurrentHour(Date date) { 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)); + } } diff --git a/API/src/main/java/cc/fascinated/common/Tuple.java b/API/src/main/java/cc/fascinated/common/Tuple.java new file mode 100644 index 0000000..971ee74 --- /dev/null +++ b/API/src/main/java/cc/fascinated/common/Tuple.java @@ -0,0 +1,21 @@ +package cc.fascinated.common; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @author Fascinated (fascinated7) + */ +@AllArgsConstructor +@Getter +public class Tuple { + /** + * The left value of the tuple. + */ + private final L left; + + /** + * The right value of the tuple. + */ + private final R right; +} diff --git a/API/src/main/java/cc/fascinated/controller/ScoresController.java b/API/src/main/java/cc/fascinated/controller/ScoresController.java index b7486df..3a5eb95 100644 --- a/API/src/main/java/cc/fascinated/controller/ScoresController.java +++ b/API/src/main/java/cc/fascinated/controller/ScoresController.java @@ -3,6 +3,7 @@ package cc.fascinated.controller; import cc.fascinated.exception.impl.BadRequestException; import cc.fascinated.platform.Platform; import cc.fascinated.services.ScoreService; +import cc.fascinated.services.UserService; import lombok.NonNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; @@ -22,9 +23,16 @@ public class ScoresController { @NonNull private final ScoreService scoreService; + /** + * The user service to use + */ + @NonNull + private final UserService userService; + @Autowired - public ScoresController(@NonNull ScoreService scoreService) { + public ScoresController(@NonNull ScoreService scoreService, @NonNull UserService userService) { this.scoreService = scoreService; + this.userService = userService; } /** @@ -54,4 +62,40 @@ public class ScoresController { public ResponseEntity getScoresCount(@PathVariable String 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 + )); + } } diff --git a/API/src/main/java/cc/fascinated/repository/ScoreRepository.java b/API/src/main/java/cc/fascinated/repository/ScoreRepository.java index c9eb5b2..79150c9 100644 --- a/API/src/main/java/cc/fascinated/repository/ScoreRepository.java +++ b/API/src/main/java/cc/fascinated/repository/ScoreRepository.java @@ -7,6 +7,7 @@ import org.springframework.data.mongodb.repository.Aggregation; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.repository.query.Param; +import java.util.Date; import java.util.List; /** @@ -39,6 +40,21 @@ public interface ScoreRepository extends MongoRepository { }) List 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 getImprovedScores(@NonNull Platform.Platforms platform, @NonNull String playerId, @NonNull Date after); + /** * Gets a score from a platform and leaderboard id. * diff --git a/API/src/main/java/cc/fascinated/services/ScoreService.java b/API/src/main/java/cc/fascinated/services/ScoreService.java index fbfa9c0..de46746 100644 --- a/API/src/main/java/cc/fascinated/services/ScoreService.java +++ b/API/src/main/java/cc/fascinated/services/ScoreService.java @@ -3,6 +3,7 @@ package cc.fascinated.services; import cc.fascinated.common.DateUtils; import cc.fascinated.common.EnumUtils; import cc.fascinated.common.MathUtils; +import cc.fascinated.common.Tuple; import cc.fascinated.model.leaderboard.Leaderboard; import cc.fascinated.model.score.Score; import cc.fascinated.model.score.TotalScoresResponse; @@ -146,23 +147,15 @@ public class ScoreService { ScoreSaberScoreToken score = token.getScore(); User user = userService.getUser(score.getLeaderboardPlayerInfo().getId()); - Score previousScore = this.scoreRepository.findScore(Platform.Platforms.SCORESABER, user.getSteamId(), leaderboard.getId()); - List previousScores = new ArrayList<>(); - if (previousScore != null) { // There is a previous score - if (previousScore.getPreviousScores() != null) { - previousScores.addAll(previousScore.getPreviousScores()); // Add the previous scores - } - 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()); - + Tuple> previousScoreTuple = this.getPreviousScore(Platform.Platforms.SCORESABER, user, leaderboard.getId()); + List previousScores = previousScoreTuple.getRight(); + Score previousScore = previousScoreTuple.getLeft(); + boolean previousScoreExists = previousScore != null && (previousScores != null && !previousScores.isEmpty()); + if (previousScoreExists) { // There is a previous score // Delete the previous score scoreRepository.delete(previousScore); - } - // There are no previous scores, so set it to null to save data - if (previousScores.isEmpty()) { + } else { + // There are no previous scores, so set it to null to save data previousScores = null; } @@ -193,8 +186,46 @@ public class ScoreService { score.getMaxCombo() ); scoreRepository.save(scoreSaberScore); - this.logScore(Platform.Platforms.SCORESABER, scoreSaberScore, user, - previousScore != null && previousScore.getScore() < scoreSaberScore.getScore()); + this.logScore(Platform.Platforms.SCORESABER, Leaderboard.getFromScoreSaberToken(leaderboard), scoreSaberScore, user, + 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> getPreviousScore(@NonNull Platform.Platforms platform, @NonNull User user, @NonNull String leaderboardId) { + Score score = this.scoreRepository.findScore(platform, user.getSteamId(), leaderboardId); + List 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 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 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); 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, improvedScore ? "Improved " : "", isRanked ? " Ranked" : "", @@ -216,7 +248,9 @@ public class ScoreService { platformName.toLowerCase(), score.getPlatformScoreId(), isRanked ? " pp: %s,".formatted(score.getPp()) : "", score.getLeaderboardId(), - user.getUsername() == null ? user.getSteamId() : user.getUsername() + leaderboard.getDifficulty().getDifficulty(), + user.getUsername() == null ? user.getSteamId() : user.getUsername(), + user.getSteamId() ); } }