diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/command/SetExpRole.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/command/SetExpRole.java index 42ef043e4..b74a802fc 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/command/SetExpRole.java +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/command/SetExpRole.java @@ -10,8 +10,6 @@ import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException; -import dev.sheldan.abstracto.core.service.RoleService; -import dev.sheldan.abstracto.core.service.management.RoleManagementService; import dev.sheldan.abstracto.experience.config.ExperienceFeatureDefinition; import dev.sheldan.abstracto.experience.service.ExperienceRoleService; import lombok.extern.slf4j.Slf4j; @@ -34,12 +32,6 @@ public class SetExpRole extends AbstractConditionableCommand { @Autowired private ExperienceRoleService experienceRoleService; - @Autowired - private RoleService roleService; - - @Autowired - private RoleManagementService roleManagementService; - @Override public CompletableFuture executeAsync(CommandContext commandContext) { Integer level = (Integer) commandContext.getParameters().getParameters().get(0); @@ -48,7 +40,7 @@ public class SetExpRole extends AbstractConditionableCommand { throw new EntityGuildMismatchException(); } log.info("Setting role {} to be used for level {} on server {}", role.getId(), level, role.getGuild().getId()); - return experienceRoleService.setRoleToLevel(role, level, commandContext.getChannel().getIdLong()) + return experienceRoleService.setRoleToLevel(role, level, commandContext.getChannel()) .thenApply(aVoid -> CommandResult.fromSuccess()); } @@ -56,8 +48,21 @@ public class SetExpRole extends AbstractConditionableCommand { public CommandConfiguration getConfiguration() { List parameters = new ArrayList<>(); List levelValidators = Arrays.asList(MinIntegerValueValidator.min(0L)); - parameters.add(Parameter.builder().name("level").validators(levelValidators).templated(true).type(Integer.class).build()); - parameters.add(Parameter.builder().name("role").templated(true).type(Role.class).build()); + Parameter level = Parameter + .builder() + .name("level") + .validators(levelValidators) + .templated(true) + .type(Integer.class) + .build(); + parameters.add(level); + Parameter role = Parameter + .builder() + .name("role") + .templated(true) + .type(Role.class) + .build(); + parameters.add(role); HelpInfo helpInfo = HelpInfo.builder().templated(true).hasExample(true).build(); return CommandConfiguration.builder() .name("setExpRole") diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/command/SyncRoles.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/command/SyncRoles.java index 7aed14f0d..14cbd41b7 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/command/SyncRoles.java +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/command/SyncRoles.java @@ -39,7 +39,7 @@ public class SyncRoles extends AbstractConditionableCommand { public CompletableFuture executeAsync(CommandContext commandContext) { AServer server = serverManagementService.loadServer(commandContext.getGuild()); log.info("Synchronizing roles on server {}", server.getId()); - return userExperienceService.syncUserRolesWithFeedback(server, commandContext.getChannel().getIdLong()) + return userExperienceService.syncUserRolesWithFeedback(server, commandContext.getChannel()) .thenApply(aVoid -> CommandResult.fromIgnored()); } diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/command/UnSetExpRole.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/command/UnSetExpRole.java index 90f84b248..8c218a900 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/command/UnSetExpRole.java +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/command/UnSetExpRole.java @@ -52,7 +52,7 @@ public class UnSetExpRole extends AbstractConditionableCommand { if(!experienceRole.isPresent()) { throw new ExperienceRoleNotFoundException(); } - return experienceRoleService.unsetRoles(actualRole, commandContext.getChannel().getIdLong()) + return experienceRoleService.unsetRoles(actualRole, commandContext.getChannel()) .thenApply(aVoid -> CommandResult.fromSuccess()); } diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/job/ExperiencePersistingJob.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/job/ExperienceCleanupJob.java similarity index 53% rename from abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/job/ExperiencePersistingJob.java rename to abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/job/ExperienceCleanupJob.java index 7840895bf..242554dc4 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/job/ExperiencePersistingJob.java +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/job/ExperienceCleanupJob.java @@ -1,6 +1,5 @@ package dev.sheldan.abstracto.experience.job; -import dev.sheldan.abstracto.experience.model.ServerExperience; import dev.sheldan.abstracto.experience.service.AUserExperienceService; import dev.sheldan.abstracto.experience.service.RunTimeExperienceService; import lombok.extern.slf4j.Slf4j; @@ -12,10 +11,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.quartz.QuartzJobBean; import org.springframework.stereotype.Component; -import java.time.Instant; -import java.util.List; -import java.util.Map; - /** * This {@link QuartzJobBean job} is executed regularly and calls the the {@link AUserExperienceService service} @@ -26,10 +21,7 @@ import java.util.Map; @DisallowConcurrentExecution @Component @PersistJobDataAfterExecution -public class ExperiencePersistingJob extends QuartzJobBean { - - @Autowired - private AUserExperienceService userExperienceService; +public class ExperienceCleanupJob extends QuartzJobBean { @Autowired private RunTimeExperienceService runTimeExperienceService; @@ -37,19 +29,9 @@ public class ExperiencePersistingJob extends QuartzJobBean { @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { runTimeExperienceService.takeLock(); + log.info("Cleaning up experience runtime storage."); try { - Map> runtimeExperience = runTimeExperienceService.getRuntimeExperience(); - log.info("Running experience persisting job."); - Long pastMinute = (Instant.now().getEpochSecond() / 60) - 1; - if(runtimeExperience.containsKey(pastMinute)) { - List foundServers = runtimeExperience.get(pastMinute); - log.info("Found experience from {} servers to persist.", foundServers.size()); - userExperienceService.handleExperienceGain(foundServers).thenAccept(aVoid -> { - runTimeExperienceService.takeLock(); - runTimeExperienceService.getRuntimeExperience().remove(pastMinute); - runTimeExperienceService.releaseLock(); - }); - } + runTimeExperienceService.cleanupRunTimeStorage(); } finally { runTimeExperienceService.releaseLock(); } diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/listener/ExperienceTrackerListener.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/listener/ExperienceTrackerListener.java index 7a0d1ca51..94fa45223 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/listener/ExperienceTrackerListener.java +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/listener/ExperienceTrackerListener.java @@ -4,9 +4,7 @@ import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.listener.DefaultListenerResult; import dev.sheldan.abstracto.core.listener.async.jda.AsyncMessageReceivedListener; import dev.sheldan.abstracto.core.listener.sync.jda.MessageReceivedListener; -import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.models.listener.MessageReceivedModel; -import dev.sheldan.abstracto.core.service.management.UserInServerManagementService; import dev.sheldan.abstracto.experience.config.ExperienceFeatureDefinition; import dev.sheldan.abstracto.experience.service.AUserExperienceService; import lombok.extern.slf4j.Slf4j; @@ -25,9 +23,6 @@ public class ExperienceTrackerListener implements AsyncMessageReceivedListener { @Autowired private AUserExperienceService userExperienceService; - @Autowired - private UserInServerManagementService userInServerManagementService; - @Override public DefaultListenerResult execute(MessageReceivedModel model) { Message message = model.getMessage(); @@ -35,8 +30,7 @@ public class ExperienceTrackerListener implements AsyncMessageReceivedListener { return DefaultListenerResult.IGNORED; } if(userExperienceService.experienceGainEnabledInChannel(message.getChannel())) { - AUserInAServer cause = userInServerManagementService.loadOrCreateUser(model.getServerId(), model.getMessage().getAuthor().getIdLong()); - userExperienceService.addExperience(cause); + userExperienceService.addExperience(message.getMember(), model.getMessage()); return DefaultListenerResult.PROCESSED; } else { return DefaultListenerResult.IGNORED; diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/listener/JoiningUserRoleListener.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/listener/JoiningUserRoleListener.java index ee3aa27ae..6b138f71b 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/listener/JoiningUserRoleListener.java +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/listener/JoiningUserRoleListener.java @@ -42,7 +42,7 @@ public class JoiningUserRoleListener implements AsyncJoinListener { Optional userExperienceOptional = userExperienceManagementService.findByUserInServerIdOptional(aUserInAServer.getUserInServerId()); if(userExperienceOptional.isPresent()) { log.info("User {} joined {} with previous experience. Setting up experience role again (if necessary).", model.getJoiningUser().getUserId(), model.getServerId()); - userExperienceService.syncForSingleUser(userExperienceOptional.get()).thenAccept(result -> + userExperienceService.syncForSingleUser(userExperienceOptional.get(), model.getMember()).thenAccept(result -> log.info("Finished re-assigning experience for re-joining user {} in server {}.", model.getJoiningUser().getUserId(), model.getServerId()) ); } else { diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/repository/ExperienceRoleRepository.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/repository/ExperienceRoleRepository.java index 2f8ca073d..496635e10 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/repository/ExperienceRoleRepository.java +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/repository/ExperienceRoleRepository.java @@ -21,6 +21,7 @@ public interface ExperienceRoleRepository extends JpaRepository findByRole(ARole role); + List findByRole_IdIn(List role); /** * Finds a list of {@link AExperienceRole experienceRoles} (if there are multiple ones, because of misconfiguration) of the given diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/repository/UserExperienceRepository.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/repository/UserExperienceRepository.java index 02dc24573..ce1f6f909 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/repository/UserExperienceRepository.java +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/repository/UserExperienceRepository.java @@ -5,6 +5,7 @@ import dev.sheldan.abstracto.experience.model.database.AUserExperience; import dev.sheldan.abstracto.experience.model.database.LeaderBoardEntryResult; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @@ -52,4 +53,8 @@ public interface UserExperienceRepository extends JpaRepository> runtimeExperience = runTimeExperienceService.getRuntimeExperience(); - Long serverId = userInAServer.getServerReference().getId(); - Long userInServerId = userInAServer.getUserInServerId(); - if(runtimeExperience.containsKey(minute)) { - log.debug("Minute {} already tracked, adding user {} in server {}.", - minute, userInAServer.getUserReference().getId(), serverId); - List existing = runtimeExperience.get(minute); - for (ServerExperience server : existing) { - if (server.getServerId().equals(serverId) && server.getUserInServerIds().stream().noneMatch(userInServerId::equals)) { - server.getUserInServerIds().add(userInServerId); - break; + Map> runtimeExperience = runTimeExperienceService.getRuntimeExperience(); + Long serverId = member.getGuild().getIdLong(); + Long userId = member.getIdLong(); + boolean receivesNewExperience = false; + if(!runtimeExperience.containsKey(serverId)) { + runtimeExperience.put(serverId, new HashMap<>()); + receivesNewExperience = true; + } else { + Map serverExperience = runtimeExperience.get(serverId); + if(!serverExperience.containsKey(userId)) { + receivesNewExperience = true; + } else { + Instant latestExperience = serverExperience.get(userId); + if(latestExperience.isBefore(Instant.now())) { + receivesNewExperience = true; } } - - } else { - log.debug("Minute {} did not exist yet. Creating new entry for user {} in server {}.", minute, userInAServer.getUserReference().getId(), serverId); - ServerExperience serverExperience = ServerExperience - .builder() - .serverId(serverId) - .build(); - serverExperience.getUserInServerIds().add(userInServerId); - runtimeExperience.put(minute, new ArrayList<>(Arrays.asList(serverExperience))); + } + if(receivesNewExperience) { + Map serverExperience = runtimeExperience.get(serverId); + // we store when the user is eligible for experience _again_ + Long maxSeconds = configService.getLongValueOrConfigDefault(EXP_COOLDOWN_SECONDS_KEY, serverId); + serverExperience.put(userId, Instant.now().plus(maxSeconds, ChronoUnit.SECONDS)); + CompletableFuture.runAsync(() -> self.addExperienceToMember(member, message), experienceUpdateExecutor); } } finally { runTimeExperienceService.releaseLock(); @@ -148,317 +172,199 @@ public class AUserExperienceServiceBean implements AUserExperienceService { return false; } - @Transactional @Override - public CompletableFuture handleExperienceGain(List servers) { - List resultFutures = new ArrayList<>(); - List> futures = new ArrayList<>(); - CompletableFuture experienceFuture = new CompletableFuture<>(); - // TODO what if there are a lot in here...., transaction size etc - servers.forEach(serverExp -> { - List> memberFutures = new ArrayList<>(); - serverExp.getUserInServerIds().forEach(userInAServerId -> { - AUserInAServer userInAServer = userInServerManagementService.loadOrCreateUser(userInAServerId); - CompletableFuture memberFuture = memberService.getMemberInServerAsync(userInAServer); - memberFutures.add(memberFuture); - }); - - FutureUtils.toSingleFutureGeneric(memberFutures).whenComplete((unused, throwable) -> { - self.updateFoundMembers(memberFutures, serverExp.getServerId(), resultFutures, futures); - experienceFuture.complete(null); - }).exceptionally(throwable -> { - experienceFuture.completeExceptionally(throwable); + public CompletableFuture syncUserRolesWithFeedback(AServer server, MessageChannel messageChannel) { + List aUserExperiences = userExperienceManagementService.loadAllUsers(server); + List userIds = aUserExperiences + .stream() + .map(aUserExperience -> aUserExperience.getUser().getUserReference().getId()) + .collect(Collectors.toList()); + log.info("Synchronizing experience roles for {} users.", userIds.size()); + CompletableFuture returnFuture = new CompletableFuture<>(); + Long serverId = server.getId(); + int supposedUserCount = userIds.size(); + memberService.getMembersInServerAsync(server.getId(), userIds).whenComplete((members, throwable) -> { + if(throwable != null) { + log.warn("Failed to load all members in server {} for syncing experience. We started with {} and got {}.", + serverId, supposedUserCount, members.size(), throwable); + } + self.syncUsers(members, serverId, messageChannel).thenAccept(unused -> { + log.info("Finished syncing users for experience roles."); + returnFuture.complete(null); + }).exceptionally(throwable1 -> { + returnFuture.complete(null); return null; }); }); - return experienceFuture - .thenCompose(unused -> FutureUtils.toSingleFutureGeneric(futures)) - .whenComplete((unused, throwable) -> self.persistExperienceChanges(resultFutures)); + return returnFuture; } @Transactional - public void updateFoundMembers(List> memberFutures, Long serverId, List resultFutures, List> futures) { - List levels = experienceLevelManagementService.getLevelConfig(); - SystemConfigProperty defaultExpMultiplier = defaultConfigManagementService.getDefaultConfig(ExperienceFeatureConfig.EXP_MULTIPLIER_KEY); - SystemConfigProperty defaultMinExp = defaultConfigManagementService.getDefaultConfig(ExperienceFeatureConfig.MIN_EXP_KEY); - SystemConfigProperty defaultMaxExp = defaultConfigManagementService.getDefaultConfig(ExperienceFeatureConfig.MAX_EXP_KEY); - AServer server = serverManagementService.loadOrCreate(serverId); - int minExp = configService.getLongValue(ExperienceFeatureConfig.MIN_EXP_KEY, serverId, defaultMinExp.getLongValue()).intValue(); - int maxExp = configService.getLongValue(ExperienceFeatureConfig.MAX_EXP_KEY, serverId, defaultMaxExp.getLongValue()).intValue(); - Double multiplier = configService.getDoubleValue(ExperienceFeatureConfig.EXP_MULTIPLIER_KEY, serverId, defaultExpMultiplier.getDoubleValue()); - PrimitiveIterator.OfInt iterator = new Random().ints(memberFutures.size(), minExp, maxExp + 1).iterator(); - levels.sort(Comparator.comparing(AExperienceLevel::getExperienceNeeded)); + public CompletableFuture syncUsers(List members, Long serverId, MessageChannel messageChannel) { + AtomicInteger currentCount = new AtomicInteger(); + MessageToSend status = getUserSyncStatusUpdateModel(0, members.size(), serverId); + Message statusMessage = messageService.createStatusMessage(status, messageChannel).join(); + + AServer server = serverManagementService.loadServer(serverId); List roles = experienceRoleManagementService.getExperienceRolesForServer(server); - List disabledExpRoles = disabledExpRoleManagementService.getDisabledRolesForServer(server); - List disabledRoles = disabledExpRoles.stream().map(ADisabledExpRole::getRole).collect(Collectors.toList()); roles.sort(Comparator.comparing(role -> role.getLevel().getLevel())); - log.info("Handling {} experiences for server {}. Using {} roles.", memberFutures.size(), serverId, roles.size()); - memberFutures.forEach(future -> { - if(!future.isCompletedExceptionally()) { - Integer gainedExperience = iterator.next(); - gainedExperience = (int) Math.floor(gainedExperience * multiplier); - Member member = future.join(); - AUserInAServer userInAServer = userInServerManagementService.loadOrCreateUser(member); - Long userInServerId = userInAServer.getUserInServerId(); - if(!roleService.hasAnyOfTheRoles(member, disabledRoles)) { - log.debug("Handling {}. The user might gain {}.", userInServerId, gainedExperience); - Optional aUserExperienceOptional = userExperienceManagementService.findByUserInServerIdOptional(userInAServer.getUserInServerId()); - if(aUserExperienceOptional.isPresent()) { - AUserExperience aUserExperience = aUserExperienceOptional.get(); - if(Boolean.FALSE.equals(aUserExperience.getExperienceGainDisabled())) { - log.debug("User {} will gain experience.", userInServerId); - Long newExperienceCount = aUserExperience.getExperience() + gainedExperience.longValue(); - AExperienceLevel newLevel = calculateLevel(levels, newExperienceCount); - CompletableFuture resultFuture = updateUserRole(aUserExperience, roles, newLevel.getLevel()); - Long newMessageCount = aUserExperience.getMessageCount() + 1L; - ExperienceGainResult calculationResult = - ExperienceGainResult - .builder() - .calculationResult(resultFuture) - .newExperience(newExperienceCount) - .newMessageCount(newMessageCount) - .newLevel(newLevel.getLevel()) - .serverId(serverId) - .userInServerId(userInAServer.getUserInServerId()) - .build(); - resultFutures.add(calculationResult); - futures.add(resultFuture); - } else { - log.debug("Experience gain was disabled. User did not gain any experience."); - } - } else { - log.info("User experience for user {} was not found. Planning to create new instance.", userInAServer.getUserInServerId()); - Long newExperience = gainedExperience.longValue(); - AExperienceLevel newLevel = calculateLevel(levels, newExperience); - Long newMessageCount = 1L; - CompletableFuture resultFuture = applyInitialRole(userInAServer, roles, newLevel.getLevel()); - ExperienceGainResult calculationResult = - ExperienceGainResult - .builder() - .calculationResult(resultFuture) - .newExperience(newExperience) - .newMessageCount(newMessageCount) - .newLevel(newLevel.getLevel()) - .createUserExperience(true) - .userInServerId(userInAServer.getUserInServerId()) - .build(); - resultFutures.add(calculationResult); - futures.add(resultFuture); - } - } else { - log.debug("User {} has a role which makes the user unable to gain experience.", userInAServer.getUserInServerId()); - } - } + + List> futures = members + .stream() + .map(member -> this.syncUser(member, roles) + .thenAccept(unused -> { + currentCount.set(currentCount.get() + 1); + log.debug("Finished synchronizing {} users.", currentCount.get()); + if(currentCount.get() % 50 == 0) { + log.info("Notifying for {} current users with synchronize.", currentCount.get()); + MessageToSend newStatus = getUserSyncStatusUpdateModel(currentCount.get(), members.size(), serverId); + messageService.updateStatusMessage(messageChannel, statusMessage.getIdLong(), newStatus); + } + })) + .collect(Collectors.toList()); + return FutureUtils.toSingleFutureGeneric(futures).thenAccept(unused -> { + MessageToSend newStatus = getUserSyncStatusUpdateModel(currentCount.get(), members.size(), serverId); + messageService.updateStatusMessage(messageChannel, statusMessage.getIdLong(), newStatus); }); } - /** - * Calculates the appropriate {@link AExperienceRole experienceRole} based on the current level and awards the referenced the {@link AUserInAServer userinAServer} - * the {@link net.dv8tion.jda.api.entities.Role role}. If the user already left the guild, this will not award a {@link net.dv8tion.jda.api.entities.Role}, but just - * return the instance in order to be persisted - * @param aUserInAServer The {@link AUserInAServer userInAServer} which does not have a {@link AUserExperience userExperience} object, - * therefore we need to calculate the appropriate role and award the role - * @param roles A list of {@link AExperienceRole experienceRoles} representing the configuration which is used to calculate the appropriate - * {@link AExperienceRole experienceRole} - * @param currentLevel The current level of the user which was reached. - * @return A {@link CompletableFuture future} containing the {@link RoleCalculationResult result} of the role calculation, which completes after the user has been awarded the role. - */ - private CompletableFuture applyInitialRole(AUserInAServer aUserInAServer, List roles, Integer currentLevel) { - AExperienceRole role = experienceRoleService.calculateRole(roles, currentLevel); - if(role == null) { - log.debug("No experience role calculated. Applying none to user {} in server {}.", - aUserInAServer.getUserReference().getId(), aUserInAServer.getServerReference().getId()); - return CompletableFuture.completedFuture(RoleCalculationResult - .builder() - .userInServerId(aUserInAServer.getUserInServerId()) - .experienceRoleId(null) - .build()); - } - Long experienceRoleId = role.getId(); - Long userInServerId = aUserInAServer.getUserInServerId(); - log.debug("Applying {} as the first experience role for user {} in server {}.", - experienceRoleId, aUserInAServer.getUserReference().getId(), aUserInAServer.getServerReference().getId()); - return roleService.addRoleToUserAsync(aUserInAServer, role.getRole()).thenApply(aVoid -> RoleCalculationResult - .builder() - .experienceRoleId(experienceRoleId) - .userInServerId(userInServerId) - .build()); + public CompletableFuture syncUser(Member member, List roles) { + AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(member); + AUserExperience userExperience = userExperienceManagementService.findByUserInServerId(aUserInAServer.getUserInServerId()); + return calculateAndApplyExperienceRole(userExperience, member, roles); } - /** - * Persists the list of {@link ExperienceGainResult results} in the database. If the creation of {@link AUserExperience userExperience} object was requested, - * this will happen here, also the correct level is selected - * @param resultFutures A list of {@link ExperienceGainResult results} which define what should be changed for the given {@link AUserExperience userExperience} object: - * The level, experience, experienceRole, message account could change, or the object could not even exist - */ - @Transactional - public void persistExperienceChanges(List resultFutures) { - // we do have the _value_ of the level, but we require the actual instance - Map levels = experienceLevelManagementService.getLevelConfigAsMap(); - log.info("Storing {} experience gain results.", resultFutures.size()); - HashMap> serverRoleMapping = new HashMap<>(); - resultFutures.forEach(experienceGainResult -> { - AUserInAServer user = userInServerManagementService.loadOrCreateUser(experienceGainResult.getUserInServerId()); - AUserExperience userExperience; - if(experienceGainResult.isCreateUserExperience()) { - userExperience = userExperienceManagementService.createUserInServer(user); - log.info("Creating new experience user for user in server {}.", experienceGainResult.getUserInServerId()); + @Override + public CompletableFuture syncForSingleUser(AUserExperience userExperience, Member member) { + List roles = experienceRoleManagementService.getExperienceRolesForServer(userExperience.getServer()); + roles.sort(Comparator.comparing(role -> role.getLevel().getLevel())); + return calculateAndApplyExperienceRole(userExperience, member, roles); + } + + private CompletableFuture calculateAndApplyExperienceRole(AUserExperience userExperience, Member member, List roles) { + AExperienceRole calculatedNewRole = experienceRoleService.calculateRole(roles, userExperience.getCurrentLevel().getLevel()); + Long oldRoleId = userExperience.getCurrentExperienceRole() != null && userExperience.getCurrentExperienceRole().getRole() != null ? userExperience.getCurrentExperienceRole().getRole().getId() : null; + Long newRoleId = calculatedNewRole != null ? calculatedNewRole.getRole().getId() : null; + + userExperience.setCurrentExperienceRole(calculatedNewRole); + + CompletableFuture returningFuture; + if(!Objects.equals(oldRoleId, newRoleId)) { + CompletableFuture addingFuture; + if(oldRoleId != null) { + addingFuture = roleService.removeRoleFromMemberAsync(member, oldRoleId); } else { - userExperience = userExperienceManagementService.findByUserInServerId(experienceGainResult.getUserInServerId()); + addingFuture = CompletableFuture.completedFuture(null); } - userExperience.setMessageCount(experienceGainResult.getNewMessageCount()); - userExperience.setExperience(experienceGainResult.getNewExperience()); - // only search the levels if the level changed, or if there is no level currently set - AExperienceLevel currentLevel = userExperience.getCurrentLevel(); - boolean userExperienceHasLevel = currentLevel != null; - if(!userExperienceHasLevel || !currentLevel.getLevel().equals(experienceGainResult.getNewLevel())) { - AExperienceLevel foundLevel = levels.get(experienceGainResult.getNewLevel()); - if(foundLevel != null) { - log.info("User {} in server {} changed the level. Old level {}. New level {}.", experienceGainResult.getUserInServerId(), experienceGainResult.getServerId(), currentLevel.getLevel(), experienceGainResult.getNewLevel()); - userExperience.setCurrentLevel(foundLevel); - } else { - log.warn("User {} was present, but no level matching the calculation result {} could be found.", userExperience.getUser().getUserReference().getId(), experienceGainResult.getNewLevel()); - } + CompletableFuture removingFeature; + if(newRoleId != null) { + removingFeature = roleService.addRoleToMemberAsync(member, newRoleId); + } else { + removingFeature = CompletableFuture.completedFuture(null); } - AServer server = user.getServerReference(); - // "Caching" the experience roles for this server - if(!serverRoleMapping.containsKey(server.getId())) { - serverRoleMapping.put(server.getId(), experienceRoleManagementService.getExperienceRolesForServer(server)); - } - RoleCalculationResult roleCalculationResult = experienceGainResult.getCalculationResult().join(); - if(roleCalculationResult.getExperienceRoleId() != null) { - AExperienceRole role = experienceRoleManagementService.getExperienceRoleById(roleCalculationResult.getExperienceRoleId()); - userExperience.setCurrentExperienceRole(role); - } - if(experienceGainResult.isCreateUserExperience()) { - userExperienceManagementService.saveUser(userExperience); - } - }); - } - - @Override - public CompletableFuture updateUserRole(AUserExperience userExperience, List roles, Integer currentLevel) { - AUserInAServer user = userExperience.getUser(); - Function returnNullRole = aVoid -> RoleCalculationResult - .builder() - .userInServerId(user.getUserInServerId()) - .experienceRoleId(null) - .build(); - Long userInServerId = user.getUserInServerId(); - Long serverId = user.getServerReference().getId(); - log.debug("Updating experience role for user {} in server {}", user.getUserReference().getId(), serverId); - AExperienceRole role = experienceRoleService.calculateRole(roles, currentLevel); - boolean currentlyHasNoExperienceRole = userExperience.getCurrentExperienceRole() == null; - // if calculation results in no role, do not add a role - if(role == null) { - log.debug("User {} in server {} does not have an experience role, according to new calculation.", - user.getUserReference().getId(), serverId); - // if the user has a experience role currently, remove it - if(!currentlyHasNoExperienceRole && !userExperience.getCurrentExperienceRole().getRole().getDeleted()){ - return roleService.removeRoleFromUserAsync(user, userExperience.getCurrentExperienceRole().getRole()) - .thenApply(returnNullRole); - } - return CompletableFuture.completedFuture(returnNullRole.apply(null)); + returningFuture = CompletableFuture.allOf(addingFuture, removingFeature); + } else { + returningFuture = CompletableFuture.completedFuture(null); } - Long experienceRoleId = role.getId(); - Long roleId = role.getRole().getId(); - // if the new role is already the one configured in the database - Long userId = user.getUserReference().getId(); - Long oldUserExperienceRoleId = currentlyHasNoExperienceRole ? 0L : userExperience.getCurrentExperienceRole().getRole().getId(); - return memberService.getMemberInServerAsync(user).thenCompose(member -> { - boolean userHasRoleAlready = roleService.memberHasRole(member, roleId); - boolean userHasOldRole = false; - boolean rolesChanged = true; - if(!currentlyHasNoExperienceRole) { - userHasOldRole = roleService.memberHasRole(member, oldUserExperienceRoleId); - rolesChanged = !roleId.equals(oldUserExperienceRoleId); - } - Function fullResult = aVoid -> RoleCalculationResult - .builder() - .experienceRoleId(experienceRoleId) - .userInServerId(userInServerId) - .build(); - // if the roles changed or - // the user does not have the new target role already - // the user still has the old role - if(!userHasRoleAlready || userHasOldRole) { - CompletableFuture removalFuture; - if(userHasOldRole && rolesChanged) { - log.info("User {} in server {} loses experience role {}.", userId, serverId, oldUserExperienceRoleId); - removalFuture = roleService.removeRoleFromMemberAsync(member, oldUserExperienceRoleId); - } else { - removalFuture = CompletableFuture.completedFuture(null); - } - CompletableFuture addRoleFuture; - if(!userHasRoleAlready) { - log.info("User {} in server {} gets a new role {} because of experience.", userId, serverId, roleId); - addRoleFuture = roleService.addRoleToMemberAsync(member, roleId); - } else { - addRoleFuture = CompletableFuture.completedFuture(null); - } - return CompletableFuture.allOf(removalFuture, addRoleFuture).thenApply(fullResult); - } - // we are turning the full calculation result regardless - return CompletableFuture.completedFuture(fullResult.apply(null)); - }); + return returningFuture; } - @Override - public List> syncUserRoles(AServer server) { - List> results = new ArrayList<>(); - List aUserExperiences = userExperienceManagementService.loadAllUsers(server); - log.info("Found {} users to synchronize", aUserExperiences.size()); - List roles = experienceRoleManagementService.getExperienceRolesForServer(server); - roles.sort(Comparator.comparing(role -> role.getLevel().getLevel())); - for (int i = 0; i < aUserExperiences.size(); i++) { - AUserExperience userExperience = aUserExperiences.get(i); - log.info("Synchronizing {} out of {}. User in Server {}.", i, aUserExperiences.size(), userExperience.getUser().getUserInServerId()); - results.add(updateUserRole(userExperience, roles, userExperience.getCurrentLevel().getLevel())); - } - return results; - } - - @Override - public CompletableFuture syncUserRolesWithFeedback(AServer server, Long channelId) { - AChannel channel = channelManagementService.loadChannel(channelId); - List aUserExperiences = userExperienceManagementService.loadAllUsers(server); - log.info("Found {} users to synchronize", aUserExperiences.size()); - List roles = experienceRoleManagementService.getExperienceRolesForServer(server); - roles.sort(Comparator.comparing(role -> role.getLevel().getLevel())); - CompletableFutureList calculations = executeActionOnUserExperiencesWithFeedBack(aUserExperiences, channel, (AUserExperience experience) -> updateUserRole(experience, roles, experience.getLevelOrDefault())); - return calculations.getMainFuture().thenAccept(aVoid -> - self.syncRolesInStorage(calculations.getObjects()) - ); - } - - /** - * Updates the actually stored experience roles in the database - * @param results The list of {@link RoleCalculationResult results} which should be applied - */ @Transactional - public void syncRolesInStorage(List results) { - HashMap experienceRoleHashMap = new HashMap<>(); - results.forEach(result -> { - if(result != null) { - AUserInAServer user = userInServerManagementService.loadOrCreateUser(result.getUserInServerId()); - AUserExperience userExperience = userExperienceManagementService.findUserInServer(user); - log.debug("Updating experience role for {} in server {} to {}", user.getUserInServerId(), user.getServerReference().getId(), result.getExperienceRoleId()); - if(result.getExperienceRoleId() != null) { - log.debug("User experience {} gets new experience role with id {}.", userExperience.getId(), result.getExperienceRoleId()); - AExperienceRole role; - if(!experienceRoleHashMap.containsKey(result.getExperienceRoleId())) { - role = experienceRoleManagementService.getExperienceRoleById(result.getExperienceRoleId()); - experienceRoleHashMap.put(result.getExperienceRoleId(), role); - } else { - role = experienceRoleHashMap.get(result.getExperienceRoleId()); - } - userExperience.setCurrentExperienceRole(role); - } else { - log.debug("User experience {} does not get a user experience role.", userExperience.getId()); - userExperience.setCurrentExperienceRole(null); + public void addExperienceToMember(Member member, Message message) { + long serverId = member.getGuild().getIdLong(); + AServer server = serverManagementService.loadOrCreate(serverId); + List disabledExpRoles = disabledExpRoleManagementService.getDisabledRolesForServer(server); + List disabledRoles = disabledExpRoles + .stream() + .map(ADisabledExpRole::getRole) + .collect(Collectors.toList()); + if(roleService.hasAnyOfTheRoles(member, disabledRoles)) { + log.debug("User {} has a experience disable role in server {} - not giving any experience.", member.getIdLong(), serverId); + return; + } + List levels = experienceLevelManagementService.getLevelConfig(); + levels.sort(Comparator.comparing(AExperienceLevel::getExperienceNeeded)); + + Long minExp = configService.getLongValueOrConfigDefault(ExperienceFeatureConfig.MIN_EXP_KEY, serverId); + Long maxExp = configService.getLongValueOrConfigDefault(ExperienceFeatureConfig.MAX_EXP_KEY, serverId); + Double multiplier = configService.getDoubleValueOrConfigDefault(ExperienceFeatureConfig.EXP_MULTIPLIER_KEY, serverId); + Long experienceRange = maxExp - minExp + 1; + Long gainedExperience = (secureRandom.nextInt(experienceRange.intValue()) + minExp); + gainedExperience = (long) Math.floor(gainedExperience * multiplier); + + List roles = experienceRoleManagementService.getExperienceRolesForServer(server); + roles.sort(Comparator.comparing(role -> role.getLevel().getLevel())); + + AUserInAServer userInAServer = userInServerManagementService.loadOrCreateUser(member); + Long userInServerId = userInAServer.getUserInServerId(); + log.debug("Handling {}. The user might gain {}.", userInServerId, gainedExperience); + Optional aUserExperienceOptional = userExperienceManagementService.findByUserInServerIdOptional(userInAServer.getUserInServerId()); + AUserExperience aUserExperience = aUserExperienceOptional.orElseGet(() -> userExperienceManagementService.createUserInServer(userInAServer)); + if(Boolean.FALSE.equals(aUserExperience.getExperienceGainDisabled())) { + Long oldExperience = aUserExperience.getExperience(); + Long newExperienceCount = oldExperience + gainedExperience; + aUserExperience.setExperience(newExperienceCount); + AExperienceLevel newLevel = calculateLevel(levels, newExperienceCount); + RoleCalculationResult result = RoleCalculationResult + .builder() + .build(); + if(!Objects.equals(newLevel.getLevel(), aUserExperience.getCurrentLevel().getLevel())) { + Integer oldLevel = aUserExperience.getCurrentLevel() != null ? aUserExperience.getCurrentLevel().getLevel() : 0; + log.info("User {} in server {} changed level. New {}, Old {}.", member.getIdLong(), + member.getGuild().getIdLong(), newLevel.getLevel(), + oldLevel); + aUserExperience.setCurrentLevel(newLevel); + AExperienceRole calculatedNewRole = experienceRoleService.calculateRole(roles, newLevel.getLevel()); + Long oldRoleId = aUserExperience.getCurrentExperienceRole() != null ? aUserExperience.getCurrentExperienceRole().getRole().getId() : null; + Long newRoleId = calculatedNewRole != null ? calculatedNewRole.getRole().getId() : null; + result.setOldRoleId(oldRoleId); + result.setNewRoleId(newRoleId); + if(message != null && featureModeService.featureModeActive(ExperienceFeatureDefinition.EXPERIENCE, serverId, ExperienceFeatureMode.LEVEL_UP_NOTIFICATION)) { + LevelUpNotificationModel model = LevelUpNotificationModel + .builder() + .memberDisplay(MemberDisplay.fromMember(member)) + .oldExperience(oldExperience) + .newExperience(newExperienceCount) + .newLevel(newLevel.getLevel()) + .oldLevel(oldLevel) + .newRole(oldRoleId != null ? RoleDisplay.fromRole(oldRoleId) : null) + .newRole(newRoleId != null ? RoleDisplay.fromRole(newRoleId) : null) + .build(); + MessageToSend messageToSend = templateService.renderEmbedTemplate("experience_level_up_notification", model); + FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, message.getChannel())).thenAccept(unused -> { + log.info("Sent level up notification to user {} in server {} in channel {}.", member.getIdLong(), serverId, message.getChannel().getIdLong()); + }).exceptionally(throwable -> { + log.warn("Failed to send level up notification to user {} in server {} in channel {}.", member.getIdLong(), serverId, message.getChannel().getIdLong()); + return null; + }); + } + aUserExperience.setCurrentExperienceRole(calculatedNewRole); + } + aUserExperience.setMessageCount(aUserExperience.getMessageCount() + 1L); + if(!aUserExperienceOptional.isPresent()) { + userExperienceManagementService.saveUser(aUserExperience); + } + if(!Objects.equals(result.getOldRoleId(), result.getNewRoleId())) { + if(result.getOldRoleId() != null) { + roleService.removeRoleFromMemberAsync(member, result.getOldRoleId()).thenAccept(unused -> { + log.debug("Removed role {} to member {} in server {}.", result.getOldRoleId(), member.getIdLong(), member.getGuild().getIdLong()); + }).exceptionally(throwable -> { + log.warn("Failed to remove role {} from {} member {} in server {}.", result.getOldRoleId(), member.getIdLong(), member.getGuild().getIdLong(), throwable); + return null; + }); + } + if(result.getNewRoleId() != null) { + roleService.addRoleToMemberAsync(member, result.getNewRoleId()).thenAccept(unused -> { + log.debug("Added role {} to member {} in server {}.", result.getOldRoleId(), member.getIdLong(), member.getGuild().getIdLong()); + }).exceptionally(throwable -> { + log.warn("Failed to add role {} to {} member {} in server {}.", result.getOldRoleId(), member.getIdLong(), member.getGuild().getIdLong(), throwable); + return null; + }); } } - }); + } else { + log.debug("Experience gain was disabled. User did not gain any experience."); + } } @Override @@ -488,29 +394,6 @@ public class AUserExperienceServiceBean implements AUserExperienceService { return userExperience; } - @Override - public CompletableFutureList executeActionOnUserExperiencesWithFeedBack(List experiences, AChannel channel, Function> toExecute) { - List> futures = new ArrayList<>(); - Long serverId = channel.getServer().getId(); - MessageToSend status = getUserSyncStatusUpdateModel(0, experiences.size(), serverId); - Message statusMessage = messageService.createStatusMessage(status, channel).join(); - int interval = Math.min(Math.max(experiences.size() / 10, 1), 100); - for (int i = 0; i < experiences.size(); i++) { - if((i % interval) == 1) { - log.info("Updating feedback message with new index {} out of {}.", i, experiences.size()); - status = getUserSyncStatusUpdateModel(i, experiences.size(), serverId); - messageService.updateStatusMessage(channel, statusMessage.getIdLong(), status); - } - AUserExperience userExperience = experiences.get(i); - futures.add(toExecute.apply(userExperience)); - log.debug("Synchronizing {} out of {}. User in server ID {}.", i, experiences.size(), userExperience.getUser().getUserInServerId()); - } - status = getUserSyncStatusUpdateModel(experiences.size(), experiences.size(), serverId); - messageService.updateStatusMessage(channel, statusMessage.getIdLong(), status); - - return new CompletableFutureList<>(futures); - } - @Override public void disableExperienceForUser(AUserInAServer userInAServer) { log.info("Disabling experience gain for user {} in server {}.", userInAServer.getUserReference().getId(), userInAServer.getServerReference().getId()); @@ -537,15 +420,6 @@ public class AUserExperienceServiceBean implements AUserExperienceService { return templateService.renderEmbedTemplate("user_sync_status_message", statusModel, serverId); } - @Override - public CompletableFuture syncForSingleUser(AUserExperience userExperience) { - AUserInAServer user = userExperience.getUser(); - log.info("Synchronizing for user {} in server {}.", user.getUserReference().getId(), user.getServerReference().getId()); - List roles = experienceRoleManagementService.getExperienceRolesForServer(user.getServerReference()); - roles.sort(Comparator.comparing(role -> role.getLevel().getLevel())); - return updateUserRole(userExperience, roles, userExperience.getLevelOrDefault()); - } - @Override public LeaderBoard findLeaderBoardData(AServer server, Integer page) { if(page <= 0) { diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/service/ExperienceRoleServiceBean.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/service/ExperienceRoleServiceBean.java index bda3babcb..ba805aabd 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/service/ExperienceRoleServiceBean.java +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/service/ExperienceRoleServiceBean.java @@ -1,27 +1,29 @@ package dev.sheldan.abstracto.experience.service; -import dev.sheldan.abstracto.core.models.database.AChannel; import dev.sheldan.abstracto.core.models.database.ARole; import dev.sheldan.abstracto.core.models.database.AServer; +import dev.sheldan.abstracto.core.service.MessageService; import dev.sheldan.abstracto.core.service.RoleService; -import dev.sheldan.abstracto.core.service.management.ChannelManagementService; import dev.sheldan.abstracto.core.service.management.RoleManagementService; -import dev.sheldan.abstracto.core.utils.CompletableFutureList; -import dev.sheldan.abstracto.experience.model.RoleCalculationResult; +import dev.sheldan.abstracto.core.templating.model.MessageToSend; +import dev.sheldan.abstracto.core.templating.service.TemplateService; +import dev.sheldan.abstracto.core.utils.FutureUtils; import dev.sheldan.abstracto.experience.model.database.AExperienceLevel; import dev.sheldan.abstracto.experience.model.database.AExperienceRole; -import dev.sheldan.abstracto.experience.model.database.AUserExperience; import dev.sheldan.abstracto.experience.model.template.LevelRole; +import dev.sheldan.abstracto.experience.model.template.UserSyncStatusModel; import dev.sheldan.abstracto.experience.service.management.ExperienceLevelManagementService; import dev.sheldan.abstracto.experience.service.management.ExperienceRoleManagementService; import lombok.extern.slf4j.Slf4j; +import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @Component @@ -34,89 +36,46 @@ public class ExperienceRoleServiceBean implements ExperienceRoleService { @Autowired private ExperienceLevelManagementService experienceLevelService; - @Autowired - private AUserExperienceService userExperienceService; - - @Autowired - private ExperienceRoleServiceBean self; - @Autowired private RoleManagementService roleManagementService; - @Autowired - private ChannelManagementService channelManagementService; - @Autowired private RoleService roleService; - /** - * UnSets the current configuration for the passed level, and sets the {@link ARole} to be used for this level - * in the given {@link AServer} - * @param role The {@link ARole} to set the level to - * @param level The level the {@link ARole} should be awarded at - */ + @Autowired + private TemplateService templateService; + + @Autowired + private MessageService messageService; + @Override - public CompletableFuture setRoleToLevel(Role role, Integer level, Long channelId) { - Long roleId = role.getIdLong(); - ARole aRoleToSet = roleManagementService.findRole(roleId); + public CompletableFuture setRoleToLevel(Role role, Integer level, GuildMessageChannel messageChannel) { + ARole aRoleToSet = roleManagementService.findRole(role.getIdLong()); List experienceRoles = getExperienceRolesAtLevel(level, aRoleToSet.getServer()); - List rolesToUnset = experienceRoles.stream().map(AExperienceRole::getRole).collect(Collectors.toList()); + List rolesToUnset = experienceRoles + .stream() + .map(AExperienceRole::getRole) + .collect(Collectors.toList()); if(rolesToUnset.size() == 1 && rolesToUnset.contains(aRoleToSet)) { return CompletableFuture.completedFuture(null); } - if(!rolesToUnset.contains(aRoleToSet)) { - rolesToUnset.add(aRoleToSet); - } AExperienceLevel experienceLevel; if(!experienceRoles.isEmpty()) { experienceLevel = experienceRoles.get(0).getLevel(); } else { experienceLevel = experienceLevelService.getLevel(level); } - AExperienceRole newExperienceRole = experienceRoleManagementService.setLevelToRole(experienceLevel, aRoleToSet); - Long newlyCreatedExperienceRoleId = newExperienceRole.getId(); - CompletableFuture future = new CompletableFuture<>(); - unsetRoles(rolesToUnset, channelId, newExperienceRole).thenAccept(aVoid -> - self.unsetRoleInDb(level, roleId) - ).thenAccept(unused -> future.complete(null)).exceptionally(throwable -> { - self.deleteExperienceRoleViaId(newlyCreatedExperienceRoleId); - future.completeExceptionally(throwable); - return null; - }); - - return future; + experienceRoleManagementService.setLevelToRole(experienceLevel, aRoleToSet); + if(!rolesToUnset.isEmpty()) { + return unsetRoles(rolesToUnset, messageChannel); + } else { + return CompletableFuture.completedFuture(null); + } } - @Transactional - public void deleteExperienceRoleViaId(Long newlyCreatedExperienceRoleId) { - AExperienceRole reLoadedRole = experienceRoleManagementService.getExperienceRoleById(newlyCreatedExperienceRoleId); - experienceRoleManagementService.unsetRole(reLoadedRole); - } - - /** - * Removes all previous defined {@link AExperienceRole experienceRoles} from the given level and sets the {@link ARole} - * (defined by its ID) to the level. - * @param level The level which the {@link ARole role} should be set to - * @param roleId The ID of the {@link Role} which should have its level set - */ - @Transactional - public void unsetRoleInDb(Integer level, Long roleId) { - log.info("Unsetting role {} from level {}.", roleId, level); - AExperienceLevel experienceLevel = experienceLevelService.getLevelOptional(level).orElseThrow(() -> new IllegalArgumentException(String.format("Could not find level %s", level))); - ARole loadedRole = roleManagementService.findRole(roleId); - experienceRoleManagementService.removeAllRoleAssignmentsForLevelInServerExceptRole(experienceLevel, loadedRole.getServer(), loadedRole); - experienceRoleManagementService.setLevelToRole(experienceLevel, loadedRole); - } - - /** - * Deletes the {@link AExperienceRole} and recalculates the experience for all users which currently had the associated - * {@link net.dv8tion.jda.api.entities.Role}. - * @param role The {@link ARole} to remove from the {@link dev.sheldan.abstracto.experience.model.database.AExperienceRole} - * configuration - */ @Override - public CompletableFuture unsetRoles(ARole role, Long feedbackChannelId) { - return unsetRoles(Arrays.asList(role), feedbackChannelId); + public CompletableFuture unsetRoles(ARole role, GuildMessageChannel messageChannel) { + return unsetRoles(Arrays.asList(role), messageChannel); } @Override @@ -125,64 +84,56 @@ public class ExperienceRoleServiceBean implements ExperienceRoleService { return experienceRoleManagementService.getExperienceRolesAtLevelInServer(levelObj, server); } - @Override - public CompletableFuture unsetRoles(List rolesToUnset, Long feedbackChannelId) { - return unsetRoles(rolesToUnset, feedbackChannelId, null); - } @Override - public CompletableFuture unsetRoles(List rolesToUnset, Long feedbackChannelId, AExperienceRole toAdd) { - if(rolesToUnset.isEmpty()) { - return CompletableFuture.completedFuture(null); + public CompletableFuture unsetRoles(List rolesToUnset, GuildMessageChannel messageChannel) { + List rolesInServer = experienceRoleManagementService.getRolesInServer(rolesToUnset); + Integer totalCount = 0; + for (AExperienceRole aExperienceRole : rolesInServer) { + totalCount += aExperienceRole.getUsers().size(); } - AServer server = rolesToUnset.get(0).getServer(); - AChannel channel = channelManagementService.loadChannel(feedbackChannelId); - List experienceRolesNecessaryToRemove = new ArrayList<>(); - List usersToUpdate = new ArrayList<>(); - rolesToUnset.forEach(role -> { - Optional roleInServerOptional = experienceRoleManagementService.getRoleInServerOptional(role); - if(roleInServerOptional.isPresent()) { - AExperienceRole experienceRole = roleInServerOptional.get(); - experienceRolesNecessaryToRemove.add(experienceRole); - usersToUpdate.addAll(experienceRole.getUsers()); - } else { - log.info("Experience role {} is not defined in server {} - skipping unset.", role.getId(), server.getId()); + AtomicInteger totalCountAtomic = new AtomicInteger(totalCount); + long serverId = messageChannel.getGuild().getIdLong(); + MessageToSend status = getUserSyncStatusUpdateModel(0, totalCount, serverId); + Message statusMessage = messageService.createStatusMessage(status, messageChannel).join(); + AtomicInteger atomicInteger = new AtomicInteger(); + List> futures = new ArrayList<>(); + rolesInServer.forEach(experienceRole -> { + experienceRole.getUsers().forEach(aUserExperience -> { + futures.add(roleService.removeRoleFromUserAsync(aUserExperience.getUser(), experienceRole.getRole()).thenAccept(unused -> { + atomicInteger.set(atomicInteger.get() + 1); + log.debug("Finished synchronizing {} users.", atomicInteger.get()); + if(atomicInteger.get() % 50 == 0) { + log.info("Notifying for {} current users with synchronize.", atomicInteger.get()); + MessageToSend newStatus = getUserSyncStatusUpdateModel(atomicInteger.get(), totalCountAtomic.get(), serverId); + messageService.updateStatusMessage(messageChannel, statusMessage.getIdLong(), newStatus); + } + })); + }); + }); + CompletableFuture returningFuture = new CompletableFuture<>(); + experienceRoleManagementService.unsetRoles(rolesInServer); + FutureUtils.toSingleFutureGeneric(futures).whenComplete((unused, throwable) -> { + MessageToSend newStatus = getUserSyncStatusUpdateModel(atomicInteger.get(), totalCountAtomic.get(), serverId); + messageService.updateStatusMessage(messageChannel, statusMessage.getIdLong(), newStatus); + if(throwable != null) { + log.warn("Failed to unset role in server {}.", serverId, throwable); } + returningFuture.complete(null); }); - log.info("Recalculating the roles for {} users, because their current role was removed from experience tracking.", usersToUpdate.size()); - List roles = experienceRoleManagementService.getExperienceRolesForServer(server); - roles.removeIf(role1 -> experienceRolesNecessaryToRemove.stream().anyMatch(aExperienceRole -> aExperienceRole.getId().equals(role1.getId()))); - if(toAdd != null) { - roles.add(toAdd); - } - roles.sort(Comparator.comparing(innerRole -> innerRole.getLevel().getLevel())); - List roleIds = experienceRolesNecessaryToRemove.stream().map(AExperienceRole::getId).collect(Collectors.toList()); - if(toAdd != null) { - roleIds.removeIf(aLong -> aLong.equals(toAdd.getRole().getId())); - } - CompletableFutureList calculationResults = userExperienceService.executeActionOnUserExperiencesWithFeedBack(usersToUpdate, channel, - (AUserExperience ex) -> userExperienceService.updateUserRole(ex, roles, ex.getLevelOrDefault())); - return calculationResults.getMainFuture().thenAccept(aVoid -> self.persistData(calculationResults, roleIds)); + return returningFuture; } - /** - * Stores the changed experience roles for all of the {@link AUserExperience userExperiences} which are referenced in the list of - * {@link RoleCalculationResult results}. This is only executed after a role is being "unset", which means, we also - * have to remove the existing {@link AExperienceRole experienceRole} - * @param results A list of {@link CompletableFuture futures} which each contain a {@link RoleCalculationResult result}, for the members who got - * their {@link AExperienceRole experienceRole} removed - * @param roleIds The IDs of the {@link AExperienceRole experienceRoles} which were removed from the experience roles - */ - @Transactional - public void persistData(CompletableFutureList results, List roleIds) { - log.info("Persisting {} role calculation results.", results.getFutures().size()); - roleIds.forEach(roleId -> { - log.info("Deleting experience role {}.", roleId); - deleteExperienceRoleViaId(roleId); - }); - userExperienceService.syncRolesInStorage(results.getObjects()); + private MessageToSend getUserSyncStatusUpdateModel(Integer current, Integer total, Long serverId) { + UserSyncStatusModel statusModel = UserSyncStatusModel + .builder() + .currentCount(current) + .totalUserCount(total) + .build(); + return templateService.renderEmbedTemplate("user_sync_status_message", statusModel, serverId); } + @Override public AExperienceRole calculateRole(List roles, Integer currentLevel) { if(roles == null || roles.isEmpty()) { diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/service/RunTimeExperienceService.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/service/RunTimeExperienceService.java index 0acf22e57..e37a41401 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/service/RunTimeExperienceService.java +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/service/RunTimeExperienceService.java @@ -1,12 +1,9 @@ package dev.sheldan.abstracto.experience.service; -import dev.sheldan.abstracto.core.metric.service.CounterMetric; -import dev.sheldan.abstracto.core.metric.service.MetricService; -import dev.sheldan.abstracto.experience.model.ServerExperience; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; +import java.time.Instant; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -20,18 +17,9 @@ import java.util.concurrent.locks.ReentrantLock; @Component public class RunTimeExperienceService { - @Autowired - private MetricService metricService; - - public static final String EXPERIENCE_RUNTIME_STORAGE = "experience.runtime.storage"; - private static final CounterMetric EXPERIENCE_RUNTIME_STORAGE_METRIC = CounterMetric - .builder() - .name(EXPERIENCE_RUNTIME_STORAGE) - .build(); - - private Map> runtimeExperience = new HashMap<>(); + private Map> runtimeExperience = new HashMap<>(); private static final Lock lock = new ReentrantLock(); - public Map> getRuntimeExperience() { + public Map> getRuntimeExperience() { return runtimeExperience; } @@ -49,12 +37,16 @@ public class RunTimeExperienceService { lock.unlock(); } - @PostConstruct - public void postConstruct() { - metricService.registerGauge(EXPERIENCE_RUNTIME_STORAGE_METRIC, runtimeExperience, serverList -> serverList.values().stream() - .mapToInt(minuteEntry -> minuteEntry.stream() - .mapToInt(individualServerList -> individualServerList.getUserInServerIds().size()).sum()).sum(), - "Number of entries in runtime experience storage"); + public void cleanupRunTimeStorage() { + Instant now = Instant.now(); + runtimeExperience.forEach((serverId, userInstantMap) -> { + List userIdsToRemove = new ArrayList<>(); + userInstantMap.forEach((userId, instant) -> { + if(instant.isBefore(now)) { + userIdsToRemove.add(userId); + } + }); + userIdsToRemove.forEach(userInstantMap::remove); + }); } - } \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/service/management/ExperienceRoleManagementServiceBean.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/service/management/ExperienceRoleManagementServiceBean.java index 7eb23c1d5..3ab797860 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/service/management/ExperienceRoleManagementServiceBean.java +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/service/management/ExperienceRoleManagementServiceBean.java @@ -3,7 +3,6 @@ package dev.sheldan.abstracto.experience.service.management; import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException; import dev.sheldan.abstracto.core.models.database.ARole; import dev.sheldan.abstracto.core.models.database.AServer; -import dev.sheldan.abstracto.core.service.management.RoleManagementService; import dev.sheldan.abstracto.experience.model.database.AExperienceLevel; import dev.sheldan.abstracto.experience.model.database.AExperienceRole; import dev.sheldan.abstracto.experience.repository.ExperienceRoleRepository; @@ -13,6 +12,7 @@ import org.springframework.stereotype.Component; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; @Component @Slf4j @@ -22,7 +22,7 @@ public class ExperienceRoleManagementServiceBean implements ExperienceRoleManage private ExperienceRoleRepository experienceRoleRepository; @Autowired - private RoleManagementService roleManagementService; + private UserExperienceManagementService userExperienceManagementService; /** * Removes *all* assignments of roles for the given level @@ -46,12 +46,30 @@ public class ExperienceRoleManagementServiceBean implements ExperienceRoleManage experienceRoleRepository.delete(role); } + @Override + public void unsetRoles(List roles) { + log.info("Deleting {} roles.", roles.size()); + roles.forEach(experienceRole -> { + userExperienceManagementService.removeExperienceRoleFromUsers(experienceRole); + }); + experienceRoleRepository.deleteAll(roles); + } + @Override public AExperienceRole getRoleInServer(ARole role) { // TODO throw different exception return this.getRoleInServerOptional(role).orElseThrow(AbstractoRunTimeException::new); } + @Override + public List getRolesInServer(List roles) { + List roleIds = roles + .stream() + .map(ARole::getId) + .collect(Collectors.toList()); + return experienceRoleRepository.findByRole_IdIn(roleIds); + } + @Override public Optional getRoleInServerOptional(ARole role) { return experienceRoleRepository.findByRole(role); diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/service/management/UserExperienceManagementServiceBean.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/service/management/UserExperienceManagementServiceBean.java index 332ac4226..8bf694380 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/service/management/UserExperienceManagementServiceBean.java +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/java/dev/sheldan/abstracto/experience/service/management/UserExperienceManagementServiceBean.java @@ -5,6 +5,7 @@ import dev.sheldan.abstracto.core.exception.UserInServerNotFoundException; import dev.sheldan.abstracto.core.models.database.AServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.experience.model.database.AExperienceLevel; +import dev.sheldan.abstracto.experience.model.database.AExperienceRole; import dev.sheldan.abstracto.experience.model.database.AUserExperience; import dev.sheldan.abstracto.experience.model.database.LeaderBoardEntryResult; import dev.sheldan.abstracto.experience.repository.UserExperienceRepository; @@ -32,6 +33,11 @@ public class UserExperienceManagementServiceBean implements UserExperienceManage return byId.orElseGet(() -> createUserInServer(aUserInAServer)); } + @Override + public void removeExperienceRoleFromUsers(AExperienceRole experienceRole) { + repository.removeExperienceRoleFromUsers(experienceRole.getId()); + } + @Override public Optional findByUserInServerIdOptional(Long userInServerId) { return repository.findById(userInServerId); diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/resources/experience-config.properties b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/resources/experience-config.properties index 43b12d05c..fa4815110 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/resources/experience-config.properties +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/resources/experience-config.properties @@ -6,4 +6,11 @@ abstracto.systemConfigs.expMultiplier.name=expMultiplier abstracto.systemConfigs.expMultiplier.doubleValue=1 abstracto.featureFlags.experience.featureName=experience -abstracto.featureFlags.experience.enabled=false \ No newline at end of file +abstracto.featureFlags.experience.enabled=false + +abstracto.systemConfigs.expCooldownSeconds.name=expCooldownSeconds +abstracto.systemConfigs.expCooldownSeconds.longValue=60 + +abstracto.featureModes.levelUpNotification.featureName=experience +abstracto.featureModes.levelUpNotification.mode=levelUpNotification +abstracto.featureModes.levelUpNotification.enabled=false \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/resources/migrations/1.4.8/collection.xml b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/resources/migrations/1.4.8/collection.xml new file mode 100644 index 000000000..2591284a9 --- /dev/null +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/resources/migrations/1.4.8/collection.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/resources/migrations/1.4.8/tables/tables.xml b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/resources/migrations/1.4.8/tables/tables.xml new file mode 100644 index 000000000..33189ef9b --- /dev/null +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/resources/migrations/1.4.8/tables/tables.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/resources/migrations/1.4.8/tables/user_experience.xml b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/resources/migrations/1.4.8/tables/user_experience.xml new file mode 100644 index 000000000..95b9e04db --- /dev/null +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/resources/migrations/1.4.8/tables/user_experience.xml @@ -0,0 +1,13 @@ + + + + + + + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/resources/migrations/1.4.8/update/jobs.xml b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/resources/migrations/1.4.8/update/jobs.xml new file mode 100644 index 000000000..948af4283 --- /dev/null +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/resources/migrations/1.4.8/update/jobs.xml @@ -0,0 +1,18 @@ + + + + + + + + name='experienceJob' + + + + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/resources/migrations/1.4.8/update/updates.xml b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/resources/migrations/1.4.8/update/updates.xml new file mode 100644 index 000000000..497c7ac6a --- /dev/null +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/resources/migrations/1.4.8/update/updates.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/resources/migrations/experience-changeLog.xml b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/resources/migrations/experience-changeLog.xml index 3c5281d58..15cf8eebe 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/resources/migrations/experience-changeLog.xml +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/main/resources/migrations/experience-changeLog.xml @@ -8,4 +8,5 @@ http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" > + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/test/java/dev/sheldan/abstracto/experience/command/SetExpRoleTest.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/test/java/dev/sheldan/abstracto/experience/command/SetExpRoleTest.java deleted file mode 100644 index 05c5c005b..000000000 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/test/java/dev/sheldan/abstracto/experience/command/SetExpRoleTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package dev.sheldan.abstracto.experience.command; - -import dev.sheldan.abstracto.core.command.execution.CommandContext; -import dev.sheldan.abstracto.core.command.execution.CommandResult; -import dev.sheldan.abstracto.core.service.RoleService; -import dev.sheldan.abstracto.core.service.management.RoleManagementService; -import dev.sheldan.abstracto.core.test.command.CommandConfigValidator; -import dev.sheldan.abstracto.core.test.command.CommandTestUtilities; -import dev.sheldan.abstracto.experience.service.ExperienceRoleService; -import net.dv8tion.jda.api.entities.Role; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnitRunner; - -import java.util.Arrays; -import java.util.concurrent.CompletableFuture; - -import static org.mockito.Mockito.when; - -@RunWith(MockitoJUnitRunner.class) -public class SetExpRoleTest { - - @InjectMocks - private SetExpRole testUnit; - - @Mock - private ExperienceRoleService experienceRoleService; - - @Mock - private RoleService roleService; - - @Mock - private RoleManagementService roleManagementService; - - private static final Long CHANNEL_ID = 4L; - - @Test - public void setExpRole() { - CommandContext noParameters = CommandTestUtilities.getNoParameters(); - Role roleToChange = Mockito.mock(Role.class); - when(roleToChange.getGuild()).thenReturn(noParameters.getGuild()); - Integer levelToSetTo = 4; - when(noParameters.getChannel().getIdLong()).thenReturn(CHANNEL_ID); - CommandContext context = CommandTestUtilities.enhanceWithParameters(noParameters, Arrays.asList(levelToSetTo, roleToChange)); - when(experienceRoleService.setRoleToLevel(roleToChange, levelToSetTo, CHANNEL_ID)).thenReturn(CompletableFuture.completedFuture(null)); - CompletableFuture result = testUnit.executeAsync(context); - CommandTestUtilities.checkSuccessfulCompletionAsync(result); - } - - @Test - public void validateCommand() { - CommandConfigValidator.validateCommandConfiguration(testUnit.getConfiguration()); - } -} diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/test/java/dev/sheldan/abstracto/experience/command/SyncRolesTest.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/test/java/dev/sheldan/abstracto/experience/command/SyncRolesTest.java index 13593657c..1da982a66 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/test/java/dev/sheldan/abstracto/experience/command/SyncRolesTest.java +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/test/java/dev/sheldan/abstracto/experience/command/SyncRolesTest.java @@ -8,6 +8,8 @@ import dev.sheldan.abstracto.core.service.management.ServerManagementService; import dev.sheldan.abstracto.core.test.command.CommandConfigValidator; import dev.sheldan.abstracto.core.test.command.CommandTestUtilities; import dev.sheldan.abstracto.experience.service.AUserExperienceService; +import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; +import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; @@ -28,21 +30,19 @@ public class SyncRolesTest { @Mock private AUserExperienceService userExperienceService; - @Mock - private ChannelManagementService channelManagementService; - @Mock private ServerManagementService serverManagementService; - private static final Long CHANNEL_ID = 4L; + @Mock + private GuildMessageChannel messageChannel; @Test public void executeCommand() { - CommandContext context = CommandTestUtilities.getNoParameters(); + CommandContext context = Mockito.mock(CommandContext.class); AServer server = Mockito.mock(AServer.class); when(serverManagementService.loadServer(context.getGuild())).thenReturn(server); - when(context.getChannel().getIdLong()).thenReturn(CHANNEL_ID); - when(userExperienceService.syncUserRolesWithFeedback(server, CHANNEL_ID)).thenReturn(CompletableFuture.completedFuture(null)); + when(context.getChannel()).thenReturn(messageChannel); + when(userExperienceService.syncUserRolesWithFeedback(server, messageChannel)).thenReturn(CompletableFuture.completedFuture(null)); CompletableFuture result = testUnit.executeAsync(context); CommandTestUtilities.checkSuccessfulCompletionAsync(result); } diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/test/java/dev/sheldan/abstracto/experience/command/UnSetExpRoleTest.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/test/java/dev/sheldan/abstracto/experience/command/UnSetExpRoleTest.java deleted file mode 100644 index 80761f72f..000000000 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/test/java/dev/sheldan/abstracto/experience/command/UnSetExpRoleTest.java +++ /dev/null @@ -1,63 +0,0 @@ -package dev.sheldan.abstracto.experience.command; - -import dev.sheldan.abstracto.core.command.execution.CommandContext; -import dev.sheldan.abstracto.core.command.execution.CommandResult; -import dev.sheldan.abstracto.core.models.database.ARole; -import dev.sheldan.abstracto.core.models.database.AServer; -import dev.sheldan.abstracto.core.service.management.RoleManagementService; -import dev.sheldan.abstracto.core.test.command.CommandConfigValidator; -import dev.sheldan.abstracto.core.test.command.CommandTestUtilities; -import dev.sheldan.abstracto.experience.model.database.AExperienceRole; -import dev.sheldan.abstracto.experience.service.ExperienceRoleService; -import dev.sheldan.abstracto.experience.service.management.ExperienceRoleManagementService; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnitRunner; - -import java.util.Arrays; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; - -import static org.mockito.Mockito.when; - -@RunWith(MockitoJUnitRunner.class) -public class UnSetExpRoleTest { - - @InjectMocks - private UnSetExpRole testUnit; - - @Mock - private ExperienceRoleService experienceRoleService; - - @Mock - private RoleManagementService roleManagementService; - - @Mock - private ExperienceRoleManagementService experienceRoleManagementService; - - private static final Long CHANNEL_ID = 4L; - - @Test - public void setUnSetExpRole() { - CommandContext noParameters = CommandTestUtilities.getNoParameters(); - ARole changedRole = Mockito.mock(ARole.class); - CommandContext context = CommandTestUtilities.enhanceWithParameters(noParameters, Arrays.asList(changedRole)); - when(context.getChannel().getIdLong()).thenReturn(CHANNEL_ID); - ARole actualRole = Mockito.mock(ARole.class); - AServer server = Mockito.mock(AServer.class); - when(actualRole.getServer()).thenReturn(server); - when(roleManagementService.findRole(changedRole.getId())).thenReturn(actualRole); - when(experienceRoleManagementService.getRoleInServerOptional(actualRole)).thenReturn(Optional.of(Mockito.mock(AExperienceRole.class))); - when(experienceRoleService.unsetRoles(actualRole, CHANNEL_ID)).thenReturn(CompletableFuture.completedFuture(null)); - CompletableFuture result = testUnit.executeAsync(context); - CommandTestUtilities.checkSuccessfulCompletionAsync(result); - } - - @Test - public void validateCommand() { - CommandConfigValidator.validateCommandConfiguration(testUnit.getConfiguration()); - } -} diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/test/java/dev/sheldan/abstracto/experience/listener/ExperienceTrackerListenerTest.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/test/java/dev/sheldan/abstracto/experience/listener/ExperienceTrackerListenerTest.java deleted file mode 100644 index 1be33b9b9..000000000 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/test/java/dev/sheldan/abstracto/experience/listener/ExperienceTrackerListenerTest.java +++ /dev/null @@ -1,60 +0,0 @@ -package dev.sheldan.abstracto.experience.listener; - -import dev.sheldan.abstracto.core.models.database.AUserInAServer; -import dev.sheldan.abstracto.core.models.listener.MessageReceivedModel; -import dev.sheldan.abstracto.core.service.management.UserInServerManagementService; -import dev.sheldan.abstracto.experience.service.AUserExperienceService; -import net.dv8tion.jda.api.entities.Message; -import net.dv8tion.jda.api.entities.MessageType; -import net.dv8tion.jda.api.entities.User; -import net.dv8tion.jda.api.entities.channel.unions.MessageChannelUnion; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnitRunner; - -import static org.mockito.Mockito.*; - -@RunWith(MockitoJUnitRunner.class) -public class ExperienceTrackerListenerTest { - - @InjectMocks - public ExperienceTrackerListener testUnit; - - @Mock - private AUserExperienceService userExperienceService; - - @Mock - private UserInServerManagementService userInServerManagementService; - - @Mock - private MessageReceivedModel model; - - @Mock - private User user; - - private static final Long SERVER_ID = 4L; - private static final Long USER_ID = 5L; - - @Test - public void testExperienceTracking() { - AUserInAServer userInAServer = Mockito.mock(AUserInAServer.class); - Message mockedMessage = Mockito.mock(Message.class); - when(mockedMessage.isFromGuild()).thenReturn(true); - when(mockedMessage.isWebhookMessage()).thenReturn(false); - MessageChannelUnion channel = Mockito.mock(MessageChannelUnion.class); - MessageType type = MessageType.DEFAULT; - when(mockedMessage.getType()).thenReturn(type); - when(userInServerManagementService.loadOrCreateUser(SERVER_ID, USER_ID)).thenReturn(userInAServer); - when(model.getMessage()).thenReturn(mockedMessage); - when(userExperienceService.experienceGainEnabledInChannel(channel)).thenReturn(true); - when(mockedMessage.getChannel()).thenReturn(channel); - when(model.getServerId()).thenReturn(SERVER_ID); - when(mockedMessage.getAuthor()).thenReturn(user); - when(user.getIdLong()).thenReturn(USER_ID); - testUnit.execute(model); - verify(userExperienceService, times(1)).addExperience(userInAServer); - } -} diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/test/java/dev/sheldan/abstracto/experience/listener/JoiningUserRoleListenerTest.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/test/java/dev/sheldan/abstracto/experience/listener/JoiningUserRoleListenerTest.java index bb654f04d..93758cc92 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/test/java/dev/sheldan/abstracto/experience/listener/JoiningUserRoleListenerTest.java +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/test/java/dev/sheldan/abstracto/experience/listener/JoiningUserRoleListenerTest.java @@ -8,6 +8,7 @@ import dev.sheldan.abstracto.core.service.management.UserInServerManagementServi import dev.sheldan.abstracto.experience.model.database.AUserExperience; import dev.sheldan.abstracto.experience.service.AUserExperienceService; import dev.sheldan.abstracto.experience.service.management.UserExperienceManagementService; +import net.dv8tion.jda.api.entities.Member; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -46,6 +47,9 @@ public class JoiningUserRoleListenerTest { @Mock private MemberJoinModel model; + @Mock + private Member member; + private static final Long SERVER_ID = 1L; private static final Long USER_ID = 2L; private static final Long USER_IN_SERVER_ID = 3L; @@ -63,7 +67,8 @@ public class JoiningUserRoleListenerTest { public void testUserWithExperienceRejoining() { AUserExperience experience = Mockito.mock(AUserExperience.class); when(userExperienceManagementService.findByUserInServerIdOptional(USER_IN_SERVER_ID)).thenReturn(Optional.of(experience)); - when(userExperienceService.syncForSingleUser(experience)).thenReturn(CompletableFuture.completedFuture(null)); + when(userExperienceService.syncForSingleUser(experience, member)).thenReturn(CompletableFuture.completedFuture(null)); + when(model.getMember()).thenReturn(member); DefaultListenerResult result = testUnit.execute(model); Assert.assertEquals(DefaultListenerResult.PROCESSED, result); } @@ -72,7 +77,7 @@ public class JoiningUserRoleListenerTest { public void testUserWithOutExperienceRejoining() { when(userExperienceManagementService.findByUserInServerIdOptional(USER_IN_SERVER_ID)).thenReturn(Optional.empty()); testUnit.execute(model); - verify(userExperienceService, times(0)).syncForSingleUser(any()); + verify(userExperienceService, times(0)).syncForSingleUser(any(), any()); } } diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/test/java/dev/sheldan/abstracto/experience/service/AUserExperienceServiceBeanTest.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/test/java/dev/sheldan/abstracto/experience/service/AUserExperienceServiceBeanTest.java deleted file mode 100644 index 35440b90a..000000000 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/test/java/dev/sheldan/abstracto/experience/service/AUserExperienceServiceBeanTest.java +++ /dev/null @@ -1,830 +0,0 @@ -package dev.sheldan.abstracto.experience.service; - -import dev.sheldan.abstracto.core.models.database.*; -import dev.sheldan.abstracto.core.models.property.SystemConfigProperty; -import dev.sheldan.abstracto.core.service.*; -import dev.sheldan.abstracto.core.service.management.ChannelManagementService; -import dev.sheldan.abstracto.core.service.management.DefaultConfigManagementService; -import dev.sheldan.abstracto.core.service.management.ServerManagementService; -import dev.sheldan.abstracto.core.service.management.UserInServerManagementService; -import dev.sheldan.abstracto.experience.config.ExperienceFeatureConfig; -import dev.sheldan.abstracto.experience.exception.NoExperienceTrackedException; -import dev.sheldan.abstracto.experience.model.*; -import dev.sheldan.abstracto.experience.model.database.AExperienceLevel; -import dev.sheldan.abstracto.experience.model.database.AExperienceRole; -import dev.sheldan.abstracto.experience.model.database.AUserExperience; -import dev.sheldan.abstracto.experience.model.database.LeaderBoardEntryResult; -import dev.sheldan.abstracto.experience.model.template.UserSyncStatusModel; -import dev.sheldan.abstracto.experience.service.management.DisabledExpRoleManagementService; -import dev.sheldan.abstracto.experience.service.management.ExperienceLevelManagementService; -import dev.sheldan.abstracto.experience.service.management.ExperienceRoleManagementService; -import dev.sheldan.abstracto.experience.service.management.UserExperienceManagementService; -import dev.sheldan.abstracto.core.templating.model.MessageToSend; -import dev.sheldan.abstracto.core.templating.service.TemplateService; -import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.Message; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.*; -import org.mockito.junit.MockitoJUnitRunner; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; - -import static org.mockito.Mockito.*; - -@RunWith(MockitoJUnitRunner.class) -public class AUserExperienceServiceBeanTest { - - @InjectMocks - private AUserExperienceServiceBean testUnit; - - @Mock - private UserExperienceManagementService userExperienceManagementService; - - @Mock - private ExperienceRoleService experienceRoleService; - - @Mock - private ExperienceLevelManagementService experienceLevelManagementService; - - @Mock - private ExperienceRoleManagementService experienceRoleManagementService; - - @Mock - private ConfigService configService; - - @Mock - private RoleService roleService; - - @Mock - private MessageService messageService; - - @Mock - private TemplateService templateService; - - @Mock - private ChannelManagementService channelManagementService; - - @Mock - private DisabledExpRoleManagementService disabledExpRoleManagementService; - - @Mock - private MemberService memberService; - - @Mock - private ServerManagementService serverManagementService; - - @Mock - private UserInServerManagementService userInServerManagementService; - - @Mock - private AUserExperienceServiceBean self; - - @Mock - private DefaultConfigManagementService defaultConfigManagementService; - - @Mock - private AUserExperience userExperience; - - @Mock - private AUserExperience userExperience2; - - @Mock - private AUserInAServer aUserInAServer; - - @Mock - private AUserInAServer aUserInAServer2; - - @Mock - private AUser user; - - @Mock - private AUser user2; - - @Mock - private ServerExperience serverExperience; - - @Mock - private AServer server; - - @Mock - private Member firstMember; - - @Mock - private Member secondMember; - - @Mock - private AExperienceLevel level0; - - @Mock - private AExperienceLevel level1; - - @Mock - private AExperienceLevel level2; - - @Mock - private AExperienceLevel level3; - - @Mock - private AExperienceRole experienceRole1; - - @Mock - private ARole aRole1; - - @Mock - private AExperienceRole experienceRole2; - - @Mock - private ARole aRole2; - - private List levels = new ArrayList<>(); - private List experienceRoles = new ArrayList<>(); - - private static final Long USER_IN_SERVER_ID = 4L; - private static final Long USER_ID = 8L; - private static final Long SERVER_ID = 9L; - private static final Long CHANNEL_ID = 7L; - private static final Long DEFAULT_MIN_EXP = 10L; - private static final Long DEFAULT_MAX_EXP = 25L; - private static final Double DEFAULT_EXP_MULTIPLIER = 1D; - private static final Long LOW_EXP = 50L; - private static final Long MID_EXP = 250L; - private static final Long HIGH_EXP = 500L; - private static final Long LVL_0_EXP = 0L; - private static final Long LVL_1_EXP = 100L; - private static final Long LVL_2_EXP = 200L; - private static final Long LVL_3_EXP = 300L; - private static final Integer ZERO_LVL = 0; - private static final Integer SECOND_LVL = 2; - - private static final Long ROLE_ID = 4L; - private static final Long SECOND_ROLE_ID = 7L; - private static final Long MESSAGE_COUNT = 10L; - - @Before - public void setup() { - this.levels = Arrays.asList(level0, level1, level2, level3); - this.experienceRoles = Arrays.asList(experienceRole1, experienceRole2); - } - - @Test - public void testCalculateLevelTooLow() { - this.levels = Arrays.asList(level0, level1); - setupLevels(1); - AExperienceLevel calculatedLevel = testUnit.calculateLevel(levels, LOW_EXP); - Assert.assertEquals(level0, calculatedLevel); - } - - @Test - public void testCalculateLevelBetweenLevels() { - this.levels = Arrays.asList(level0, level1, level2); - setupLevels(3); - AExperienceLevel calculatedLevel = testUnit.calculateLevel(levels, MID_EXP); - Assert.assertEquals(level2, calculatedLevel); - } - - @Test - public void testCalculateLevelTooHigh() { - this.levels = Arrays.asList(level0, level1, level2, level3); - setupLevels(3); - AExperienceLevel calculatedLevel = testUnit.calculateLevel(levels, HIGH_EXP); - Assert.assertEquals(level3, calculatedLevel); - } - - @Test - public void testUpdateUserExperienceLevelChanged() { - AUserExperience experienceToCalculate = Mockito.mock(AUserExperience.class); - when(experienceToCalculate.getUser()).thenReturn(aUserInAServer); - when(aUserInAServer.getUserReference()).thenReturn(user); - when(experienceToCalculate.getCurrentLevel()).thenReturn(level0); - when(level0.getLevel()).thenReturn(ZERO_LVL); - when(level2.getLevel()).thenReturn(SECOND_LVL); - setupLevels(3); - Assert.assertTrue(testUnit.updateUserLevel(experienceToCalculate, levels, MID_EXP)); - verify(experienceToCalculate, times(1)).setCurrentLevel(level2); - } - - @Test - public void testUpdateUserExperienceLevelNotChanged() { - AUserExperience experienceToCalculate = Mockito.mock(AUserExperience.class); - when(experienceToCalculate.getCurrentLevel()).thenReturn(level2); - when(experienceToCalculate.getExperience()).thenReturn(MID_EXP); - setupLevels(3); - Assert.assertFalse(testUnit.updateUserLevel(experienceToCalculate, levels, experienceToCalculate.getExperience())); - } - - @Test - public void testHandleExperienceGainSingleUser() { - when(serverExperience.getUserInServerIds()).thenReturn(Arrays.asList(USER_IN_SERVER_ID)); - when(userInServerManagementService.loadOrCreateUser(USER_IN_SERVER_ID)).thenReturn(aUserInAServer); - when(memberService.getMemberInServerAsync(aUserInAServer)).thenReturn(CompletableFuture.completedFuture(firstMember)); - when(serverExperience.getServerId()).thenReturn(SERVER_ID); - testUnit.handleExperienceGain(Arrays.asList(serverExperience)).join(); - ArgumentCaptor>> listArgumentCaptor = ArgumentCaptor.forClass(List.class); - verify(self, times(1)).updateFoundMembers(listArgumentCaptor.capture(), eq(SERVER_ID), anyList(), anyList()); - Assert.assertEquals(firstMember, listArgumentCaptor.getValue().get(0).join()); - } - - @Test - public void testHandleExperienceMemberFailed() { - when(serverExperience.getUserInServerIds()).thenReturn(Arrays.asList(USER_IN_SERVER_ID)); - when(userInServerManagementService.loadOrCreateUser(USER_IN_SERVER_ID)).thenReturn(aUserInAServer); - CompletableFuture future = new CompletableFuture<>(); - when(memberService.getMemberInServerAsync(aUserInAServer)).thenReturn(future); - future.completeExceptionally(new RuntimeException()); - when(serverExperience.getServerId()).thenReturn(SERVER_ID); - testUnit.handleExperienceGain(Arrays.asList(serverExperience)).join(); - ArgumentCaptor>> listArgumentCaptor = ArgumentCaptor.forClass(List.class); - verify(self, times(1)).updateFoundMembers(listArgumentCaptor.capture(), eq(SERVER_ID), anyList(), anyList()); - Assert.assertTrue(listArgumentCaptor.getValue().get(0).isCompletedExceptionally()); - } - - @Test - public void testGainExpSingleUserLvlUpOneServerWithoutRole() { - /* - * In this scenario, the user has a role before, but the config changed, and now there are no experience roles. - * Hence the user should lose the experience role. - */ - setupServerId(); - setupLevels(3); - when(experienceRole1.getRole()).thenReturn(aRole1); - setExperienceRoleLevels(); - setupServerConfig(); - setupDefaultConfig(); - setupLevelsAndRolesAndNoDisallowed(); - setupUserInServer(); - when(userExperience.getMessageCount()).thenReturn(MESSAGE_COUNT); - when(experienceRoleService.calculateRole(eq(experienceRoles), any())).thenReturn(null); - when(userExperience.getExperience()).thenReturn(500L); - when(userExperienceManagementService.findByUserInServerIdOptional(USER_IN_SERVER_ID)).thenReturn(Optional.of(userExperience)); - when(userExperience.getCurrentExperienceRole()).thenReturn(experienceRole1); - when(userExperience.getUser()).thenReturn(aUserInAServer); - when(roleService.removeRoleFromUserAsync(aUserInAServer, aRole1)).thenReturn(CompletableFuture.completedFuture(null)); - ArrayList experienceResults = new ArrayList<>(); - ArrayList> roleCalculationResults = new ArrayList<>(); - List> memberFutures = Arrays.asList(CompletableFuture.completedFuture(firstMember)); - testUnit.updateFoundMembers(memberFutures, SERVER_ID, experienceResults, roleCalculationResults); - Assert.assertEquals(1, experienceResults.size()); - ExperienceGainResult result = experienceResults.get(0); - Assert.assertEquals(MESSAGE_COUNT + 1, result.getNewMessageCount().longValue()); - Assert.assertEquals(1, roleCalculationResults.size()); - RoleCalculationResult roleCalcResult = roleCalculationResults.get(0).join(); - Assert.assertNull(roleCalcResult.getExperienceRoleId()); - Assert.assertEquals(USER_IN_SERVER_ID, roleCalcResult.getUserInServerId()); - verify(roleService, times(1)).removeRoleFromUserAsync(aUserInAServer, aRole1); - verify(roleService, times(0)).addRoleToUserAsync(any(AUserInAServer.class), any()); - } - - @Test - public void testLevelUpGainingNewRoleButUserAlreadyHasRole() { - setupServerId(); - setupServerConfig(); - setupDefaultConfig(); - setupLevelsAndRolesAndNoDisallowed(); - when(memberService.getMemberInServerAsync(aUserInAServer)).thenReturn(CompletableFuture.completedFuture(firstMember)); - setupUserInServer(); - when(userExperience.getExperience()).thenReturn(199L); - when(experienceRole1.getLevel()).thenReturn(level0); - when(experienceRole2.getRole()).thenReturn(aRole2); - when(experienceRole2.getLevel()).thenReturn(level1); - AExperienceRole newRole = experienceRole2; - when(aRole2.getId()).thenReturn(ROLE_ID); - when(experienceRoleService.calculateRole(eq(experienceRoles), any())).thenReturn(newRole); - when(roleService.memberHasRole(firstMember, ROLE_ID)).thenReturn(true); - when(userExperienceManagementService.findByUserInServerIdOptional(USER_IN_SERVER_ID)).thenReturn(Optional.of(userExperience)); - ArrayList experienceResults = new ArrayList<>(); - ArrayList> roleCalculationResults = new ArrayList<>(); - List> memberFutures = Arrays.asList(CompletableFuture.completedFuture(firstMember)); - testUnit.updateFoundMembers(memberFutures, SERVER_ID, experienceResults, roleCalculationResults); - verify(roleService, times(0)).addRoleToMember(any(AUserInAServer.class), any(ARole.class)); - verify(roleService, times(0)).removeRoleFromUser(any(AUserInAServer.class), any(ARole.class)); - } - - @Test - public void testLevelUpNotGainingNewRole() { - setupServerId(); - when(experienceRole1.getRole()).thenReturn(aRole1); - setExperienceRoleLevels(); - when(aRole1.getId()).thenReturn(ROLE_ID); - setupServerConfig(); - setupDefaultConfig(); - setupLevelsAndRolesAndNoDisallowed(); - when(memberService.getMemberInServerAsync(aUserInAServer)).thenReturn(CompletableFuture.completedFuture(firstMember)); - setupUserInServer(); - when(userExperience.getExperience()).thenReturn(500L); - when(userExperienceManagementService.findByUserInServerIdOptional(USER_IN_SERVER_ID)).thenReturn(Optional.of(userExperience)); - AExperienceRole newRole = experienceRole1; - when(userExperience.getCurrentExperienceRole()).thenReturn(newRole); - when(experienceRoleService.calculateRole(eq(experienceRoles), any())).thenReturn(newRole); - - when(roleService.memberHasRole(firstMember, ROLE_ID)).thenReturn(true); - ArrayList experienceResults = new ArrayList<>(); - ArrayList> roleCalculationResults = new ArrayList<>(); - List> memberFutures = Arrays.asList(CompletableFuture.completedFuture(firstMember)); - testUnit.updateFoundMembers(memberFutures, SERVER_ID, experienceResults, roleCalculationResults); - verify(roleService, times(0)).addRoleToMember(any(AUserInAServer.class), any(ARole.class)); - verify(roleService, times(0)).removeRoleFromUser(any(AUserInAServer.class), any(ARole.class)); - } - - @Test - public void testHandleExperienceForUserNotLevelingUpWithoutExistingRole() { - setupServerId(); - when(userExperience.getCurrentExperienceRole()).thenReturn(null); - setupServerConfig(); - setupDefaultConfig(); - setupLevelsAndRolesAndNoDisallowed(); - when(memberService.getMemberInServerAsync(aUserInAServer)).thenReturn(CompletableFuture.completedFuture(firstMember)); - setupUserInServer(); - when(userExperience.getExperience()).thenReturn(500L); - AExperienceRole newRole = experienceRole1; - when(experienceRoleService.calculateRole(eq(experienceRoles), any())).thenReturn(newRole); - when(aRole1.getId()).thenReturn(ROLE_ID); - setupTwoExperienceRoles(); - when(roleService.memberHasRole(firstMember, ROLE_ID)).thenReturn(false); - when(roleService.addRoleToMemberAsync(firstMember, ROLE_ID)).thenReturn(CompletableFuture.completedFuture(null)); - when(userExperienceManagementService.findByUserInServerIdOptional(USER_IN_SERVER_ID)).thenReturn(Optional.of(userExperience)); - when(roleService.addRoleToMemberAsync(firstMember, ROLE_ID)).thenReturn(CompletableFuture.completedFuture(null)); - ArrayList experienceResults = new ArrayList<>(); - ArrayList> roleCalculationResults = new ArrayList<>(); - List> memberFutures = Arrays.asList(CompletableFuture.completedFuture(firstMember)); - testUnit.updateFoundMembers(memberFutures, SERVER_ID, experienceResults, roleCalculationResults); - verify(roleService, times(0)).addRoleToUserAsync(any(AUserInAServer.class), any(ARole.class)); - verify(roleService, times(0)).removeRoleFromUserAsync(any(AUserInAServer.class), any()); - } - - @Test - public void handleExpGainWithTooLittleForRole() { - setupServerId(); - setupServerConfig(); - setupDefaultConfig(); - setupLevelsAndRolesAndNoDisallowed(); - setupUserInServer(); - when(userExperience.getExperience()).thenReturn(50L); - setExperienceRoleLevels(); - when(userExperience.getCurrentExperienceRole()).thenReturn(null); - when(experienceRoleService.calculateRole(eq(experienceRoles), any())).thenReturn(null); - - ArrayList experienceResults = new ArrayList<>(); - ArrayList> roleCalculationResults = new ArrayList<>(); - List> memberFutures = Arrays.asList(CompletableFuture.completedFuture(firstMember)); - testUnit.updateFoundMembers(memberFutures, SERVER_ID, experienceResults, roleCalculationResults); - verify(roleService, times(0)).removeRoleFromUserAsync(any(AUserInAServer.class), any()); - verify(roleService, times(0)).addRoleToUserAsync(any(AUserInAServer.class), any()); - } - - @Test - public void testUserHasExperienceRoleButNotAnymore() { - setupServerId(); - setupServerConfig(); - setupDefaultConfig(); - setupLevelsAndRolesAndNoDisallowed(); - setupUserInServer(); - when(userExperience.getExperience()).thenReturn(50L); - AExperienceRole previousExperienceRole = experienceRole1; - when(userExperience.getCurrentExperienceRole()).thenReturn(previousExperienceRole); - when(experienceRole1.getRole()).thenReturn(aRole1); - setExperienceRoleLevels(); - when(experienceRoleService.calculateRole(eq(experienceRoles), any())).thenReturn(null); - - when(roleService.removeRoleFromUserAsync(eq(aUserInAServer), any())).thenReturn(CompletableFuture.completedFuture(null)); - when(userExperienceManagementService.findByUserInServerIdOptional(USER_IN_SERVER_ID)).thenReturn(Optional.of(userExperience)); - ArrayList experienceResults = new ArrayList<>(); - ArrayList> roleCalculationResults = new ArrayList<>(); - List> memberFutures = Arrays.asList(CompletableFuture.completedFuture(firstMember)); - testUnit.updateFoundMembers(memberFutures, SERVER_ID, experienceResults, roleCalculationResults); - verify(roleService, times(0)).addRoleToUserAsync(eq(aUserInAServer), any()); - verify(roleService, times(1)).removeRoleFromUserAsync(eq(aUserInAServer), any()); - } - - @Test - public void testHandleExperienceGainForGainDisabledForUser() { - setupServerId(); - setupServerConfig(); - setupDefaultConfig(); - setupLevelsAndRolesAndNoDisallowed(); - setExperienceRoleLevels(); - when(userExperience.getExperienceGainDisabled()).thenReturn(true); - - ArrayList experienceResults = new ArrayList<>(); - ArrayList> roleCalculationResults = new ArrayList<>(); - when(aUserInAServer.getUserInServerId()).thenReturn(USER_IN_SERVER_ID); - when(userInServerManagementService.loadOrCreateUser(firstMember)).thenReturn(aUserInAServer); - List> memberFutures = Arrays.asList(CompletableFuture.completedFuture(firstMember)); - - when(userExperienceManagementService.findByUserInServerIdOptional(USER_IN_SERVER_ID)).thenReturn(Optional.of(userExperience)); - testUnit.updateFoundMembers(memberFutures, SERVER_ID, experienceResults, roleCalculationResults); - verify(roleService, times(0)).removeRoleFromUserAsync(eq(aUserInAServer), any()); - verify(roleService, times(0)).addRoleToUserAsync(eq(aUserInAServer), any()); - } - - @Test - public void testHandleExperienceGainForGainDisabledForRole() { - setupServerId(); - setupServerConfig(); - setupDefaultConfig(); - setupLevelsAndRolesAndNoDisallowed(); - setExperienceRoleLevels(); - when(experienceRole1.getLevel()).thenReturn(level0); - when(experienceRole2.getLevel()).thenReturn(level1); - ArrayList experienceResults = new ArrayList<>(); - ArrayList> roleCalculationResults = new ArrayList<>(); - when(roleService.hasAnyOfTheRoles(eq(firstMember), anyList())).thenReturn(true); - when(aUserInAServer.getUserInServerId()).thenReturn(USER_IN_SERVER_ID); - when(userInServerManagementService.loadOrCreateUser(firstMember)).thenReturn(aUserInAServer); - List> memberFutures = Arrays.asList(CompletableFuture.completedFuture(firstMember)); - testUnit.updateFoundMembers(memberFutures, SERVER_ID, experienceResults, roleCalculationResults); - verify(roleService, times(0)).removeRoleFromUser(aUserInAServer, aRole1); - verify(roleService, times(0)).addRoleToMember(eq(aUserInAServer), any(ARole.class)); - } - - @Test - public void testHandleExperienceForUserNotLevelingUpWithExistingRole() { - setupServerId(); - when(userExperienceManagementService.findByUserInServerIdOptional(USER_IN_SERVER_ID)).thenReturn(Optional.of(userExperience)); - when(userExperience.getUser()).thenReturn(aUserInAServer); - when(aUserInAServer.getUserReference()).thenReturn(user); - when(aUserInAServer.getServerReference()).thenReturn(server); - when(roleService.hasAnyOfTheRoles(eq(firstMember), anyList())).thenReturn(false); - when(user.getId()).thenReturn(USER_ID); - setExperienceRoleLevels(); - - setupServerConfig(); - setupDefaultConfig(); - setupLevelsAndRolesAndNoDisallowed(); - - when(aUserInAServer.getUserInServerId()).thenReturn(USER_IN_SERVER_ID); - when(userInServerManagementService.loadOrCreateUser(firstMember)).thenReturn(aUserInAServer); - - ArrayList experienceResults = new ArrayList<>(); - ArrayList> roleCalculationResults = new ArrayList<>(); - List> memberFutures = Arrays.asList(CompletableFuture.completedFuture(firstMember)); - testUnit.updateFoundMembers(memberFutures, SERVER_ID, experienceResults, roleCalculationResults); - verify(roleService, times(0)).removeRoleFromUser(aUserInAServer, aRole1); - verify(roleService, times(0)).addRoleToMember(eq(aUserInAServer), any(ARole.class)); - } - - @Test - public void testSyncNoRoleUserGettingRole2() { - userExperience.setCurrentExperienceRole(null); - AExperienceRole afterRole = experienceRole1; - when(experienceRole1.getRole()).thenReturn(aRole1); - when(experienceRole1.getId()).thenReturn(ROLE_ID); - when(aRole1.getId()).thenReturn(ROLE_ID); - when(aUserInAServer.getUserReference()).thenReturn(user); - when(aUserInAServer.getServerReference()).thenReturn(server); - when(userExperience.getUser()).thenReturn(aUserInAServer); - - when(memberService.getMemberInServerAsync(aUserInAServer)).thenReturn(CompletableFuture.completedFuture(firstMember)); - - when(experienceRoleManagementService.getExperienceRolesForServer(server)).thenReturn(experienceRoles); - when(experienceRole1.getLevel()).thenReturn(level0); - when(experienceRole2.getLevel()).thenReturn(level1); - when(experienceRoleService.calculateRole(experienceRoles, userExperience.getLevelOrDefault())).thenReturn(afterRole); - when(memberService.getMemberInServerAsync(userExperience.getUser())).thenReturn(CompletableFuture.completedFuture(firstMember)); - when(roleService.addRoleToMemberAsync(firstMember, ROLE_ID)).thenReturn(CompletableFuture.completedFuture(null)); - CompletableFuture calculationFuture = testUnit.syncForSingleUser(userExperience); - RoleCalculationResult result = calculationFuture.join(); - Assert.assertEquals(ROLE_ID, result.getExperienceRoleId()); - } - - @Test - public void testSyncUserLosingRole() { - AExperienceRole beforeRole = experienceRole1; - when(userExperience.getCurrentExperienceRole()).thenReturn(beforeRole); - when(aUserInAServer.getUserReference()).thenReturn(user); - when(aUserInAServer.getServerReference()).thenReturn(server); - when(userExperience.getUser()).thenReturn(aUserInAServer); - when(experienceRole1.getRole()).thenReturn(aRole1); - - when(experienceRoleManagementService.getExperienceRolesForServer(server)).thenReturn(experienceRoles); - when(experienceRoleService.calculateRole(experienceRoles, userExperience.getLevelOrDefault())).thenReturn(null); - when(roleService.removeRoleFromUserAsync(aUserInAServer, aRole1)).thenReturn(CompletableFuture.completedFuture(null)); - when(experienceRole1.getLevel()).thenReturn(level0); - when(experienceRole2.getLevel()).thenReturn(level1); - CompletableFuture calculationFuture = testUnit.syncForSingleUser(userExperience); - RoleCalculationResult result = calculationFuture.join(); - Assert.assertNull(result.getExperienceRoleId()); - } - - @Test - public void testSyncUserKeepingRole() { - AExperienceRole beforeRole = experienceRole1; - when(userExperience.getCurrentExperienceRole()).thenReturn(beforeRole); - AExperienceRole afterRole = experienceRole1; - when(aUserInAServer.getUserReference()).thenReturn(user); - when(aUserInAServer.getServerReference()).thenReturn(server); - when(userExperience.getUser()).thenReturn(aUserInAServer); - when(aRole1.getId()).thenReturn(ROLE_ID); - when(experienceRole1.getId()).thenReturn(ROLE_ID); - when(experienceRole1.getRole()).thenReturn(aRole1); - when(memberService.getMemberInServerAsync(aUserInAServer)).thenReturn(CompletableFuture.completedFuture(firstMember)); - - when(experienceRoleManagementService.getExperienceRolesForServer(server)).thenReturn(experienceRoles); - when(experienceRoleService.calculateRole(experienceRoles, userExperience.getLevelOrDefault())).thenReturn(afterRole); - when(memberService.getMemberInServerAsync(userExperience.getUser())).thenReturn(CompletableFuture.completedFuture(firstMember)); - when(roleService.memberHasRole(firstMember, ROLE_ID)).thenReturn(true); - when(experienceRole1.getLevel()).thenReturn(level0); - when(experienceRole2.getLevel()).thenReturn(level1); - CompletableFuture calculationFuture = testUnit.syncForSingleUser(userExperience); - RoleCalculationResult result = calculationFuture.join(); - Assert.assertEquals(ROLE_ID, result.getExperienceRoleId()); - } - - @Test - public void testSyncUserChangingRole() { - AExperienceRole beforeRole = experienceRole1; - when(userExperience.getCurrentExperienceRole()).thenReturn(beforeRole); - AExperienceRole afterRole = experienceRole2; - when(aUserInAServer.getUserReference()).thenReturn(user); - when(aUserInAServer.getServerReference()).thenReturn(server); - when(userExperience.getUser()).thenReturn(aUserInAServer); - - when(aRole1.getId()).thenReturn(ROLE_ID); - when(aRole2.getId()).thenReturn(SECOND_ROLE_ID); - when(experienceRole1.getRole()).thenReturn(aRole1); - when(experienceRole2.getRole()).thenReturn(aRole2); - when(experienceRole2.getId()).thenReturn(SECOND_ROLE_ID); - - when(roleService.memberHasRole(firstMember, ROLE_ID)).thenReturn(true); - - when(memberService.getMemberInServerAsync(aUserInAServer)).thenReturn(CompletableFuture.completedFuture(firstMember)); - - when(experienceRoleManagementService.getExperienceRolesForServer(server)).thenReturn(experienceRoles); - when(experienceRoleService.calculateRole(experienceRoles, userExperience.getLevelOrDefault())).thenReturn(afterRole); - when(memberService.getMemberInServerAsync(aUserInAServer)).thenReturn(CompletableFuture.completedFuture(firstMember)); - when(experienceRole1.getLevel()).thenReturn(level0); - when(experienceRole2.getLevel()).thenReturn(level1); - when(roleService.memberHasRole(firstMember, SECOND_ROLE_ID)).thenReturn(false); - when(roleService.removeRoleFromMemberAsync(firstMember, ROLE_ID)).thenReturn(CompletableFuture.completedFuture(null)); - when(roleService.addRoleToMemberAsync(firstMember, SECOND_ROLE_ID)).thenReturn(CompletableFuture.completedFuture(null)); - CompletableFuture calculationFuture = testUnit.syncForSingleUser(userExperience); - RoleCalculationResult result = calculationFuture.join(); - Assert.assertEquals(SECOND_ROLE_ID, result.getExperienceRoleId()); - } - - @Test - public void testDisablingExperienceForUser() { - AUserExperience experience = Mockito.mock(AUserExperience.class); - when(aUserInAServer.getUserReference()).thenReturn(user); - when(aUserInAServer.getServerReference()).thenReturn(server); - when(userExperienceManagementService.findUserInServer(aUserInAServer)).thenReturn(experience); - testUnit.disableExperienceForUser(aUserInAServer); - verify(experience, times(1)).setExperienceGainDisabled(true); - } - - @Test - public void testEnablingExpForUser() { - AUserExperience experience = Mockito.mock(AUserExperience.class); - when(aUserInAServer.getUserReference()).thenReturn(user); - when(aUserInAServer.getServerReference()).thenReturn(server); - when(userExperienceManagementService.findUserInServer(aUserInAServer)).thenReturn(experience); - testUnit.enableExperienceForUser(aUserInAServer); - verify(experience, times(1)).setExperienceGainDisabled(false); - } - - @Test - public void testFindLeaderBoardData() { - executeLeaderBoardTest(server, 1); - } - - @Test - public void testFindLeaderBoardDataSecondPage() { - executeLeaderBoardTest(server, 2); - } - - @Test(expected = IllegalArgumentException.class) - public void testIllegalLeaderBoardPage() { - testUnit.findLeaderBoardData(server, -1); - } - - @Test - public void testSyncAllUsers() { - AExperienceRole beforeRole = experienceRole1; - - when(userExperience.getCurrentExperienceRole()).thenReturn(beforeRole); - AExperienceRole afterRole = experienceRole2; - when(aUserInAServer.getUserReference()).thenReturn(user); - when(user.getId()).thenReturn(8L); - when(aUserInAServer.getServerReference()).thenReturn(server); - when(userExperience.getUser()).thenReturn(aUserInAServer); - when(userExperience.getCurrentLevel()).thenReturn(level0); - - when(aUserInAServer2.getUserReference()).thenReturn(user2); - when(user2.getId()).thenReturn(9L); - when(aUserInAServer2.getServerReference()).thenReturn(server); - when(userExperience2.getUser()).thenReturn(aUserInAServer2); - when(userExperience2.getCurrentLevel()).thenReturn(level0); - - when(userExperience2.getCurrentExperienceRole()).thenReturn(beforeRole); - - when(memberService.getMemberInServerAsync(aUserInAServer)).thenReturn(CompletableFuture.completedFuture(firstMember)); - when(memberService.getMemberInServerAsync(aUserInAServer2)).thenReturn(CompletableFuture.completedFuture(secondMember)); - - - when(aRole1.getId()).thenReturn(ROLE_ID); - when(aRole2.getId()).thenReturn(SECOND_ROLE_ID); - when(experienceRole1.getRole()).thenReturn(aRole1); - when(experienceRole2.getRole()).thenReturn(aRole2); - when(experienceRole2.getId()).thenReturn(SECOND_ROLE_ID); - - when(experienceRoleManagementService.getExperienceRolesForServer(server)).thenReturn(experienceRoles); - when(experienceRoleService.calculateRole(experienceRoles, userExperience.getLevelOrDefault())).thenReturn(afterRole); - when(experienceRoleService.calculateRole(experienceRoles, userExperience2.getLevelOrDefault())).thenReturn(afterRole); - when(memberService.getMemberInServerAsync(aUserInAServer)).thenReturn(CompletableFuture.completedFuture(firstMember)); - when(memberService.getMemberInServerAsync(aUserInAServer2)).thenReturn(CompletableFuture.completedFuture(secondMember)); - when(roleService.memberHasRole(firstMember, SECOND_ROLE_ID)).thenReturn(false); - when(roleService.memberHasRole(firstMember, ROLE_ID)).thenReturn(true); - when(roleService.memberHasRole(secondMember, SECOND_ROLE_ID)).thenReturn(true); - when(roleService.removeRoleFromMemberAsync(firstMember, ROLE_ID)).thenReturn(CompletableFuture.completedFuture(null)); - when(roleService.addRoleToMemberAsync(firstMember,SECOND_ROLE_ID)).thenReturn(CompletableFuture.completedFuture(null)); - List experiences = Arrays.asList(userExperience, userExperience2); - when(userExperienceManagementService.loadAllUsers(server)).thenReturn(experiences); - when(experienceRole1.getLevel()).thenReturn(level0); - when(experienceRole2.getLevel()).thenReturn(level1); - List> calculationFutures = testUnit.syncUserRoles(server); - verify(roleService, times(0)).removeRoleFromMemberAsync(secondMember, ROLE_ID); - verify(roleService, times(0)).addRoleToMemberAsync(secondMember, SECOND_ROLE_ID); - RoleCalculationResult firstResult = calculationFutures.get(0).join(); - Assert.assertEquals(SECOND_ROLE_ID, firstResult.getExperienceRoleId()); - RoleCalculationResult secondResult = calculationFutures.get(1).join(); - Assert.assertEquals(SECOND_ROLE_ID, secondResult.getExperienceRoleId()); - } - - @Test - public void testGetRankForUser() { - int rank = 1; - AUserExperience experienceObj = Mockito.mock(AUserExperience.class); - when(aUserInAServer.getUserReference()).thenReturn(user); - when(aUserInAServer.getUserInServerId()).thenReturn(USER_IN_SERVER_ID); - when(userExperienceManagementService.findByUserInServerIdOptional(USER_IN_SERVER_ID)).thenReturn(Optional.of(experienceObj)); - LeaderBoardEntryResult leaderBoardEntryTest = Mockito.mock(LeaderBoardEntryResult.class); - when(leaderBoardEntryTest.getRank()).thenReturn(rank); - when(userExperienceManagementService.getRankOfUserInServer(experienceObj)).thenReturn(leaderBoardEntryTest); - LeaderBoardEntry rankOfUserInServer = testUnit.getRankOfUserInServer(aUserInAServer); - Assert.assertEquals(experienceObj, rankOfUserInServer.getExperience()); - Assert.assertEquals(rank, rankOfUserInServer.getRank().intValue()); - } - - @Test(expected = NoExperienceTrackedException.class) - public void testGetRankForUserNoExperienceFound() { - when(aUserInAServer.getUserInServerId()).thenReturn(USER_IN_SERVER_ID); - when(aUserInAServer.getUserReference()).thenReturn(user); - when(userExperienceManagementService.findByUserInServerIdOptional(USER_IN_SERVER_ID)).thenReturn(Optional.empty()); - testUnit.getRankOfUserInServer(aUserInAServer); - } - - @Test - public void testGetRankWhenRankReturnsNull() { - AUserExperience experienceObj = Mockito.mock(AUserExperience.class); - when(aUserInAServer.getUserReference()).thenReturn(user); - when(aUserInAServer.getUserInServerId()).thenReturn(USER_IN_SERVER_ID); - when(userExperienceManagementService.findByUserInServerIdOptional(USER_IN_SERVER_ID)).thenReturn(Optional.of(experienceObj)); - when(userExperienceManagementService.getRankOfUserInServer(experienceObj)).thenReturn(null); - LeaderBoardEntry rankOfUserInServer = testUnit.getRankOfUserInServer(aUserInAServer); - Assert.assertEquals(experienceObj, rankOfUserInServer.getExperience()); - Assert.assertEquals(0, rankOfUserInServer.getRank().intValue()); - } - - @Test - public void testSyncRolesWithFeedBack() { - AChannel channel = Mockito.mock(AChannel.class); - when(channel.getServer()).thenReturn(server); - when(server.getId()).thenReturn(SERVER_ID); - List experiences = getUserExperiences(25); - - checkStatusMessages(server, channel, experiences, 13); - } - - @Test - public void testSyncRolesWithNoUsers() { - AChannel channel = Mockito.mock(AChannel.class); - List experiences = new ArrayList<>(); - when(channel.getServer()).thenReturn(server); - when(server.getId()).thenReturn(SERVER_ID); - checkStatusMessages(server, channel, experiences, 1); - } - - private void checkStatusMessages(AServer server, AChannel channel, List experiences, int messageCount) { - when(userExperienceManagementService.loadAllUsers(server)).thenReturn(experiences); - MessageToSend statusMessage = Mockito.mock(MessageToSend.class); - when(templateService.renderEmbedTemplate(eq("user_sync_status_message"), any(UserSyncStatusModel.class), eq(SERVER_ID))).thenReturn(statusMessage); - long messageId = 5L; - Message statusMessageJDA = Mockito.mock(Message.class); - when(statusMessageJDA.getIdLong()).thenReturn(messageId); - when(messageService.createStatusMessage(statusMessage, channel)).thenReturn(CompletableFuture.completedFuture(statusMessageJDA)); - when(channelManagementService.loadChannel(CHANNEL_ID)).thenReturn(channel); - testUnit.syncUserRolesWithFeedback(server, CHANNEL_ID); - verify(messageService, times(messageCount)).updateStatusMessage(channel, messageId, statusMessage); - } - - private void setupUserInServer() { - when(userExperienceManagementService.findByUserInServerIdOptional(USER_IN_SERVER_ID)).thenReturn(Optional.of(userExperience)); - when(userInServerManagementService.loadOrCreateUser(firstMember)).thenReturn(aUserInAServer); - when(aUserInAServer.getUserReference()).thenReturn(user); - when(aUserInAServer.getUserInServerId()).thenReturn(USER_IN_SERVER_ID); - when(memberService.getMemberInServerAsync(aUserInAServer)).thenReturn(CompletableFuture.completedFuture(firstMember)); - when(userExperience.getUser()).thenReturn(aUserInAServer); - when(aUserInAServer.getServerReference()).thenReturn(server); - when(user.getId()).thenReturn(USER_ID); - } - - private void setupTwoExperienceRoles() { - when(experienceRole1.getRole()).thenReturn(aRole1); - setExperienceRoleLevels(); - } - - private void setupServerId() { - when(server.getId()).thenReturn(SERVER_ID); - when(serverManagementService.loadOrCreate(SERVER_ID)).thenReturn(server); - } - - private void setupServerConfig() { - when(configService.getLongValue(ExperienceFeatureConfig.MIN_EXP_KEY, SERVER_ID, DEFAULT_MIN_EXP)).thenReturn(20L); - when(configService.getLongValue(ExperienceFeatureConfig.MAX_EXP_KEY, SERVER_ID, DEFAULT_MAX_EXP)).thenReturn(50L); - when(configService.getDoubleValue(ExperienceFeatureConfig.EXP_MULTIPLIER_KEY, SERVER_ID, DEFAULT_EXP_MULTIPLIER)).thenReturn(1.2); - } - - private void executeLeaderBoardTest(AServer server, Integer page) { - int pageSize = 10; - List experiences = Arrays.asList(userExperience, userExperience2); - when(userExperience.getExperience()).thenReturn(LOW_EXP); - when(userExperience.getCurrentLevel()).thenReturn(level0); - when(userExperience.getUser()).thenReturn(aUserInAServer); - when(userExperience2.getExperience()).thenReturn(MID_EXP); - when(userExperience2.getCurrentLevel()).thenReturn(level1); - when(userExperience2.getUser()).thenReturn(aUserInAServer2); - when(userExperienceManagementService.findLeaderBoardUsersPaginated(server, page - 1, pageSize)).thenReturn(experiences); - LeaderBoard leaderBoardData = testUnit.findLeaderBoardData(server, page); - page--; - List entries = leaderBoardData.getEntries(); - LeaderBoardEntry firstEntry = entries.get(0); - Assert.assertEquals(LOW_EXP, firstEntry.getExperience().getExperience()); - Assert.assertEquals(level0, firstEntry.getExperience().getCurrentLevel()); - Assert.assertEquals(aUserInAServer, firstEntry.getExperience().getUser()); - Assert.assertEquals((page * pageSize) + 1, firstEntry.getRank().intValue()); - LeaderBoardEntry secondEntry = entries.get(1); - Assert.assertEquals(MID_EXP, secondEntry.getExperience().getExperience()); - Assert.assertEquals(level1, secondEntry.getExperience().getCurrentLevel()); - Assert.assertEquals(aUserInAServer2, secondEntry.getExperience().getUser()); - Assert.assertEquals((page * pageSize) + 2, secondEntry.getRank().intValue()); - Assert.assertEquals(2, entries.size()); - } - - private void setExperienceRoleLevels() { - when(experienceRole1.getLevel()).thenReturn(level0); - when(experienceRole2.getLevel()).thenReturn(level1); - } - - private void setupLevelsAndRolesAndNoDisallowed() { - when(experienceLevelManagementService.getLevelConfig()).thenReturn(levels); - when(experienceRoleManagementService.getExperienceRolesForServer(server)).thenReturn(experienceRoles); - when(disabledExpRoleManagementService.getDisabledRolesForServer(server)).thenReturn(new ArrayList<>()); - } - - private void setupDefaultConfig() { - SystemConfigProperty minExpProperty = Mockito.mock(SystemConfigProperty.class); - when(minExpProperty.getLongValue()).thenReturn(DEFAULT_MIN_EXP); - when(defaultConfigManagementService.getDefaultConfig(ExperienceFeatureConfig.MIN_EXP_KEY)).thenReturn(minExpProperty); - SystemConfigProperty maxExpProperty = Mockito.mock(SystemConfigProperty.class); - when(maxExpProperty.getLongValue()).thenReturn(DEFAULT_MAX_EXP); - when(defaultConfigManagementService.getDefaultConfig(ExperienceFeatureConfig.MAX_EXP_KEY)).thenReturn(maxExpProperty); - SystemConfigProperty expMultiplierProperty = Mockito.mock(SystemConfigProperty.class); - when(expMultiplierProperty.getDoubleValue()).thenReturn(DEFAULT_EXP_MULTIPLIER); - when(defaultConfigManagementService.getDefaultConfig(ExperienceFeatureConfig.EXP_MULTIPLIER_KEY)).thenReturn(expMultiplierProperty); - } - - protected List getUserExperiences(int count) { - List experiences = new ArrayList<>(); - for (int i = 0; i < count; i++) { - AUserExperience experience = Mockito.mock(AUserExperience.class); - when(experience.getUser()).thenReturn(aUserInAServer); - when(aUserInAServer.getServerReference()).thenReturn(server); - when(aUserInAServer.getUserReference()).thenReturn(user); - experiences.add(experience); - } - return experiences; - } - - private void setupLevels(int count) { - if(count >= 0) { - when(level0.getExperienceNeeded()).thenReturn(LVL_0_EXP); - } - if(count >= 1) { - when(level1.getExperienceNeeded()).thenReturn(LVL_1_EXP); - } - if(count >= 2) { - when(level2.getExperienceNeeded()).thenReturn(LVL_2_EXP); - } - if(count >= 3) { - when(level3.getExperienceNeeded()).thenReturn(LVL_3_EXP); - } - } - - -} diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/test/java/dev/sheldan/abstracto/experience/service/ExperienceRoleServiceBeanTest.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/test/java/dev/sheldan/abstracto/experience/service/ExperienceRoleServiceBeanTest.java index 26c92571d..f8ca994ff 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/test/java/dev/sheldan/abstracto/experience/service/ExperienceRoleServiceBeanTest.java +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-impl/src/test/java/dev/sheldan/abstracto/experience/service/ExperienceRoleServiceBeanTest.java @@ -53,21 +53,6 @@ public class ExperienceRoleServiceBeanTest { private static final Long CHANNEL_ID = 4L; private static final Long ROLE_ID = 5L; - @Test - public void testUnsetRoleInDb() { - Integer levelCount = 10; - AExperienceLevel level = Mockito.mock(AExperienceLevel.class); - ARole roleToChange = Mockito.mock(ARole.class); - when(roleToChange.getServer()).thenReturn(server); - when(experienceLevelService.getLevelOptional(levelCount)).thenReturn(Optional.of(level)); - when(roleManagementService.findRole(roleToChange.getId())).thenReturn(roleToChange); - testingUnit.unsetRoleInDb(levelCount, roleToChange.getId()); - - verify(experienceRoleManagementService, times(1)).removeAllRoleAssignmentsForLevelInServerExceptRole(level, server, roleToChange); - verify(experienceRoleManagementService, times(1)).setLevelToRole(level, roleToChange); - verify(experienceRoleManagementService, times(0)).getExperienceRolesForServer(server); - } - @Test public void testCalculateRoleForLevelInBetween() { List roles = getExperienceRoles(); diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/config/ExperienceExecutorConfig.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/config/ExperienceExecutorConfig.java new file mode 100644 index 000000000..d4e1eec0e --- /dev/null +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/config/ExperienceExecutorConfig.java @@ -0,0 +1,19 @@ +package dev.sheldan.abstracto.experience.config; + +import dev.sheldan.abstracto.core.service.ExecutorService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.TaskExecutor; + +@Configuration +public class ExperienceExecutorConfig { + + @Autowired + private ExecutorService executorService; + + @Bean(name = "experienceUpdateExecutor") + public TaskExecutor experienceUpdateExecutor() { + return executorService.setupExecutorFor("experienceUpdateExecutor"); + } +} diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/config/ExperienceFeatureConfig.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/config/ExperienceFeatureConfig.java index a71ddddd3..88d2ed9c8 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/config/ExperienceFeatureConfig.java +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/config/ExperienceFeatureConfig.java @@ -2,11 +2,14 @@ package dev.sheldan.abstracto.experience.config; import dev.sheldan.abstracto.core.config.FeatureConfig; import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.config.FeatureMode; import org.springframework.stereotype.Component; import java.util.Arrays; import java.util.List; +import static dev.sheldan.abstracto.experience.config.ExperienceFeatureMode.LEVEL_UP_NOTIFICATION; + /** * {@link FeatureConfig} instance containing the required configuration concerning system config and post targets for * the {@link ExperienceFeatureDefinition} feature. @@ -26,6 +29,7 @@ public class ExperienceFeatureConfig implements FeatureConfig { * The multiplier which is applied to each calculated gained experience */ public static final String EXP_MULTIPLIER_KEY = "expMultiplier"; + public static final String EXP_COOLDOWN_SECONDS_KEY = "expCooldownSeconds"; @Override public FeatureDefinition getFeature() { @@ -37,6 +41,11 @@ public class ExperienceFeatureConfig implements FeatureConfig { */ @Override public List getRequiredSystemConfigKeys() { - return Arrays.asList(EXP_MULTIPLIER_KEY, MIN_EXP_KEY, MAX_EXP_KEY); + return Arrays.asList(EXP_MULTIPLIER_KEY, MIN_EXP_KEY, MAX_EXP_KEY, EXP_COOLDOWN_SECONDS_KEY); + } + + @Override + public List getAvailableModes() { + return Arrays.asList(LEVEL_UP_NOTIFICATION); } } diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/config/ExperienceFeatureMode.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/config/ExperienceFeatureMode.java new file mode 100644 index 000000000..f0a95598c --- /dev/null +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/config/ExperienceFeatureMode.java @@ -0,0 +1,15 @@ +package dev.sheldan.abstracto.experience.config; + +import dev.sheldan.abstracto.core.config.FeatureMode; +import lombok.Getter; + +@Getter +public enum ExperienceFeatureMode implements FeatureMode { + LEVEL_UP_NOTIFICATION("levelUpNotification"); + + private final String key; + + ExperienceFeatureMode(String key) { + this.key = key; + } +} diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/model/ExperienceGainResult.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/model/ExperienceGainResult.java index 616ef1f79..da97f5d6c 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/model/ExperienceGainResult.java +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/model/ExperienceGainResult.java @@ -15,10 +15,6 @@ import java.util.concurrent.CompletableFuture; @Setter @Builder public class ExperienceGainResult { - /** - * The calculation result contained in a {@link CompletableFuture future}. The future is necessary, because the calculation both calculates the new role - * and removes/adds {@link net.dv8tion.jda.api.entities.Role role} to the {@link net.dv8tion.jda.api.entities.Member member} - */ private CompletableFuture calculationResult; /** * The ID of the {@link dev.sheldan.abstracto.core.models.database.AUserInAServer user} for which this is the result diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/model/RoleCalculationResult.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/model/RoleCalculationResult.java index 62b2facf5..28fdf95b7 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/model/RoleCalculationResult.java +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/model/RoleCalculationResult.java @@ -4,20 +4,10 @@ import lombok.Builder; import lombok.Getter; import lombok.Setter; -/** - * The result of calculating the appropriate {@link dev.sheldan.abstracto.experience.model.database.AExperienceRole role} for a {@link dev.sheldan.abstracto.experience.model.database.AUserExperience user} - * in a server. - */ @Getter @Setter @Builder public class RoleCalculationResult { - /** - * The ID of the {@link dev.sheldan.abstracto.experience.model.database.AExperienceRole role} which was given to the user. Can be null, in case no role is given. - */ - private Long experienceRoleId; - /** - * The ID of a {@link dev.sheldan.abstracto.core.models.database.AUserInAServer user} for who the role was calculated for. - */ - private Long userInServerId; + private Long oldRoleId; + private Long newRoleId; } diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/model/ServerExperience.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/model/ServerExperience.java deleted file mode 100644 index e1af51af7..000000000 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/model/ServerExperience.java +++ /dev/null @@ -1,27 +0,0 @@ -package dev.sheldan.abstracto.experience.model; - -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; - -import java.util.ArrayList; -import java.util.List; - -/** - * Container object to store the experience in runtime and group it together. This basically is just a list of users who were tracked by experience. - * The actual calculation of the appropriate experience amount is done later. - */ -@Getter -@Setter -@Builder -public class ServerExperience { - /** - * The ID of the {@link dev.sheldan.abstracto.core.models.database.AServer} for which this experience were collected - */ - private Long serverId; - /** - * A list of IDs of the {@link dev.sheldan.abstracto.core.models.database.AUserInAServer} which should be given experience - */ - @Builder.Default - private List userInServerIds = new ArrayList<>(); -} diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/model/template/LevelUpNotificationModel.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/model/template/LevelUpNotificationModel.java new file mode 100644 index 000000000..aed8e177e --- /dev/null +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/model/template/LevelUpNotificationModel.java @@ -0,0 +1,20 @@ +package dev.sheldan.abstracto.experience.model.template; + +import dev.sheldan.abstracto.core.models.template.display.MemberDisplay; +import dev.sheldan.abstracto.core.models.template.display.RoleDisplay; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Builder +public class LevelUpNotificationModel { + private MemberDisplay memberDisplay; + private Integer oldLevel; + private Integer newLevel; + private RoleDisplay oldRole; + private RoleDisplay newRole; + private Long oldExperience; + private Long newExperience; +} diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/service/AUserExperienceService.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/service/AUserExperienceService.java index bf7acafbd..f32b5a0a0 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/service/AUserExperienceService.java +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/service/AUserExperienceService.java @@ -1,21 +1,17 @@ package dev.sheldan.abstracto.experience.service; -import dev.sheldan.abstracto.core.models.database.AChannel; import dev.sheldan.abstracto.core.models.database.AServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer; -import dev.sheldan.abstracto.core.utils.CompletableFutureList; import dev.sheldan.abstracto.experience.model.LeaderBoard; import dev.sheldan.abstracto.experience.model.LeaderBoardEntry; -import dev.sheldan.abstracto.experience.model.RoleCalculationResult; -import dev.sheldan.abstracto.experience.model.ServerExperience; import dev.sheldan.abstracto.experience.model.database.AExperienceLevel; -import dev.sheldan.abstracto.experience.model.database.AExperienceRole; import dev.sheldan.abstracto.experience.model.database.AUserExperience; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import java.util.List; import java.util.concurrent.CompletableFuture; -import java.util.function.Function; /** * Service providing the required mechanisms to provide experience tracking. @@ -24,12 +20,7 @@ import java.util.function.Function; */ public interface AUserExperienceService { String EXPERIENCE_GAIN_CHANNEL_GROUP_KEY = "experienceGain"; - /** - * Adds the given {@link AUserInAServer userInAServer} to the list of user who gained experience in the current minute. - * Does not add the user to the list of users, if it is already in there. - * @param userInAServer The {@link AUserInAServer userInAServer} to be added to the list of users gaining experience - */ - void addExperience(AUserInAServer userInAServer); + void addExperience(Member member, Message message); /** * Calculates the appropriate level for the given experience amount according to the given {@link AExperienceLevel levels} @@ -51,58 +42,9 @@ public interface AUserExperienceService { */ boolean updateUserLevel(AUserExperience userExperience, List levels, Long experienceCount); - /** - * Iterates through the given list of {@link AServer servers} and increases the experience of the users contained in the - * {@link ServerExperience serverExperience} object, also increments the level and changes the role if necessary. - * This uses the respective configurable max/minExp and multiplier for each {@link AServer server} and increases the message count - * of each user by 1. - * @param serverExp The list of {@link AServer servers} containing the users which get experience - * @return A {@link CompletableFuture future} completing when the experience gain was calculated and roles were assigned - */ - CompletableFuture handleExperienceGain(List serverExp); + CompletableFuture syncUserRolesWithFeedback(AServer server, MessageChannel messageChannel); - /** - * Calculates the currently appropriate {@link AExperienceRole} for the given user and updates the role on the - * {@link net.dv8tion.jda.api.entities.Member} and ond the {@link AUserExperience}. Effectively synchronizes the - * state in the server and the database. - * @param userExperience The {@link AUserExperience userExperience} object to recalculate the {@link AExperienceRole experienceRole} for - * @param roles The list of {@link AExperienceRole roles} used as a role configuration - * @param currentLevel The current level of the user - * @return A {@link CompletableFuture future} containing the {@link RoleCalculationResult result} of the role calculation, - * completing after the role of the {@link net.dv8tion.jda.api.entities.Member} has been updated, if any - */ - CompletableFuture updateUserRole(AUserExperience userExperience, List roles, Integer currentLevel); - - /** - * Synchronizes the state ({@link AExperienceRole}, {@link net.dv8tion.jda.api.entities.Role}) - * of all the users provided in the {@link AServer} object in the {@link AUserExperience} - * and on the {@link net.dv8tion.jda.api.entities.Member} according - * to how much experience the user has. Runs completely in the background. - * @param server The {@link AServer} to update the users for - * @return The list of {@link CompletableFuture futures} for each update of the users in the {@link AServer server} - */ - List> syncUserRoles(AServer server); - - /** - * Synchronizes the state ({@link AExperienceRole}, {@link net.dv8tion.jda.api.entities.Role}) - * of all the users provided in the {@link AServer} object in the {@link AUserExperience} - * and on the {@link net.dv8tion.jda.api.entities.Member} according - * to how much experience the user has. This method provides feedback back to the user in the provided {@link AChannel channel} - * while the process is going own. - * @param server The {@link AServer} to update users for - * @param channelId The ID of a {@link AChannel channel} in which the {@link dev.sheldan.abstracto.experience.model.template.UserSyncStatusModel statusUpdate} - * should be posted to - * @return A {@link CompletableFuture future} which completes after all the role changes have been completed - */ - CompletableFuture syncUserRolesWithFeedback(AServer server, Long channelId); - - /** - * Recalculates the role of a single user in a server and synchronize the {@link net.dv8tion.jda.api.entities.Role} - * in the {@link net.dv8tion.jda.api.entities.Guild} - * @param userExperience The {@link AUserExperience} to synchronize the role for - * @return A {@link CompletableFuture future} which completes after the roles have been synced for the given {@link AUserInAServer user} - */ - CompletableFuture syncForSingleUser(AUserExperience userExperience); + CompletableFuture syncForSingleUser(AUserExperience userExperience, Member member); /** * Loads the desired page of the ordered complete leaderboard from the {@link AServer} and returns the information as a {@link LeaderBoard} @@ -122,19 +64,6 @@ public interface AUserExperienceService { */ LeaderBoardEntry getRankOfUserInServer(AUserInAServer userInAServer); - /** - * Provides a method to execute an action on a list of {@link AUserExperience} and provide feedback in the given {@link AChannel} - * in the form of {@link dev.sheldan.abstracto.experience.model.template.UserSyncStatusModel} to be rendered with a certain - * template - * @param experiences The list of {@link AUserExperience} to be working on - * @param channel The {@link AChannel} used to provide feedback to the user - * @param toExecute The {@link Function} which should be executed on each element of the passed list, - * this function needs to take a {@link AUserExperience userExperience} as parameter and returns a {@link CompletableFuture} - * with a {@link RoleCalculationResult} for each of them. These futures are then returned. - * @return A {@link CompletableFutureList completeFutureList} which represents the individual {@link RoleCalculationResult results} and a primary future, which is completed after all of the individual ones are - */ - CompletableFutureList executeActionOnUserExperiencesWithFeedBack(List experiences, AChannel channel, Function> toExecute); - /** * Disables the experience gain for a user directly. This sets the `experienceGainDisabled` on the respective {@link AUserExperience} object to true * @param userInAServer The {@link AUserInAServer} to disable experience gain for @@ -147,12 +76,6 @@ public interface AUserExperienceService { */ void enableExperienceForUser(AUserInAServer userInAServer); - /** - * Updates the actually stored experience roles in the database - * @param results The list of {@link RoleCalculationResult} which should be updated in the database - */ - void syncRolesInStorage(List results); - boolean experienceGainEnabledInChannel(MessageChannel messageChannel); AUserExperience createUserExperienceForUser(AUserInAServer aUserInAServer, Long experience, Long messageCount); diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/service/ExperienceRoleService.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/service/ExperienceRoleService.java index 90535612b..7bcdf8d95 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/service/ExperienceRoleService.java +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/service/ExperienceRoleService.java @@ -7,6 +7,7 @@ import dev.sheldan.abstracto.experience.model.database.AExperienceRole; import dev.sheldan.abstracto.experience.model.database.AUserExperience; import dev.sheldan.abstracto.experience.model.template.LevelRole; import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -15,34 +16,12 @@ import java.util.concurrent.CompletableFuture; * Service providing several methods surrounding {@link dev.sheldan.abstracto.experience.model.database.AExperienceRole experienceRole}. */ public interface ExperienceRoleService { - /** - * Creates an {@link dev.sheldan.abstracto.experience.model.database.AExperienceRole experienceRole} according to the given - * parameters. This actually updates the {@link net.dv8tion.jda.api.entities.Member members} - * which currently possessed the given role before and provides a display to see how far the progress is - * @param role The {@link ARole role} to set the level to - * @param level The new level the {@link ARole role} should be awarded at - * @param channelId The ID of the {@link dev.sheldan.abstracto.core.models.database.AChannel} in which the status updates - * should be sent to - * @return A {@link CompletableFuture future} which completes, after all the updates on the {@link net.dv8tion.jda.api.entities.Member} - * have been completed - */ - CompletableFuture setRoleToLevel(Role role, Integer level, Long channelId); - /** - * Removes the role from the {@link dev.sheldan.abstracto.experience.model.database.AExperienceRole} configuration, - * this will also update all the {@link net.dv8tion.jda.api.entities.Member} which previously had this role and re-calculates - * a new {@link AExperienceRole experienceRole} for them while also updating them in the guild - * @param role The {@link ARole} to remove from the {@link dev.sheldan.abstracto.experience.model.database.AExperienceRole} - * configuration - * @param channelId The ID of the {@link dev.sheldan.abstracto.core.models.database.AChannel} in which the status updates - * should be sent to - * @return A {@link CompletableFuture future} which completes, after all the updates on the {@link net.dv8tion.jda.api.entities.Member} - * have been completed - */ - CompletableFuture unsetRoles(ARole role, Long channelId); + CompletableFuture setRoleToLevel(Role role, Integer level, GuildMessageChannel messageChannel); + + CompletableFuture unsetRoles(ARole role, GuildMessageChannel messageChannel); List getExperienceRolesAtLevel(Integer level, AServer server); - CompletableFuture unsetRoles(List roles, Long channelId); - CompletableFuture unsetRoles(List roles, Long channelId, AExperienceRole toAdd); + CompletableFuture unsetRoles(List roles, GuildMessageChannel messageChannel); /** * Calculates the appropriate {@link AExperienceRole experienceRole} based on the provided list of {@link AExperienceRole experienceRole} diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/service/management/ExperienceRoleManagementService.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/service/management/ExperienceRoleManagementService.java index 67294a52d..ed72ac26b 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/service/management/ExperienceRoleManagementService.java +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/service/management/ExperienceRoleManagementService.java @@ -37,6 +37,7 @@ public interface ExperienceRoleManagementService { * @param role The {@link AExperienceRole experienceRole} to delete. */ void unsetRole(AExperienceRole role); + void unsetRoles(List role); /** * Retrieves the {@link AExperienceRole experienceRole} which uses the given {@link ARole role} in the {@link AServer server} @@ -44,6 +45,7 @@ public interface ExperienceRoleManagementService { * @return the {@link AExperienceRole experienceRole} which uses the given {@link ARole role} */ AExperienceRole getRoleInServer(ARole role); + List getRolesInServer(List role); /** * Retrieves a possible {@link AExperienceRole role}, if it exists, for the given {@link ARole}. Returns an empty Optional if it does not exist diff --git a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/service/management/UserExperienceManagementService.java b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/service/management/UserExperienceManagementService.java index ede3a344e..52b8f9c2f 100644 --- a/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/service/management/UserExperienceManagementService.java +++ b/abstracto-application/abstracto-modules/experience-tracking/experience-tracking-int/src/main/java/dev/sheldan/abstracto/experience/service/management/UserExperienceManagementService.java @@ -3,6 +3,7 @@ package dev.sheldan.abstracto.experience.service.management; import dev.sheldan.abstracto.core.models.database.AServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer; +import dev.sheldan.abstracto.experience.model.database.AExperienceRole; import dev.sheldan.abstracto.experience.model.database.AUserExperience; import dev.sheldan.abstracto.experience.model.database.LeaderBoardEntryResult; @@ -19,6 +20,7 @@ public interface UserExperienceManagementService { * @return The {@link AUserExperience userExperience} object representing the {@link AUserInAServer userInAServer} */ AUserExperience findUserInServer(AUserInAServer aUserInAServer); + void removeExperienceRoleFromUsers(AExperienceRole experienceRole); /** * Retrieves a possible {@link AUserExperience userExperience} for the given ID of the {@link AUserInAServer}. diff --git a/abstracto-application/abstracto-modules/logging/logging-impl/src/main/java/dev/sheldan/abstracto/logging/listener/JoinLogger.java b/abstracto-application/abstracto-modules/logging/logging-impl/src/main/java/dev/sheldan/abstracto/logging/listener/JoinLogger.java index c30977d5b..fad9de0a3 100644 --- a/abstracto-application/abstracto-modules/logging/logging-impl/src/main/java/dev/sheldan/abstracto/logging/listener/JoinLogger.java +++ b/abstracto-application/abstracto-modules/logging/logging-impl/src/main/java/dev/sheldan/abstracto/logging/listener/JoinLogger.java @@ -29,13 +29,6 @@ public class JoinLogger implements AsyncJoinListener { @Autowired private PostTargetService postTargetService; - @Autowired - private MemberService memberService; - - @Autowired - private JoinLogger self; - - @Override public DefaultListenerResult execute(MemberJoinModel listenerModel) { MemberJoinLogModel model = MemberJoinLogModel diff --git a/abstracto-application/abstracto-modules/logging/logging-impl/src/main/java/dev/sheldan/abstracto/logging/listener/LeaveLogger.java b/abstracto-application/abstracto-modules/logging/logging-impl/src/main/java/dev/sheldan/abstracto/logging/listener/LeaveLogger.java index 60e0d95ae..96b832cc3 100644 --- a/abstracto-application/abstracto-modules/logging/logging-impl/src/main/java/dev/sheldan/abstracto/logging/listener/LeaveLogger.java +++ b/abstracto-application/abstracto-modules/logging/logging-impl/src/main/java/dev/sheldan/abstracto/logging/listener/LeaveLogger.java @@ -3,6 +3,7 @@ package dev.sheldan.abstracto.logging.listener; import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.listener.DefaultListenerResult; import dev.sheldan.abstracto.core.listener.async.jda.AsyncLeaveListener; +import dev.sheldan.abstracto.core.models.ServerUser; import dev.sheldan.abstracto.core.models.listener.MemberLeaveModel; import dev.sheldan.abstracto.core.service.MemberService; import dev.sheldan.abstracto.core.service.PostTargetService; @@ -27,13 +28,6 @@ public class LeaveLogger implements AsyncLeaveListener { @Autowired private PostTargetService postTargetService; - @Autowired - private MemberService memberService; - - @Autowired - private LeaveLogger self; - - @Override public FeatureDefinition getFeature() { return LoggingFeatureDefinition.LOGGING; @@ -41,8 +35,14 @@ public class LeaveLogger implements AsyncLeaveListener { @Override public DefaultListenerResult execute(MemberLeaveModel listenerModel) { + ServerUser leavingUser = ServerUser + .builder() + .userId(listenerModel.getUser().getIdLong()) + .serverId(listenerModel.getServerId()) + .build(); MemberLeaveModel model = MemberLeaveModel .builder() + .leavingUser(leavingUser) .user(listenerModel.getUser()) .build(); log.debug("Logging leave event for user {} in server {}.", listenerModel.getUser().getIdLong(), listenerModel.getServerId()); diff --git a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/models/template/display/RoleDisplay.java b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/models/template/display/RoleDisplay.java index ef07ffb81..a5042087b 100644 --- a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/models/template/display/RoleDisplay.java +++ b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/models/template/display/RoleDisplay.java @@ -21,6 +21,14 @@ public class RoleDisplay { .build(); } + public static RoleDisplay fromRole(Long roleId) { + return RoleDisplay + .builder() + .roleId(roleId) + .roleMention("<@&" + roleId + '>') + .build(); + } + public static RoleDisplay fromARole(ARole role) { return RoleDisplay .builder()