Compare commits

...

18 Commits

Author SHA1 Message Date
Sheldan
118b4c0e5d [maven-release-plugin] prepare release abstracto-application-1.2.14 2021-05-26 21:51:33 +02:00
Sheldan
95a639a733 [AB-272] improving leaderboard performance
fixing leaderboard returning wrong pages
making template cache update duration longer
2021-05-26 21:41:04 +02:00
Sheldan
919b52a607 [maven-release-plugin] prepare for next development iteration 2021-05-26 14:48:13 +02:00
Sheldan
a9229d4b28 [maven-release-plugin] prepare release abstracto-application-1.2.13 2021-05-26 14:48:06 +02:00
Sheldan
47ce03ac25 [AB-xxx] fixing version 2021-05-26 14:37:57 +02:00
Sheldan
cef96515aa Revert "[maven-release-plugin] prepare release abstracto-application-1.3.13"
This reverts commit 072d680e
2021-05-26 14:16:21 +02:00
Sheldan
23ac70bb69 [maven-release-plugin] prepare for next development iteration 2021-05-26 13:52:20 +02:00
Sheldan
072d680e2f [maven-release-plugin] prepare release abstracto-application-1.3.13 2021-05-26 13:52:11 +02:00
Sheldan
04b3e073aa [AB-270] fixing test 2021-05-26 11:51:45 +02:00
Sheldan
08e2a31f15 [AB-270] adding a channel group to define the channels in which experience gain is disabled
adding a service method to enable migration
2021-05-26 11:29:34 +02:00
Sheldan
184271ff7c [AB-247/AB-252] fixing issue with set experience role failing to consider the correct experience roles
adding levelRoles command
ignoring bots from experience gain
adding a bit more logging to user experience handling
2021-05-26 01:22:37 +02:00
Sheldan
c4fe73b3f8 [AB-251] adding optional member parameter to rank command to show the rank of another member 2021-05-25 00:24:07 +02:00
Sheldan
2dc73ef3b9 [AB-267] fixing some combinations of reaction characters being ignored 2021-05-24 19:05:41 +02:00
Sheldan
2dd02cdae8 [AB-266] fixing online status and activity not showing up in userinfo 2021-05-24 18:32:41 +02:00
Sheldan
d39ee4359f [AB-231] fixing parameter letting less than required commands to the commands, in case the parameters were not able to be parsed (expected emote got string), the check for parameter count as only done before actually parsing the parameters 2021-05-24 18:16:39 +02:00
Sheldan
d7cc7f579f [AB-254] fixing exception for unSuggest in case the suggestion message has been deleted 2021-05-24 17:53:58 +02:00
Sheldan
7d30afbd2c [AB-262] adding feature mode to suggestion to be automatically reminded of a suggestion after a configurable amount of time 2021-05-24 17:39:19 +02:00
Sheldan
f4c1dcb27f [maven-release-plugin] prepare for next development iteration 2021-05-23 23:26:29 +02:00
128 changed files with 906 additions and 319 deletions

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>assignable-roles</artifactId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>assignable-roles</artifactId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>assignable-roles-int</artifactId>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>entertainment</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -151,6 +151,11 @@ public class EntertainmentServiceBean implements EntertainmentService {
}
continue;
}
if(replacedCombos.contains(charAsString) && (!usedReplacements.contains(charAsString) || allowDuplicates)) {
usedReplacements.add(charAsString);
result.add(charAsString);
continue;
}
// reject any other character, as the ones we can deal with
if (!this.reactMapping.getSingle().containsKey(charAsString)) {
log.info("Cannot find mapping. Not replacing with emote.");

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>entertainment</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>experience-tracking</artifactId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

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

View File

@@ -0,0 +1,73 @@
package dev.sheldan.abstracto.experience.command;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
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.models.database.AServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.experience.config.ExperienceFeatureDefinition;
import dev.sheldan.abstracto.experience.model.template.LevelRole;
import dev.sheldan.abstracto.experience.model.template.LevelRolesModel;
import dev.sheldan.abstracto.experience.service.ExperienceRoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@Component
public class LevelRoles extends AbstractConditionableCommand {
@Autowired
private ExperienceRoleService experienceRoleService;
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private ChannelService channelService;
private static final String LEVEL_ROLES_TEMPLATE_KEY = "levelRoles_response";
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
AServer server = serverManagementService.loadServer(commandContext.getGuild());
List<LevelRole> levelRoles = experienceRoleService.loadLevelRoleConfigForServer(server);
levelRoles = levelRoles.stream().sorted(Comparator.comparingInt(LevelRole::getLevel).reversed()).collect(Collectors.toList());
LevelRolesModel model = LevelRolesModel
.builder()
.levelRoles(levelRoles)
.build();
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInTextChannelList(LEVEL_ROLES_TEMPLATE_KEY, model, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("levelRoles")
.module(ExperienceModuleDefinition.EXPERIENCE)
.async(true)
.templated(true)
.supportsEmbedException(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return ExperienceFeatureDefinition.EXPERIENCE;
}
}

View File

@@ -6,7 +6,6 @@ import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.execution.ContextConverter;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.ChannelService;
@@ -24,11 +23,13 @@ import dev.sheldan.abstracto.experience.service.management.UserExperienceManagem
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -66,12 +67,17 @@ public class Rank extends AbstractConditionableCommand {
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
RankModel rankModel = (RankModel) ContextConverter.slimFromCommandContext(commandContext, RankModel.class);
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(commandContext.getAuthor());
List<Object> parameters = commandContext.getParameters().getParameters();
Member parameter = !parameters.isEmpty() ? (Member) parameters.get(0) : commandContext.getAuthor();
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(parameter);
LeaderBoardEntry userRank = userExperienceService.getRankOfUserInServer(aUserInAServer);
CompletableFuture<LeaderBoardEntryModel> future = converter.fromLeaderBoardEntry(userRank);
CompletableFuture<List<LeaderBoardEntryModel>> future = converter.fromLeaderBoardEntry(Arrays.asList(userRank));
RankModel rankModel = RankModel
.builder()
.member(parameter)
.build();
return future.thenCompose(leaderBoardEntryModel ->
self.renderAndSendRank(commandContext, rankModel, leaderBoardEntryModel)
self.renderAndSendRank(commandContext, rankModel, leaderBoardEntryModel.get(0))
).thenApply(result -> CommandResult.fromIgnored());
}
@@ -89,6 +95,7 @@ public class Rank extends AbstractConditionableCommand {
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
parameters.add(Parameter.builder().name("member").templated(true).type(Member.class).optional(true).build());
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("rank")

View File

@@ -10,12 +10,16 @@ import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.service.management.RoleManagementService;
import dev.sheldan.abstracto.experience.config.ExperienceFeatureDefinition;
import dev.sheldan.abstracto.experience.exception.ExperienceRoleNotFoundException;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
/**
@@ -31,13 +35,20 @@ public class UnSetExpRole extends AbstractConditionableCommand {
@Autowired
private RoleManagementService roleManagementService;
@Autowired
private ExperienceRoleManagementService experienceRoleManagementService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
ARole role = (ARole) commandContext.getParameters().getParameters().get(0);
ARole actualRole = roleManagementService.findRole(role.getId());
// do not check for the existence of the role, because if the role was deleted, users should be able
// to get rid of it in the configuration
return experienceRoleService.unsetRole(actualRole, commandContext.getChannel().getIdLong())
Optional<AExperienceRole> experienceRole = experienceRoleManagementService.getRoleInServerOptional(actualRole);
if(!experienceRole.isPresent()) {
throw new ExperienceRoleNotFoundException();
}
return experienceRoleService.unsetRoles(actualRole, commandContext.getChannel().getIdLong())
.thenApply(aVoid -> CommandResult.fromSuccess());
}

View File

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

View File

@@ -31,12 +31,16 @@ public class ExperienceTrackerListener implements AsyncMessageReceivedListener {
@Override
public DefaultListenerResult execute(MessageReceivedModel model) {
Message message = model.getMessage();
if(!message.isFromGuild() || message.isWebhookMessage() || message.getType().isSystem()) {
if(!message.isFromGuild() || message.isWebhookMessage() || message.getType().isSystem() || message.getAuthor().isBot()) {
return DefaultListenerResult.IGNORED;
}
if(userExperienceService.experienceGainEnabledInChannel(message.getChannel())) {
AUserInAServer cause = userInServerManagementService.loadOrCreateUser(model.getServerId(), model.getMessage().getAuthor().getIdLong());
userExperienceService.addExperience(cause);
return DefaultListenerResult.PROCESSED;
} else {
return DefaultListenerResult.IGNORED;
}
AUserInAServer cause = userInServerManagementService.loadOrCreateUser(model.getServerId(), model.getMessage().getAuthor().getIdLong());
userExperienceService.addExperience(cause);
return DefaultListenerResult.PROCESSED;
}
@Override

View File

@@ -1,15 +1,9 @@
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.models.database.AUserInAServer;
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.core.service.management.*;
import dev.sheldan.abstracto.core.utils.CompletableFutureList;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.experience.config.ExperienceFeatureConfig;
@@ -26,6 +20,7 @@ import dev.sheldan.abstracto.core.templating.service.TemplateService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@@ -82,6 +77,9 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
@Autowired
private ChannelManagementService channelManagementService;
@Autowired
private ChannelGroupService channelGroupService;
@Autowired
private DefaultConfigManagementService defaultConfigManagementService;
@@ -94,24 +92,26 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
try {
Long minute = Instant.now().getEpochSecond() / 60;
Map<Long, List<ServerExperience>> 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(), userInAServer.getServerReference().getId());
minute, userInAServer.getUserReference().getId(), serverId);
List<ServerExperience> existing = runtimeExperience.get(minute);
for (ServerExperience server : existing) {
if (server.getServerId().equals(userInAServer.getServerReference().getId()) && server.getUserInServerIds().stream().noneMatch(userInAServer1 -> userInAServer.getUserInServerId().equals(userInAServer1))) {
server.getUserInServerIds().add(userInAServer.getUserInServerId());
if (server.getServerId().equals(serverId) && server.getUserInServerIds().stream().noneMatch(userInServerId::equals)) {
server.getUserInServerIds().add(userInServerId);
break;
}
}
} else {
log.debug("Minute {} did not exist yet. Creating new entry for user {} in server {}.", minute, userInAServer.getUserReference().getId(), userInAServer.getServerReference().getId());
log.debug("Minute {} did not exist yet. Creating new entry for user {} in server {}.", minute, userInAServer.getUserReference().getId(), serverId);
ServerExperience serverExperience = ServerExperience
.builder()
.serverId(userInAServer.getServerReference().getId())
.serverId(serverId)
.build();
serverExperience.getUserInServerIds().add(userInAServer.getUserInServerId());
serverExperience.getUserInServerIds().add(userInServerId);
runtimeExperience.put(minute, new ArrayList<>(Arrays.asList(serverExperience)));
}
} finally {
@@ -180,7 +180,6 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
SystemConfigProperty defaultMinExp = defaultConfigManagementService.getDefaultConfig(ExperienceFeatureConfig.MIN_EXP_KEY);
SystemConfigProperty defaultMaxExp = defaultConfigManagementService.getDefaultConfig(ExperienceFeatureConfig.MAX_EXP_KEY);
AServer server = serverManagementService.loadOrCreate(serverId);
log.info("Handling {} experience for server {}", memberFutures.size(), 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());
@@ -190,18 +189,21 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
List<ADisabledExpRole> disabledExpRoles = disabledExpRoleManagementService.getDisabledRolesForServer(server);
List<ARole> 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 gains {}", userInAServer.getUserReference().getId(), gainedExperience);
log.debug("Handling {}. The user might gain {}.", userInServerId, gainedExperience);
Optional<AUserExperience> 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<RoleCalculationResult> resultFuture = updateUserRole(aUserExperience, roles, newLevel.getLevel());
@@ -240,7 +242,7 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
futures.add(resultFuture);
}
} else {
log.info("User {} has a role which makes the user unable to gain experience.", userInAServer.getUserInServerId());
log.debug("User {} has a role which makes the user unable to gain experience.", userInAServer.getUserInServerId());
}
}
});
@@ -296,7 +298,7 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
AUserExperience userExperience;
if(experienceGainResult.isCreateUserExperience()) {
userExperience = userExperienceManagementService.createUserInServer(user);
log.info("Creating new experience user {}", experienceGainResult.getUserInServerId());
log.info("Creating new experience user for user in server {}.", experienceGainResult.getUserInServerId());
} else {
userExperience = userExperienceManagementService.findByUserInServerId(experienceGainResult.getUserInServerId());
}
@@ -346,7 +348,7 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
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){
if(!currentlyHasNoExperienceRole && !userExperience.getCurrentExperienceRole().getRole().getDeleted()){
return roleService.removeRoleFromUserFuture(user, userExperience.getCurrentExperienceRole().getRole())
.thenApply(returnNullRole);
}
@@ -373,16 +375,17 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
// 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)) {
log.info("User {} in server {} gets a new role {} because of experience.", userId, serverId, roleId);
if(!userHasRoleAlready || userHasOldRole) {
CompletableFuture<Void> 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<Void> addRoleFuture;
if(!userHasRoleAlready) {
log.info("User {} in server {} gets a new role {} because of experience.", userId, serverId, roleId);
addRoleFuture = roleService.addRoleToMemberFuture(member, roleId);
} else {
addRoleFuture = CompletableFuture.completedFuture(null);
@@ -403,7 +406,7 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
roles.sort(Comparator.comparing(role -> role.getLevel().getLevel()));
for (int i = 0; i < aUserExperiences.size(); i++) {
AUserExperience userExperience = aUserExperiences.get(i);
log.debug("Synchronizing {} out of {}", i, aUserExperiences.size());
log.info("Synchronizing {} out of {}. User in Server {}.", i, aUserExperiences.size(), userExperience.getUser().getUserInServerId());
results.add(updateUserRole(userExperience, roles, userExperience.getCurrentLevel().getLevel()));
}
return results;
@@ -424,7 +427,7 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
/**
* Updates the actually stored experience roles in the database
* @param results The list of {@link RoleCalculationResult} which should be applied
* @param results The list of {@link RoleCalculationResult results} which should be applied
*/
@Transactional
public void syncRolesInStorage(List<RoleCalculationResult> results) {
@@ -452,6 +455,33 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
});
}
@Override
public boolean experienceGainEnabledInChannel(MessageChannel messageChannel) {
AChannel channel = channelManagementService.loadChannel(messageChannel.getIdLong());
List<AChannelGroup> channelGroups = channelGroupService.getChannelGroupsOfChannelWithType(channel, EXPERIENCE_GAIN_CHANNEL_GROUP_KEY);
if(!channelGroups.isEmpty()) {
return channelGroups.stream().noneMatch(AChannelGroup::getEnabled);
}
return true;
}
@Override
public AUserExperience createUserExperienceForUser(AUserInAServer aUserInAServer, Long experience, Long messageCount) {
List<AExperienceLevel> levels = experienceLevelManagementService.getLevelConfig();
levels.sort(Comparator.comparing(AExperienceLevel::getExperienceNeeded));
return createUserExperienceForUser(aUserInAServer, experience, messageCount, levels);
}
@Override
public AUserExperience createUserExperienceForUser(AUserInAServer aUserInAServer, Long experience, Long messageCount, List<AExperienceLevel> levels) {
AExperienceLevel level = calculateLevel(levels, experience);
AUserExperience userExperience = userExperienceManagementService.createUserInServer(aUserInAServer);
userExperience.setCurrentLevel(level);
userExperience.setExperience(experience);
userExperience.setMessageCount(messageCount);
return userExperience;
}
@Override
public CompletableFutureList<RoleCalculationResult> executeActionOnUserExperiencesWithFeedBack(List<AUserExperience> experiences, AChannel channel, Function<AUserExperience, CompletableFuture<RoleCalculationResult>> toExecute) {
List<CompletableFuture<RoleCalculationResult>> futures = new ArrayList<>();
@@ -461,12 +491,13 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
int interval = Math.min(Math.max(experiences.size() / 10, 1), 100);
for (int i = 0; i < experiences.size(); i++) {
if((i % interval) == 1) {
log.debug("Updating feedback message with new index {} out of {}", i, experiences.size());
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);
}
futures.add(toExecute.apply(experiences.get(i)));
log.debug("Synchronizing {} out of {}", i, experiences.size());
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);
@@ -483,8 +514,8 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
@Override
public void enableExperienceForUser(AUserInAServer userInAServer) {
AUserExperience userExperience = userExperienceManagementService.findUserInServer(userInAServer);
log.info("Enabling experience gain for user {} in server {}.", userInAServer.getUserReference().getId(), userInAServer.getServerReference().getId());
AUserExperience userExperience = userExperienceManagementService.findUserInServer(userInAServer);
userExperience.setExperienceGainDisabled(false);
}
@@ -503,7 +534,7 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
@Override
public CompletableFuture<RoleCalculationResult> syncForSingleUser(AUserExperience userExperience) {
AUserInAServer user = userExperience.getUser();
log.info("Synchronizing for user {} in server {}", user.getUserReference().getId(), user.getServerReference().getId());
log.info("Synchronizing for user {} in server {}.", user.getUserReference().getId(), user.getServerReference().getId());
List<AExperienceRole> roles = experienceRoleManagementService.getExperienceRolesForServer(user.getServerReference());
roles.sort(Comparator.comparing(role -> role.getLevel().getLevel()));
return updateUserRole(userExperience, roles, userExperience.getLevelOrDefault());
@@ -517,12 +548,13 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
page--;
int pageSize = 10;
log.debug("Loading leaderboard page {} for server {}.", page, server.getId());
List<AUserExperience> experiences = userExperienceManagementService.findLeaderBoardUsersPaginated(server, page * pageSize, (page + 1) * pageSize);
List<AUserExperience> experiences = userExperienceManagementService.findLeaderBoardUsersPaginated(server, page, pageSize);
List<LeaderBoardEntry> entries = new ArrayList<>();
log.debug("Found {} experiences.", experiences.size());
int pageOffset = page * pageSize;
for (int i = 0; i < experiences.size(); i++) {
AUserExperience userExperience = experiences.get(i);
entries.add(LeaderBoardEntry.builder().experience(userExperience).rank((page * pageSize) + i + 1).build());
entries.add(LeaderBoardEntry.builder().experience(userExperience).rank(pageOffset + i + 1).build());
}
return LeaderBoard.builder().entries(entries).build();
}

View File

@@ -57,7 +57,7 @@ public class ExperienceLevelServiceBean implements ExperienceLevelService {
@Override
public Long calculateExperienceToNextLevel(Integer level, Long currentExperience) {
AExperienceLevel nextLevel = experienceLevelManagementService.getLevel(level + 1)
AExperienceLevel nextLevel = experienceLevelManagementService.getLevelOptional(level + 1)
.orElseThrow(() -> new AbstractoRunTimeException(String.format("Could not find level %s", level)));
return nextLevel.getExperienceNeeded() - currentExperience;
}

View File

@@ -3,6 +3,7 @@ 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.RoleService;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.RoleManagementService;
import dev.sheldan.abstracto.core.utils.CompletableFutureList;
@@ -10,6 +11,7 @@ import dev.sheldan.abstracto.experience.model.RoleCalculationResult;
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.service.management.ExperienceLevelManagementService;
import dev.sheldan.abstracto.experience.service.management.ExperienceRoleManagementService;
import lombok.extern.slf4j.Slf4j;
@@ -18,9 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@@ -46,6 +46,9 @@ public class ExperienceRoleServiceBean implements ExperienceRoleService {
@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}
@@ -55,14 +58,43 @@ public class ExperienceRoleServiceBean implements ExperienceRoleService {
@Override
public CompletableFuture<Void> setRoleToLevel(Role role, Integer level, Long channelId) {
Long roleId = role.getIdLong();
ARole aRole = roleManagementService.findRole(roleId);
return unsetRole(aRole, channelId).thenAccept(aVoid ->
ARole aRoleToSet = roleManagementService.findRole(roleId);
List<AExperienceRole> experienceRoles = getExperienceRolesAtLevel(level, aRoleToSet.getServer());
List<ARole> 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<Void> 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;
}
@Transactional
public void deleteExperienceRoleViaId(Long newlyCreatedExperienceRoleId) {
AExperienceRole reLoadedRole = experienceRoleManagementService.getExperienceRoleById(newlyCreatedExperienceRoleId);
experienceRoleManagementService.unsetRole(reLoadedRole);
}
/**
* Removes all previous defined {@link AExperienceRole experienceRoles} from the given leve and sets the {@link ARole}
* 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
@@ -70,9 +102,9 @@ public class ExperienceRoleServiceBean implements ExperienceRoleService {
@Transactional
public void unsetRoleInDb(Integer level, Long roleId) {
log.info("Unsetting role {} from level {}.", roleId, level);
AExperienceLevel experienceLevel = experienceLevelService.getLevel(level).orElseThrow(() -> new IllegalArgumentException(String.format("Could not find level %s", level)));
AExperienceLevel experienceLevel = experienceLevelService.getLevelOptional(level).orElseThrow(() -> new IllegalArgumentException(String.format("Could not find level %s", level)));
ARole loadedRole = roleManagementService.findRole(roleId);
experienceRoleManagementService.removeAllRoleAssignmentsForLevelInServer(experienceLevel, loadedRole.getServer());
experienceRoleManagementService.removeAllRoleAssignmentsForLevelInServerExceptRole(experienceLevel, loadedRole.getServer(), loadedRole);
experienceRoleManagementService.setLevelToRole(experienceLevel, loadedRole);
}
@@ -83,31 +115,54 @@ public class ExperienceRoleServiceBean implements ExperienceRoleService {
* configuration
*/
@Override
public CompletableFuture<Void> unsetRole(ARole role, Long feedbackChannelId) {
AChannel channel = channelManagementService.loadChannel(feedbackChannelId);
Optional<AExperienceRole> roleInServerOptional = experienceRoleManagementService.getRoleInServerOptional(role);
if(roleInServerOptional.isPresent()) {
AExperienceRole roleInServer = roleInServerOptional.get();
if(!roleInServer.getUsers().isEmpty()) {
log.info("Recalculating the roles for {} users, because their current role was removed from experience tracking.", roleInServer.getUsers().size());
List<AExperienceRole> roles = experienceRoleManagementService.getExperienceRolesForServer(role.getServer());
roles.removeIf(role1 -> role1.getId().equals(roleInServer.getId()));
roles.sort(Comparator.comparing(innerRole -> innerRole.getLevel().getLevel()));
Long roleId = role.getId();
CompletableFutureList<RoleCalculationResult> calculationResults = userExperienceService.executeActionOnUserExperiencesWithFeedBack(roleInServer.getUsers(), channel,
(AUserExperience ex) -> userExperienceService.updateUserRole(ex, roles, ex.getLevelOrDefault()));
return calculationResults.getMainFuture().thenAccept(aVoid ->
self.persistData(calculationResults, roleId)
);
} else {
log.info("Roles does not have any active users, no need to remove them.");
experienceRoleManagementService.unsetRole(roleInServer);
return CompletableFuture.completedFuture(null);
}
} else {
log.info("Experience role is not define in server - skipping unset.");
public CompletableFuture<Void> unsetRoles(ARole role, Long feedbackChannelId) {
return unsetRoles(Arrays.asList(role), feedbackChannelId);
}
@Override
public List<AExperienceRole> getExperienceRolesAtLevel(Integer level, AServer server) {
AExperienceLevel levelObj = experienceLevelService.getLevel(level);
return experienceRoleManagementService.getExperienceRolesAtLevelInServer(levelObj, server);
}
@Override
public CompletableFuture<Void> unsetRoles(List<ARole> rolesToUnset, Long feedbackChannelId) {
return unsetRoles(rolesToUnset, feedbackChannelId, null);
}
@Override
public CompletableFuture<Void> unsetRoles(List<ARole> rolesToUnset, Long feedbackChannelId, AExperienceRole toAdd) {
if(rolesToUnset.isEmpty()) {
return CompletableFuture.completedFuture(null);
}
AServer server = rolesToUnset.get(0).getServer();
AChannel channel = channelManagementService.loadChannel(feedbackChannelId);
List<AExperienceRole> experienceRolesNecessaryToRemove = new ArrayList<>();
List<AUserExperience> usersToUpdate = new ArrayList<>();
rolesToUnset.forEach(role -> {
Optional<AExperienceRole> 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());
}
});
log.info("Recalculating the roles for {} users, because their current role was removed from experience tracking.", usersToUpdate.size());
List<AExperienceRole> 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<Long> roleIds = experienceRolesNecessaryToRemove.stream().map(AExperienceRole::getId).collect(Collectors.toList());
if(toAdd != null) {
roleIds.removeIf(aLong -> aLong.equals(toAdd.getRole().getId()));
}
CompletableFutureList<RoleCalculationResult> calculationResults = userExperienceService.executeActionOnUserExperiencesWithFeedBack(usersToUpdate, channel,
(AUserExperience ex) -> userExperienceService.updateUserRole(ex, roles, ex.getLevelOrDefault()));
return calculationResults.getMainFuture().thenAccept(aVoid -> self.persistData(calculationResults, roleIds));
}
/**
@@ -116,13 +171,15 @@ public class ExperienceRoleServiceBean implements ExperienceRoleService {
* 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 roleId The ID of the {@link AExperienceRole experienceRole} which was removed from the experience roles
* @param roleIds The IDs of the {@link AExperienceRole experienceRoles} which were removed from the experience roles
*/
@Transactional
public void persistData(CompletableFutureList<RoleCalculationResult> results, Long roleId) {
log.info("Persisting {} role calculation results after changing the role {}.", results.getFutures().size(), roleId);
AExperienceRole roleInServer = experienceRoleManagementService.getExperienceRoleById(roleId);
experienceRoleManagementService.unsetRole(roleInServer);
public void persistData(CompletableFutureList<RoleCalculationResult> results, List<Long> 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());
}
@@ -153,4 +210,21 @@ public class ExperienceRoleServiceBean implements ExperienceRoleService {
return aExperienceRole != null ? aExperienceRole.getLevel() : AExperienceLevel.builder().level(200).build();
}
@Override
public List<LevelRole> loadLevelRoleConfigForServer(AServer server) {
List<AExperienceRole> roles = experienceRoleManagementService.getExperienceRolesForServer(server);
List<LevelRole> levelRoles = new ArrayList<>();
roles.forEach(aExperienceRole -> {
Role role = roleService.getRoleFromGuild(aExperienceRole.getRole());
LevelRole levelRole = LevelRole
.builder()
.role(role)
.roleId(aExperienceRole.getId())
.level(aExperienceRole.getLevel().getLevel())
.build();
levelRoles.add(levelRole);
});
return levelRoles;
}
}

View File

@@ -1,5 +1,6 @@
package dev.sheldan.abstracto.experience.service.management;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.experience.model.database.AExperienceLevel;
import dev.sheldan.abstracto.experience.repository.ExperienceLevelRepository;
import org.springframework.beans.factory.annotation.Autowired;
@@ -9,7 +10,7 @@ import java.util.List;
import java.util.Optional;
@Component
public class ExperienceLevelManagementServiceBean implements ExperienceLevelManagementService {
public class ExperienceLevelManagementServiceBean implements ExperienceLevelManagementService {
@Autowired
private ExperienceLevelRepository experienceLevelRepository;
@@ -30,10 +31,15 @@ public class ExperienceLevelManagementServiceBean implements ExperienceLevelM
}
@Override
public Optional<AExperienceLevel> getLevel(Integer level) {
public Optional<AExperienceLevel> getLevelOptional(Integer level) {
return experienceLevelRepository.findById(level);
}
@Override
public AExperienceLevel getLevel(Integer level) {
return getLevelOptional(level).orElseThrow(() -> new AbstractoRunTimeException("Level not found."));
}
@Override
public List<AExperienceLevel> getLevelConfig() {
return experienceLevelRepository.findAll();

View File

@@ -30,10 +30,14 @@ public class ExperienceRoleManagementServiceBean implements ExperienceRoleManage
* @param server The server in which this should happen
*/
@Override
public void removeAllRoleAssignmentsForLevelInServer(AExperienceLevel level, AServer server) {
public void removeAllRoleAssignmentsForLevelInServerExceptRole(AExperienceLevel level, AServer server, ARole role) {
List<AExperienceRole> existingExperienceRoles = experienceRoleRepository.findByLevelAndRoleServer(level, server);
log.info("Removing all role assignments ({}) for level {} in server {}.", existingExperienceRoles.size(), level.getLevel(), server.getId());
existingExperienceRoles.forEach(existingRole -> experienceRoleRepository.delete(existingRole));
existingExperienceRoles.forEach(existingRole -> {
if(!existingRole.getRole().getId().equals(role.getId())) {
experienceRoleRepository.delete(existingRole);
}
});
}
@Override
@@ -97,4 +101,9 @@ public class ExperienceRoleManagementServiceBean implements ExperienceRoleManage
}
return experienceRole;
}
@Override
public List<AExperienceRole> getExperienceRolesAtLevelInServer(AExperienceLevel level, AServer server) {
return experienceRoleRepository.findByLevelAndRoleServer(level, server);
}
}

View File

@@ -45,7 +45,7 @@ public class UserExperienceManagementServiceBean implements UserExperienceManage
@Override
public AUserExperience createUserInServer(AUserInAServer aUserInAServer) {
log.info("Creating user experience for user {} in server {}.", aUserInAServer.getUserReference().getId(),aUserInAServer.getServerReference().getId());
AExperienceLevel startingLevel = experienceLevelManagementService.getLevel(0).orElseThrow(() -> new AbstractoRunTimeException(String.format("Could not find level %s", 0)));
AExperienceLevel startingLevel = experienceLevelManagementService.getLevelOptional(0).orElseThrow(() -> new AbstractoRunTimeException(String.format("Could not find level %s", 0)));
return AUserExperience
.builder()
.experience(0L)
@@ -64,8 +64,8 @@ public class UserExperienceManagementServiceBean implements UserExperienceManage
}
@Override
public List<AUserExperience> findLeaderBoardUsersPaginated(AServer aServer, Integer start, Integer end) {
return repository.findTop10ByUser_ServerReferenceOrderByExperienceDesc(aServer, PageRequest.of(start, end));
public List<AUserExperience> findLeaderBoardUsersPaginated(AServer aServer, Integer page, Integer size) {
return repository.findTop10ByUser_ServerReferenceOrderByExperienceDesc(aServer, PageRequest.of(page, size));
}
@Override

View File

@@ -0,0 +1,14 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="experience_gain_channel_group_type-insertion">
<insert tableName="channel_group_type">
<column name="group_type_key" value="experienceGain"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -64,6 +64,11 @@
<column name="module_id" valueComputed="${experienceModule}"/>
<column name="feature_id" valueComputed="${experienceFeature}"/>
</insert>
<insert tableName="command">
<column name="name" value="levelRoles"/>
<column name="module_id" valueComputed="${experienceModule}"/>
<column name="feature_id" valueComputed="${experienceFeature}"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -11,4 +11,5 @@
<include file="module.xml" relativeToChangelogFile="true"/>
<include file="command.xml" relativeToChangelogFile="true"/>
<include file="experience_job.xml" relativeToChangelogFile="true"/>
<include file="channel_group_types.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

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

View File

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

View File

@@ -6,7 +6,9 @@ import dev.sheldan.abstracto.core.models.database.ARole;
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;
@@ -15,6 +17,7 @@ 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;
@@ -31,6 +34,9 @@ public class UnSetExpRoleTest {
@Mock
private RoleManagementService roleManagementService;
@Mock
private ExperienceRoleManagementService experienceRoleManagementService;
private static final Long CHANNEL_ID = 4L;
@Test
@@ -41,7 +47,8 @@ public class UnSetExpRoleTest {
when(context.getChannel().getIdLong()).thenReturn(CHANNEL_ID);
ARole actualRole = Mockito.mock(ARole.class);
when(roleManagementService.findRole(changedRole.getId())).thenReturn(actualRole);
when(experienceRoleService.unsetRole(actualRole, CHANNEL_ID)).thenReturn(CompletableFuture.completedFuture(null));
when(experienceRoleManagementService.getRoleInServerOptional(actualRole)).thenReturn(Optional.of(Mockito.mock(AExperienceRole.class)));
when(experienceRoleService.unsetRoles(actualRole, CHANNEL_ID)).thenReturn(CompletableFuture.completedFuture(null));
CompletableFuture<CommandResult> result = testUnit.executeAsync(context);
CommandTestUtilities.checkSuccessfulCompletionAsync(result);
}

View File

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

View File

@@ -5,6 +5,7 @@ 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.MessageChannel;
import net.dv8tion.jda.api.entities.MessageType;
import net.dv8tion.jda.api.entities.User;
import org.junit.Test;
@@ -43,10 +44,13 @@ public class ExperienceTrackerListenerTest {
Message mockedMessage = Mockito.mock(Message.class);
when(mockedMessage.isFromGuild()).thenReturn(true);
when(mockedMessage.isWebhookMessage()).thenReturn(false);
MessageChannel channel = Mockito.mock(MessageChannel.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);

View File

@@ -409,7 +409,6 @@ public class AUserExperienceServiceBeanTest {
setupDefaultConfig();
setupLevelsAndRolesAndNoDisallowed();
setExperienceRoleLevels();
when(aUserInAServer.getUserReference()).thenReturn(user);
when(userExperience.getExperienceGainDisabled()).thenReturn(true);
ArrayList<ExperienceGainResult> experienceResults = new ArrayList<>();
@@ -760,7 +759,7 @@ public class AUserExperienceServiceBeanTest {
when(userExperience2.getExperience()).thenReturn(MID_EXP);
when(userExperience2.getCurrentLevel()).thenReturn(level1);
when(userExperience2.getUser()).thenReturn(aUserInAServer2);
when(userExperienceManagementService.findLeaderBoardUsersPaginated(server, (page - 1) * pageSize, page * pageSize)).thenReturn(experiences);
when(userExperienceManagementService.findLeaderBoardUsersPaginated(server, page - 1, pageSize)).thenReturn(experiences);
LeaderBoard leaderBoardData = testUnit.findLeaderBoardData(server, page);
page--;
List<LeaderBoardEntry> entries = leaderBoardData.getEntries();

View File

@@ -38,7 +38,7 @@ public class ExperienceLevelServiceBeanTest {
public void testExperienceToNextLevelCalculation() {
AExperienceLevel level = mock(AExperienceLevel.class);
when(level.getExperienceNeeded()).thenReturn(15L);
when(experienceLevelManagementService.getLevel(51)).thenReturn(Optional.of(level));
when(experienceLevelManagementService.getLevelOptional(51)).thenReturn(Optional.of(level));
Long experience = testingUnit.calculateExperienceToNextLevel(50, 10L);
Assert.assertEquals(5L, experience.longValue());
}
@@ -55,7 +55,7 @@ public class ExperienceLevelServiceBeanTest {
@Test(expected = AbstractoRunTimeException.class)
public void testExperienceToNextLevelCalculationOverExistingLevel() {
when(experienceLevelManagementService.getLevel(51)).thenReturn(Optional.empty());
when(experienceLevelManagementService.getLevelOptional(51)).thenReturn(Optional.empty());
testingUnit.calculateExperienceToNextLevel(50, 10L);
}

View File

@@ -59,65 +59,21 @@ public class ExperienceRoleServiceBeanTest {
private static final Long CHANNEL_ID = 4L;
private static final Long ROLE_ID = 5L;
@Test
public void testSettingRoleToLevelWithoutOldUsers() {
Integer levelCount = 10;
Role roleToChange = Mockito.mock(Role.class);
ARole role = Mockito.mock(ARole.class);
when(roleToChange.getIdLong()).thenReturn(ROLE_ID);
when(roleManagementService.findRole(ROLE_ID)).thenReturn(role);
AExperienceRole previousExperienceRole = Mockito.mock(AExperienceRole.class);
when(experienceRoleManagementService.getRoleInServerOptional(role)).thenReturn(Optional.of(previousExperienceRole));
CompletableFuture<Void> future = testingUnit.setRoleToLevel(roleToChange, levelCount, CHANNEL_ID);
future.join();
verify(experienceRoleManagementService, times(1)).unsetRole(previousExperienceRole);
verify(self, times(1)).unsetRoleInDb(levelCount, ROLE_ID);
}
@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.getLevel(levelCount)).thenReturn(Optional.of(level));
when(experienceLevelService.getLevelOptional(levelCount)).thenReturn(Optional.of(level));
when(roleManagementService.findRole(roleToChange.getId())).thenReturn(roleToChange);
testingUnit.unsetRoleInDb(levelCount, roleToChange.getId());
verify(experienceRoleManagementService, times(1)).removeAllRoleAssignmentsForLevelInServer(level, server);
verify(experienceRoleManagementService, times(1)).removeAllRoleAssignmentsForLevelInServerExceptRole(level, server, roleToChange);
verify(experienceRoleManagementService, times(1)).setLevelToRole(level, roleToChange);
verify(experienceRoleManagementService, times(0)).getExperienceRolesForServer(server);
}
@Test
public void testSettingRoleToLevelExistingUsers() {
Integer levelCount = 10;
Role roleToChange = Mockito.mock(Role.class);
ARole role = Mockito.mock(ARole.class);
when(roleToChange.getIdLong()).thenReturn(ROLE_ID);
when(roleManagementService.findRole(ROLE_ID)).thenReturn(role);
when(role.getServer()).thenReturn(server);
AUserExperience firstUser = Mockito.mock(AUserExperience.class);
AUserExperience secondUser = Mockito.mock(AUserExperience.class);
List<AUserExperience> users = Arrays.asList(firstUser, secondUser);
AExperienceRole previousExperienceRole = Mockito.mock(AExperienceRole.class);
when(previousExperienceRole.getUsers()).thenReturn(users);
AExperienceRole newExperienceRole = Mockito.mock(AExperienceRole.class);
when(experienceRoleManagementService.getRoleInServerOptional(role)).thenReturn(Optional.of(previousExperienceRole));
when(experienceRoleManagementService.getExperienceRolesForServer(server)).thenReturn(new ArrayList<>(Arrays.asList(newExperienceRole, previousExperienceRole)));
List<CompletableFuture<RoleCalculationResult>> futures = new ArrayList<>();
futures.add(CompletableFuture.completedFuture(null));
AChannel feedbackChannel = Mockito.mock(AChannel.class);
when(channelManagementService.loadChannel(CHANNEL_ID)).thenReturn(feedbackChannel);
CompletableFutureList<RoleCalculationResult> futuresList = new CompletableFutureList<>(futures);
when(userExperienceService.executeActionOnUserExperiencesWithFeedBack(eq(users), eq(feedbackChannel), any())).thenReturn(futuresList);
CompletableFuture<Void> future = testingUnit.setRoleToLevel(roleToChange, levelCount, CHANNEL_ID);
future.join();
verify(experienceRoleManagementService, times(0)).unsetRole(previousExperienceRole);
}
@Test
public void testCalculateRoleForLevelInBetween() {
List<AExperienceRole> roles = getExperienceRoles();

View File

@@ -54,7 +54,7 @@ public class ExperienceLevelManagementServiceBeanTest {
when(level.getLevel()).thenReturn(levelValue);
when(level.getExperienceNeeded()).thenReturn(experienceAmount);
when(experienceLevelRepository.findById(levelValue)).thenReturn(Optional.of(level));
Optional<AExperienceLevel> foundLevelOptional = testUnit.getLevel(levelValue);
Optional<AExperienceLevel> foundLevelOptional = testUnit.getLevelOptional(levelValue);
Assert.assertTrue(foundLevelOptional.isPresent());
if(foundLevelOptional.isPresent()) {
AExperienceLevel foundLevel = foundLevelOptional.get();
@@ -70,7 +70,7 @@ public class ExperienceLevelManagementServiceBeanTest {
int levelValue = 1;
Optional<AExperienceLevel> level = Optional.empty();
when(experienceLevelRepository.findById(levelValue)).thenReturn(level);
Optional<AExperienceLevel> foundLevelOptional = testUnit.getLevel(levelValue);
Optional<AExperienceLevel> foundLevelOptional = testUnit.getLevelOptional(levelValue);
Assert.assertFalse(foundLevelOptional.isPresent());
}

View File

@@ -50,9 +50,14 @@ public class ExperienceRoleManagementServiceBeanTest {
public void testRemovingAllRoleAssignmentsForLevel() {
when(level.getLevel()).thenReturn(10);
AExperienceRole secondRole = Mockito.mock(AExperienceRole.class);
ARole anotherARole = Mockito.mock(ARole.class);
when(anotherARole.getId()).thenReturn(ROLE_ID + 1);
when(role.getId()).thenReturn(ROLE_ID);
when(experienceRole.getRole()).thenReturn(anotherARole);
when(secondRole.getRole()).thenReturn(anotherARole);
List<AExperienceRole> experienceRoles = Arrays.asList(experienceRole, secondRole);
when(experienceRoleRepository.findByLevelAndRoleServer(level, server)).thenReturn(experienceRoles);
testUnit.removeAllRoleAssignmentsForLevelInServer(level, server);
testUnit.removeAllRoleAssignmentsForLevelInServerExceptRole(level, server, role);
verify(experienceRoleRepository, times(1)).findByLevelAndRoleServer(level, server);
verify(experienceRoleRepository, times(experienceRoles.size())).delete(roleArgumentCaptor.capture());
List<AExperienceRole> allValues = roleArgumentCaptor.getAllValues();
@@ -66,7 +71,7 @@ public class ExperienceRoleManagementServiceBeanTest {
when(level.getLevel()).thenReturn(10);
List<AExperienceRole> experienceRoles = new ArrayList<>();
when(experienceRoleRepository.findByLevelAndRoleServer(level, server)).thenReturn(experienceRoles);
testUnit.removeAllRoleAssignmentsForLevelInServer(level, server);
testUnit.removeAllRoleAssignmentsForLevelInServerExceptRole(level, server, role);
verify(experienceRoleRepository, times(1)).findByLevelAndRoleServer(level, server);
verify(experienceRoleRepository, times(experienceRoles.size())).delete(roleArgumentCaptor.capture());
List<AExperienceRole> allValues = roleArgumentCaptor.getAllValues();

View File

@@ -62,7 +62,7 @@ public class UserExperienceManagementServiceBeanTest {
when(repository.findById(USER_IN_SERVER_ID)).thenReturn(Optional.empty());
AExperienceLevel startLevel = Mockito.mock(AExperienceLevel.class);
when(startLevel.getLevel()).thenReturn(START_LEVEL);
when(experienceLevelManagementService.getLevel(START_LEVEL)).thenReturn(Optional.of(startLevel));
when(experienceLevelManagementService.getLevelOptional(START_LEVEL)).thenReturn(Optional.of(startLevel));
AUserExperience userInServer = testUnit.findUserInServer(user);
Assert.assertEquals(0L, userInServer.getExperience().longValue());
Assert.assertEquals(0L, userInServer.getMessageCount().longValue());
@@ -81,7 +81,7 @@ public class UserExperienceManagementServiceBeanTest {
when(user.getUserReference()).thenReturn(aUser);
AExperienceLevel startLevel = Mockito.mock(AExperienceLevel.class);
when(startLevel.getLevel()).thenReturn(START_LEVEL);
when(experienceLevelManagementService.getLevel(START_LEVEL)).thenReturn(Optional.of(startLevel));
when(experienceLevelManagementService.getLevelOptional(START_LEVEL)).thenReturn(Optional.of(startLevel));
AUserExperience userInServer = testUnit.createUserInServer(user);
Assert.assertEquals(0L, userInServer.getExperience().longValue());
Assert.assertEquals(0L, userInServer.getMessageCount().longValue());

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>experience-tracking</artifactId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -0,0 +1,23 @@
package dev.sheldan.abstracto.experience.exception;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.templating.Templatable;
import org.springframework.stereotype.Component;
@Component
public class ExperienceRoleNotFoundException extends AbstractoRunTimeException implements Templatable {
public ExperienceRoleNotFoundException() {
super("Experience role was not found for role.");
}
@Override
public String getTemplateName() {
return "experience_role_not_found_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

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

View File

@@ -0,0 +1,13 @@
package dev.sheldan.abstracto.experience.model.template;
import lombok.Builder;
import lombok.Getter;
import net.dv8tion.jda.api.entities.Role;
@Getter
@Builder
public class LevelRole {
private Role role;
private Long roleId;
private Integer level;
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.abstracto.experience.model.template;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
@Builder
public class LevelRolesModel {
private List<LevelRole> levelRoles;
}

View File

@@ -1,9 +1,9 @@
package dev.sheldan.abstracto.experience.model.template;
import dev.sheldan.abstracto.core.models.context.SlimUserInitiatedServerContext;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import net.dv8tion.jda.api.entities.Member;
/**
* Object containing the provided property to render the rank command template. This includes the
@@ -12,8 +12,8 @@ import lombok.experimental.SuperBuilder;
*/
@Getter
@Setter
@SuperBuilder
public class RankModel extends SlimUserInitiatedServerContext {
@Builder
public class RankModel {
/**
* The {@link LeaderBoardEntryModel} containing the experience information about the user executing the rank
* command.
@@ -23,4 +23,8 @@ public class RankModel extends SlimUserInitiatedServerContext {
* The necessary experience to the next level up.
*/
private Long experienceToNextLevel;
/**
* The member to show the rank for
*/
private Member member;
}

View File

@@ -11,6 +11,7 @@ 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.MessageChannel;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -22,6 +23,7 @@ import java.util.function.Function;
* user in the guild and retrieving {@link LeaderBoard leaderboard} data.
*/
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.
@@ -150,4 +152,9 @@ public interface AUserExperienceService {
* @param results The list of {@link RoleCalculationResult} which should be updated in the database
*/
void syncRolesInStorage(List<RoleCalculationResult> results);
boolean experienceGainEnabledInChannel(MessageChannel messageChannel);
AUserExperience createUserExperienceForUser(AUserInAServer aUserInAServer, Long experience, Long messageCount);
AUserExperience createUserExperienceForUser(AUserInAServer aUserInAServer, Long experience, Long messageCount, List<AExperienceLevel> levels);
}

View File

@@ -5,6 +5,7 @@ import dev.sheldan.abstracto.core.models.database.AServer;
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 net.dv8tion.jda.api.entities.Role;
import java.util.List;
@@ -38,7 +39,10 @@ public interface ExperienceRoleService {
* @return A {@link CompletableFuture future} which completes, after all the updates on the {@link net.dv8tion.jda.api.entities.Member}
* have been completed
*/
CompletableFuture<Void> unsetRole(ARole role, Long channelId);
CompletableFuture<Void> unsetRoles(ARole role, Long channelId);
List<AExperienceRole> getExperienceRolesAtLevel(Integer level, AServer server);
CompletableFuture<Void> unsetRoles(List<ARole> roles, Long channelId);
CompletableFuture<Void> unsetRoles(List<ARole> roles, Long channelId, AExperienceRole toAdd);
/**
* Calculates the appropriate {@link AExperienceRole experienceRole} based on the provided list of {@link AExperienceRole experienceRole}
@@ -57,4 +61,5 @@ public interface ExperienceRoleService {
* @return The next {@link AExperienceLevel} a {@link AExperienceRole} is awarded at, this will be null if there are no roles or there is no further role to reach
*/
AExperienceLevel getLevelOfNextRole(AExperienceLevel startLevel, AServer server);
List<LevelRole> loadLevelRoleConfigForServer(AServer server);
}

View File

@@ -29,7 +29,8 @@ public interface ExperienceLevelManagementService {
* @param level The {@link AExperienceLevel level} of the wanted level number
* @return Returns an optional containing the {@link AExperienceLevel level} if it exists, and empty otherwise
*/
Optional<AExperienceLevel> getLevel(Integer level);
Optional<AExperienceLevel> getLevelOptional(Integer level);
AExperienceLevel getLevel(Integer level);
/**
* Loads the complete level configuration and returns all found {@link AExperienceLevel levels} from the database.

View File

@@ -23,13 +23,14 @@ public interface ExperienceRoleManagementService {
* @return the created or updated {@link AExperienceRole experienceRole}
*/
AExperienceRole setLevelToRole(AExperienceLevel level, ARole role);
List<AExperienceRole> getExperienceRolesAtLevelInServer(AExperienceLevel level, AServer server);
/**
* Deletes *all* (if there are multiple by some chance) roles which were set to be given at the provided {@link AExperienceLevel level} in the {@link AServer server}
* @param level The level to remove the roles for
* @param server The server in which this should happen
*/
void removeAllRoleAssignmentsForLevelInServer(AExperienceLevel level, AServer server);
void removeAllRoleAssignmentsForLevelInServerExceptRole(AExperienceLevel level, AServer server, ARole role);
/**
* Deletes a singular {@link AExperienceRole experienceRole} directly.

View File

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

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>invite-filter</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>invite-filter</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>link-embed</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>link-embed</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>logging</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>logging</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>moderation</artifactId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>moderation</artifactId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>modmail</artifactId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>modmail</artifactId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto</groupId>
<artifactId>abstracto-application</artifactId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>profanity-filter</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>profanity-filter</artifactId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>remind</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -111,7 +111,7 @@ public class RemindServiceBean implements ReminderService {
parameters.put("reminderId", reminder.getId().toString());
JobParameters jobParameters = JobParameters.builder().parameters(parameters).build();
String triggerKey = schedulerService.executeJobWithParametersOnce("reminderJob", "utility", jobParameters, Date.from(reminder.getTargetDate()));
log.info("Starting scheduled job with trigger {} to execute reminder. {}", triggerKey, reminder.getId());
log.info("Starting scheduled job with trigger {} to execute reminder {}.", triggerKey, reminder.getId());
reminder.setJobTriggerKey(triggerKey);
reminderManagementService.saveReminder(reminder);
}

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>remind</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>repost-detection</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>repost-detection</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>starboard</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>starboard</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>statistic</artifactId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>statistic</artifactId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -21,6 +21,13 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.scheduling</groupId>
<artifactId>scheduling-int</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
<properties>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>suggestion</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -0,0 +1,40 @@
package dev.sheldan.abstracto.suggestion.job;
import dev.sheldan.abstracto.core.models.ServerSpecificId;
import dev.sheldan.abstracto.suggestion.service.SuggestionService;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
@Slf4j
@DisallowConcurrentExecution
@Component
@Getter
@Setter
@PersistJobDataAfterExecution
public class SuggestionReminderJob extends QuartzJobBean {
@Autowired
private SuggestionService suggestionService;
private Long suggestionId;
private Long serverId;
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
log.info("Executing suggestion reminder job for suggestion {} in server {}", suggestionId, serverId);
try {
suggestionService.remindAboutSuggestion(new ServerSpecificId(serverId, suggestionId));
} catch (Exception exception) {
log.error("Suggestion reminder job failed.", exception);
}
}
}

View File

@@ -1,5 +1,8 @@
package dev.sheldan.abstracto.suggestion.service;
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import dev.sheldan.abstracto.core.models.ServerSpecificId;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.*;
@@ -8,11 +11,16 @@ import dev.sheldan.abstracto.core.service.management.UserInServerManagementServi
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.scheduling.model.JobParameters;
import dev.sheldan.abstracto.scheduling.service.SchedulerService;
import dev.sheldan.abstracto.suggestion.config.SuggestionFeatureDefinition;
import dev.sheldan.abstracto.suggestion.config.SuggestionFeatureMode;
import dev.sheldan.abstracto.suggestion.config.SuggestionPostTarget;
import dev.sheldan.abstracto.suggestion.exception.UnSuggestNotPossibleException;
import dev.sheldan.abstracto.suggestion.model.database.Suggestion;
import dev.sheldan.abstracto.suggestion.model.database.SuggestionState;
import dev.sheldan.abstracto.suggestion.model.template.SuggestionLog;
import dev.sheldan.abstracto.suggestion.model.template.SuggestionReminderModel;
import dev.sheldan.abstracto.suggestion.service.management.SuggestionManagementService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.*;
@@ -24,6 +32,8 @@ import org.springframework.transaction.annotation.Transactional;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -36,6 +46,7 @@ public class SuggestionServiceBean implements SuggestionService {
public static final String SUGGESTION_YES_EMOTE = "suggestionYes";
public static final String SUGGESTION_NO_EMOTE = "suggestionNo";
public static final String SUGGESTION_COUNTER_KEY = "suggestion";
public static final String SUGGESTION_REMINDER_TEMPLATE_KEY = "suggest_suggestion_reminder";
@Autowired
private SuggestionManagementService suggestionManagementService;
@@ -73,6 +84,15 @@ public class SuggestionServiceBean implements SuggestionService {
@Autowired
private UserService userService;
@Autowired
private SchedulerService schedulerService;
@Autowired
private FeatureModeService featureModeService;
@Autowired
private ConfigService configService;
@Value("${abstracto.feature.suggestion.removalMaxAge}")
private Long removalMaxAgeSeconds;
@@ -114,8 +134,25 @@ public class SuggestionServiceBean implements SuggestionService {
@Transactional
public void persistSuggestionInDatabase(Member member, String text, Message message, Long suggestionId, Message commandMessage) {
log.info("Persisting suggestion {} for server {} in database.", suggestionId, member.getGuild().getId());
suggestionManagementService.createSuggestion(member, text, message, suggestionId, commandMessage);
Long serverId = member.getGuild().getIdLong();
log.info("Persisting suggestion {} for server {} in database.", suggestionId, serverId);
Suggestion suggestion = suggestionManagementService.createSuggestion(member, text, message, suggestionId, commandMessage);
if(featureModeService.featureModeActive(SuggestionFeatureDefinition.SUGGEST, serverId, SuggestionFeatureMode.SUGGESTION_REMINDER)) {
String triggerKey = scheduleSuggestionReminder(serverId, suggestionId);
suggestion.setSuggestionReminderJobTriggerKey(triggerKey);
}
}
private String scheduleSuggestionReminder(Long serverId, Long suggestionId) {
HashMap<Object, Object> parameters = new HashMap<>();
parameters.put("serverId", serverId.toString());
parameters.put("suggestionId", suggestionId.toString());
JobParameters jobParameters = JobParameters.builder().parameters(parameters).build();
Long days = configService.getLongValueOrConfigDefault(SuggestionService.SUGGESTION_REMINDER_DAYS_CONFIG_KEY, serverId);
Instant targetDate = Instant.now().plus(days, ChronoUnit.DAYS);
String triggerKey = schedulerService.executeJobWithParametersOnce("suggestionReminderJob", "suggestion", jobParameters, Date.from(targetDate));
log.info("Starting scheduled job with trigger {} to execute suggestion reminder in server {} for suggestion {}.", triggerKey, serverId, suggestionId);
return triggerKey;
}
@Override
@@ -123,6 +160,7 @@ public class SuggestionServiceBean implements SuggestionService {
Long serverId = commandMessage.getGuild().getIdLong();
Suggestion suggestion = suggestionManagementService.getSuggestion(serverId, suggestionId);
suggestionManagementService.setSuggestionState(suggestion, SuggestionState.ACCEPTED);
cancelSuggestionReminder(suggestion);
log.info("Accepting suggestion {} in server {}.", suggestionId, suggestion.getServer().getId());
return updateSuggestion(commandMessage.getMember(), text, suggestion);
}
@@ -132,6 +170,7 @@ public class SuggestionServiceBean implements SuggestionService {
Long serverId = commandMessage.getGuild().getIdLong();
Suggestion suggestion = suggestionManagementService.getSuggestion(serverId, suggestionId);
suggestionManagementService.setSuggestionState(suggestion, SuggestionState.VETOED);
cancelSuggestionReminder(suggestion);
log.info("Vetoing suggestion {} in server {}.", suggestionId, suggestion.getServer().getId());
return updateSuggestion(commandMessage.getMember(), text, suggestion);
}
@@ -186,6 +225,7 @@ public class SuggestionServiceBean implements SuggestionService {
Long serverId = commandMessage.getGuild().getIdLong();
Suggestion suggestion = suggestionManagementService.getSuggestion(serverId, suggestionId);
suggestionManagementService.setSuggestionState(suggestion, SuggestionState.REJECTED);
cancelSuggestionReminder(suggestion);
log.info("Rejecting suggestion {} in server {}.", suggestionId, suggestion.getServer().getId());
return updateSuggestion(commandMessage.getMember(), text, suggestion);
}
@@ -199,7 +239,12 @@ public class SuggestionServiceBean implements SuggestionService {
throw new UnSuggestNotPossibleException();
}
return messageService.deleteMessageInChannelInServer(suggestion.getServer().getId(), suggestion.getChannel().getId(), suggestion.getMessageId())
.thenAccept(unused -> self.deleteSuggestion(suggestionId, serverId));
.thenAccept(unused -> self.deleteSuggestion(suggestionId, serverId))
.exceptionally(throwable -> {
log.info("Suggestion message for suggestion {} in server {} did not exist anymore - ignoring.", suggestionId, serverId);
self.deleteSuggestion(suggestionId, serverId);
return null;
});
}
@Override
@@ -213,8 +258,50 @@ public class SuggestionServiceBean implements SuggestionService {
suggestionManagementService.deleteSuggestion(suggestionsToRemove);
}
@Override
@Transactional
public CompletableFuture<Void> remindAboutSuggestion(ServerSpecificId suggestionId) {
Long serverId = suggestionId.getServerId();
Suggestion suggestion = suggestionManagementService.getSuggestion(serverId, suggestionId.getId());
ServerChannelMessage suggestionServerChannelMessage = ServerChannelMessage
.builder()
.serverId(serverId)
.channelId(suggestion.getChannel().getId())
.messageId(suggestion.getMessageId())
.build();
ServerChannelMessage commandServerChannelMessage = ServerChannelMessage
.builder()
.serverId(serverId)
.channelId(suggestion.getCommandChannel().getId())
.messageId(suggestion.getCommandMessageId())
.build();
SuggestionReminderModel model = SuggestionReminderModel
.builder()
.serverId(serverId)
.serverUser(ServerUser.fromAUserInAServer(suggestion.getSuggester()))
.suggestionCreationDate(suggestion.getCreated())
.suggestionId(suggestionId.getId())
.suggestionMessage(suggestionServerChannelMessage)
.suggestionCommandMessage(commandServerChannelMessage)
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(SUGGESTION_REMINDER_TEMPLATE_KEY, model, serverId);
log.info("Reminding about suggestion {} in server {}.", suggestionId.getId(), serverId);
List<CompletableFuture<Message>> completableFutures = postTargetService.sendEmbedInPostTarget(messageToSend, SuggestionPostTarget.SUGGESTION, serverId);
return FutureUtils.toSingleFutureGeneric(completableFutures);
}
@Override
public void cancelSuggestionReminder(Suggestion suggestion) {
if(suggestion.getSuggestionReminderJobTriggerKey() != null) {
log.info("Cancelling reminder for suggestion {} in server {}.", suggestion.getSuggestionId().getId(), suggestion.getSuggestionId().getServerId());
schedulerService.stopTrigger(suggestion.getSuggestionReminderJobTriggerKey());
}
}
@Transactional
public void deleteSuggestion(Long suggestionId, Long serverId) {
suggestionManagementService.deleteSuggestion(serverId, suggestionId);
Suggestion suggestion = suggestionManagementService.getSuggestion(serverId, suggestionId);
cancelSuggestionReminder(suggestion);
suggestionManagementService.deleteSuggestion(suggestion);
}
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="tables/tables.xml" relativeToChangelogFile="true"/>
<include file="seedData/data.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,10 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="suggestionReminderJob.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,18 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="suggestion-reminder-job-insert">
<insert tableName="scheduler_job">
<column name="name" value="suggestionReminderJob"/>
<column name="group_name" value="suggestion"/>
<column name="clazz" value="dev.sheldan.abstracto.suggestion.job.SuggestionReminderJob"/>
<column name="active" value="true"/>
<column name="recovery" value="false"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,15 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="suggestion-addTriggerColumn">
<addColumn tableName="suggestion" >
<column name="job_trigger_key"
type="varchar(255)"/>
</addColumn>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,10 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="suggestion.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -8,4 +8,5 @@
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="1.0-suggestion/collection.xml" relativeToChangelogFile="true"/>
<include file="1.2.12/collection.xml" relativeToChangelogFile="true"/>
<include file="1.2.13/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -2,6 +2,14 @@ abstracto.featureFlags.suggestion.featureName=suggestion
abstracto.featureFlags.suggestion.enabled=false
abstracto.postTargets.suggestions.name=suggestions
abstracto.postTargets.suggestionReminder.name=suggestionReminder
abstracto.feature.suggestion.removalMaxAge=3600
abstracto.feature.suggestion.removalDays=2
abstracto.feature.suggestion.removalDays=2
abstracto.featureModes.suggestionReminder.featureName=suggestion
abstracto.featureModes.suggestionReminder.mode=suggestionReminder
abstracto.featureModes.suggestionReminder.enabled=false
abstracto.systemConfigs.suggestionReminderDays.name=suggestionReminderDays
abstracto.systemConfigs.suggestionReminderDays.longValue=7

View File

@@ -11,9 +11,10 @@ import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.suggestion.config.SuggestionFeatureDefinition;
import dev.sheldan.abstracto.suggestion.config.SuggestionFeatureMode;
import dev.sheldan.abstracto.suggestion.config.SuggestionPostTarget;
import dev.sheldan.abstracto.suggestion.exception.SuggestionNotFoundException;
import dev.sheldan.abstracto.suggestion.exception.UnSuggestNotPossibleException;
import dev.sheldan.abstracto.suggestion.model.database.Suggestion;
import dev.sheldan.abstracto.suggestion.model.database.SuggestionState;
import dev.sheldan.abstracto.suggestion.model.template.SuggestionLog;
@@ -76,6 +77,9 @@ public class SuggestionServiceBeanTest {
@Mock
private ServerManagementService serverManagementService;
@Mock
private FeatureModeService featureModeService;
@Mock
private UserService userService;
@@ -125,10 +129,11 @@ public class SuggestionServiceBeanTest {
@Test
public void testCreateSuggestion() {
when(member.getGuild()).thenReturn(guild);
when(guild.getId()).thenReturn("5");
when(guild.getIdLong()).thenReturn(SERVER_ID);
String text = "text";
Message message = Mockito.mock(Message.class);
Message commandMessage = Mockito.mock(Message.class);
when(featureModeService.featureModeActive(SuggestionFeatureDefinition.SUGGEST, SERVER_ID, SuggestionFeatureMode.SUGGESTION_REMINDER)).thenReturn(false);
testUnit.persistSuggestionInDatabase(member, text, message, SUGGESTION_ID, commandMessage);
verify(suggestionManagementService, times(1)).createSuggestion(member, text, message, SUGGESTION_ID, commandMessage);
}

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>suggestion</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -2,7 +2,9 @@ package dev.sheldan.abstracto.suggestion.config;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.config.PostTargetEnum;
import dev.sheldan.abstracto.suggestion.service.SuggestionService;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@@ -18,11 +20,21 @@ public class SuggestionFeatureConfig implements FeatureConfig {
@Override
public List<PostTargetEnum> getRequiredPostTargets() {
return Arrays.asList(SuggestionPostTarget.SUGGESTION);
return Arrays.asList(SuggestionPostTarget.SUGGESTION, SuggestionPostTarget.SUGGESTION_REMINDER);
}
@Override
public List<String> getRequiredEmotes() {
return Arrays.asList("suggestionYes", "suggestionNo");
}
@Override
public List<FeatureMode> getAvailableModes() {
return Arrays.asList(SuggestionFeatureMode.SUGGESTION_REMINDER);
}
@Override
public List<String> getRequiredSystemConfigKeys() {
return Arrays.asList(SuggestionService.SUGGESTION_REMINDER_DAYS_CONFIG_KEY);
}
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.suggestion.config;
import dev.sheldan.abstracto.core.config.FeatureMode;
import lombok.Getter;
@Getter
public enum SuggestionFeatureMode implements FeatureMode {
SUGGESTION_REMINDER("suggestionReminder");
private final String key;
SuggestionFeatureMode(String key) {
this.key = key;
}
}

View File

@@ -5,7 +5,7 @@ import lombok.Getter;
@Getter
public enum SuggestionPostTarget implements PostTargetEnum {
SUGGESTION("suggestions");
SUGGESTION("suggestions"), SUGGESTION_REMINDER("suggestionReminder");
private String key;

View File

@@ -66,4 +66,7 @@ public class Suggestion implements Serializable {
@Column(name = "command_message_id")
private Long commandMessageId;
@Column(name = "job_trigger_key")
private String suggestionReminderJobTriggerKey;
}

View File

@@ -0,0 +1,21 @@
package dev.sheldan.abstracto.suggestion.model.template;
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import dev.sheldan.abstracto.core.models.ServerUser;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.time.Instant;
@Getter
@Setter
@Builder
public class SuggestionReminderModel {
private Long serverId;
private Long suggestionId;
private Instant suggestionCreationDate;
private ServerChannelMessage suggestionMessage;
private ServerChannelMessage suggestionCommandMessage;
private ServerUser serverUser;
}

View File

@@ -1,15 +1,20 @@
package dev.sheldan.abstracto.suggestion.service;
import dev.sheldan.abstracto.core.models.ServerSpecificId;
import dev.sheldan.abstracto.suggestion.model.database.Suggestion;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import java.util.concurrent.CompletableFuture;
public interface SuggestionService {
String SUGGESTION_REMINDER_DAYS_CONFIG_KEY = "suggestionReminderDays";
CompletableFuture<Void> createSuggestionMessage(Message commandMessage, String text);
CompletableFuture<Void> acceptSuggestion(Long suggestionId, Message commandMessage, String text);
CompletableFuture<Void> vetoSuggestion(Long suggestionId, Message commandMessage, String text);
CompletableFuture<Void> rejectSuggestion(Long suggestionId, Message commandMessage, String text);
CompletableFuture<Void> removeSuggestion(Long suggestionId, Member member);
void cleanUpSuggestions();
CompletableFuture<Void> remindAboutSuggestion(ServerSpecificId suggestionId);
void cancelSuggestionReminder(Suggestion suggestion);
}

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>utility</artifactId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>utility</artifactId>
<version>1.2.12</version>
<version>1.2.14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Some files were not shown because too many files have changed in this diff Show More