[AB-272] improving leaderboard performance

fixing leaderboard returning wrong pages
making template cache update duration longer
This commit is contained in:
Sheldan
2021-05-26 21:41:04 +02:00
parent 919b52a607
commit 95a639a733
16 changed files with 98 additions and 90 deletions

View File

@@ -33,7 +33,6 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
/** /**
* Shows the experience gain information of the top 10 users in the server, or if a page number is provided as a parameter, only the members which are on this page. * Shows the experience gain information of the top 10 users in the server, or if a page number is provided as a parameter, only the members which are on this page.
@@ -69,18 +68,18 @@ public class LeaderBoardCommand extends AbstractConditionableCommand {
AServer server = serverManagementService.loadServer(commandContext.getGuild()); AServer server = serverManagementService.loadServer(commandContext.getGuild());
LeaderBoard leaderBoard = userExperienceService.findLeaderBoardData(server, page); LeaderBoard leaderBoard = userExperienceService.findLeaderBoardData(server, page);
LeaderBoardModel leaderBoardModel = (LeaderBoardModel) ContextConverter.slimFromCommandContext(commandContext, LeaderBoardModel.class); LeaderBoardModel leaderBoardModel = (LeaderBoardModel) ContextConverter.slimFromCommandContext(commandContext, LeaderBoardModel.class);
List<CompletableFuture<LeaderBoardEntryModel>> futures = new ArrayList<>(); List<CompletableFuture> futures = new ArrayList<>();
List<CompletableFuture<LeaderBoardEntryModel>> completableFutures = converter.fromLeaderBoard(leaderBoard); CompletableFuture<List<LeaderBoardEntryModel>> completableFutures = converter.fromLeaderBoard(leaderBoard);
futures.addAll(completableFutures); futures.add(completableFutures);
log.info("Rendering leaderboard for page {} in server {} for user {}.", page, commandContext.getAuthor().getId(), commandContext.getGuild().getId()); log.info("Rendering leaderboard for page {} in server {} for user {}.", page, commandContext.getAuthor().getId(), commandContext.getGuild().getId());
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(commandContext.getAuthor()); AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(commandContext.getAuthor());
LeaderBoardEntry userRank = userExperienceService.getRankOfUserInServer(aUserInAServer); LeaderBoardEntry userRank = userExperienceService.getRankOfUserInServer(aUserInAServer);
CompletableFuture<LeaderBoardEntryModel> userRankFuture = converter.fromLeaderBoardEntry(userRank); CompletableFuture<List<LeaderBoardEntryModel>> userRankFuture = converter.fromLeaderBoardEntry(Arrays.asList(userRank));
futures.add(userRankFuture); futures.add(userRankFuture);
return FutureUtils.toSingleFutureGeneric(futures).thenCompose(aVoid -> { return FutureUtils.toSingleFuture(futures).thenCompose(aVoid -> {
List<LeaderBoardEntryModel> finalModels = completableFutures.stream().map(CompletableFuture::join).collect(Collectors.toList()); List<LeaderBoardEntryModel> finalModels = completableFutures.join();
leaderBoardModel.setUserExperiences(finalModels); leaderBoardModel.setUserExperiences(finalModels);
leaderBoardModel.setUserExecuting(userRankFuture.join()); leaderBoardModel.setUserExecuting(userRankFuture.join().get(0));
MessageToSend messageToSend = templateService.renderEmbedTemplate(LEADER_BOARD_POST_EMBED_TEMPLATE, leaderBoardModel, commandContext.getGuild().getIdLong()); MessageToSend messageToSend = templateService.renderEmbedTemplate(LEADER_BOARD_POST_EMBED_TEMPLATE, leaderBoardModel, commandContext.getGuild().getIdLong());
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, commandContext.getChannel())); return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, commandContext.getChannel()));
}).thenApply(aVoid -> CommandResult.fromIgnored()); }).thenApply(aVoid -> CommandResult.fromIgnored());

View File

@@ -29,6 +29,7 @@ import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -70,13 +71,13 @@ public class Rank extends AbstractConditionableCommand {
Member parameter = !parameters.isEmpty() ? (Member) parameters.get(0) : commandContext.getAuthor(); Member parameter = !parameters.isEmpty() ? (Member) parameters.get(0) : commandContext.getAuthor();
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(parameter); AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(parameter);
LeaderBoardEntry userRank = userExperienceService.getRankOfUserInServer(aUserInAServer); LeaderBoardEntry userRank = userExperienceService.getRankOfUserInServer(aUserInAServer);
CompletableFuture<LeaderBoardEntryModel> future = converter.fromLeaderBoardEntry(userRank); CompletableFuture<List<LeaderBoardEntryModel>> future = converter.fromLeaderBoardEntry(Arrays.asList(userRank));
RankModel rankModel = RankModel RankModel rankModel = RankModel
.builder() .builder()
.member(parameter) .member(parameter)
.build(); .build();
return future.thenCompose(leaderBoardEntryModel -> return future.thenCompose(leaderBoardEntryModel ->
self.renderAndSendRank(commandContext, rankModel, leaderBoardEntryModel) self.renderAndSendRank(commandContext, rankModel, leaderBoardEntryModel.get(0))
).thenApply(result -> CommandResult.fromIgnored()); ).thenApply(result -> CommandResult.fromIgnored());
} }

View File

@@ -1,6 +1,5 @@
package dev.sheldan.abstracto.experience.converter; package dev.sheldan.abstracto.experience.converter;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.MemberService; import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.experience.model.LeaderBoard; import dev.sheldan.abstracto.experience.model.LeaderBoard;
import dev.sheldan.abstracto.experience.model.LeaderBoardEntry; import dev.sheldan.abstracto.experience.model.LeaderBoardEntry;
@@ -11,11 +10,14 @@ import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.stream.Collectors;
/** /**
* Converter used to convert from {@link LeaderBoard leaderBoard} to a list of {@link LeaderBoardEntryModel leaderBoardEntryModels} * Converter used to convert from {@link LeaderBoard leaderBoard} to a list of {@link LeaderBoardEntryModel leaderBoardEntryModels}
@@ -40,40 +42,36 @@ public class LeaderBoardModelConverter {
* @return The list of {@link LeaderBoardEntryModel leaderboarEntryModels} which contain the fully fledged information provided to the * @return The list of {@link LeaderBoardEntryModel leaderboarEntryModels} which contain the fully fledged information provided to the
* leader board template * leader board template
*/ */
public List<CompletableFuture<LeaderBoardEntryModel>> fromLeaderBoard(LeaderBoard leaderBoard) { public CompletableFuture<List<LeaderBoardEntryModel>> fromLeaderBoard(LeaderBoard leaderBoard) {
List<CompletableFuture<LeaderBoardEntryModel>> models = new ArrayList<>();
log.debug("Converting {} entries to a list of leaderboard entries.", leaderBoard.getEntries().size()); log.debug("Converting {} entries to a list of leaderboard entries.", leaderBoard.getEntries().size());
leaderBoard.getEntries().forEach(leaderBoardEntry -> { return fromLeaderBoardEntry(leaderBoard.getEntries());
CompletableFuture<LeaderBoardEntryModel> entry = fromLeaderBoardEntry(leaderBoardEntry); }
models.add(entry);
public CompletableFuture<List<LeaderBoardEntryModel>> fromLeaderBoardEntry(List<LeaderBoardEntry> leaderBoardEntries) {
List<Long> userIds = new ArrayList<>();
Long serverId = leaderBoardEntries.get(0).getExperience().getServer().getId();
Map<Long, LeaderBoardEntryModel> models = leaderBoardEntries
.stream()
.map(leaderBoardEntry -> {
AUserExperience experience = leaderBoardEntry.getExperience();
Long userId = experience.getUser().getUserReference().getId();
userIds.add(userId);
return LeaderBoardEntryModel
.builder()
.userId(userId)
.experience(experience.getExperience())
.messageCount(experience.getMessageCount())
.level(experience.getLevelOrDefault())
.rank(leaderBoardEntry.getRank())
.build();
})
.collect(Collectors.toMap(LeaderBoardEntryModel::getUserId, Function.identity()));
return memberService.getMembersInServerAsync(serverId, userIds).thenApply(members -> {
members.forEach(member -> models.get(member.getIdLong()).setMember(member));
return new ArrayList<>(models.values())
.stream()
.sorted(Comparator.comparing(LeaderBoardEntryModel::getRank)).
collect(Collectors.toList());
}); });
return models;
}
/**
* Converts the given {@link LeaderBoardEntry entry} to a {@link LeaderBoardEntryModel model}, which provides a reference to the
* {@link Member member} object of the given {@link AUserInAServer user} for convenience in the template
* @param leaderBoardEntry The {@link LeaderBoardEntry entry} to be converted
* @return The {@link LeaderBoardEntryModel model} accompanied with the {@link Member member} reference, might be null, if the
* user left the guild
*/
public CompletableFuture<LeaderBoardEntryModel> fromLeaderBoardEntry(LeaderBoardEntry leaderBoardEntry) {
AUserInAServer entryUser = leaderBoardEntry.getExperience().getUser();
Long userInServerId = leaderBoardEntry.getExperience().getUser().getUserInServerId();
Integer rank = leaderBoardEntry.getRank();
return memberService.getMemberInServerAsync(entryUser.getServerReference().getId(), entryUser.getUserReference().getId())
.thenApply(member -> self.buildLeaderBoardModel(userInServerId, member, rank))
.exceptionally(throwable -> self.buildLeaderBoardModel(userInServerId, null, rank));
}
@Transactional
public LeaderBoardEntryModel buildLeaderBoardModel(Long userInServerId, Member member, Integer rank) {
AUserExperience experience = userExperienceManagementService.findByUserInServerId(userInServerId);
return LeaderBoardEntryModel
.builder()
.experience(experience)
.member(member)
.rank(rank)
.build();
} }
} }

View File

@@ -548,12 +548,13 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
page--; page--;
int pageSize = 10; int pageSize = 10;
log.debug("Loading leaderboard page {} for server {}.", page, server.getId()); log.debug("Loading leaderboard page {} for server {}.", page, server.getId());
List<AUserExperience> experiences = userExperienceManagementService.findLeaderBoardUsersPaginated(server, page * pageSize, (page + 1) * pageSize); List<AUserExperience> experiences = userExperienceManagementService.findLeaderBoardUsersPaginated(server, page, pageSize);
List<LeaderBoardEntry> entries = new ArrayList<>(); List<LeaderBoardEntry> entries = new ArrayList<>();
log.debug("Found {} experiences.", experiences.size()); log.debug("Found {} experiences.", experiences.size());
int pageOffset = page * pageSize;
for (int i = 0; i < experiences.size(); i++) { for (int i = 0; i < experiences.size(); i++) {
AUserExperience userExperience = experiences.get(i); AUserExperience userExperience = experiences.get(i);
entries.add(LeaderBoardEntry.builder().experience(userExperience).rank((page * pageSize) + i + 1).build()); entries.add(LeaderBoardEntry.builder().experience(userExperience).rank(pageOffset + i + 1).build());
} }
return LeaderBoard.builder().entries(entries).build(); return LeaderBoard.builder().entries(entries).build();
} }

View File

@@ -64,8 +64,8 @@ public class UserExperienceManagementServiceBean implements UserExperienceManage
} }
@Override @Override
public List<AUserExperience> findLeaderBoardUsersPaginated(AServer aServer, Integer start, Integer end) { public List<AUserExperience> findLeaderBoardUsersPaginated(AServer aServer, Integer page, Integer size) {
return repository.findTop10ByUser_ServerReferenceOrderByExperienceDesc(aServer, PageRequest.of(start, end)); return repository.findTop10ByUser_ServerReferenceOrderByExperienceDesc(aServer, PageRequest.of(page, size));
} }
@Override @Override

View File

@@ -74,11 +74,11 @@ public class LeaderBoardCommandTest {
AUserInAServer userInAServer = Mockito.mock(AUserInAServer.class); AUserInAServer userInAServer = Mockito.mock(AUserInAServer.class);
when(userInServerManagementService.loadOrCreateUser(context.getAuthor())).thenReturn(userInAServer); when(userInServerManagementService.loadOrCreateUser(context.getAuthor())).thenReturn(userInAServer);
when(userExperienceService.findLeaderBoardData(server, expectedPage)).thenReturn(leaderBoard); when(userExperienceService.findLeaderBoardData(server, expectedPage)).thenReturn(leaderBoard);
when(converter.fromLeaderBoard(leaderBoard)).thenReturn(new ArrayList<>()); when(converter.fromLeaderBoard(leaderBoard)).thenReturn(CompletableFuture.completedFuture(null));
LeaderBoardEntry executingUserRank = Mockito.mock(LeaderBoardEntry.class); LeaderBoardEntry executingUserRank = Mockito.mock(LeaderBoardEntry.class);
when(userExperienceService.getRankOfUserInServer(userInAServer)).thenReturn(executingUserRank); when(userExperienceService.getRankOfUserInServer(userInAServer)).thenReturn(executingUserRank);
LeaderBoardEntryModel leaderBoardEntryModel = Mockito.mock(LeaderBoardEntryModel.class); LeaderBoardEntryModel leaderBoardEntryModel = Mockito.mock(LeaderBoardEntryModel.class);
when(converter.fromLeaderBoardEntry(executingUserRank)).thenReturn(CompletableFuture.completedFuture(leaderBoardEntryModel)); when(converter.fromLeaderBoardEntry(Arrays.asList(executingUserRank))).thenReturn(CompletableFuture.completedFuture(Arrays.asList(leaderBoardEntryModel)));
MessageToSend messageToSend = Mockito.mock(MessageToSend.class); MessageToSend messageToSend = Mockito.mock(MessageToSend.class);
when(templateService.renderEmbedTemplate(eq(LeaderBoardCommand.LEADER_BOARD_POST_EMBED_TEMPLATE), any(LeaderBoardModel.class), eq(SERVER_ID))).thenReturn(messageToSend); when(templateService.renderEmbedTemplate(eq(LeaderBoardCommand.LEADER_BOARD_POST_EMBED_TEMPLATE), any(LeaderBoardModel.class), eq(SERVER_ID))).thenReturn(messageToSend);
CompletableFuture<CommandResult> result = testUnit.executeAsync(context); CompletableFuture<CommandResult> result = testUnit.executeAsync(context);

View File

@@ -73,7 +73,7 @@ public class RankTest {
when(userInServerManagementService.loadOrCreateUser(context.getAuthor())).thenReturn(aUserInAServer); when(userInServerManagementService.loadOrCreateUser(context.getAuthor())).thenReturn(aUserInAServer);
when(userExperienceService.getRankOfUserInServer(aUserInAServer)).thenReturn(leaderBoardEntry); when(userExperienceService.getRankOfUserInServer(aUserInAServer)).thenReturn(leaderBoardEntry);
LeaderBoardEntryModel leaderBoardEntryModel = Mockito.mock(LeaderBoardEntryModel.class); LeaderBoardEntryModel leaderBoardEntryModel = Mockito.mock(LeaderBoardEntryModel.class);
when(converter.fromLeaderBoardEntry(leaderBoardEntry)).thenReturn(CompletableFuture.completedFuture(leaderBoardEntryModel)); when(converter.fromLeaderBoardEntry(Arrays.asList(leaderBoardEntry))).thenReturn(CompletableFuture.completedFuture(Arrays.asList(leaderBoardEntryModel)));
when(self.renderAndSendRank(eq(context), any(RankModel.class), eq(leaderBoardEntryModel))).thenReturn(CompletableFuture.completedFuture(null)); when(self.renderAndSendRank(eq(context), any(RankModel.class), eq(leaderBoardEntryModel))).thenReturn(CompletableFuture.completedFuture(null));
CompletableFuture<CommandResult> result = testUnit.executeAsync(context); CompletableFuture<CommandResult> result = testUnit.executeAsync(context);
CommandTestUtilities.checkSuccessfulCompletionAsync(result); CommandTestUtilities.checkSuccessfulCompletionAsync(result);

View File

@@ -6,9 +6,11 @@ import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.MemberService; import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.experience.model.LeaderBoard; import dev.sheldan.abstracto.experience.model.LeaderBoard;
import dev.sheldan.abstracto.experience.model.LeaderBoardEntry; import dev.sheldan.abstracto.experience.model.LeaderBoardEntry;
import dev.sheldan.abstracto.experience.model.database.AExperienceLevel;
import dev.sheldan.abstracto.experience.model.database.AUserExperience; import dev.sheldan.abstracto.experience.model.database.AUserExperience;
import dev.sheldan.abstracto.experience.model.template.LeaderBoardEntryModel; import dev.sheldan.abstracto.experience.model.template.LeaderBoardEntryModel;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import org.hibernate.event.spi.ClearEventListener;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -40,6 +42,9 @@ public class LeaderBoardModelConverterTest {
private static final Long USER_ID_2 = 6L; private static final Long USER_ID_2 = 6L;
private static final Long USER_IN_SERVER_ID = 7L; private static final Long USER_IN_SERVER_ID = 7L;
private static final Long USER_IN_SERVER_ID_2 = 8L; private static final Long USER_IN_SERVER_ID_2 = 8L;
private static final Long EXPERIENCE = 9L;
private static final Long MESSAGES = 10L;
private static final Integer LEVEL = 54;
@Test @Test
public void testFromLeaderBoard() { public void testFromLeaderBoard() {
@@ -47,49 +52,33 @@ public class LeaderBoardModelConverterTest {
LeaderBoardEntry entry = getEntry(firstRank, USER_ID, USER_IN_SERVER_ID); LeaderBoardEntry entry = getEntry(firstRank, USER_ID, USER_IN_SERVER_ID);
Integer secondRank = 2; Integer secondRank = 2;
LeaderBoardEntryModel firstEntryModel = Mockito.mock(LeaderBoardEntryModel.class);
LeaderBoardEntryModel secondEntryModel = Mockito.mock(LeaderBoardEntryModel.class);
LeaderBoardEntry entry2 = getEntry(secondRank, USER_ID_2, USER_IN_SERVER_ID_2); LeaderBoardEntry entry2 = getEntry(secondRank, USER_ID_2, USER_IN_SERVER_ID_2);
List<LeaderBoardEntry> entries = Arrays.asList(entry, entry2); List<LeaderBoardEntry> entries = Arrays.asList(entry, entry2);
LeaderBoard leaderBoard = Mockito.mock(LeaderBoard.class); LeaderBoard leaderBoard = Mockito.mock(LeaderBoard.class);
when(leaderBoard.getEntries()).thenReturn(entries); when(leaderBoard.getEntries()).thenReturn(entries);
Member member = Mockito.mock(Member.class); Member member = Mockito.mock(Member.class);
when(memberService.getMemberInServerAsync(SERVER_ID, USER_ID)).thenReturn(CompletableFuture.completedFuture(member)); when(member.getIdLong()).thenReturn(USER_ID);
when(memberService.getMemberInServerAsync(SERVER_ID, USER_ID_2)).thenReturn(CompletableFuture.completedFuture(member)); when(memberService.getMembersInServerAsync(SERVER_ID, Arrays.asList(USER_ID, USER_ID_2))).thenReturn(CompletableFuture.completedFuture(Arrays.asList(member)));
when(self.buildLeaderBoardModel(USER_IN_SERVER_ID, member, firstRank)).thenReturn(firstEntryModel); CompletableFuture<List<LeaderBoardEntryModel>> leaderBoardEntryModels = testUnit.fromLeaderBoard(leaderBoard);
when(self.buildLeaderBoardModel(USER_IN_SERVER_ID_2, member, secondRank)).thenReturn(secondEntryModel); LeaderBoardEntryModel firstEntry = leaderBoardEntryModels.join().get(0);
List<CompletableFuture<LeaderBoardEntryModel>> leaderBoardEntryModels = testUnit.fromLeaderBoard(leaderBoard); Assert.assertEquals(USER_ID, firstEntry.getUserId());
LeaderBoardEntryModel firstEntry = leaderBoardEntryModels.get(0).join(); LeaderBoardEntryModel secondEntry = leaderBoardEntryModels.join().get(1);
Assert.assertEquals(firstEntryModel, firstEntry); Assert.assertEquals(USER_ID_2, secondEntry.getUserId());
LeaderBoardEntryModel secondEntry = leaderBoardEntryModels.get(1).join(); Assert.assertEquals(entries.size(), leaderBoardEntryModels.join().size());
Assert.assertEquals(secondEntryModel, secondEntry);
Assert.assertEquals(entries.size(), leaderBoardEntryModels.size());
}
@Test
public void testFromEntry() {
Integer rank = 2;
LeaderBoardEntry entry = getEntry(rank, USER_ID, USER_IN_SERVER_ID);
Member member = Mockito.mock(Member.class);
LeaderBoardEntryModel entryModelMock = Mockito.mock(LeaderBoardEntryModel.class);
when(memberService.getMemberInServerAsync(SERVER_ID, USER_ID)).thenReturn(CompletableFuture.completedFuture(member));
when(self.buildLeaderBoardModel(USER_IN_SERVER_ID, member, rank)).thenReturn(entryModelMock);
CompletableFuture<LeaderBoardEntryModel> leaderBoardEntryModel = testUnit.fromLeaderBoardEntry(entry);
LeaderBoardEntryModel entryModel = leaderBoardEntryModel.join();
Assert.assertEquals(entryModelMock, entryModel);
} }
private LeaderBoardEntry getEntry(Integer rank, Long userId, Long userInServerId) { private LeaderBoardEntry getEntry(Integer rank, Long userId, Long userInServerId) {
AUserExperience experience = Mockito.mock(AUserExperience.class); AUserExperience experience = Mockito.mock(AUserExperience.class);
when(experience.getMessageCount()).thenReturn(MESSAGES);
when(experience.getExperience()).thenReturn(EXPERIENCE);
AUserInAServer userInAServer = Mockito.mock(AUserInAServer.class); AUserInAServer userInAServer = Mockito.mock(AUserInAServer.class);
when(experience.getUser()).thenReturn(userInAServer); when(experience.getUser()).thenReturn(userInAServer);
AUser user = Mockito.mock(AUser.class); AUser user = Mockito.mock(AUser.class);
when(userInAServer.getUserReference()).thenReturn(user); when(userInAServer.getUserReference()).thenReturn(user);
when(userInAServer.getUserInServerId()).thenReturn(userInServerId);
when(user.getId()).thenReturn(userId); when(user.getId()).thenReturn(userId);
AServer server = Mockito.mock(AServer.class); AServer server = Mockito.mock(AServer.class);
when(experience.getServer()).thenReturn(server);
when(server.getId()).thenReturn(SERVER_ID); when(server.getId()).thenReturn(SERVER_ID);
when(userInAServer.getServerReference()).thenReturn(server);
LeaderBoardEntry entry = Mockito.mock(LeaderBoardEntry.class); LeaderBoardEntry entry = Mockito.mock(LeaderBoardEntry.class);
when(entry.getRank()).thenReturn(rank); when(entry.getRank()).thenReturn(rank);
when(entry.getExperience()).thenReturn(experience); when(entry.getExperience()).thenReturn(experience);

View File

@@ -759,7 +759,7 @@ public class AUserExperienceServiceBeanTest {
when(userExperience2.getExperience()).thenReturn(MID_EXP); when(userExperience2.getExperience()).thenReturn(MID_EXP);
when(userExperience2.getCurrentLevel()).thenReturn(level1); when(userExperience2.getCurrentLevel()).thenReturn(level1);
when(userExperience2.getUser()).thenReturn(aUserInAServer2); when(userExperience2.getUser()).thenReturn(aUserInAServer2);
when(userExperienceManagementService.findLeaderBoardUsersPaginated(server, (page - 1) * pageSize, page * pageSize)).thenReturn(experiences); when(userExperienceManagementService.findLeaderBoardUsersPaginated(server, page - 1, pageSize)).thenReturn(experiences);
LeaderBoard leaderBoardData = testUnit.findLeaderBoardData(server, page); LeaderBoard leaderBoardData = testUnit.findLeaderBoardData(server, page);
page--; page--;
List<LeaderBoardEntry> entries = leaderBoardData.getEntries(); List<LeaderBoardEntry> entries = leaderBoardData.getEntries();

View File

@@ -3,6 +3,7 @@ package dev.sheldan.abstracto.experience.model.template;
import dev.sheldan.abstracto.experience.model.database.AUserExperience; import dev.sheldan.abstracto.experience.model.database.AUserExperience;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import java.io.Serializable; import java.io.Serializable;
@@ -15,13 +16,14 @@ import java.io.Serializable;
@Getter @Getter
@Builder @Builder
public class LeaderBoardEntryModel implements Serializable { public class LeaderBoardEntryModel implements Serializable {
/** private Long experience;
* The {@link AUserExperience experience} for this particular user in the server private Long messageCount;
*/ private Long userId;
private AUserExperience experience; private Integer level;
/** /**
* The {@link Member member} associated wit this user experience, might be null if the user left he server. * The {@link Member member} associated wit this user experience, might be null if the user left he server.
*/ */
@Setter
private transient Member member; private transient Member member;
/** /**
* The position this {@link dev.sheldan.abstracto.core.models.database.AUserInAServer user} in this server has, ordered by experience {@link AUserExperience#experience} * The position this {@link dev.sheldan.abstracto.core.models.database.AUserInAServer user} in this server has, ordered by experience {@link AUserExperience#experience}

View File

@@ -52,11 +52,11 @@ public interface UserExperienceManagementService {
/** /**
* Retrieves a list of {@link AUserExperience} ordered by {@link AUserExperience#experience} and only returns the positions between {@code start} and @{code end}. * Retrieves a list of {@link AUserExperience} ordered by {@link AUserExperience#experience} and only returns the positions between {@code start} and @{code end}.
* @param server The {@link AServer} to retrieve the users for * @param server The {@link AServer} to retrieve the users for
* @param start The start index in the complete ordered list to return the {@link AUserExperience} elements for * @param page The page to retrieve
* @param end The end index for which to return a sublist of {@link AUserExperience} elements for * @param size The size of each page
* @return A list desc ordered by {@link AUserExperience#experience} only containing the elements between {@code start} and @{code end} * @return A list desc ordered by {@link AUserExperience#experience} only containing the elements between {@code start} and @{code end}
*/ */
List<AUserExperience> findLeaderBoardUsersPaginated(AServer server, Integer start, Integer end); List<AUserExperience> findLeaderBoardUsersPaginated(AServer server, Integer page, Integer size);
/** /**
* Returns the {@link LeaderBoardEntryResult} of the given {@link AUserExperience}. * Returns the {@link LeaderBoardEntryResult} of the given {@link AUserExperience}.

View File

@@ -15,6 +15,7 @@ import net.dv8tion.jda.api.entities.User;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -79,6 +80,19 @@ public class MemberServiceBean implements MemberService {
} }
} }
@Override
public CompletableFuture<List<Member>> getMembersInServerAsync(Long serverId, List<Long> memberIds) {
log.debug("Retrieving member {} in server {} from cache.", memberIds, serverId);
Guild guildById = guildService.getGuildById(serverId);
CompletableFuture<List<Member>> future = new CompletableFuture<>();
if(guildById != null) {
guildById.retrieveMembersByIds(memberIds).onSuccess(future::complete).onError(future::completeExceptionally);
} else {
throw new GuildNotFoundException(serverId);
}
return future;
}
@Override @Override
public CompletableFuture<Member> retrieveMemberInServer(ServerUser serverUser) { public CompletableFuture<Member> retrieveMemberInServer(ServerUser serverUser) {
return getMemberInServerAsync(serverUser.getServerId(), serverUser.getUserId()); return getMemberInServerAsync(serverUser.getServerId(), serverUser.getUserId());

View File

@@ -49,6 +49,8 @@ public class FreemarkerConfiguration {
Configuration configuration = factory.createConfiguration(); Configuration configuration = factory.createConfiguration();
configuration.setSharedVariable("fmtDuration", durationMethod); configuration.setSharedVariable("fmtDuration", durationMethod);
configuration.setSharedVariable("formatDate", instantMethod); configuration.setSharedVariable("formatDate", instantMethod);
// 10 minutes template cache
configuration.setTemplateUpdateDelayMilliseconds(600000);
List<String> macrosToLoad = macroManagementService.loadAllMacros().stream() List<String> macrosToLoad = macroManagementService.loadAllMacros().stream()
.map(AutoLoadMacro::getKey).collect(Collectors.toList()); .map(AutoLoadMacro::getKey).collect(Collectors.toList());
configuration.setAutoIncludes(macrosToLoad); configuration.setAutoIncludes(macrosToLoad);

View File

@@ -36,7 +36,7 @@ public class AServer implements SnowFlake, Serializable {
@Transient @Transient
private boolean fake; private boolean fake;
@OneToOne(mappedBy = "server", cascade = CascadeType.ALL) @OneToOne(mappedBy = "server", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@PrimaryKeyJoinColumn @PrimaryKeyJoinColumn
private AllowedMention allowedMention; private AllowedMention allowedMention;

View File

@@ -21,11 +21,11 @@ public class AUserInAServer implements Serializable {
@Column(name = "user_in_server_id", nullable = false) @Column(name = "user_in_server_id", nullable = false)
private Long userInServerId; private Long userInServerId;
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false) @JoinColumn(name = "user_id", nullable = false)
private AUser userReference; private AUser userReference;
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
@JoinColumn(name = "server_id", nullable = false) @JoinColumn(name = "server_id", nullable = false)
private AServer serverReference; private AServer serverReference;

View File

@@ -9,6 +9,7 @@ import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.User;
import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public interface MemberService { public interface MemberService {
@@ -16,6 +17,7 @@ public interface MemberService {
CompletableFuture<GuildChannelMember> getServerChannelUserAsync(Long serverId, Long channelId, Long userId); CompletableFuture<GuildChannelMember> getServerChannelUserAsync(Long serverId, Long channelId, Long userId);
Member getMemberInServer(Long serverId, Long memberId); Member getMemberInServer(Long serverId, Long memberId);
CompletableFuture<Member> getMemberInServerAsync(Long serverId, Long memberId); CompletableFuture<Member> getMemberInServerAsync(Long serverId, Long memberId);
CompletableFuture<List<Member>> getMembersInServerAsync(Long serverId, List<Long> memberIds);
CompletableFuture<Member> retrieveMemberInServer(ServerUser serverUser); CompletableFuture<Member> retrieveMemberInServer(ServerUser serverUser);
CompletableFuture<User> retrieveUserById(Long userId); CompletableFuture<User> retrieveUserById(Long userId);
boolean isUserInGuild(AUserInAServer aUserInAServer); boolean isUserInGuild(AUserInAServer aUserInAServer);