impl user history tracking
Some checks failed
Deploy API / docker (17, 3.8.5) (push) Failing after 14s
Some checks failed
Deploy API / docker (17, 3.8.5) (push) Failing after 14s
This commit is contained in:
parent
8dfdc8c535
commit
de89182c5d
13
API/pom.xml
13
API/pom.xml
@ -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;
|
||||||
|
Reference in New Issue
Block a user