impl user history tracking
Some checks failed
Deploy API / docker (17, 3.8.5) (push) Failing after 14s

This commit is contained in:
Lee 2024-08-01 23:44:20 +01:00
parent 8dfdc8c535
commit de89182c5d
17 changed files with 143 additions and 76 deletions

@ -62,10 +62,6 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId> <artifactId>spring-boot-starter-websocket</artifactId>
@ -84,15 +80,6 @@
<groupId>com.konghq</groupId> <groupId>com.konghq</groupId>
<artifactId>unirest-modules-jackson</artifactId> <artifactId>unirest-modules-jackson</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.questdb</groupId>
<artifactId>questdb</artifactId>
<version>8.1.0</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<!-- Libraries --> <!-- Libraries -->
<dependency> <dependency>

@ -18,8 +18,6 @@ import java.util.Objects;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@EnableJpaRepositories(basePackages = "cc.fascinated.repository.couchdb")
@EnableMongoRepositories(basePackages = "cc.fascinated.repository.mongo")
@EnableScheduling @EnableScheduling
@SpringBootApplication(exclude = UserDetailsServiceAutoConfiguration.class) @SpringBootApplication(exclude = UserDetailsServiceAutoConfiguration.class)
@Log4j2(topic = "Score Tracker") @Log4j2(topic = "Score Tracker")

@ -4,6 +4,7 @@ import lombok.experimental.UtilityClass;
import java.time.Instant; import java.time.Instant;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Date; import java.util.Date;
/** /**
@ -23,4 +24,17 @@ public class DateUtils {
public static Date getDateFromString(String date) { public static Date getDateFromString(String date) {
return Date.from(Instant.from(FORMATTER.parse(date))); return Date.from(Instant.from(FORMATTER.parse(date)));
} }
/**
* Aligns the date to the current hour.
* <p>
* eg: 00:05 -> 00:00
* </p>
*
* @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));
}
} }

@ -1,15 +1,17 @@
package cc.fascinated.model.user; 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.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter; import lombok.*;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Document;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
/** /**
@ -24,7 +26,8 @@ public class User {
/** /**
* The ID of the user. * The ID of the user.
*/ */
@Id @JsonIgnore @Id
@JsonIgnore
private final UUID id; private final UUID id;
/** /**
@ -39,7 +42,8 @@ public class User {
/** /**
* The ID of the users steam profile. * The ID of the users steam profile.
*/ */
@Indexed @JsonProperty("id") @Indexed
@JsonProperty("id")
private String steamId; private String steamId;
/** /**
@ -56,4 +60,31 @@ public class User {
* The user's ScoreSaber account. * The user's ScoreSaber account.
*/ */
public ScoreSaberAccount scoresaberAccount; public ScoreSaberAccount scoresaberAccount;
/**
* The user's statistic history.
*/
@JsonIgnore
public Map<Platform.Platforms, Map<Date, Statistic>> statistics;
/**
* Gets the statistics for a platform.
*
* @param platform the platform to get the statistics for
* @return the statistics
*/
public Map<Date, Statistic> 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);
}
} }

@ -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;
}

@ -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;
}
}

@ -1,25 +1,26 @@
package cc.fascinated.platform.impl; package cc.fascinated.platform.impl;
import cc.fascinated.common.DateUtils;
import cc.fascinated.common.MathUtils; import cc.fascinated.common.MathUtils;
import cc.fascinated.model.score.Score; import cc.fascinated.model.score.Score;
import cc.fascinated.model.score.TotalScoresResponse;
import cc.fascinated.model.token.ScoreSaberAccountToken; import cc.fascinated.model.token.ScoreSaberAccountToken;
import cc.fascinated.model.token.ScoreSaberLeaderboardPageToken; import cc.fascinated.model.token.ScoreSaberLeaderboardPageToken;
import cc.fascinated.model.token.ScoreSaberLeaderboardToken; import cc.fascinated.model.token.ScoreSaberLeaderboardToken;
import cc.fascinated.model.user.User; import cc.fascinated.model.user.User;
import cc.fascinated.model.user.statistic.impl.ScoreSaberStatistic;
import cc.fascinated.platform.CurvePoint; import cc.fascinated.platform.CurvePoint;
import cc.fascinated.platform.Platform; import cc.fascinated.platform.Platform;
import cc.fascinated.services.QuestDBService; import cc.fascinated.services.QuestDBService;
import cc.fascinated.services.ScoreSaberService; import cc.fascinated.services.ScoreSaberService;
import cc.fascinated.services.ScoreService; import cc.fascinated.services.ScoreService;
import cc.fascinated.services.UserService; import cc.fascinated.services.UserService;
import io.questdb.client.Sender;
import lombok.NonNull; import lombok.NonNull;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn; import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -150,33 +151,29 @@ public class ScoreSaberPlatform extends Platform {
continue; continue;
} }
ScoreSaberAccountToken account = scoreSaberService.getAccount(user); // Get the account from the ScoreSaber API ScoreSaberAccountToken account = scoreSaberService.getAccount(user); // Get the account from the ScoreSaber API
try (Sender sender = questDBService.getSender()) { user.addStatistic(this.getPlatform(), DateUtils.alignToCurrentHour(new Date()), new ScoreSaberStatistic(
sender.table("player") account.getRank(),
.symbol("platform", this.getPlatform().getPlatformName()) account.getCountryRank(),
.symbol("user_id", user.getId().toString()) account.getScoreStats().getTotalScore(),
.doubleColumn("pp", account.getPp()) account.getScoreStats().getTotalRankedScore(),
.longColumn("rank", account.getRank()) account.getScoreStats().getAverageRankedAccuracy(),
.longColumn("country_rank", account.getCountryRank()) account.getScoreStats().getTotalPlayCount()
.longColumn("total_score", account.getScoreStats().getTotalScore()) )); // Add the statistic to the user's history
.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();
}
} }
} }
@Override @Override
public void updateMetrics() { public void updateMetrics() {
try (Sender sender = questDBService.getSender()) { // todo: switch to InfluxDB?
TotalScoresResponse totalScores = ScoreService.INSTANCE.getTotalScores(this.getPlatform());
sender.table("metrics") // try (Sender sender = questDBService.getSender()) {
.symbol("platform", this.getPlatform().getPlatformName()) // TotalScoresResponse totalScores = ScoreService.INSTANCE.getTotalScores(this.getPlatform());
.longColumn("total_scores", totalScores.getTotalScores()) // sender.table("metrics")
.longColumn("total_ranked_scores", totalScores.getTotalRankedScores()) // .symbol("platform", this.getPlatform().getPlatformName())
.atNow(); // .longColumn("total_scores", totalScores.getTotalScores())
} // .longColumn("total_ranked_scores", totalScores.getTotalRankedScores())
// .atNow();
// }
} }
@Override @Override

@ -1,4 +1,4 @@
package cc.fascinated.repository.mongo; package cc.fascinated.repository;
import cc.fascinated.model.Counter; import cc.fascinated.model.Counter;
import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.MongoRepository;

@ -1,4 +1,4 @@
package cc.fascinated.repository.mongo; package cc.fascinated.repository;
import cc.fascinated.model.score.Score; import cc.fascinated.model.score.Score;
import cc.fascinated.platform.Platform; import cc.fascinated.platform.Platform;

@ -1,4 +1,4 @@
package cc.fascinated.repository.mongo; package cc.fascinated.repository;
import cc.fascinated.model.token.ScoreSaberLeaderboardToken; import cc.fascinated.model.token.ScoreSaberLeaderboardToken;
import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.MongoRepository;

@ -1,4 +1,4 @@
package cc.fascinated.repository.mongo; package cc.fascinated.repository;
import cc.fascinated.model.user.User; import cc.fascinated.model.user.User;
import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.MongoRepository;

@ -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<TrackedPlatformMetric, String> {
/**
* SELECT
* platform,
* last(total_scores) - first(total_scores) AS scores_set,
* timestamp
* FROM metrics
* TIMESTAMP(timestamp)
* SAMPLE BY 1d
* ORDER BY timestamp DESC;
*/
}

@ -1,7 +1,7 @@
package cc.fascinated.services; package cc.fascinated.services;
import cc.fascinated.model.Counter; import cc.fascinated.model.Counter;
import cc.fascinated.repository.mongo.CounterRepository; import cc.fascinated.repository.CounterRepository;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;

@ -58,10 +58,10 @@ public class PlatformService {
/** /**
* Updates the platform players. * Updates the platform players.
* <p> * <p>
* This method is scheduled to run every 15 minutes. * This method is scheduled to run every day at midnight.
* </p> * </p>
*/ */
@Scheduled(cron = "0 */15 * * * *") @Scheduled(cron = "0 0 0 * * *")
public void updatePlayerMetrics() { public void updatePlayerMetrics() {
log.info("Updating %s platform player metrics...".formatted(this.platforms.size())); log.info("Updating %s platform player metrics...".formatted(this.platforms.size()));
for (Platform platform : this.platforms) { for (Platform platform : this.platforms) {

@ -6,7 +6,7 @@ import cc.fascinated.model.token.ScoreSaberAccountToken;
import cc.fascinated.model.token.ScoreSaberLeaderboardPageToken; import cc.fascinated.model.token.ScoreSaberLeaderboardPageToken;
import cc.fascinated.model.token.ScoreSaberLeaderboardToken; import cc.fascinated.model.token.ScoreSaberLeaderboardToken;
import cc.fascinated.model.user.User; import cc.fascinated.model.user.User;
import cc.fascinated.repository.mongo.ScoreSaberLeaderboardRepository; import cc.fascinated.repository.ScoreSaberLeaderboardRepository;
import kong.unirest.core.HttpResponse; import kong.unirest.core.HttpResponse;
import lombok.NonNull; import lombok.NonNull;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;

@ -13,7 +13,7 @@ import cc.fascinated.model.token.ScoreSaberPlayerScoreToken;
import cc.fascinated.model.token.ScoreSaberScoreToken; import cc.fascinated.model.token.ScoreSaberScoreToken;
import cc.fascinated.model.user.User; import cc.fascinated.model.user.User;
import cc.fascinated.platform.Platform; import cc.fascinated.platform.Platform;
import cc.fascinated.repository.mongo.ScoreRepository; import cc.fascinated.repository.ScoreRepository;
import lombok.NonNull; import lombok.NonNull;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;

@ -5,7 +5,7 @@ import cc.fascinated.exception.impl.BadRequestException;
import cc.fascinated.model.token.ScoreSaberAccountToken; import cc.fascinated.model.token.ScoreSaberAccountToken;
import cc.fascinated.model.user.ScoreSaberAccount; import cc.fascinated.model.user.ScoreSaberAccount;
import cc.fascinated.model.user.User; import cc.fascinated.model.user.User;
import cc.fascinated.repository.mongo.UserRepository; import cc.fascinated.repository.UserRepository;
import lombok.NonNull; import lombok.NonNull;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;