api: optimize pagination!!
Some checks failed
Deploy API / docker (17, 3.8.5) (push) Failing after 30s

This commit is contained in:
Lee 2024-08-05 08:53:07 +01:00
parent 1f1c55d41f
commit 131a5c2efe
4 changed files with 66 additions and 33 deletions

@ -5,6 +5,7 @@ import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
/**
@ -17,10 +18,15 @@ public class PaginationBuilder<T> {
*/
private int itemsPerPage;
/**
* The total number of items.
*/
private int totalItems;
/**
* The items to paginate.
*/
private List<T> items;
private Function<FetchItems, List<T>> items;
/**
* Sets the number of items per page.
@ -33,14 +39,25 @@ public class PaginationBuilder<T> {
return this;
}
/**
* Sets the total number of items.
*
* @param totalItems The total number of items.
* @return The pagination builder.
*/
public PaginationBuilder<T> totalItems(Supplier<Integer> totalItems) {
this.totalItems = totalItems.get();
return this;
}
/**
* Sets the items to paginate.
*
* @param getItems The items to paginate.
* @return The pagination builder.
*/
public PaginationBuilder<T> fillItems(Supplier<List<T>> getItems) {
this.items = getItems.get();
public PaginationBuilder<T> items(Function<FetchItems, List<T>> getItems) {
this.items = getItems;
return this;
}
@ -56,23 +73,43 @@ public class PaginationBuilder<T> {
/**
* Gets a page of items.
*
* @param page The page number.
* @param page The page number.
* @return The page.
*/
public Page<T> getPage(int page) {
int totalItems = this.items.size();
int totalPages = (int) Math.ceil((double) totalItems / this.itemsPerPage);
List<T> items = this.items.apply(new FetchItems(page, this.itemsPerPage));
int totalPages = (int) Math.ceil((double) this.totalItems / this.itemsPerPage);
if (page < 1 || page > totalPages) {
throw new BadRequestException("Invalid page number");
}
List<T> items = this.items.subList((page - 1) * this.itemsPerPage, Math.min(page * this.itemsPerPage, totalItems));
return new Page<>(
items,
new Page.Metadata(page, totalPages, totalItems)
new Page.Metadata(page, totalPages, this.totalItems)
);
}
@AllArgsConstructor
@Getter
public static class FetchItems {
/**
* The current page.
*/
private final int currentPage;
/**
* The items per page.
*/
private final int itemsPerPage;
/**
* The amount of items to skip.
*/
public int skipAmount() {
return (currentPage - 1) * itemsPerPage;
}
}
@AllArgsConstructor
@Getter
public static class Page<T> {

@ -31,4 +31,18 @@ public class ScoreSaberScoreResponse extends ScoreSaberScore {
this.user = user;
this.leaderboard = leaderboard;
}
/**
* Creates a new score saber score response.
*
* @param score the score to create the response from
* @param user the user that set the score
* @param leaderboard the leaderboard the score was set on
* @return the score saber score response
*/
public static ScoreSaberScoreResponse fromScore(ScoreSaberScore score, UserDTO user, Leaderboard leaderboard) {
return new ScoreSaberScoreResponse(score.getId(), score.getPlayerId(), score.getPlatform(), score.getPlatformScoreId(), score.getLeaderboardId(),
score.getRank(), score.getAccuracy(), score.getPp(), score.getScore(), score.getModifiers(), score.getMisses(), score.getBadCuts(),
score.getDeviceInformation(), score.getTimestamp(), score.getWeight(), score.getMultiplier(), score.getMaxCombo(), user, leaderboard);
}
}

@ -24,9 +24,10 @@ public interface ScoreRepository extends MongoRepository<Score, Long> {
@Aggregation(pipeline = {
"{ $match: { platform: ?0, pp: { $gt: 0 } } }",
"{ $sort: { pp: -1 } }",
"{ $skip: ?2 }",
"{ $limit: ?1 }",
})
List<Score> getTopRankedScores(@NonNull Platform.Platforms platform, int amount);
List<Score> getTopRankedScores(@NonNull Platform.Platforms platform, int amount, int skip);
/**
* Gets all the ranked scores from the platform.

@ -80,36 +80,17 @@ public class ScoreService {
*/
public PaginationBuilder.Page<ScoreSaberScoreResponse> getTopRankedScores(@NonNull Platform.Platforms platform, int pageNumber, boolean scoresOnly) {
PaginationBuilder<ScoreSaberScoreResponse> builder = new PaginationBuilder<ScoreSaberScoreResponse>().build();
builder.itemsPerPage(50);
builder.fillItems(() -> {
List<Score> foundScores = this.scoreRepository.getTopRankedScores(platform, 250);
builder.itemsPerPage(15);
builder.totalItems(() -> this.scoreRepository.getTotalRankedScores(platform));
builder.items((fetchItems) -> {
List<Score> foundScores = this.scoreRepository.getTopRankedScores(platform, fetchItems.getItemsPerPage(), fetchItems.skipAmount());
List<ScoreSaberScoreResponse> scores = new ArrayList<>();
for (Score score : foundScores) {
ScoreSaberScore scoreSaberScore = (ScoreSaberScore) score;
UserDTO user = scoresOnly ? null : userService.getUser(score.getPlayerId()).getAsDTO();
Leaderboard leaderboard = scoresOnly ? null : Leaderboard.getFromScoreSaberToken(scoreSaberService.getLeaderboard(score.getLeaderboardId()));
scores.add(new ScoreSaberScoreResponse(
score.getId(),
score.getPlayerId(),
score.getPlatform(),
score.getPlatformScoreId(),
score.getLeaderboardId(),
score.getRank(),
score.getAccuracy(),
score.getPp(),
score.getScore(),
score.getModifiers(),
score.getMisses(),
score.getBadCuts(),
score.getDeviceInformation(),
score.getTimestamp(),
scoreSaberScore.getWeight(),
scoreSaberScore.getMultiplier(),
scoreSaberScore.getMaxCombo(),
user,
leaderboard
));
scores.add(ScoreSaberScoreResponse.fromScore(scoreSaberScore, user, leaderboard));
}
return scores;
});