From de89182c5d594022cbb34fb85154cd4d0f6f3c2a Mon Sep 17 00:00:00 2001 From: Liam Date: Thu, 1 Aug 2024 23:44:20 +0100 Subject: [PATCH] impl user history tracking --- API/pom.xml | 13 ------ API/src/main/java/cc/fascinated/Main.java | 2 - .../java/cc/fascinated/common/DateUtils.java | 14 ++++++ .../java/cc/fascinated/model/user/User.java | 43 +++++++++++++++--- .../model/user/statistic/Statistic.java | 23 ++++++++++ .../statistic/impl/ScoreSaberStatistic.java | 38 ++++++++++++++++ .../platform/impl/ScoreSaberPlatform.java | 45 +++++++++---------- .../{mongo => }/CounterRepository.java | 2 +- .../{mongo => }/ScoreRepository.java | 2 +- .../ScoreSaberLeaderboardRepository.java | 2 +- .../{mongo => }/UserRepository.java | 2 +- .../repository/couchdb/MetricsRepository.java | 21 --------- .../fascinated/services/CounterService.java | 2 +- .../fascinated/services/PlatformService.java | 4 +- .../services/ScoreSaberService.java | 2 +- .../cc/fascinated/services/ScoreService.java | 2 +- .../cc/fascinated/services/UserService.java | 2 +- 17 files changed, 143 insertions(+), 76 deletions(-) create mode 100644 API/src/main/java/cc/fascinated/model/user/statistic/Statistic.java create mode 100644 API/src/main/java/cc/fascinated/model/user/statistic/impl/ScoreSaberStatistic.java rename API/src/main/java/cc/fascinated/repository/{mongo => }/CounterRepository.java (85%) rename API/src/main/java/cc/fascinated/repository/{mongo => }/ScoreRepository.java (98%) rename API/src/main/java/cc/fascinated/repository/{mongo => }/ScoreSaberLeaderboardRepository.java (87%) rename API/src/main/java/cc/fascinated/repository/{mongo => }/UserRepository.java (96%) delete mode 100644 API/src/main/java/cc/fascinated/repository/couchdb/MetricsRepository.java diff --git a/API/pom.xml b/API/pom.xml index c8d29c8..d6b929e 100644 --- a/API/pom.xml +++ b/API/pom.xml @@ -62,10 +62,6 @@ org.springframework.boot spring-boot-starter-web - - org.springframework.boot - spring-boot-starter-data-jpa - org.springframework.boot spring-boot-starter-websocket @@ -84,15 +80,6 @@ com.konghq unirest-modules-jackson - - org.questdb - questdb - 8.1.0 - - - org.postgresql - postgresql - diff --git a/API/src/main/java/cc/fascinated/Main.java b/API/src/main/java/cc/fascinated/Main.java index f36cd55..7a00870 100644 --- a/API/src/main/java/cc/fascinated/Main.java +++ b/API/src/main/java/cc/fascinated/Main.java @@ -18,8 +18,6 @@ import java.util.Objects; /** * @author Fascinated (fascinated7) */ -@EnableJpaRepositories(basePackages = "cc.fascinated.repository.couchdb") -@EnableMongoRepositories(basePackages = "cc.fascinated.repository.mongo") @EnableScheduling @SpringBootApplication(exclude = UserDetailsServiceAutoConfiguration.class) @Log4j2(topic = "Score Tracker") diff --git a/API/src/main/java/cc/fascinated/common/DateUtils.java b/API/src/main/java/cc/fascinated/common/DateUtils.java index 3775823..f5e2aa2 100644 --- a/API/src/main/java/cc/fascinated/common/DateUtils.java +++ b/API/src/main/java/cc/fascinated/common/DateUtils.java @@ -4,6 +4,7 @@ import lombok.experimental.UtilityClass; import java.time.Instant; import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; import java.util.Date; /** @@ -23,4 +24,17 @@ public class DateUtils { public static Date getDateFromString(String date) { return Date.from(Instant.from(FORMATTER.parse(date))); } + + /** + * Aligns the date to the current hour. + *

+ * eg: 00:05 -> 00:00 + *

+ * + * @param date The date to align. + * @return The aligned date. + */ + public static Date alignToCurrentHour(Date date) { + return Date.from(Instant.ofEpochMilli(date.getTime()).truncatedTo(ChronoUnit.HOURS)); + } } diff --git a/API/src/main/java/cc/fascinated/model/user/User.java b/API/src/main/java/cc/fascinated/model/user/User.java index fbfc9bf..3908c93 100644 --- a/API/src/main/java/cc/fascinated/model/user/User.java +++ b/API/src/main/java/cc/fascinated/model/user/User.java @@ -1,15 +1,17 @@ package cc.fascinated.model.user; +import cc.fascinated.model.user.statistic.Statistic; +import cc.fascinated.platform.Platform; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.Setter; -import lombok.ToString; +import lombok.*; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.mapping.Document; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; import java.util.UUID; /** @@ -24,7 +26,8 @@ public class User { /** * The ID of the user. */ - @Id @JsonIgnore + @Id + @JsonIgnore private final UUID id; /** @@ -39,7 +42,8 @@ public class User { /** * The ID of the users steam profile. */ - @Indexed @JsonProperty("id") + @Indexed + @JsonProperty("id") private String steamId; /** @@ -56,4 +60,31 @@ public class User { * The user's ScoreSaber account. */ public ScoreSaberAccount scoresaberAccount; + + /** + * The user's statistic history. + */ + @JsonIgnore + public Map> statistics; + + /** + * Gets the statistics for a platform. + * + * @param platform the platform to get the statistics for + * @return the statistics + */ + public Map getStatistics(@NonNull Platform.Platforms platform) { + return statistics.computeIfAbsent(platform, k -> new HashMap<>()); + } + + /** + * Adds a statistic to the user's history. + * + * @param platform the platform to add the statistic for + * @param date the date of the statistic + * @param statistic the statistic to add + */ + public void addStatistic(@NonNull Platform.Platforms platform, @NonNull Date date, @NonNull Statistic statistic) { + getStatistics(platform).put(date, statistic); + } } diff --git a/API/src/main/java/cc/fascinated/model/user/statistic/Statistic.java b/API/src/main/java/cc/fascinated/model/user/statistic/Statistic.java new file mode 100644 index 0000000..a5d5b3e --- /dev/null +++ b/API/src/main/java/cc/fascinated/model/user/statistic/Statistic.java @@ -0,0 +1,23 @@ +package cc.fascinated.model.user.statistic; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.data.mongodb.core.mapping.Document; + +/** + * @author Fascinated (fascinated7) + */ +@AllArgsConstructor +@Getter +@Document("player_histories") +public class Statistic { + /** + * The rank of the player. + */ + private final int rank; + + /** + * The pp of the player. + */ + private final int countryRank; +} diff --git a/API/src/main/java/cc/fascinated/model/user/statistic/impl/ScoreSaberStatistic.java b/API/src/main/java/cc/fascinated/model/user/statistic/impl/ScoreSaberStatistic.java new file mode 100644 index 0000000..e1820a8 --- /dev/null +++ b/API/src/main/java/cc/fascinated/model/user/statistic/impl/ScoreSaberStatistic.java @@ -0,0 +1,38 @@ +package cc.fascinated.model.user.statistic.impl; + +import cc.fascinated.model.user.statistic.Statistic; +import lombok.Getter; + +/** + * @author Fascinated (fascinated7) + */ +@Getter +public class ScoreSaberStatistic extends Statistic { + /** + * The total score of the player. + */ + private final long totalScore; + + /** + * The total ranked score of the player. + */ + private final long totalRankedScore; + + /** + * The average ranked accuracy of the player. + */ + private final double averageRankedAccuracy; + + /** + * The total play count of the player. + */ + private final int totalPlayCount; + + public ScoreSaberStatistic(int rank, int countryRank, long totalScore, long totalRankedScore, double averageRankedAccuracy, int totalPlayCount) { + super(rank, countryRank); + this.totalScore = totalScore; + this.totalRankedScore = totalRankedScore; + this.averageRankedAccuracy = averageRankedAccuracy; + this.totalPlayCount = totalPlayCount; + } +} diff --git a/API/src/main/java/cc/fascinated/platform/impl/ScoreSaberPlatform.java b/API/src/main/java/cc/fascinated/platform/impl/ScoreSaberPlatform.java index af97705..a4c3140 100644 --- a/API/src/main/java/cc/fascinated/platform/impl/ScoreSaberPlatform.java +++ b/API/src/main/java/cc/fascinated/platform/impl/ScoreSaberPlatform.java @@ -1,25 +1,26 @@ package cc.fascinated.platform.impl; +import cc.fascinated.common.DateUtils; import cc.fascinated.common.MathUtils; import cc.fascinated.model.score.Score; -import cc.fascinated.model.score.TotalScoresResponse; import cc.fascinated.model.token.ScoreSaberAccountToken; import cc.fascinated.model.token.ScoreSaberLeaderboardPageToken; import cc.fascinated.model.token.ScoreSaberLeaderboardToken; import cc.fascinated.model.user.User; +import cc.fascinated.model.user.statistic.impl.ScoreSaberStatistic; import cc.fascinated.platform.CurvePoint; import cc.fascinated.platform.Platform; import cc.fascinated.services.QuestDBService; import cc.fascinated.services.ScoreSaberService; import cc.fascinated.services.ScoreService; import cc.fascinated.services.UserService; -import io.questdb.client.Sender; import lombok.NonNull; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.DependsOn; import org.springframework.stereotype.Component; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -150,33 +151,29 @@ public class ScoreSaberPlatform extends Platform { continue; } ScoreSaberAccountToken account = scoreSaberService.getAccount(user); // Get the account from the ScoreSaber API - try (Sender sender = questDBService.getSender()) { - sender.table("player") - .symbol("platform", this.getPlatform().getPlatformName()) - .symbol("user_id", user.getId().toString()) - .doubleColumn("pp", account.getPp()) - .longColumn("rank", account.getRank()) - .longColumn("country_rank", account.getCountryRank()) - .longColumn("total_score", account.getScoreStats().getTotalScore()) - .longColumn("total_ranked_score", account.getScoreStats().getTotalRankedScore()) - .doubleColumn("average_ranked_accuracy", account.getScoreStats().getAverageRankedAccuracy()) - .longColumn("total_play_count", account.getScoreStats().getTotalPlayCount()) - .longColumn("ranked_play_count", account.getScoreStats().getRankedPlayCount()) - .atNow(); - } + user.addStatistic(this.getPlatform(), DateUtils.alignToCurrentHour(new Date()), new ScoreSaberStatistic( + account.getRank(), + account.getCountryRank(), + account.getScoreStats().getTotalScore(), + account.getScoreStats().getTotalRankedScore(), + account.getScoreStats().getAverageRankedAccuracy(), + account.getScoreStats().getTotalPlayCount() + )); // Add the statistic to the user's history } } @Override public void updateMetrics() { - try (Sender sender = questDBService.getSender()) { - TotalScoresResponse totalScores = ScoreService.INSTANCE.getTotalScores(this.getPlatform()); - sender.table("metrics") - .symbol("platform", this.getPlatform().getPlatformName()) - .longColumn("total_scores", totalScores.getTotalScores()) - .longColumn("total_ranked_scores", totalScores.getTotalRankedScores()) - .atNow(); - } + // todo: switch to InfluxDB? + +// try (Sender sender = questDBService.getSender()) { +// TotalScoresResponse totalScores = ScoreService.INSTANCE.getTotalScores(this.getPlatform()); +// sender.table("metrics") +// .symbol("platform", this.getPlatform().getPlatformName()) +// .longColumn("total_scores", totalScores.getTotalScores()) +// .longColumn("total_ranked_scores", totalScores.getTotalRankedScores()) +// .atNow(); +// } } @Override diff --git a/API/src/main/java/cc/fascinated/repository/mongo/CounterRepository.java b/API/src/main/java/cc/fascinated/repository/CounterRepository.java similarity index 85% rename from API/src/main/java/cc/fascinated/repository/mongo/CounterRepository.java rename to API/src/main/java/cc/fascinated/repository/CounterRepository.java index 7888f40..2ea8b5a 100644 --- a/API/src/main/java/cc/fascinated/repository/mongo/CounterRepository.java +++ b/API/src/main/java/cc/fascinated/repository/CounterRepository.java @@ -1,4 +1,4 @@ -package cc.fascinated.repository.mongo; +package cc.fascinated.repository; import cc.fascinated.model.Counter; import org.springframework.data.mongodb.repository.MongoRepository; diff --git a/API/src/main/java/cc/fascinated/repository/mongo/ScoreRepository.java b/API/src/main/java/cc/fascinated/repository/ScoreRepository.java similarity index 98% rename from API/src/main/java/cc/fascinated/repository/mongo/ScoreRepository.java rename to API/src/main/java/cc/fascinated/repository/ScoreRepository.java index 48532fa..bb57a75 100644 --- a/API/src/main/java/cc/fascinated/repository/mongo/ScoreRepository.java +++ b/API/src/main/java/cc/fascinated/repository/ScoreRepository.java @@ -1,4 +1,4 @@ -package cc.fascinated.repository.mongo; +package cc.fascinated.repository; import cc.fascinated.model.score.Score; import cc.fascinated.platform.Platform; diff --git a/API/src/main/java/cc/fascinated/repository/mongo/ScoreSaberLeaderboardRepository.java b/API/src/main/java/cc/fascinated/repository/ScoreSaberLeaderboardRepository.java similarity index 87% rename from API/src/main/java/cc/fascinated/repository/mongo/ScoreSaberLeaderboardRepository.java rename to API/src/main/java/cc/fascinated/repository/ScoreSaberLeaderboardRepository.java index fdb57e4..22ad72f 100644 --- a/API/src/main/java/cc/fascinated/repository/mongo/ScoreSaberLeaderboardRepository.java +++ b/API/src/main/java/cc/fascinated/repository/ScoreSaberLeaderboardRepository.java @@ -1,4 +1,4 @@ -package cc.fascinated.repository.mongo; +package cc.fascinated.repository; import cc.fascinated.model.token.ScoreSaberLeaderboardToken; import org.springframework.data.mongodb.repository.MongoRepository; diff --git a/API/src/main/java/cc/fascinated/repository/mongo/UserRepository.java b/API/src/main/java/cc/fascinated/repository/UserRepository.java similarity index 96% rename from API/src/main/java/cc/fascinated/repository/mongo/UserRepository.java rename to API/src/main/java/cc/fascinated/repository/UserRepository.java index edacae4..7918dbb 100644 --- a/API/src/main/java/cc/fascinated/repository/mongo/UserRepository.java +++ b/API/src/main/java/cc/fascinated/repository/UserRepository.java @@ -1,4 +1,4 @@ -package cc.fascinated.repository.mongo; +package cc.fascinated.repository; import cc.fascinated.model.user.User; import org.springframework.data.mongodb.repository.MongoRepository; diff --git a/API/src/main/java/cc/fascinated/repository/couchdb/MetricsRepository.java b/API/src/main/java/cc/fascinated/repository/couchdb/MetricsRepository.java deleted file mode 100644 index 62d94ee..0000000 --- a/API/src/main/java/cc/fascinated/repository/couchdb/MetricsRepository.java +++ /dev/null @@ -1,21 +0,0 @@ -package cc.fascinated.repository.couchdb; - -import cc.fascinated.model.platform.TrackedPlatformMetric; -import org.springframework.data.repository.CrudRepository; - -/** - * @author Fascinated (fascinated7) - */ -public interface MetricsRepository extends CrudRepository { - - /** - * SELECT - * platform, - * last(total_scores) - first(total_scores) AS scores_set, - * timestamp - * FROM metrics - * TIMESTAMP(timestamp) - * SAMPLE BY 1d - * ORDER BY timestamp DESC; - */ -} diff --git a/API/src/main/java/cc/fascinated/services/CounterService.java b/API/src/main/java/cc/fascinated/services/CounterService.java index 2019939..897bf95 100644 --- a/API/src/main/java/cc/fascinated/services/CounterService.java +++ b/API/src/main/java/cc/fascinated/services/CounterService.java @@ -1,7 +1,7 @@ package cc.fascinated.services; import cc.fascinated.model.Counter; -import cc.fascinated.repository.mongo.CounterRepository; +import cc.fascinated.repository.CounterRepository; import lombok.AllArgsConstructor; import lombok.Getter; import org.springframework.beans.factory.annotation.Autowired; diff --git a/API/src/main/java/cc/fascinated/services/PlatformService.java b/API/src/main/java/cc/fascinated/services/PlatformService.java index 55c38df..681424d 100644 --- a/API/src/main/java/cc/fascinated/services/PlatformService.java +++ b/API/src/main/java/cc/fascinated/services/PlatformService.java @@ -58,10 +58,10 @@ public class PlatformService { /** * Updates the platform players. *

- * This method is scheduled to run every 15 minutes. + * This method is scheduled to run every day at midnight. *

*/ - @Scheduled(cron = "0 */15 * * * *") + @Scheduled(cron = "0 0 0 * * *") public void updatePlayerMetrics() { log.info("Updating %s platform player metrics...".formatted(this.platforms.size())); for (Platform platform : this.platforms) { diff --git a/API/src/main/java/cc/fascinated/services/ScoreSaberService.java b/API/src/main/java/cc/fascinated/services/ScoreSaberService.java index c28a875..91dad97 100644 --- a/API/src/main/java/cc/fascinated/services/ScoreSaberService.java +++ b/API/src/main/java/cc/fascinated/services/ScoreSaberService.java @@ -6,7 +6,7 @@ import cc.fascinated.model.token.ScoreSaberAccountToken; import cc.fascinated.model.token.ScoreSaberLeaderboardPageToken; import cc.fascinated.model.token.ScoreSaberLeaderboardToken; import cc.fascinated.model.user.User; -import cc.fascinated.repository.mongo.ScoreSaberLeaderboardRepository; +import cc.fascinated.repository.ScoreSaberLeaderboardRepository; import kong.unirest.core.HttpResponse; import lombok.NonNull; import lombok.extern.log4j.Log4j2; diff --git a/API/src/main/java/cc/fascinated/services/ScoreService.java b/API/src/main/java/cc/fascinated/services/ScoreService.java index bc8258f..17eda03 100644 --- a/API/src/main/java/cc/fascinated/services/ScoreService.java +++ b/API/src/main/java/cc/fascinated/services/ScoreService.java @@ -13,7 +13,7 @@ import cc.fascinated.model.token.ScoreSaberPlayerScoreToken; import cc.fascinated.model.token.ScoreSaberScoreToken; import cc.fascinated.model.user.User; import cc.fascinated.platform.Platform; -import cc.fascinated.repository.mongo.ScoreRepository; +import cc.fascinated.repository.ScoreRepository; import lombok.NonNull; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; diff --git a/API/src/main/java/cc/fascinated/services/UserService.java b/API/src/main/java/cc/fascinated/services/UserService.java index d8f7921..357b572 100644 --- a/API/src/main/java/cc/fascinated/services/UserService.java +++ b/API/src/main/java/cc/fascinated/services/UserService.java @@ -5,7 +5,7 @@ import cc.fascinated.exception.impl.BadRequestException; import cc.fascinated.model.token.ScoreSaberAccountToken; import cc.fascinated.model.user.ScoreSaberAccount; import cc.fascinated.model.user.User; -import cc.fascinated.repository.mongo.UserRepository; +import cc.fascinated.repository.UserRepository; import lombok.NonNull; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired;