api: re-impl histories (muchhhhhhhhhh better now)
Some checks failed
Deploy API / docker (17, 3.8.5) (push) Failing after 37s
Some checks failed
Deploy API / docker (17, 3.8.5) (push) Failing after 37s
This commit is contained in:
parent
29f5d5983a
commit
7b0c9f54ff
@ -3,17 +3,36 @@ package cc.fascinated.common;
|
|||||||
import lombok.experimental.UtilityClass;
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Fascinated (fascinated7)
|
* @author Fascinated (fascinated7)
|
||||||
*/
|
*/
|
||||||
@UtilityClass
|
@UtilityClass
|
||||||
public class DateUtils {
|
public class DateUtils {
|
||||||
|
private static final ZoneId ZONE_ID = ZoneId.of("Europe/London");
|
||||||
|
private static final DateTimeFormatter ISO_FORMATTER = DateTimeFormatter.ISO_INSTANT
|
||||||
|
.withLocale(Locale.UK)
|
||||||
|
.withZone(ZONE_ID);
|
||||||
|
private static final DateTimeFormatter SIMPLE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd")
|
||||||
|
.withLocale(Locale.UK)
|
||||||
|
.withZone(ZONE_ID);
|
||||||
|
|
||||||
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_INSTANT;
|
/**
|
||||||
|
* Gets the date from a string.
|
||||||
|
*
|
||||||
|
* @param date The date string.
|
||||||
|
* @return The date.
|
||||||
|
*/
|
||||||
|
public static Date getDateFromIsoString(String date) {
|
||||||
|
return Date.from(Instant.from(ISO_FORMATTER.parse(date)));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the date from a string.
|
* Gets the date from a string.
|
||||||
@ -22,13 +41,25 @@ public class DateUtils {
|
|||||||
* @return The date.
|
* @return The date.
|
||||||
*/
|
*/
|
||||||
public static Date getDateFromString(String date) {
|
public static Date getDateFromString(String date) {
|
||||||
return Date.from(Instant.from(FORMATTER.parse(date)));
|
LocalDate localDate = LocalDate.parse(date, SIMPLE_FORMATTER);
|
||||||
|
ZonedDateTime zonedDateTime = localDate.atStartOfDay(ZONE_ID);
|
||||||
|
return Date.from(zonedDateTime.toInstant());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a date to a string.
|
||||||
|
*
|
||||||
|
* @param date The date to format.
|
||||||
|
* @return The formatted date.
|
||||||
|
*/
|
||||||
|
public String formatDate(Date date) {
|
||||||
|
return SIMPLE_FORMATTER.format(date.toInstant());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Aligns the date to the current hour.
|
* Aligns the date to the current hour.
|
||||||
* <p>
|
* <p>
|
||||||
* eg: 00:05 -> 00:00
|
* eg: 00:05 -> 00:00
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param date The date to align.
|
* @param date The date to align.
|
||||||
@ -47,4 +78,13 @@ public class DateUtils {
|
|||||||
public static Date getDaysAgo(int days) {
|
public static Date getDaysAgo(int days) {
|
||||||
return Date.from(Instant.now().minus(days, ChronoUnit.DAYS));
|
return Date.from(Instant.now().minus(days, ChronoUnit.DAYS));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the date for midnight today.
|
||||||
|
*
|
||||||
|
* @return The date.
|
||||||
|
*/
|
||||||
|
public static Date getMidnightToday() {
|
||||||
|
return Date.from(Instant.now().truncatedTo(ChronoUnit.DAYS));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,8 @@ public class UserController {
|
|||||||
/**
|
/**
|
||||||
* The user service to use
|
* The user service to use
|
||||||
*/
|
*/
|
||||||
@NonNull private final UserService userService;
|
@NonNull
|
||||||
|
private final UserService userService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public UserController(@NonNull UserService userService) {
|
public UserController(@NonNull UserService userService) {
|
||||||
@ -36,4 +37,18 @@ public class UserController {
|
|||||||
public ResponseEntity<UserDTO> getUser(@PathVariable String id) {
|
public ResponseEntity<UserDTO> getUser(@PathVariable String id) {
|
||||||
return ResponseEntity.ok(userService.getUser(id).getAsDTO());
|
return ResponseEntity.ok(userService.getUser(id).getAsDTO());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A GET mapping to retrieve a user's statistic
|
||||||
|
* history using the users steam id.
|
||||||
|
*
|
||||||
|
* @param id the id of the user
|
||||||
|
* @return the user's statistic history
|
||||||
|
* @throws BadRequestException if the user is not found
|
||||||
|
*/
|
||||||
|
@ResponseBody
|
||||||
|
@GetMapping(value = "/histories/{id}")
|
||||||
|
public ResponseEntity<?> getUserHistories(@PathVariable String id) {
|
||||||
|
return ResponseEntity.ok(userService.getUser(id).getHistory().getPreviousHistories(30));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
package cc.fascinated.model.score;
|
||||||
|
|
||||||
|
import cc.fascinated.model.user.hmd.DeviceController;
|
||||||
|
import cc.fascinated.model.user.hmd.DeviceHeadset;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fascinated (fascinated7)
|
||||||
|
*/
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public class DeviceInformation {
|
||||||
|
/**
|
||||||
|
* The headset that was used to set the score.
|
||||||
|
*/
|
||||||
|
private final DeviceHeadset headset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The left controller that was used to set the score.
|
||||||
|
*/
|
||||||
|
private final DeviceController leftController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The right controller that was used to set the score.
|
||||||
|
*/
|
||||||
|
private final DeviceController rightController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the device information contains unknown values.
|
||||||
|
*
|
||||||
|
* @return if the device information contains unknown values
|
||||||
|
*/
|
||||||
|
public boolean containsUnknownDevices() {
|
||||||
|
return headset == DeviceHeadset.UNKNOWN
|
||||||
|
|| leftController == DeviceController.UNKNOWN
|
||||||
|
|| rightController == DeviceController.UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
@ -36,7 +36,8 @@ public class Score {
|
|||||||
/**
|
/**
|
||||||
* The ID of the player that set the score.
|
* The ID of the player that set the score.
|
||||||
*/
|
*/
|
||||||
@Indexed @JsonIgnore
|
@Indexed
|
||||||
|
@JsonIgnore
|
||||||
private final String playerId;
|
private final String playerId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -59,7 +60,8 @@ public class Score {
|
|||||||
/**
|
/**
|
||||||
* The ID of the leaderboard the score was set on.
|
* The ID of the leaderboard the score was set on.
|
||||||
*/
|
*/
|
||||||
@Indexed @JsonIgnore
|
@Indexed
|
||||||
|
@JsonIgnore
|
||||||
private final String leaderboardId;
|
private final String leaderboardId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -100,6 +102,14 @@ public class Score {
|
|||||||
*/
|
*/
|
||||||
private final Integer badCuts;
|
private final Integer badCuts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The device information that was used to set the score.
|
||||||
|
* <p>
|
||||||
|
* Headset and controllers information.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
private final DeviceInformation deviceInformation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The score history for map the score was set on.
|
* The score history for map the score was set on.
|
||||||
*/
|
*/
|
||||||
@ -155,4 +165,13 @@ public class Score {
|
|||||||
public List<Score> getPreviousScores() {
|
public List<Score> getPreviousScores() {
|
||||||
return previousScores == null ? List.of() : previousScores;
|
return previousScores == null ? List.of() : previousScores;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets if the score is ranked.
|
||||||
|
*
|
||||||
|
* @return true if the score is ranked, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean isRanked() {
|
||||||
|
return pp != null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package cc.fascinated.model.score.impl.scoresaber;
|
package cc.fascinated.model.score.impl.scoresaber;
|
||||||
|
|
||||||
|
import cc.fascinated.model.score.DeviceInformation;
|
||||||
import cc.fascinated.model.score.Score;
|
import cc.fascinated.model.score.Score;
|
||||||
import cc.fascinated.platform.Platform;
|
import cc.fascinated.platform.Platform;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@ -28,10 +29,10 @@ public class ScoreSaberScore extends Score {
|
|||||||
private final int maxCombo;
|
private final int maxCombo;
|
||||||
|
|
||||||
public ScoreSaberScore(long id, String playerId, Platform.Platforms platform, String platformScoreId, String leaderboardId, int rank,
|
public ScoreSaberScore(long id, String playerId, Platform.Platforms platform, String platformScoreId, String leaderboardId, int rank,
|
||||||
double accuracy, Double pp, int score, String[] modifiers, Integer misses, Integer badCuts, List<Score> previousScores,
|
double accuracy, Double pp, int score, String[] modifiers, Integer misses, Integer badCuts, DeviceInformation deviceInformation,
|
||||||
Date timestamp, Double weight, double multiplier, int maxCombo) {
|
List<Score> previousScores, Date timestamp, Double weight, double multiplier, int maxCombo) {
|
||||||
super(id, playerId, platform, platformScoreId, leaderboardId, rank, accuracy, pp, score, modifiers, misses,
|
super(id, playerId, platform, platformScoreId, leaderboardId, rank, accuracy, pp, score, modifiers, misses,
|
||||||
badCuts, previousScores, timestamp);
|
badCuts, deviceInformation, previousScores, timestamp);
|
||||||
this.weight = weight;
|
this.weight = weight;
|
||||||
this.multiplier = multiplier;
|
this.multiplier = multiplier;
|
||||||
this.maxCombo = maxCombo;
|
this.maxCombo = maxCombo;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package cc.fascinated.model.score.impl.scoresaber;
|
package cc.fascinated.model.score.impl.scoresaber;
|
||||||
|
|
||||||
import cc.fascinated.model.leaderboard.Leaderboard;
|
import cc.fascinated.model.leaderboard.Leaderboard;
|
||||||
|
import cc.fascinated.model.score.DeviceInformation;
|
||||||
import cc.fascinated.model.score.Score;
|
import cc.fascinated.model.score.Score;
|
||||||
import cc.fascinated.model.user.UserDTO;
|
import cc.fascinated.model.user.UserDTO;
|
||||||
import cc.fascinated.platform.Platform;
|
import cc.fascinated.platform.Platform;
|
||||||
@ -25,10 +26,11 @@ public class ScoreSaberScoreResponse extends ScoreSaberScore {
|
|||||||
private final Leaderboard leaderboard;
|
private final Leaderboard leaderboard;
|
||||||
|
|
||||||
public ScoreSaberScoreResponse(long id, String playerId, Platform.Platforms platform, String platformScoreId, String leaderboardId, int rank,
|
public ScoreSaberScoreResponse(long id, String playerId, Platform.Platforms platform, String platformScoreId, String leaderboardId, int rank,
|
||||||
double accuracy, double pp, int score, String[] modifiers, int misses, int badCuts, List<Score> previousScores,
|
double accuracy, double pp, int score, String[] modifiers, int misses, int badCuts, DeviceInformation deviceInformation,
|
||||||
Date timestamp, double weight, double multiplier, int maxCombo, UserDTO user, Leaderboard leaderboard) {
|
List<Score> previousScores, Date timestamp, double weight, double multiplier, int maxCombo, UserDTO user,
|
||||||
|
Leaderboard leaderboard) {
|
||||||
super(id, playerId, platform, platformScoreId, leaderboardId, rank, accuracy, pp, score, modifiers, misses, badCuts,
|
super(id, playerId, platform, platformScoreId, leaderboardId, rank, accuracy, pp, score, modifiers, misses, badCuts,
|
||||||
previousScores, timestamp, weight, multiplier, maxCombo);
|
deviceInformation, previousScores, timestamp, weight, multiplier, maxCombo);
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.leaderboard = leaderboard;
|
this.leaderboard = leaderboard;
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ public class ScoreSaberAccount {
|
|||||||
token.getCountry(),
|
token.getCountry(),
|
||||||
token.getRank(),
|
token.getRank(),
|
||||||
token.getCountryRank(),
|
token.getCountryRank(),
|
||||||
DateUtils.getDateFromString(token.getFirstSeen()),
|
DateUtils.getDateFromIsoString(token.getFirstSeen()),
|
||||||
new Date()
|
new Date()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
package cc.fascinated.model.user;
|
package cc.fascinated.model.user;
|
||||||
|
|
||||||
import cc.fascinated.model.user.statistic.Statistic;
|
import cc.fascinated.model.user.history.History;
|
||||||
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.*;
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
import lombok.extern.log4j.Log4j2;
|
||||||
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.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -21,11 +20,10 @@ import java.util.UUID;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
|
@Log4j2
|
||||||
@ToString
|
@ToString
|
||||||
@Document("user")
|
@Document("user")
|
||||||
public class User {
|
public class User {
|
||||||
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("EEE MMM d HH:mm:ss zzz yyyy");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ID of the user.
|
* The ID of the user.
|
||||||
*/
|
*/
|
||||||
@ -67,55 +65,16 @@ public class User {
|
|||||||
/**
|
/**
|
||||||
* The user's statistic history.
|
* The user's statistic history.
|
||||||
*/
|
*/
|
||||||
public Map<Platform.Platforms, Map<String, Statistic>> histories;
|
public History history;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The user's history points history.
|
* Gets the user's statistic history
|
||||||
*/
|
*/
|
||||||
@JsonIgnore
|
public History getHistory() {
|
||||||
public Map<Platform.Platforms, Map<Date, Statistic>> getHistories() {
|
if (this.history == null) {
|
||||||
if (this.histories == null) {
|
this.history = new History();
|
||||||
this.histories = new HashMap<>();
|
|
||||||
}
|
}
|
||||||
Map<Platform.Platforms, Map<Date, Statistic>> toReturn = new HashMap<>();
|
return this.history;
|
||||||
for (Platform.Platforms platform : histories.keySet()) {
|
|
||||||
toReturn.put(platform, getHistory(platform));
|
|
||||||
}
|
|
||||||
return toReturn;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the history points for a platform.
|
|
||||||
*
|
|
||||||
* @param platform the platform to get the statistics for
|
|
||||||
* @return the statistics
|
|
||||||
*/
|
|
||||||
@SneakyThrows
|
|
||||||
public Map<Date, Statistic> getHistory(@NonNull Platform.Platforms platform) {
|
|
||||||
if (this.histories == null) {
|
|
||||||
this.histories = new HashMap<>();
|
|
||||||
}
|
|
||||||
Map<String, Statistic> statisticMap = this.histories.computeIfAbsent(platform, k -> new HashMap<>());
|
|
||||||
Map<Date, Statistic> statistics = new HashMap<>();
|
|
||||||
for (Map.Entry<String, Statistic> entry : statisticMap.entrySet()) {
|
|
||||||
statistics.put(DATE_FORMAT.parse(entry.getKey()), entry.getValue());
|
|
||||||
}
|
|
||||||
return statistics;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a history point 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 addHistory(@NonNull Platform.Platforms platform, @NonNull Date date, @NonNull Statistic statistic) {
|
|
||||||
if (this.histories == null) {
|
|
||||||
this.histories = new HashMap<>();
|
|
||||||
}
|
|
||||||
Map<String, Statistic> statisticMap = this.histories.computeIfAbsent(platform, k -> new HashMap<>());
|
|
||||||
statisticMap.put(String.valueOf(date.toString()), statistic);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
package cc.fascinated.model.user.history;
|
||||||
|
|
||||||
|
import cc.fascinated.common.DateUtils;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fascinated (fascinated7)
|
||||||
|
*/
|
||||||
|
public class History {
|
||||||
|
/**
|
||||||
|
* The user's history points in time.
|
||||||
|
*/
|
||||||
|
private Map<String, HistoryPoint> histories;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The user's history points history.
|
||||||
|
*/
|
||||||
|
@JsonIgnore
|
||||||
|
public Map<Date, HistoryPoint> getHistories() {
|
||||||
|
if (this.histories == null) {
|
||||||
|
this.histories = new HashMap<>();
|
||||||
|
}
|
||||||
|
Map<Date, HistoryPoint> toReturn = new HashMap<>();
|
||||||
|
this.histories.forEach((key, value) -> toReturn.put(DateUtils.getDateFromString(key), value));
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the user's history for today.
|
||||||
|
*
|
||||||
|
* @return the user's history for today
|
||||||
|
*/
|
||||||
|
@JsonIgnore
|
||||||
|
public HistoryPoint getTodayHistory() {
|
||||||
|
if (this.histories == null) {
|
||||||
|
this.histories = new HashMap<>();
|
||||||
|
}
|
||||||
|
Date midnight = DateUtils.getMidnightToday();
|
||||||
|
return this.histories.computeIfAbsent(DateUtils.formatDate(midnight), key -> new HistoryPoint());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the user's history for a specific date.
|
||||||
|
*
|
||||||
|
* @param date the date to get the history for
|
||||||
|
* @return the user's history for the date
|
||||||
|
*/
|
||||||
|
public HistoryPoint getHistoryForDate(Date date) {
|
||||||
|
if (this.histories == null) {
|
||||||
|
this.histories = new HashMap<>();
|
||||||
|
}
|
||||||
|
return this.histories.get(DateUtils.formatDate(date));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the user's HistoryPoint history for
|
||||||
|
* an amount of days ago.
|
||||||
|
*
|
||||||
|
* @param days the amount of days ago
|
||||||
|
* @return the user's HistoryPoint history
|
||||||
|
*/
|
||||||
|
public TreeMap<String, HistoryPoint> getPreviousHistories(int days) {
|
||||||
|
Date date = DateUtils.getDaysAgo(days);
|
||||||
|
Map<String, HistoryPoint> toReturn = new HashMap<>();
|
||||||
|
for (Map.Entry<Date, HistoryPoint> history : getHistories().entrySet()) {
|
||||||
|
if (history.getKey().after(date)) {
|
||||||
|
toReturn.put(DateUtils.formatDate(history.getKey()), history.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the history by date (newest > oldest)
|
||||||
|
TreeMap<String, HistoryPoint> sorted = new TreeMap<>(Comparator.comparing(DateUtils::getDateFromString).reversed());
|
||||||
|
sorted.putAll(toReturn);
|
||||||
|
return sorted;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
package cc.fascinated.model.user.history;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fascinated (fascinated7)
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class HistoryPoint {
|
||||||
|
/**
|
||||||
|
* The rank of the player.
|
||||||
|
*/
|
||||||
|
private Integer rank;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pp of the player.
|
||||||
|
*/
|
||||||
|
private Integer countryRank;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pp of the player.
|
||||||
|
*/
|
||||||
|
private Double pp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play count of all the player's scores.
|
||||||
|
*/
|
||||||
|
private Integer totalPlayCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play count of all the player's ranked scores.
|
||||||
|
*/
|
||||||
|
private Integer totalRankedPlayCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play count for this day's unranked scores.
|
||||||
|
*/
|
||||||
|
private Integer unrankedPlayCount = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play count for this day's ranked scores.
|
||||||
|
*/
|
||||||
|
private Integer rankedPlayCount = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increment the total ranked play count for this day.
|
||||||
|
*/
|
||||||
|
public void incrementRankedPlayCount() {
|
||||||
|
rankedPlayCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increment the total unranked play count for this day.
|
||||||
|
*/
|
||||||
|
public void incrementUnrankedPlayCount() {
|
||||||
|
unrankedPlayCount++;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package cc.fascinated.model.user.hmd;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fascinated (fascinated7)
|
||||||
|
*/
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public enum DeviceController {
|
||||||
|
UNKNOWN("Unknown"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Oculus Controllers
|
||||||
|
*/
|
||||||
|
OCULUS_QUEST_TOUCH("Touch"),
|
||||||
|
OCULUS_QUEST_2_TOUCH("Quest 2 Touch"),
|
||||||
|
OCULUS_QUEST_3_TOUCH("Quest 3 Touch"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HP Controllers
|
||||||
|
*/
|
||||||
|
HP_REVERB("HP Reverb"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valve Controllers
|
||||||
|
*/
|
||||||
|
VALVE_KNUCKLES("Knuckles");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The controller name
|
||||||
|
*/
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a controller by its name.
|
||||||
|
*
|
||||||
|
* @param name the name of the controller
|
||||||
|
* @return the controller
|
||||||
|
*/
|
||||||
|
public static DeviceController getByName(String name) {
|
||||||
|
for (DeviceController deviceController : values()) {
|
||||||
|
if (deviceController.getName().equalsIgnoreCase(name)) {
|
||||||
|
return deviceController;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package cc.fascinated.model.user.hmd;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fascinated (fascinated7)
|
||||||
|
*/
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public enum DeviceHeadset {
|
||||||
|
UNKNOWN("Unknown"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Oculus HMDs
|
||||||
|
*/
|
||||||
|
OCULUS_CV1("Rift"),
|
||||||
|
OCULUS_QUEST("Quest"),
|
||||||
|
OCULUS_QUEST_2("Quest 2"),
|
||||||
|
OCULUS_QUEST_3("Quest 3"),
|
||||||
|
OCULUS_RIFT_S("Rift S"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTC HMDs
|
||||||
|
*/
|
||||||
|
HTC_VIVE("Vive"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HP HMDs
|
||||||
|
*/
|
||||||
|
HP_REVERB("HP Reverb"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valve HMDs
|
||||||
|
*/
|
||||||
|
VALVE_INDEX("Valve Index");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the headset.
|
||||||
|
*/
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a headset by its name.
|
||||||
|
*
|
||||||
|
* @param name the name of the headset
|
||||||
|
* @return the headset
|
||||||
|
*/
|
||||||
|
public static DeviceHeadset getByName(String name) {
|
||||||
|
for (DeviceHeadset deviceHeadset : values()) {
|
||||||
|
if (deviceHeadset.getName().equalsIgnoreCase(name)) {
|
||||||
|
return deviceHeadset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -1,23 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -72,7 +72,7 @@ public abstract class Platform {
|
|||||||
* Called to update the players
|
* Called to update the players
|
||||||
* data in QuestDB.
|
* data in QuestDB.
|
||||||
*/
|
*/
|
||||||
public abstract void updatePlayers();
|
public abstract void trackPlayerMetrics();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called to update the metrics
|
* Called to update the metrics
|
||||||
|
@ -7,7 +7,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.model.user.statistic.impl.ScoreSaberStatistic;
|
import cc.fascinated.model.user.history.HistoryPoint;
|
||||||
import cc.fascinated.platform.CurvePoint;
|
import cc.fascinated.platform.CurvePoint;
|
||||||
import cc.fascinated.platform.Platform;
|
import cc.fascinated.platform.Platform;
|
||||||
import cc.fascinated.services.ScoreSaberService;
|
import cc.fascinated.services.ScoreSaberService;
|
||||||
@ -137,21 +137,19 @@ public class ScoreSaberPlatform extends Platform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updatePlayers() {
|
public void trackPlayerMetrics() {
|
||||||
Date date = DateUtils.alignToCurrentHour(new Date());
|
Date date = DateUtils.getMidnightToday();
|
||||||
for (User user : this.userService.getUsers(false)) {
|
for (User user : this.userService.getUsers(false)) {
|
||||||
if (!user.isLinkedAccount()) { // Check if the user has linked their account
|
if (!user.isLinkedAccount()) { // Check if the user has linked their account
|
||||||
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
|
||||||
user.addHistory(this.getPlatform(), date, new ScoreSaberStatistic(
|
HistoryPoint history = user.getHistory().getHistoryForDate(date);
|
||||||
account.getRank(),
|
history.setPp(account.getPp());
|
||||||
account.getCountryRank(),
|
history.setRank(account.getRank());
|
||||||
account.getScoreStats().getTotalScore(),
|
history.setCountryRank(account.getCountryRank());
|
||||||
account.getScoreStats().getTotalRankedScore(),
|
history.setTotalPlayCount(account.getScoreStats().getTotalPlayCount());
|
||||||
account.getScoreStats().getAverageRankedAccuracy(),
|
history.setTotalRankedPlayCount((int) account.getScoreStats().getTotalRankedScore());
|
||||||
account.getScoreStats().getTotalPlayCount()
|
|
||||||
)); // Add the statistic to the user's history
|
|
||||||
this.userService.saveUser(user); // Save the user
|
this.userService.saveUser(user); // Save the user
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ public class PlatformService {
|
|||||||
/**
|
/**
|
||||||
* Updates the platform metrics.
|
* Updates the platform metrics.
|
||||||
* <p>
|
* <p>
|
||||||
* This method is scheduled to run every minute.
|
* This method is scheduled to run every 5 minutes.
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
@Scheduled(cron = "0 */5 * * * *")
|
@Scheduled(cron = "0 */5 * * * *")
|
||||||
@ -65,7 +65,7 @@ public class PlatformService {
|
|||||||
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) {
|
||||||
platform.updatePlayers();
|
platform.trackPlayerMetrics();
|
||||||
}
|
}
|
||||||
log.info("Finished updating platform player metrics.");
|
log.info("Finished updating platform player metrics.");
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import cc.fascinated.common.EnumUtils;
|
|||||||
import cc.fascinated.common.MathUtils;
|
import cc.fascinated.common.MathUtils;
|
||||||
import cc.fascinated.common.Tuple;
|
import cc.fascinated.common.Tuple;
|
||||||
import cc.fascinated.model.leaderboard.Leaderboard;
|
import cc.fascinated.model.leaderboard.Leaderboard;
|
||||||
|
import cc.fascinated.model.score.DeviceInformation;
|
||||||
import cc.fascinated.model.score.Score;
|
import cc.fascinated.model.score.Score;
|
||||||
import cc.fascinated.model.score.TotalScoresResponse;
|
import cc.fascinated.model.score.TotalScoresResponse;
|
||||||
import cc.fascinated.model.score.impl.scoresaber.ScoreSaberScore;
|
import cc.fascinated.model.score.impl.scoresaber.ScoreSaberScore;
|
||||||
@ -14,6 +15,9 @@ 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.model.user.UserDTO;
|
import cc.fascinated.model.user.UserDTO;
|
||||||
|
import cc.fascinated.model.user.history.HistoryPoint;
|
||||||
|
import cc.fascinated.model.user.hmd.DeviceController;
|
||||||
|
import cc.fascinated.model.user.hmd.DeviceHeadset;
|
||||||
import cc.fascinated.platform.Platform;
|
import cc.fascinated.platform.Platform;
|
||||||
import cc.fascinated.repository.ScoreRepository;
|
import cc.fascinated.repository.ScoreRepository;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
@ -96,6 +100,7 @@ public class ScoreService {
|
|||||||
score.getModifiers(),
|
score.getModifiers(),
|
||||||
score.getMisses(),
|
score.getMisses(),
|
||||||
score.getBadCuts(),
|
score.getBadCuts(),
|
||||||
|
score.getDeviceInformation(),
|
||||||
score.getPreviousScores(),
|
score.getPreviousScores(),
|
||||||
score.getTimestamp(),
|
score.getTimestamp(),
|
||||||
scoreSaberScore.getWeight(),
|
scoreSaberScore.getWeight(),
|
||||||
@ -130,6 +135,12 @@ public class ScoreService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the total scores for the platform.
|
||||||
|
*
|
||||||
|
* @param platform The platform to get the scores from.
|
||||||
|
* @return The total scores.
|
||||||
|
*/
|
||||||
public TotalScoresResponse getTotalScores(Platform.Platforms platform) {
|
public TotalScoresResponse getTotalScores(Platform.Platforms platform) {
|
||||||
return new TotalScoresResponse(
|
return new TotalScoresResponse(
|
||||||
scoreRepository.getTotalScores(platform),
|
scoreRepository.getTotalScores(platform),
|
||||||
@ -179,15 +190,27 @@ public class ScoreService {
|
|||||||
modifiers.length == 0 ? null : modifiers, // no modifiers, set to null to save data
|
modifiers.length == 0 ? null : modifiers, // no modifiers, set to null to save data
|
||||||
score.getMissedNotes() == 0 ? null : score.getMissedNotes(), // no misses, set to null to save data
|
score.getMissedNotes() == 0 ? null : score.getMissedNotes(), // no misses, set to null to save data
|
||||||
score.getBadCuts() == 0 ? null : score.getBadCuts(), // no bad cuts, set to null to save data
|
score.getBadCuts() == 0 ? null : score.getBadCuts(), // no bad cuts, set to null to save data
|
||||||
|
new DeviceInformation(
|
||||||
|
score.getDeviceHmd() == null ? DeviceHeadset.UNKNOWN : DeviceHeadset.getByName(score.getDeviceHmd()),
|
||||||
|
score.getDeviceControllerLeft() == null ? DeviceController.UNKNOWN : DeviceController.getByName(score.getDeviceControllerLeft()),
|
||||||
|
score.getDeviceControllerRight() == null ? DeviceController.UNKNOWN : DeviceController.getByName(score.getDeviceControllerRight())
|
||||||
|
),
|
||||||
previousScores,
|
previousScores,
|
||||||
DateUtils.getDateFromString(score.getTimeSet()),
|
DateUtils.getDateFromIsoString(score.getTimeSet()),
|
||||||
score.getWeight() == 0 ? null : score.getWeight(), // no weight, set to null to save data
|
score.getWeight() == 0 ? null : score.getWeight(), // no weight, set to null to save data
|
||||||
score.getMultiplier(),
|
score.getMultiplier(),
|
||||||
score.getMaxCombo()
|
score.getMaxCombo()
|
||||||
);
|
);
|
||||||
scoreRepository.save(scoreSaberScore);
|
this.saveScore(user, scoreSaberScore);
|
||||||
this.logScore(Platform.Platforms.SCORESABER, Leaderboard.getFromScoreSaberToken(leaderboard), scoreSaberScore, user,
|
this.logScore(Platform.Platforms.SCORESABER, Leaderboard.getFromScoreSaberToken(leaderboard), scoreSaberScore, user,
|
||||||
previousScoreExists && previousScore.getScore() < scoreSaberScore.getScore());
|
previousScoreExists && previousScore.getScore() < scoreSaberScore.getScore());
|
||||||
|
if (scoreSaberScore.getDeviceInformation().containsUnknownDevices()) {
|
||||||
|
log.warn(" - Score contains unknown device: hmd: {}, controller left: {}, controller right: {}",
|
||||||
|
score.getDeviceHmd(),
|
||||||
|
score.getDeviceControllerLeft(),
|
||||||
|
score.getDeviceControllerRight()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -239,6 +262,22 @@ public class ScoreService {
|
|||||||
return scoreRepository.getBestImprovedScores(platform, DateUtils.getDaysAgo(days));
|
return scoreRepository.getBestImprovedScores(platform, DateUtils.getDaysAgo(days));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves a score.
|
||||||
|
*
|
||||||
|
* @param score The score to save.
|
||||||
|
*/
|
||||||
|
public void saveScore(User user, Score score) {
|
||||||
|
HistoryPoint todayHistory = user.getHistory().getTodayHistory();
|
||||||
|
if (score.isRanked()) {
|
||||||
|
todayHistory.incrementRankedPlayCount();
|
||||||
|
} else {
|
||||||
|
todayHistory.incrementUnrankedPlayCount();
|
||||||
|
}
|
||||||
|
userService.saveUser(user); // Save the user
|
||||||
|
scoreRepository.save(score); // Save the score
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs a score.
|
* Logs a score.
|
||||||
*
|
*
|
||||||
@ -250,6 +289,7 @@ public class ScoreService {
|
|||||||
@NonNull User user, boolean improvedScore) {
|
@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: {}, difficulty: {}, player: {} ({})",
|
log.info("[{}] {}Tracked{} Score! id: {}, acc: {}%, {} score id: {},{} leaderboard: {}, difficulty: {}, player: {} ({})",
|
||||||
platformName,
|
platformName,
|
||||||
improvedScore ? "Improved " : "",
|
improvedScore ? "Improved " : "",
|
||||||
|
Reference in New Issue
Block a user