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

@ -31,4 +31,18 @@ public class ScoreSaberScoreResponse extends ScoreSaberScore {
this.user = user; this.user = user;
this.leaderboard = leaderboard; 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 = { @Aggregation(pipeline = {
"{ $match: { platform: ?0, pp: { $gt: 0 } } }", "{ $match: { platform: ?0, pp: { $gt: 0 } } }",
"{ $sort: { pp: -1 } }", "{ $sort: { pp: -1 } }",
"{ $skip: ?2 }",
"{ $limit: ?1 }", "{ $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. * 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) { public PaginationBuilder.Page<ScoreSaberScoreResponse> getTopRankedScores(@NonNull Platform.Platforms platform, int pageNumber, boolean scoresOnly) {
PaginationBuilder<ScoreSaberScoreResponse> builder = new PaginationBuilder<ScoreSaberScoreResponse>().build(); PaginationBuilder<ScoreSaberScoreResponse> builder = new PaginationBuilder<ScoreSaberScoreResponse>().build();
builder.itemsPerPage(50); builder.itemsPerPage(15);
builder.fillItems(() -> { builder.totalItems(() -> this.scoreRepository.getTotalRankedScores(platform));
List<Score> foundScores = this.scoreRepository.getTopRankedScores(platform, 250); builder.items((fetchItems) -> {
List<Score> foundScores = this.scoreRepository.getTopRankedScores(platform, fetchItems.getItemsPerPage(), fetchItems.skipAmount());
List<ScoreSaberScoreResponse> scores = new ArrayList<>(); List<ScoreSaberScoreResponse> scores = new ArrayList<>();
for (Score score : foundScores) { for (Score score : foundScores) {
ScoreSaberScore scoreSaberScore = (ScoreSaberScore) score; ScoreSaberScore scoreSaberScore = (ScoreSaberScore) score;
UserDTO user = scoresOnly ? null : userService.getUser(score.getPlayerId()).getAsDTO(); UserDTO user = scoresOnly ? null : userService.getUser(score.getPlayerId()).getAsDTO();
Leaderboard leaderboard = scoresOnly ? null : Leaderboard.getFromScoreSaberToken(scoreSaberService.getLeaderboard(score.getLeaderboardId())); Leaderboard leaderboard = scoresOnly ? null : Leaderboard.getFromScoreSaberToken(scoreSaberService.getLeaderboard(score.getLeaderboardId()));
scores.add(new ScoreSaberScoreResponse( scores.add(ScoreSaberScoreResponse.fromScore(scoreSaberScore, user, leaderboard));
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
));
} }
return scores; return scores;
}); });