[AB-308] adding a separate type for assignable role places to enable booster only places

adding more detailed logging to assignable roles
adding some fall through logic to the banned listener to always log at least the basic information
refactoring some command structure for showing configuration, so the command actually executes the message response
fixing potential exception case for starboard updates causing the message ID to not be persisted
This commit is contained in:
Sheldan
2021-07-18 19:20:14 +02:00
parent 32056cd6b9
commit 7117ac26d3
34 changed files with 445 additions and 78 deletions

View File

@@ -2,6 +2,7 @@ package dev.sheldan.abstracto.assignableroles.command;
import dev.sheldan.abstracto.assignableroles.config.AssignableRoleFeatureDefinition; import dev.sheldan.abstracto.assignableroles.config.AssignableRoleFeatureDefinition;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace; import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlaceType;
import dev.sheldan.abstracto.assignableroles.service.AssignableRolePlaceService; import dev.sheldan.abstracto.assignableroles.service.AssignableRolePlaceService;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand; import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
@@ -41,11 +42,15 @@ public class CreateAssignableRolePost extends AbstractConditionableCommand {
String name = (String) parameters.get(0); String name = (String) parameters.get(0);
TextChannel channel = (TextChannel) parameters.get(1); TextChannel channel = (TextChannel) parameters.get(1);
String text = (String) parameters.get(2); String text = (String) parameters.get(2);
AssignableRolePlaceType type = AssignableRolePlaceType.DEFAULT;
if(parameters.size() > 3) {
type = (AssignableRolePlaceType) parameters.get(3);
}
if(!channel.getGuild().equals(commandContext.getGuild())) { if(!channel.getGuild().equals(commandContext.getGuild())) {
throw new EntityGuildMismatchException(); throw new EntityGuildMismatchException();
} }
AChannel chosenChannel = channelManagementService.loadChannel(channel.getIdLong()); AChannel chosenChannel = channelManagementService.loadChannel(channel.getIdLong());
service.createAssignableRolePlace(name, chosenChannel, text); service.createAssignableRolePlace(name, chosenChannel, text, type);
return CommandResult.fromSuccess(); return CommandResult.fromSuccess();
} }
@@ -54,10 +59,11 @@ public class CreateAssignableRolePost extends AbstractConditionableCommand {
List<ParameterValidator> rolePlaceNameValidator = Arrays.asList(MaxStringLengthValidator.max(AssignableRolePlace.ASSIGNABLE_PLACE_NAME_LIMIT)); List<ParameterValidator> rolePlaceNameValidator = Arrays.asList(MaxStringLengthValidator.max(AssignableRolePlace.ASSIGNABLE_PLACE_NAME_LIMIT));
Parameter rolePostName = Parameter.builder().name("name").validators(rolePlaceNameValidator).type(String.class).templated(true).build(); Parameter rolePostName = Parameter.builder().name("name").validators(rolePlaceNameValidator).type(String.class).templated(true).build();
Parameter channel = Parameter.builder().name("channel").type(TextChannel.class).templated(true).build(); Parameter channel = Parameter.builder().name("channel").type(TextChannel.class).templated(true).build();
Parameter type = Parameter.builder().name("type").type(AssignableRolePlaceType.class).templated(true).optional(true).build();
List<ParameterValidator> rolePlaceDescriptionValidator = Arrays.asList(MaxStringLengthValidator.max(AssignableRolePlace.ASSIGNABLE_PLACE_NAME_LIMIT)); List<ParameterValidator> rolePlaceDescriptionValidator = Arrays.asList(MaxStringLengthValidator.max(AssignableRolePlace.ASSIGNABLE_PLACE_NAME_LIMIT));
Parameter text = Parameter.builder().name("text").validators(rolePlaceDescriptionValidator).type(String.class).remainder(true).optional(true).templated(true).build(); Parameter text = Parameter.builder().name("text").validators(rolePlaceDescriptionValidator).type(String.class).templated(true).build();
List<String> aliases = Arrays.asList("crRPl", "crAssRoPl"); List<String> aliases = Arrays.asList("crRPl", "crAssRoPl");
List<Parameter> parameters = Arrays.asList(rolePostName, channel, text); List<Parameter> parameters = Arrays.asList(rolePostName, channel, text, type);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build(); HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder() return CommandConfiguration.builder()
.name("createAssignableRolePlace") .name("createAssignableRolePlace")

View File

@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.assignableroles.command; package dev.sheldan.abstracto.assignableroles.command;
import dev.sheldan.abstracto.assignableroles.config.AssignableRoleFeatureDefinition; import dev.sheldan.abstracto.assignableroles.config.AssignableRoleFeatureDefinition;
import dev.sheldan.abstracto.assignableroles.model.template.AssignableRolePlaceConfig;
import dev.sheldan.abstracto.assignableroles.service.AssignableRolePlaceService; import dev.sheldan.abstracto.assignableroles.service.AssignableRolePlaceService;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand; import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
@@ -9,8 +10,9 @@ import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition; 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.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -24,21 +26,24 @@ import java.util.concurrent.CompletableFuture;
@Component @Component
public class ShowAssignableRolePlaceConfig extends AbstractConditionableCommand { public class ShowAssignableRolePlaceConfig extends AbstractConditionableCommand {
@Autowired @Autowired
private AssignableRolePlaceService service; private AssignableRolePlaceService service;
@Autowired @Autowired
private ServerManagementService serverManagementService; private ServerManagementService serverManagementService;
@Autowired
private ChannelService channelService;
public static final String ASSIGNABLE_ROLES_CONFIG_POST_TEMPLATE_KEY = "assignable_roles_config_post";
@Override @Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) { public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters(); List<Object> parameters = commandContext.getParameters().getParameters();
String name = (String) parameters.get(0); String name = (String) parameters.get(0);
AServer server = serverManagementService.loadServer(commandContext.getGuild()); AssignableRolePlaceConfig config = service.getAssignableRolePlaceConfig(commandContext.getGuild(), name);
// TODO refactor to return something to be posted in this command here instead of relying it to be posted somewhere else return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInTextChannelList(ASSIGNABLE_ROLES_CONFIG_POST_TEMPLATE_KEY, config, commandContext.getChannel()))
return service.showAssignablePlaceConfig(server, name, commandContext.getChannel()) .thenApply(unused -> CommandResult.fromSuccess());
.thenApply(unused -> CommandResult.fromIgnored());
} }
@Override @Override
@@ -51,7 +56,7 @@ public class ShowAssignableRolePlaceConfig extends AbstractConditionableCommand
.module(AssignableRoleModuleDefinition.ASSIGNABLE_ROLES) .module(AssignableRoleModuleDefinition.ASSIGNABLE_ROLES)
.templated(true) .templated(true)
.async(true) .async(true)
.causesReaction(true) .causesReaction(false)
.supportsEmbedException(true) .supportsEmbedException(true)
.parameters(parameters) .parameters(parameters)
.help(helpInfo) .help(helpInfo)

View File

@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.assignableroles.command; package dev.sheldan.abstracto.assignableroles.command;
import dev.sheldan.abstracto.assignableroles.config.AssignableRoleFeatureDefinition; import dev.sheldan.abstracto.assignableroles.config.AssignableRoleFeatureDefinition;
import dev.sheldan.abstracto.assignableroles.model.template.AssignablePlaceOverview;
import dev.sheldan.abstracto.assignableroles.service.AssignableRolePlaceService; import dev.sheldan.abstracto.assignableroles.service.AssignableRolePlaceService;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand; import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration; import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
@@ -9,7 +10,9 @@ import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult; import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.models.database.AServer; 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.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -28,11 +31,16 @@ public class ShowAssignableRolePlaces extends AbstractConditionableCommand {
@Autowired @Autowired
private ServerManagementService serverManagementService; private ServerManagementService serverManagementService;
@Autowired
private ChannelService channelService;
public static final String ASSIGNABLE_ROLE_PLACES_OVERVIEW_TEMPLATE_KEY = "assignable_role_places_overview";
@Override @Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) { public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
AServer server = serverManagementService.loadServer(commandContext.getGuild()); AssignablePlaceOverview model = service.getAssignableRolePlaceOverview(commandContext.getGuild());
return service.showAllAssignableRolePlaces(server, commandContext.getChannel()) return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInTextChannelList(ASSIGNABLE_ROLE_PLACES_OVERVIEW_TEMPLATE_KEY, model, commandContext.getChannel()))
.thenApply(aVoid -> CommandResult.fromIgnored()); .thenApply(unused -> CommandResult.fromSuccess());
} }
@Override @Override
@@ -42,7 +50,6 @@ public class ShowAssignableRolePlaces extends AbstractConditionableCommand {
.name("showAssignableRolePlaces") .name("showAssignableRolePlaces")
.module(AssignableRoleModuleDefinition.ASSIGNABLE_ROLES) .module(AssignableRoleModuleDefinition.ASSIGNABLE_ROLES)
.templated(true) .templated(true)
.causesReaction(true)
.async(true) .async(true)
.supportsEmbedException(true) .supportsEmbedException(true)
.help(helpInfo) .help(helpInfo)

View File

@@ -2,11 +2,13 @@ package dev.sheldan.abstracto.assignableroles.listener;
import dev.sheldan.abstracto.assignableroles.config.AssignableRoleFeatureDefinition; import dev.sheldan.abstracto.assignableroles.config.AssignableRoleFeatureDefinition;
import dev.sheldan.abstracto.assignableroles.exception.AssignableRoleNotFoundException; import dev.sheldan.abstracto.assignableroles.exception.AssignableRoleNotFoundException;
import dev.sheldan.abstracto.assignableroles.exception.BoosterAssignableRolePlaceMemberNotBoostingException;
import dev.sheldan.abstracto.assignableroles.model.condition.AssignableRoleConditionResult; import dev.sheldan.abstracto.assignableroles.model.condition.AssignableRoleConditionResult;
import dev.sheldan.abstracto.assignableroles.model.AssignableRolePlacePayload; import dev.sheldan.abstracto.assignableroles.model.AssignableRolePlacePayload;
import dev.sheldan.abstracto.assignableroles.model.condition.AssignableRolePlaceConditionModel; import dev.sheldan.abstracto.assignableroles.model.condition.AssignableRolePlaceConditionModel;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRole; import dev.sheldan.abstracto.assignableroles.model.database.AssignableRole;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace; import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlaceType;
import dev.sheldan.abstracto.assignableroles.model.database.AssignedRoleUser; import dev.sheldan.abstracto.assignableroles.model.database.AssignedRoleUser;
import dev.sheldan.abstracto.assignableroles.model.template.AssignableRoleSuccessNotificationModel; import dev.sheldan.abstracto.assignableroles.model.template.AssignableRoleSuccessNotificationModel;
import dev.sheldan.abstracto.assignableroles.service.AssignableRoleConditionServiceBean; import dev.sheldan.abstracto.assignableroles.service.AssignableRoleConditionServiceBean;
@@ -73,9 +75,10 @@ public class AssignableRoleButtonClickedListener implements ButtonClickedListene
@Override @Override
public ButtonClickedListenerResult execute(ButtonClickedListenerModel model) { public ButtonClickedListenerResult execute(ButtonClickedListenerModel model) {
ButtonClickEvent event = model.getEvent(); ButtonClickEvent event = model.getEvent();
AssignableRolePlacePayload payload = (AssignableRolePlacePayload) model.getDeserializedPayload(); Member member = event.getMember();
AssignableRolePlace place = assignableRolePlaceManagementService.findByPlaceId(payload.getPlaceId()); if(event.getGuild() != null && member != null) {
if(event.getGuild() != null && event.getMember() != null) { AssignableRolePlacePayload payload = (AssignableRolePlacePayload) model.getDeserializedPayload();
AssignableRolePlace place = assignableRolePlaceManagementService.findByPlaceId(payload.getPlaceId());
Guild guild = event.getGuild(); Guild guild = event.getGuild();
List<Role> removedRoles = new ArrayList<>(); List<Role> removedRoles = new ArrayList<>();
Role roleById = guild.getRoleById(payload.getRoleId()); Role roleById = guild.getRoleById(payload.getRoleId());
@@ -88,18 +91,22 @@ public class AssignableRoleButtonClickedListener implements ButtonClickedListene
throw new AssignableRoleNotFoundException(payload.getRoleId()); throw new AssignableRoleNotFoundException(payload.getRoleId());
} }
if(roleById != null) { if(roleById != null) {
boolean memberHasRole = event boolean memberHasRole = member
.getMember()
.getRoles() .getRoles()
.stream() .stream()
.anyMatch(memberRole -> memberRole.getIdLong() == payload.getRoleId()); .anyMatch(memberRole -> memberRole.getIdLong() == payload.getRoleId());
if(!memberHasRole) { if(!memberHasRole) {
if(place.getType().equals(AssignableRolePlaceType.BOOSTER) && member.getTimeBoosted() == null) {
throw new BoosterAssignableRolePlaceMemberNotBoostingException();
}
AssignableRole assignableRole = assignableRoleOptional.get(); AssignableRole assignableRole = assignableRoleOptional.get();
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(event.getMember()); AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(member);
if(!assignableRole.getConditions().isEmpty()) { if(!assignableRole.getConditions().isEmpty()) {
log.debug("Evaluating {} conditions for assignable role {}.", assignableRole.getConditions().size(), assignableRole.getId());
AssignableRoleConditionResult conditionResult = AssignableRoleConditionResult conditionResult =
assignableRoleConditionServiceBean.evaluateConditions(assignableRole.getConditions(), aUserInAServer, roleById); assignableRoleConditionServiceBean.evaluateConditions(assignableRole.getConditions(), aUserInAServer, roleById);
if(!conditionResult.getFulfilled()) { if(!conditionResult.getFulfilled()) {
log.info("One condition failed to be fullfilled - notifying user.");
self.notifyUserAboutConditionFail(model, event.getInteraction(), conditionResult.getModel()); self.notifyUserAboutConditionFail(model, event.getInteraction(), conditionResult.getModel());
return ButtonClickedListenerResult.ACKNOWLEDGED; return ButtonClickedListenerResult.ACKNOWLEDGED;
} }
@@ -116,9 +123,10 @@ public class AssignableRoleButtonClickedListener implements ButtonClickedListene
.map(roleOfUser -> guild.getRoleById(roleOfUser.getRole().getId())) .map(roleOfUser -> guild.getRoleById(roleOfUser.getRole().getId()))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.collect(Collectors.toList()); .collect(Collectors.toList());
log.info("Removing {} because of unique role configuration in place {}.", rolesToRemove.size(), place.getId());
removedRoles.addAll(rolesToRemove); removedRoles.addAll(rolesToRemove);
List<CompletableFuture<Void>> removalFutures = new ArrayList<>(); List<CompletableFuture<Void>> removalFutures = new ArrayList<>();
rolesToRemove.forEach(roleToRemove -> removalFutures.add(roleService.removeRoleFromUserAsync(event.getMember(), roleToRemove))); rolesToRemove.forEach(roleToRemove -> removalFutures.add(roleService.removeRoleFromUserAsync(member, roleToRemove)));
removalFuture = new CompletableFutureList<>(removalFutures).getMainFuture(); removalFuture = new CompletableFutureList<>(removalFutures).getMainFuture();
} else { } else {
removalFuture = CompletableFuture.completedFuture(null); removalFuture = CompletableFuture.completedFuture(null);
@@ -126,29 +134,29 @@ public class AssignableRoleButtonClickedListener implements ButtonClickedListene
} else { } else {
removalFuture = CompletableFuture.completedFuture(null); removalFuture = CompletableFuture.completedFuture(null);
} }
CompletableFuture<Void> roleAdditionFuture = roleService.addRoleToMemberAsync(event.getMember(), roleById); CompletableFuture<Void> roleAdditionFuture = roleService.addRoleToMemberAsync(member, roleById);
CompletableFuture.allOf(removalFuture, roleAdditionFuture).whenComplete((unused, throwable) -> { CompletableFuture.allOf(removalFuture, roleAdditionFuture).whenComplete((unused, throwable) -> {
if(throwable != null) { if(throwable != null) {
log.error("Failed to either add or remove roles for assignable role place {} in server {}.", payload.getPlaceId(), guild.getIdLong()); log.error("Failed to either add or remove roles for assignable role place {} in server {}.", payload.getPlaceId(), guild.getIdLong());
} }
if(!roleAdditionFuture.isCompletedExceptionally()) { if(!roleAdditionFuture.isCompletedExceptionally()) {
log.info("Added role {} to member {} in server {} for assignable role interaction {} on component {}.", log.info("Added role {} to member {} in server {} for assignable role interaction {} on component {}.",
roleById.getId(), event.getMember().getId(), guild.getIdLong(), event.getInteraction().getId(), event.getComponentId()); roleById.getId(), member.getId(), guild.getIdLong(), event.getInteraction().getId(), event.getComponentId());
self.notifyUser(model, true, roleById, event.getInteraction(), removedRoles).thenAccept(unused1 -> { self.notifyUser(model, true, roleById, event.getInteraction(), removedRoles).thenAccept(unused1 -> {
log.info("Persisting adding assignable role update for user {} in server {} of role {}.", event.getMember().getIdLong(), guild.getIdLong(), roleById.getId()); log.info("Persisting adding assignable role update for user {} in server {} of role {}.", member.getIdLong(), guild.getIdLong(), roleById.getId());
self.persistAssignableUser(event.getMember(), payload, false); self.persistAssignableUser(member, payload, false);
}); });
} }
}); });
} else { } else {
roleService.removeRoleFromUserAsync(event.getMember(), roleById) roleService.removeRoleFromUserAsync(member, roleById)
.thenAccept(unused -> { .thenAccept(unused -> {
self.notifyUser(model, false, roleById, event.getInteraction(), new ArrayList<>()); self.notifyUser(model, false, roleById, event.getInteraction(), new ArrayList<>());
log.info("Removed role {} from member {} in server {} for assignable role interaction {} on component {}.", log.info("Removed role {} from member {} in server {} for assignable role interaction {} on component {}.",
roleById.getId(), event.getMember().getId(), guild.getIdLong(), event.getInteraction().getId(), event.getComponentId()); roleById.getId(), member.getId(), guild.getIdLong(), event.getInteraction().getId(), event.getComponentId());
}).thenAccept(unused -> { }).thenAccept(unused -> {
log.info("Persisting remove assignable role update for user {} in server {} of role {}.", event.getMember().getIdLong(), guild.getIdLong(), roleById.getId()); log.info("Persisting remove assignable role update for user {} in server {} of role {}.", member.getIdLong(), guild.getIdLong(), roleById.getId());
self.persistAssignableUser(event.getMember(), payload, true); self.persistAssignableUser(member, payload, true);
}); });
} }
} else { } else {
@@ -183,6 +191,8 @@ public class AssignableRoleButtonClickedListener implements ButtonClickedListene
@Transactional @Transactional
public CompletableFuture<Void> notifyUser(ButtonClickedListenerModel model, boolean roleAdded, Role role, ButtonInteraction buttonInteraction, List<Role> removedRoles) { public CompletableFuture<Void> notifyUser(ButtonClickedListenerModel model, boolean roleAdded, Role role, ButtonInteraction buttonInteraction, List<Role> removedRoles) {
log.info("Notifying user {} in server {} in channel {} about role change with role {}.",
buttonInteraction.getUser().getIdLong(), buttonInteraction.getGuild().getIdLong(), buttonInteraction.getChannel().getIdLong(), role.getId());
AssignableRoleSuccessNotificationModel notificationModel = AssignableRoleSuccessNotificationModel AssignableRoleSuccessNotificationModel notificationModel = AssignableRoleSuccessNotificationModel
.builder() .builder()
.added(roleAdded) .added(roleAdded)
@@ -196,6 +206,8 @@ public class AssignableRoleButtonClickedListener implements ButtonClickedListene
@Transactional @Transactional
public CompletableFuture<Void> notifyUserAboutConditionFail(ButtonClickedListenerModel model, ButtonInteraction buttonInteraction, public CompletableFuture<Void> notifyUserAboutConditionFail(ButtonClickedListenerModel model, ButtonInteraction buttonInteraction,
AssignableRolePlaceConditionModel conditionModel) { AssignableRolePlaceConditionModel conditionModel) {
log.info("Notifying user {} in server {} in channel {} about failed condition.", buttonInteraction.getUser().getIdLong(),
buttonInteraction.getGuild().getIdLong(), buttonInteraction.getChannel().getIdLong());
return FutureUtils.toSingleFutureGeneric( return FutureUtils.toSingleFutureGeneric(
interactionService.sendMessageToInteraction("assignable_role_condition_notification", conditionModel, buttonInteraction.getHook())) ; interactionService.sendMessageToInteraction("assignable_role_condition_notification", conditionModel, buttonInteraction.getHook())) ;
} }

View File

@@ -0,0 +1,112 @@
package dev.sheldan.abstracto.assignableroles.listener;
import dev.sheldan.abstracto.assignableroles.config.AssignableRoleFeatureDefinition;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRole;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlaceType;
import dev.sheldan.abstracto.assignableroles.model.database.AssignedRoleUser;
import dev.sheldan.abstracto.assignableroles.service.AssignableRoleService;
import dev.sheldan.abstracto.assignableroles.service.management.AssignableRoleManagementService;
import dev.sheldan.abstracto.assignableroles.service.management.AssignedRoleUserManagementService;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncMemberBoostTimeUpdateListener;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.listener.BoostTimeUpdatedModel;
import dev.sheldan.abstracto.core.service.RoleService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role;
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.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@Component
@Slf4j
public class AssignableRolePlaceBoostTimeUpdateListener implements AsyncMemberBoostTimeUpdateListener {
@Autowired
private AssignedRoleUserManagementService assignedRoleUserManagementService;
@Autowired
private AssignableRoleManagementService assignableRoleManagementService;
@Autowired
private RoleService roleService;
@Autowired
private AssignableRoleService assignableRoleService;
@Autowired
private AssignableRolePlaceBoostTimeUpdateListener self;
@Override
public DefaultListenerResult execute(BoostTimeUpdatedModel model) {
Member member = model.getMember();
if(member.getTimeBoosted() == null) {
removeAssignedBoosterRoles(member);
return DefaultListenerResult.PROCESSED;
}
return DefaultListenerResult.IGNORED;
}
private void removeAssignedBoosterRoles(Member member) {
log.info("Member {} in server {} stopped boosting.", member.getIdLong(), member.getGuild().getIdLong());
ServerUser serverUser = ServerUser.fromMember(member);
Optional<AssignedRoleUser> assignedRoleUserOptional = assignedRoleUserManagementService.findByUserInServerOptional(serverUser);
if(assignedRoleUserOptional.isPresent()) {
AssignedRoleUser assignedRoleUser = assignedRoleUserOptional.get();
List<AssignableRole> boosterRoles = assignableRoleManagementService.getAssignableRolesFromAssignableUserWithPlaceType(assignedRoleUser, AssignableRolePlaceType.BOOSTER);
if(!boosterRoles.isEmpty()) {
log.info("Removing {} assignable role mappings.", boosterRoles.size());
Guild guild = member.getGuild();
List<Role> actualRolesToDelete = boosterRoles
.stream()
.map(assignableRole -> guild.getRoleById(assignableRole.getRole().getId()))
.filter(Objects::nonNull)
.collect(Collectors.toList());
log.debug("Which translated to {} roles in reality.", actualRolesToDelete.size());
List<CompletableFuture<Void>> list = new ArrayList<>();
actualRolesToDelete.forEach(role -> list.add(roleService.removeRoleFromUserAsync(member, role)));
FutureUtils.toSingleFutureGeneric(list)
.thenAccept(unused -> self.clearPersistedBoosterAssignableRoles(member))
.exceptionally(throwable -> {
log.warn("One or more roles might have failed to remove. ", throwable);
self.clearPersistedBoosterAssignableRoles(member);
return null;
});
} else {
log.info("Member {} in server {} did not have boost roles - doing nothing.", member.getIdLong(), member.getGuild().getIdLong());
}
} else {
log.info("Member (ID {}) in server (ID: {}), who was not tracked via assignable roles, stopped boosting - doing nothing.",
member.getIdLong(), member.getGuild().getIdLong());
}
}
@Transactional
public void clearPersistedBoosterAssignableRoles(Member member) {
ServerUser serverUser = ServerUser.fromMember(member);
Optional<AssignedRoleUser> assignedRoleUserOptional = assignedRoleUserManagementService.findByUserInServerOptional(serverUser);
if(assignedRoleUserOptional.isPresent()) {
AssignedRoleUser assignedRoleUser = assignedRoleUserOptional.get();
List<AssignableRole> boosterRoles = assignableRoleManagementService.getAssignableRolesFromAssignableUserWithPlaceType(assignedRoleUser, AssignableRolePlaceType.BOOSTER);
assignableRoleService.removeAssignableRolesFromAssignableRoleUser(boosterRoles, assignedRoleUser);
} else {
log.warn("No assigned role user found for member {} in server {}.", member.getIdLong(), member.getGuild().getIdLong());
}
}
@Override
public FeatureDefinition getFeature() {
return AssignableRoleFeatureDefinition.ASSIGNABLE_ROLES;
}
}

View File

@@ -1,12 +1,17 @@
package dev.sheldan.abstracto.assignableroles.repository; package dev.sheldan.abstracto.assignableroles.repository;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRole; import dev.sheldan.abstracto.assignableroles.model.database.AssignableRole;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlaceType;
import dev.sheldan.abstracto.assignableroles.model.database.AssignedRoleUser;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.List;
/** /**
* Repository to manage the access to the table managed by {@link AssignableRole assignableRole} * Repository to manage the access to the table managed by {@link AssignableRole assignableRole}
*/ */
@Repository @Repository
public interface AssignableRoleRepository extends JpaRepository<AssignableRole, Long> { public interface AssignableRoleRepository extends JpaRepository<AssignableRole, Long> {
List<AssignableRole> findByAssignedUsersContainingAndAssignablePlace_Type(AssignedRoleUser roleUser, AssignableRolePlaceType type);
} }

View File

@@ -16,6 +16,7 @@ import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.management.ServerManagementService; import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService; import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.Role;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -27,6 +28,7 @@ import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Component @Component
@Slf4j
public class AssignableRoleConditionServiceBean implements AssignableRoleConditionService { public class AssignableRoleConditionServiceBean implements AssignableRoleConditionService {
@Autowired @Autowired
@@ -55,12 +57,15 @@ public class AssignableRoleConditionServiceBean implements AssignableRoleConditi
@Override @Override
public AssignableRoleConditionResult evaluateConditions(List<AssignableRoleCondition> conditions, AUserInAServer aUserInAServer, Role role) { public AssignableRoleConditionResult evaluateConditions(List<AssignableRoleCondition> conditions, AUserInAServer aUserInAServer, Role role) {
log.debug("Evaluating {} conditions for role {}.", conditions.size(), role.getId());
for (AssignableRoleCondition condition : conditions) { for (AssignableRoleCondition condition : conditions) {
if(assignableRoleConditionEvaluators != null) { if(assignableRoleConditionEvaluators != null) {
Optional<AssignableRoleConditionEvaluator> evaluatorOptional = findEvaluatorForCondition(condition.getType()); Optional<AssignableRoleConditionEvaluator> evaluatorOptional = findEvaluatorForCondition(condition.getType());
if(evaluatorOptional.isPresent()) { if(evaluatorOptional.isPresent()) {
AssignableRoleConditionEvaluator evaluator = evaluatorOptional.get(); AssignableRoleConditionEvaluator evaluator = evaluatorOptional.get();
log.debug("Evaluating condition {} with evaluator {}.", condition.getType(), evaluator.getClass());
if(!evaluator.fulfillsCondition(condition, aUserInAServer)) { if(!evaluator.fulfillsCondition(condition, aUserInAServer)) {
log.info("Condition {} failed for role {} in server {}.", condition.getType(), role.getId(), aUserInAServer.getServerReference().getId());
return AssignableRoleConditionResult.fromFail(condition.getType(), evaluator.createNotificationModel(condition, role)); return AssignableRoleConditionResult.fromFail(condition.getType(), evaluator.createNotificationModel(condition, role));
} }
} }
@@ -94,6 +99,7 @@ public class AssignableRoleConditionServiceBean implements AssignableRoleConditi
if(assignableRoleConditionManagementService.findAssignableRoleCondition(assignableRole, type).isPresent()) { if(assignableRoleConditionManagementService.findAssignableRoleCondition(assignableRole, type).isPresent()) {
throw new AssignableRoleConditionAlreadyExistsException(); throw new AssignableRoleConditionAlreadyExistsException();
} }
log.info("Creating new condition for role {} in place {} in server {}.", place.getId(), role.getId(), role.getGuild().getIdLong());
return assignableRoleConditionManagementService.createAssignableRoleCondition(assignableRole, type, value); return assignableRoleConditionManagementService.createAssignableRoleCondition(assignableRole, type, value);
} }
@@ -106,6 +112,7 @@ public class AssignableRoleConditionServiceBean implements AssignableRoleConditi
if(!existingCondition.isPresent()) { if(!existingCondition.isPresent()) {
throw new AssignableRoleConditionDoesNotExistException(); throw new AssignableRoleConditionDoesNotExistException();
} }
log.info("Deleting assignable role condition on place {} for role {} in server {}.", place.getId(), role.getId(), role.getGuild().getIdLong());
existingCondition.ifPresent(condition -> assignableRoleConditionManagementService.deleteAssignableRoleCondition(condition)); existingCondition.ifPresent(condition -> assignableRoleConditionManagementService.deleteAssignableRoleCondition(condition));
} }

View File

@@ -5,6 +5,7 @@ import dev.sheldan.abstracto.assignableroles.exception.*;
import dev.sheldan.abstracto.assignableroles.model.AssignableRolePlacePayload; import dev.sheldan.abstracto.assignableroles.model.AssignableRolePlacePayload;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRole; import dev.sheldan.abstracto.assignableroles.model.database.AssignableRole;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace; import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlaceType;
import dev.sheldan.abstracto.assignableroles.model.template.*; import dev.sheldan.abstracto.assignableroles.model.template.*;
import dev.sheldan.abstracto.assignableroles.service.management.*; import dev.sheldan.abstracto.assignableroles.service.management.*;
import dev.sheldan.abstracto.core.command.exception.CommandParameterKeyValueWrongTypeException; import dev.sheldan.abstracto.core.command.exception.CommandParameterKeyValueWrongTypeException;
@@ -21,7 +22,6 @@ import dev.sheldan.abstracto.core.service.*;
import dev.sheldan.abstracto.core.service.management.*; import dev.sheldan.abstracto.core.service.management.*;
import dev.sheldan.abstracto.core.templating.model.MessageToSend; import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService; import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.interactions.components.ButtonStyle; import net.dv8tion.jda.api.interactions.components.ButtonStyle;
@@ -38,9 +38,7 @@ import java.util.stream.Collectors;
@Slf4j @Slf4j
public class AssignableRolePlaceServiceBean implements AssignableRolePlaceService { public class AssignableRolePlaceServiceBean implements AssignableRolePlaceService {
public static final String ASSIGNABLE_ROLES_CONFIG_POST_TEMPLATE_KEY = "assignable_roles_config_post";
public static final String ASSIGNABLE_ROLES_POST_TEMPLATE_KEY = "assignable_roles_post"; public static final String ASSIGNABLE_ROLES_POST_TEMPLATE_KEY = "assignable_roles_post";
public static final String ASSIGNABLE_ROLE_PLACES_OVERVIEW_TEMPLATE_KEY = "assignable_role_places_overview";
public static final int MAX_ASSIGNABLE_ROLES_PER_POST = ComponentService.MAX_BUTTONS_PER_ROW * 5; public static final int MAX_ASSIGNABLE_ROLES_PER_POST = ComponentService.MAX_BUTTONS_PER_ROW * 5;
public static final String ASSIGNABLE_ROLE_COMPONENT_ORIGIN = "assignableRoleButton"; public static final String ASSIGNABLE_ROLE_COMPONENT_ORIGIN = "assignableRoleButton";
@Autowired @Autowired
@@ -85,12 +83,15 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
@Autowired @Autowired
private AssignableRoleConditionService assignableRoleConditionService; private AssignableRoleConditionService assignableRoleConditionService;
@Autowired
private ServerManagementService serverManagementService;
@Override @Override
public void createAssignableRolePlace(String name, AChannel channel, String text) { public void createAssignableRolePlace(String name, AChannel channel, String text, AssignableRolePlaceType type) {
if (rolePlaceManagementService.doesPlaceExist(channel.getServer(), name)) { if (rolePlaceManagementService.doesPlaceExist(channel.getServer(), name)) {
throw new AssignableRolePlaceAlreadyExistsException(name); throw new AssignableRolePlaceAlreadyExistsException(name);
} }
rolePlaceManagementService.createPlace(name, channel, text); rolePlaceManagementService.createPlace(name, channel, text, type);
} }
@Override @Override
@@ -98,6 +99,7 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
public CompletableFuture<Void> addRoleToAssignableRolePlace(AServer server, String placeName, Role role, FullEmote fakeEmote, String description) { public CompletableFuture<Void> addRoleToAssignableRolePlace(AServer server, String placeName, Role role, FullEmote fakeEmote, String description) {
AssignableRolePlace assignableRolePlace = rolePlaceManagementService.findByServerAndKey(server, placeName); AssignableRolePlace assignableRolePlace = rolePlaceManagementService.findByServerAndKey(server, placeName);
if (assignableRolePlace.getAssignableRoles().size() > MAX_ASSIGNABLE_ROLES_PER_POST) { if (assignableRolePlace.getAssignableRoles().size() > MAX_ASSIGNABLE_ROLES_PER_POST) {
log.info("Assignable role place {} has already {} roles. Not possible to add more.", assignableRolePlace.getId(), assignableRolePlace.getAssignableRoles().size());
throw new AssignableRolePlaceMaximumRolesException(); throw new AssignableRolePlaceMaximumRolesException();
} }
if (assignableRolePlace.getAssignableRoles().stream().anyMatch(assignableRole -> assignableRole.getRole().getId().equals(role.getIdLong()))) { if (assignableRolePlace.getAssignableRoles().stream().anyMatch(assignableRole -> assignableRole.getRole().getId().equals(role.getIdLong()))) {
@@ -113,16 +115,17 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
throw new EmoteNotUsableException(fakeEmote.getEmote()); throw new EmoteNotUsableException(fakeEmote.getEmote());
} }
} }
log.debug("There are already message posts on for the assignable role place {}.", assignableRolePlace.getId());
Optional<TextChannel> channelOptional = channelService.getTextChannelFromServerOptional(server.getId(), assignableRolePlace.getChannel().getId()); Optional<TextChannel> channelOptional = channelService.getTextChannelFromServerOptional(server.getId(), assignableRolePlace.getChannel().getId());
if (channelOptional.isPresent()) { if (channelOptional.isPresent()) {
TextChannel textChannel = channelOptional.get(); TextChannel textChannel = channelOptional.get();
String buttonId = componentService.generateComponentId(); String buttonId = componentService.generateComponentId();
String emoteMarkdown = fakeEmote != null ? fakeEmote.getEmoteRepr() : null; String emoteMarkdown = fakeEmote != null ? fakeEmote.getEmoteRepr() : null;
if (assignableRolePlace.getMessageId() != null) { if (assignableRolePlace.getMessageId() != null) {
log.debug("Assignable role place {} has already message post with ID {} - updating.", assignableRolePlace.getId(), assignableRolePlace.getMessageId());
return componentService.addButtonToMessage(assignableRolePlace.getMessageId(), textChannel, buttonId, description, emoteMarkdown, ButtonStyle.PRIMARY) return componentService.addButtonToMessage(assignableRolePlace.getMessageId(), textChannel, buttonId, description, emoteMarkdown, ButtonStyle.PRIMARY)
.thenAccept(message -> self.persistAssignableRoleAddition(placeId, role, description, fakeEmote, buttonId)); .thenAccept(message -> self.persistAssignableRoleAddition(placeId, role, description, fakeEmote, buttonId));
} else { } else {
log.info("Assignable role place {} is not yet setup - only adding role to the database.", assignableRolePlace.getId());
self.persistAssignableRoleAddition(placeId, role, description, fakeEmote, buttonId); self.persistAssignableRoleAddition(placeId, role, description, fakeEmote, buttonId);
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} }
@@ -134,6 +137,7 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
@Transactional @Transactional
public void persistAssignableRoleAddition(Long placeId, Role role, String description, FullEmote fakeEmote, String componentId) { public void persistAssignableRoleAddition(Long placeId, Role role, String description, FullEmote fakeEmote, String componentId) {
AssignableRolePlace place = assignableRolePlaceManagementServiceBean.findByPlaceId(placeId); AssignableRolePlace place = assignableRolePlaceManagementServiceBean.findByPlaceId(placeId);
log.info("Adding role {} to assignable role place {} with component ID {}.", role.getId(), place, componentId);
ComponentPayload payload = persistButtonCallback(place, componentId, role.getIdLong()); ComponentPayload payload = persistButtonCallback(place, componentId, role.getIdLong());
assignableRoleManagementServiceBean.addRoleToPlace(fakeEmote, role, description, place, payload); assignableRoleManagementServiceBean.addRoleToPlace(fakeEmote, role, description, place, payload);
} }
@@ -144,6 +148,7 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
Long assignableRolePlaceId = assignableRolePlace.getId(); Long assignableRolePlaceId = assignableRolePlace.getId();
for (AssignableRole assignableRole : assignableRolePlace.getAssignableRoles()) { for (AssignableRole assignableRole : assignableRolePlace.getAssignableRoles()) {
if (assignableRole.getRole().getId().equals(role.getId())) { if (assignableRole.getRole().getId().equals(role.getId())) {
log.info("Found {} role to be removed - removing button from place.", role.getId());
return removeButtonFromAssignableRolePlace(assignableRole, assignableRolePlace).thenAccept(aVoid -> return removeButtonFromAssignableRolePlace(assignableRole, assignableRolePlace).thenAccept(aVoid ->
self.deleteAssignableRoleFromPlace(assignableRolePlaceId, assignableRole.getId()) self.deleteAssignableRoleFromPlace(assignableRolePlaceId, assignableRole.getId())
); );
@@ -154,9 +159,12 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
private CompletableFuture<Void> removeButtonFromAssignableRolePlace(AssignableRole assignableRole, AssignableRolePlace assignableRolePlace) { private CompletableFuture<Void> removeButtonFromAssignableRolePlace(AssignableRole assignableRole, AssignableRolePlace assignableRolePlace) {
String componentId = assignableRole.getComponentPayload().getId(); String componentId = assignableRole.getComponentPayload().getId();
log.debug("Component ID to remove {} for role {}", componentId, assignableRole.getRole().getId());
return channelService.retrieveMessageInChannel(assignableRolePlace.getServer().getId(), assignableRolePlace.getChannel().getId(), assignableRolePlace.getMessageId()) return channelService.retrieveMessageInChannel(assignableRolePlace.getServer().getId(), assignableRolePlace.getChannel().getId(), assignableRolePlace.getMessageId())
.thenCompose(message -> .thenCompose(message -> {
componentService.removeComponentWithId(message, componentId, true) log.debug("Updating message {} to remove component with ID {}.", message.getIdLong(), componentId);
return componentService.removeComponentWithId(message, componentId, true);
}
); );
} }
@@ -232,8 +240,10 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
private CompletableFuture<Void> deleteExistingMessagePostsForPlace(AssignableRolePlace assignableRolePlace) { private CompletableFuture<Void> deleteExistingMessagePostsForPlace(AssignableRolePlace assignableRolePlace) {
if (assignableRolePlace.getMessageId() != null) { if (assignableRolePlace.getMessageId() != null) {
log.info("Deleting old message {} for assignable role place {}.", assignableRolePlace.getMessageId(), assignableRolePlace.getId());
return messageService.deleteMessageInChannelInServer(assignableRolePlace.getServer().getId(), assignableRolePlace.getChannel().getId(), assignableRolePlace.getMessageId()); return messageService.deleteMessageInChannelInServer(assignableRolePlace.getServer().getId(), assignableRolePlace.getChannel().getId(), assignableRolePlace.getMessageId());
} else { } else {
log.info("Assignable role place {} was not yet set up - no message ID tracked.", assignableRolePlace.getMessageId());
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} }
} }
@@ -299,12 +309,11 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
} }
@Override @Override
public CompletableFuture<Void> showAssignablePlaceConfig(AServer server, String name, TextChannel channel) { public AssignableRolePlaceConfig getAssignableRolePlaceConfig(Guild guild, String name) {
Guild guild = guildService.getGuildById(server.getId()); AServer server = serverManagementService.loadServer(guild);
AssignableRolePlace place = rolePlaceManagementService.findByServerAndKey(server, name); AssignableRolePlace place = rolePlaceManagementService.findByServerAndKey(server, name);
log.info("Showing assignable role place config for place {} in channel {} on server {}.", place.getId(), channel.getId(), server.getId()); log.info("Generating assignable role place config for place {} on server {}.", place.getId(), guild.getIdLong());
AssignableRolePlaceConfig configModel = convertPlaceToAssignableRolePlaceConfig(guild, place); return convertPlaceToAssignableRolePlaceConfig(guild, place);
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInTextChannelList(ASSIGNABLE_ROLES_CONFIG_POST_TEMPLATE_KEY, configModel, channel));
} }
private AssignableRolePlaceConfig convertPlaceToAssignableRolePlaceConfig(Guild guild, AssignableRolePlace place) { private AssignableRolePlaceConfig convertPlaceToAssignableRolePlaceConfig(Guild guild, AssignableRolePlace place) {
@@ -326,6 +335,7 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
return AssignableRolePlaceConfig return AssignableRolePlaceConfig
.builder() .builder()
.roles(roles) .roles(roles)
.type(place.getType())
.placeName(place.getKey()) .placeName(place.getKey())
.placeText(place.getText()) .placeText(place.getText())
.uniqueRoles(place.getUniqueRoles()) .uniqueRoles(place.getUniqueRoles())
@@ -336,6 +346,8 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
@Override @Override
public CompletableFuture<Void> moveAssignableRolePlace(AServer server, String name, TextChannel newChannel) { public CompletableFuture<Void> moveAssignableRolePlace(AServer server, String name, TextChannel newChannel) {
AssignableRolePlace place = rolePlaceManagementService.findByServerAndKey(server, name); AssignableRolePlace place = rolePlaceManagementService.findByServerAndKey(server, name);
log.info("Moving assignable role place {} from channel {} to channel {} in guild {}.",
place.getId(), place.getChannel().getId(), newChannel.getId(), newChannel.getGuild().getIdLong());
CompletableFuture<Void> oldPostDeletionFuture = deleteExistingMessagePostsForPlace(place); CompletableFuture<Void> oldPostDeletionFuture = deleteExistingMessagePostsForPlace(place);
Long serverId = server.getId(); Long serverId = server.getId();
Long assignablePlaceId = place.getId(); Long assignablePlaceId = place.getId();
@@ -363,13 +375,14 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
@Transactional @Transactional
public void updateAssignableRolePlaceChannel(String name, TextChannel textChannel) { public void updateAssignableRolePlaceChannel(String name, TextChannel textChannel) {
AChannel channel = channelManagementService.loadChannel(textChannel.getIdLong()); AChannel channel = channelManagementService.loadChannel(textChannel.getIdLong());
log.info("Setting assignable role place to channel {}.", textChannel.getIdLong());
rolePlaceManagementService.moveAssignableRolePlace(name, channel); rolePlaceManagementService.moveAssignableRolePlace(name, channel);
} }
@Override @Override
public CompletableFuture<Void> deleteAssignableRolePlace(AServer server, String name) { public CompletableFuture<Void> deleteAssignableRolePlace(AServer server, String name) {
AssignableRolePlace place = rolePlaceManagementService.findByServerAndKey(server, name); AssignableRolePlace place = rolePlaceManagementService.findByServerAndKey(server, name);
log.info("Deleting assignable role place {}.", place.getId());
Long placeId = place.getId(); Long placeId = place.getId();
CompletableFuture<Void> deleteFuture = deleteExistingMessagePostsForPlace(place); CompletableFuture<Void> deleteFuture = deleteExistingMessagePostsForPlace(place);
return deleteFuture.thenAccept(unused -> self.deleteAssignableRolePlaceInDatabase(placeId)); return deleteFuture.thenAccept(unused -> self.deleteAssignableRolePlaceInDatabase(placeId));
@@ -405,25 +418,24 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
} }
@Override @Override
public CompletableFuture<Void> showAllAssignableRolePlaces(AServer server, TextChannel channel) { public AssignablePlaceOverview getAssignableRolePlaceOverview(Guild guild) {
AServer server = serverManagementService.loadServer(guild);
List<AssignableRolePlace> assignableRolePlaces = rolePlaceManagementService.findAllByServer(server); List<AssignableRolePlace> assignableRolePlaces = rolePlaceManagementService.findAllByServer(server);
Guild guild = channel.getGuild();
List<AssignableRolePlaceConfig> placeConfigs = assignableRolePlaces List<AssignableRolePlaceConfig> placeConfigs = assignableRolePlaces
.stream() .stream()
.map(place -> convertPlaceToAssignableRolePlaceConfig(guild, place)) .map(place -> convertPlaceToAssignableRolePlaceConfig(guild, place))
.collect(Collectors.toList()); .collect(Collectors.toList());
AssignablePlaceOverview overViewModel = AssignablePlaceOverview log.info("Showing overview over all assignable role places for server {}.", server.getId());
return AssignablePlaceOverview
.builder() .builder()
.places(placeConfigs) .places(placeConfigs)
.build(); .build();
log.info("Showing overview over all assignable role places for server {} in channel {}.", server.getId(), channel.getId());
List<CompletableFuture<Message>> promises = channelService.sendEmbedTemplateInTextChannelList(ASSIGNABLE_ROLE_PLACES_OVERVIEW_TEMPLATE_KEY, overViewModel, channel);
return CompletableFuture.allOf(promises.toArray(new CompletableFuture[0]));
} }
private CompletableFuture<Void> sendAssignablePostMessage(AssignableRolePlace place, TextChannel channel) { private CompletableFuture<Void> sendAssignablePostMessage(AssignableRolePlace place, TextChannel channel) {
AssignablePostMessage model = prepareAssignablePostMessageModel(place); AssignablePostMessage model = prepareAssignablePostMessageModel(place);
MessageToSend messageToSend = templateService.renderEmbedTemplate(ASSIGNABLE_ROLES_POST_TEMPLATE_KEY, model, place.getServer().getId()); MessageToSend messageToSend = templateService.renderEmbedTemplate(ASSIGNABLE_ROLES_POST_TEMPLATE_KEY, model, place.getServer().getId());
log.info("Sending message for assignable role place {}.", place.getId());
CompletableFuture<Message> postFuture = channelService.sendMessageToSendToChannel(messageToSend, channel).get(0); CompletableFuture<Message> postFuture = channelService.sendMessageToSendToChannel(messageToSend, channel).get(0);
Long placeId = model.getPlaceId(); Long placeId = model.getPlaceId();
return postFuture.thenCompose(unused -> { return postFuture.thenCompose(unused -> {
@@ -443,6 +455,7 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
public void persistAssignablePlaceMessageId(Long placeId, CompletableFuture<Message> messageFuture) { public void persistAssignablePlaceMessageId(Long placeId, CompletableFuture<Message> messageFuture) {
AssignableRolePlace place = assignableRolePlaceManagementServiceBean.findByPlaceId(placeId); AssignableRolePlace place = assignableRolePlaceManagementServiceBean.findByPlaceId(placeId);
Message message = messageFuture.join(); Message message = messageFuture.join();
log.info("Setting message ID of assignable role place {} to {}.", placeId, message.getIdLong());
place.setMessageId(message.getIdLong()); place.setMessageId(message.getIdLong());
} }
@@ -491,7 +504,6 @@ public class AssignableRolePlaceServiceBean implements AssignableRolePlaceServic
log.info("Sending assignable role place posts for place {} in channel {} in server {}.", assignableRolePlace.getId(), channel.getId(), serverId); log.info("Sending assignable role place posts for place {} in channel {} in server {}.", assignableRolePlace.getId(), channel.getId(), serverId);
return sendAssignablePostMessage(assignableRolePlace, channel); return sendAssignablePostMessage(assignableRolePlace, channel);
} else { } else {
log.warn("Channel to create assignable role post in does not exist.");
throw new AssignableRolePlaceChannelDoesNotExistException(assignableRolePlace.getChannel().getId(), assignableRolePlace.getKey()); throw new AssignableRolePlaceChannelDoesNotExistException(assignableRolePlace.getChannel().getId(), assignableRolePlace.getKey());
} }
} }

View File

@@ -24,6 +24,7 @@ import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -147,6 +148,13 @@ public class AssignableRoleServiceBean implements AssignableRoleService {
throw new AssignableRoleNotFoundException(roleId); throw new AssignableRoleNotFoundException(roleId);
} }
@Override
public void removeAssignableRolesFromAssignableRoleUser(List<AssignableRole> roles, AssignedRoleUser roleUser) {
log.info("Removing {} assignable roles from user {} in server {}.", roles.size(), roleUser.getUser().getUserReference().getId(),
roleUser.getUser().getServerReference().getId());
roles.forEach(assignableRole -> assignedRoleUserManagementServiceBean.removeAssignedRoleFromUser(assignableRole, roleUser));
}
@PostConstruct @PostConstruct
public void postConstruct() { public void postConstruct() {
metricService.registerCounter(ASSIGNABLE_ROLES_ASSIGNED, "Assignable roles assigned."); metricService.registerCounter(ASSIGNABLE_ROLES_ASSIGNED, "Assignable roles assigned.");

View File

@@ -4,12 +4,14 @@ import dev.sheldan.abstracto.assignableroles.model.condition.AssignableRoleCondi
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRole; import dev.sheldan.abstracto.assignableroles.model.database.AssignableRole;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRoleCondition; import dev.sheldan.abstracto.assignableroles.model.database.AssignableRoleCondition;
import dev.sheldan.abstracto.assignableroles.repository.AssignableRoleConditionRepository; import dev.sheldan.abstracto.assignableroles.repository.AssignableRoleConditionRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.Optional; import java.util.Optional;
@Component @Component
@Slf4j
public class AssignableRoleConditionManagementServiceBean implements AssignableRoleConditionManagementService { public class AssignableRoleConditionManagementServiceBean implements AssignableRoleConditionManagementService {
@Autowired @Autowired
@@ -23,12 +25,14 @@ public class AssignableRoleConditionManagementServiceBean implements AssignableR
.type(type) .type(type)
.conditionValue(value) .conditionValue(value)
.build(); .build();
log.info("Creating condition of type {} for assignable role {}", assignableRole.getId(), type);
assignableRole.getConditions().add(condition); assignableRole.getConditions().add(condition);
return repository.save(condition); return repository.save(condition);
} }
@Override @Override
public void deleteAssignableRoleCondition(AssignableRoleCondition condition) { public void deleteAssignableRoleCondition(AssignableRoleCondition condition) {
log.info("Deleting condition {}.", condition.getId());
repository.delete(condition); repository.delete(condition);
} }

View File

@@ -2,6 +2,8 @@ package dev.sheldan.abstracto.assignableroles.service.management;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRole; import dev.sheldan.abstracto.assignableroles.model.database.AssignableRole;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace; import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlaceType;
import dev.sheldan.abstracto.assignableroles.model.database.AssignedRoleUser;
import dev.sheldan.abstracto.assignableroles.repository.AssignableRoleRepository; import dev.sheldan.abstracto.assignableroles.repository.AssignableRoleRepository;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException; import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.models.FullEmote; import dev.sheldan.abstracto.core.models.FullEmote;
@@ -15,6 +17,8 @@ import net.dv8tion.jda.api.entities.Role;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.List;
@Component @Component
@Slf4j @Slf4j
public class AssignableRoleManagementServiceBean implements AssignableRoleManagementService { public class AssignableRoleManagementServiceBean implements AssignableRoleManagementService {
@@ -51,8 +55,6 @@ public class AssignableRoleManagementServiceBean implements AssignableRoleManage
return roleToAdd; return roleToAdd;
} }
@Override @Override
public AssignableRole getByAssignableRoleId(Long assignableRoleId) { public AssignableRole getByAssignableRoleId(Long assignableRoleId) {
return repository.findById(assignableRoleId).orElseThrow(() -> new AbstractoRunTimeException("Assignable role not found")); return repository.findById(assignableRoleId).orElseThrow(() -> new AbstractoRunTimeException("Assignable role not found"));
@@ -63,4 +65,9 @@ public class AssignableRoleManagementServiceBean implements AssignableRoleManage
repository.delete(assignableRole); repository.delete(assignableRole);
} }
@Override
public List<AssignableRole> getAssignableRolesFromAssignableUserWithPlaceType(AssignedRoleUser user, AssignableRolePlaceType type) {
return repository.findByAssignedUsersContainingAndAssignablePlace_Type(user, type);
}
} }

View File

@@ -2,6 +2,7 @@ package dev.sheldan.abstracto.assignableroles.service.management;
import dev.sheldan.abstracto.assignableroles.exception.AssignableRolePlaceNotFoundException; import dev.sheldan.abstracto.assignableroles.exception.AssignableRolePlaceNotFoundException;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace; import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlaceType;
import dev.sheldan.abstracto.assignableroles.repository.AssignableRolePlaceRepository; import dev.sheldan.abstracto.assignableroles.repository.AssignableRolePlaceRepository;
import dev.sheldan.abstracto.core.models.database.AChannel; import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer; import dev.sheldan.abstracto.core.models.database.AServer;
@@ -20,12 +21,18 @@ public class AssignableRolePlaceManagementServiceBean implements AssignableRoleP
private AssignableRolePlaceRepository repository; private AssignableRolePlaceRepository repository;
@Override @Override
public AssignableRolePlace createPlace(String name, AChannel channel, String text) { public AssignableRolePlace createPlace(String name, AChannel channel, String text, AssignableRolePlaceType type) {
boolean unique = false;
if(type.equals(AssignableRolePlaceType.BOOSTER)) {
unique = true;
}
AssignableRolePlace place = AssignableRolePlace AssignableRolePlace place = AssignableRolePlace
.builder() .builder()
.channel(channel) .channel(channel)
.server(channel.getServer()) .server(channel.getServer())
.text(text) .text(text)
.uniqueRoles(unique)
.type(type)
.key(name) .key(name)
.build(); .build();
log.info("Creating assignable role place in channel {} on server {}.", channel.getId(), channel.getServer().getId()); log.info("Creating assignable role place in channel {} on server {}.", channel.getId(), channel.getServer().getId());

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="tables/tables.xml" relativeToChangelogFile="true"/>
</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="assignable_role_place-add_type">
<addColumn tableName="assignable_role_place">
<column name="type" type="VARCHAR2(128)" defaultValue="DEFAULT"/>
</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="assignable_role_place.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -7,4 +7,5 @@
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" > http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="1.0-assignableRoles/collection.xml" relativeToChangelogFile="true"/> <include file="1.0-assignableRoles/collection.xml" relativeToChangelogFile="true"/>
<include file="1.3.4/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog> </databaseChangeLog>

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.assignableroles.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
public class BoosterAssignableRolePlaceMemberNotBoostingException extends AbstractoTemplatableException {
@Override
public String getTemplateName() {
return "assignable_role_booster_place_member_not_boosting_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -85,4 +85,8 @@ public class AssignableRolePlace implements Serializable {
@Column(name = "updated", insertable = false, updatable = false) @Column(name = "updated", insertable = false, updatable = false)
private Instant updated; private Instant updated;
@Enumerated(EnumType.STRING)
@Column(name = "type")
private AssignableRolePlaceType type;
} }

View File

@@ -0,0 +1,9 @@
package dev.sheldan.abstracto.assignableroles.model.database;
import dev.sheldan.abstracto.core.command.execution.CommandParameterKey;
import lombok.Getter;
@Getter
public enum AssignableRolePlaceType implements CommandParameterKey {
DEFAULT, BOOSTER
}

View File

@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.assignableroles.model.template; package dev.sheldan.abstracto.assignableroles.model.template;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace; import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlaceType;
import dev.sheldan.abstracto.core.models.template.display.ChannelDisplay; import dev.sheldan.abstracto.core.models.template.display.ChannelDisplay;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
@@ -19,6 +20,7 @@ public class AssignableRolePlaceConfig {
private String placeText; private String placeText;
private ChannelDisplay channelDisplay; private ChannelDisplay channelDisplay;
private Boolean uniqueRoles; private Boolean uniqueRoles;
private AssignableRolePlaceType type;
/** /**
* The {@link AssignableRolePlaceConfig roles} which are contained in this {@link AssignableRolePlace} * The {@link AssignableRolePlaceConfig roles} which are contained in this {@link AssignableRolePlace}
*/ */

View File

@@ -2,17 +2,21 @@ package dev.sheldan.abstracto.assignableroles.service;
import dev.sheldan.abstracto.assignableroles.config.AssignableRolePlaceParameterKey; import dev.sheldan.abstracto.assignableroles.config.AssignableRolePlaceParameterKey;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace; import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlaceType;
import dev.sheldan.abstracto.assignableroles.model.template.AssignablePlaceOverview;
import dev.sheldan.abstracto.assignableroles.model.template.AssignableRolePlaceConfig;
import dev.sheldan.abstracto.core.models.FullEmote; import dev.sheldan.abstracto.core.models.FullEmote;
import dev.sheldan.abstracto.core.models.database.AChannel; import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.ARole; import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.models.database.AServer; import dev.sheldan.abstracto.core.models.database.AServer;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.TextChannel; import net.dv8tion.jda.api.entities.TextChannel;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public interface AssignableRolePlaceService { public interface AssignableRolePlaceService {
void createAssignableRolePlace(String name, AChannel channel, String text); void createAssignableRolePlace(String name, AChannel channel, String text, AssignableRolePlaceType type);
CompletableFuture<Void> addRoleToAssignableRolePlace(AServer server, String placeName, Role role, FullEmote emote, String description); CompletableFuture<Void> addRoleToAssignableRolePlace(AServer server, String placeName, Role role, FullEmote emote, String description);
@@ -42,7 +46,7 @@ public interface AssignableRolePlaceService {
void multipleAssignableRolePlace(AssignableRolePlace place); void multipleAssignableRolePlace(AssignableRolePlace place);
CompletableFuture<Void> showAssignablePlaceConfig(AServer server, String name, TextChannel channel); AssignableRolePlaceConfig getAssignableRolePlaceConfig(Guild guild, String name);
CompletableFuture<Void> moveAssignableRolePlace(AServer server, String name, TextChannel newChannel); CompletableFuture<Void> moveAssignableRolePlace(AServer server, String name, TextChannel newChannel);
@@ -52,5 +56,5 @@ public interface AssignableRolePlaceService {
CompletableFuture<Void> changeConfiguration(AServer server, String name, AssignableRolePlaceParameterKey keyToChange, String newValue); CompletableFuture<Void> changeConfiguration(AServer server, String name, AssignableRolePlaceParameterKey keyToChange, String newValue);
CompletableFuture<Void> showAllAssignableRolePlaces(AServer server, TextChannel channel); AssignablePlaceOverview getAssignableRolePlaceOverview(Guild guild);
} }

View File

@@ -2,11 +2,13 @@ package dev.sheldan.abstracto.assignableroles.service;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRole; import dev.sheldan.abstracto.assignableroles.model.database.AssignableRole;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace; import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace;
import dev.sheldan.abstracto.assignableroles.model.database.AssignedRoleUser;
import dev.sheldan.abstracto.core.models.database.ARole; import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.Role;
import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
/** /**
@@ -67,4 +69,6 @@ public interface AssignableRoleService {
AssignableRole getAssignableRoleInPlace(AssignableRolePlace place, ARole role); AssignableRole getAssignableRoleInPlace(AssignableRolePlace place, ARole role);
AssignableRole getAssignableRoleInPlace(AssignableRolePlace place, Long roleId); AssignableRole getAssignableRoleInPlace(AssignableRolePlace place, Long roleId);
void removeAssignableRolesFromAssignableRoleUser(List<AssignableRole> roles, AssignedRoleUser roleUser);
} }

View File

@@ -2,14 +2,19 @@ package dev.sheldan.abstracto.assignableroles.service.management;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRole; import dev.sheldan.abstracto.assignableroles.model.database.AssignableRole;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace; import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlaceType;
import dev.sheldan.abstracto.assignableroles.model.database.AssignedRoleUser;
import dev.sheldan.abstracto.core.models.FullEmote; import dev.sheldan.abstracto.core.models.FullEmote;
import dev.sheldan.abstracto.core.models.database.ComponentPayload; import dev.sheldan.abstracto.core.models.database.ComponentPayload;
import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.Role;
import java.util.List;
public interface AssignableRoleManagementService { public interface AssignableRoleManagementService {
AssignableRole addRoleToPlace(FullEmote emote, Role role, String description, AssignableRolePlace place, ComponentPayload componentPayload); AssignableRole addRoleToPlace(FullEmote emote, Role role, String description, AssignableRolePlace place, ComponentPayload componentPayload);
AssignableRole getByAssignableRoleId(Long assignableRoleId); AssignableRole getByAssignableRoleId(Long assignableRoleId);
void deleteAssignableRole(AssignableRole assignableRole); void deleteAssignableRole(AssignableRole assignableRole);
List<AssignableRole> getAssignableRolesFromAssignableUserWithPlaceType(AssignedRoleUser user, AssignableRolePlaceType type);
} }

View File

@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.assignableroles.service.management; package dev.sheldan.abstracto.assignableroles.service.management;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace; import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlace;
import dev.sheldan.abstracto.assignableroles.model.database.AssignableRolePlaceType;
import dev.sheldan.abstracto.core.models.database.AChannel; import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer; import dev.sheldan.abstracto.core.models.database.AServer;
@@ -9,7 +10,7 @@ import java.util.Optional;
public interface AssignableRolePlaceManagementService { public interface AssignableRolePlaceManagementService {
AssignableRolePlace createPlace(String name, AChannel channel, String text); AssignableRolePlace createPlace(String name, AChannel channel, String text, AssignableRolePlaceType type);
boolean doesPlaceExist(AServer server, String name); boolean doesPlaceExist(AServer server, String name);

View File

@@ -36,7 +36,7 @@ public class ActivityServiceBean implements ActivityService {
List<CustomActivity> activities = activityManagementService.getAllActivities(); List<CustomActivity> activities = activityManagementService.getAllActivities();
if(!activities.isEmpty()) { if(!activities.isEmpty()) {
CustomActivity chosen = activities.get(secureRandom.nextInt(activities.size())); CustomActivity chosen = activities.get(secureRandom.nextInt(activities.size()));
log.info("Chosen {} activity.", chosen.getId()); log.info("Chosen activity {}.", chosen.getId());
switchToActivity(chosen); switchToActivity(chosen);
} else { } else {
log.info("No activities configured."); log.info("No activities configured.");

View File

@@ -50,6 +50,7 @@ public class UserBannedListener implements AsyncUserBannedListener {
.queue(auditLogEntries -> { .queue(auditLogEntries -> {
if(auditLogEntries.isEmpty()) { if(auditLogEntries.isEmpty()) {
log.info("Did not find recent bans in guild {}.", model.getServerId()); log.info("Did not find recent bans in guild {}.", model.getServerId());
self.sendBannedNotification(model.getUser(), null, null, model.getServerId());
return; return;
} }
Optional<AuditLogEntry> banEntryOptional = auditLogEntries Optional<AuditLogEntry> banEntryOptional = auditLogEntries
@@ -65,6 +66,9 @@ public class UserBannedListener implements AsyncUserBannedListener {
log.info("Did not find the banned user in the most recent bans for guild {}. Not adding audit log information.", model.getServerId()); log.info("Did not find the banned user in the most recent bans for guild {}. Not adding audit log information.", model.getServerId());
self.sendBannedNotification(model.getUser(), null, null, model.getServerId()); self.sendBannedNotification(model.getUser(), null, null, model.getServerId());
} }
}, throwable -> {
log.error("Retrieving bans for guild {} failed - logging ban regardless.", model.getServerId());
self.sendBannedNotification(model.getUser(), null, null, model.getServerId());
}); });
return DefaultListenerResult.PROCESSED; return DefaultListenerResult.PROCESSED;
} }

View File

@@ -175,15 +175,30 @@ public class StarboardServiceBean implements StarboardService {
public CompletableFuture<Void> updateStarboardPost(StarboardPost post, CachedMessage message, List<AUserInAServer> userExceptAuthor) { public CompletableFuture<Void> updateStarboardPost(StarboardPost post, CachedMessage message, List<AUserInAServer> userExceptAuthor) {
int starCount = userExceptAuthor.size(); int starCount = userExceptAuthor.size();
log.info("Updating starboard post {} in server {} with reactors {}.", post.getId(), post.getSourceChannel().getServer().getId(), starCount); log.info("Updating starboard post {} in server {} with reactors {}.", post.getId(), post.getSourceChannel().getServer().getId(), starCount);
return buildStarboardPostModel(message, starCount).thenCompose(starboardPostModel -> { Long starboardPostId = post.getId();
MessageToSend messageToSend = templateService.renderEmbedTemplate(STARBOARD_POST_TEMPLATE, starboardPostModel, message.getServerId()); Long postMessageId = post.getStarboardMessageId();
List<CompletableFuture<Message>> futures = postTargetService.editOrCreatedInPostTarget(post.getStarboardMessageId(), messageToSend, StarboardPostTarget.STARBOARD, message.getServerId()); return buildStarboardPostModel(message, starCount)
Long starboardPostId = post.getId(); .thenCompose(starboardPostModel -> self.sendStarboardPost(postMessageId, message, starboardPostId, starboardPostModel));
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenAccept(aVoid -> { }
Optional<StarboardPost> innerPost = starboardPostManagementService.findByStarboardPostId(starboardPostId);
innerPost.ifPresent(starboardPost -> starboardPostManagementService.setStarboardPostMessageId(starboardPost, futures.get(0).join().getIdLong())); @Transactional
}); public CompletableFuture<Void> sendStarboardPost(Long starboardPostMessageId, CachedMessage message, Long starboardPostId, StarboardPostModel starboardPostModel) {
}); MessageToSend messageToSend = templateService.renderEmbedTemplate(STARBOARD_POST_TEMPLATE, starboardPostModel, message.getServerId());
log.info("Updating/Creating message with ID {} for post {}", starboardPostMessageId, starboardPostId);
List<CompletableFuture<Message>> futures = postTargetService.editOrCreatedInPostTarget(starboardPostMessageId, messageToSend, StarboardPostTarget.STARBOARD, message.getServerId());
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenAccept(aVoid -> self.persistStarboardPostMessageId(starboardPostId, futures));
}
@Transactional
public void persistStarboardPostMessageId(Long starboardPostId, List<CompletableFuture<Message>> futures) {
Optional<StarboardPost> innerPost = starboardPostManagementService.findByStarboardPostId(starboardPostId);
long messageId = futures.get(0).join().getIdLong();
log.debug("Updating starboard post {} to message ID {}.", starboardPostId, messageId);
innerPost.ifPresent(starboardPost -> starboardPostManagementService.setStarboardPostMessageId(starboardPost, messageId));
if(!innerPost.isPresent()) {
log.warn("Starboard post {} was not found. Not updating message ID to {}.", starboardPostId, messageId);
}
} }
@Override @Override

View File

@@ -219,7 +219,6 @@ public class StarboardServiceBeanTest {
@Test @Test
public void testUpdateStarboardPost() { public void testUpdateStarboardPost() {
Long newPostId = 37L;
Long oldPostId = 36L; Long oldPostId = 36L;
AChannel sourceChannel = Mockito.mock(AChannel.class); AChannel sourceChannel = Mockito.mock(AChannel.class);
when(sourceChannel.getServer()).thenReturn(server); when(sourceChannel.getServer()).thenReturn(server);
@@ -234,22 +233,16 @@ public class StarboardServiceBeanTest {
when(post.getStarboardMessageId()).thenReturn(oldPostId); when(post.getStarboardMessageId()).thenReturn(oldPostId);
when(post.getSourceChannel()).thenReturn(sourceChannel); when(post.getSourceChannel()).thenReturn(sourceChannel);
when(post.getId()).thenReturn(starboardPostId); when(post.getId()).thenReturn(starboardPostId);
MessageToSend postMessage = Mockito.mock(MessageToSend.class);
when(templateService.renderEmbedTemplate(eq(StarboardServiceBean.STARBOARD_POST_TEMPLATE), starboardPostModelArgumentCaptor.capture(), eq(SERVER_ID))).thenReturn(postMessage);
when(postTargetService.editOrCreatedInPostTarget(oldPostId, postMessage, StarboardPostTarget.STARBOARD, SERVER_ID)).thenReturn(Arrays.asList(CompletableFuture.completedFuture(sendPost)));
when(sendPost.getIdLong()).thenReturn(newPostId);
SystemConfigProperty config = Mockito.mock(SystemConfigProperty.class); SystemConfigProperty config = Mockito.mock(SystemConfigProperty.class);
when(config.getLongValue()).thenReturn(1L); when(config.getLongValue()).thenReturn(1L);
when(defaultConfigManagementService.getDefaultConfig(StarboardFeatureConfig.STAR_LEVELS_CONFIG_KEY)).thenReturn(config); when(defaultConfigManagementService.getDefaultConfig(StarboardFeatureConfig.STAR_LEVELS_CONFIG_KEY)).thenReturn(config);
when(defaultConfigManagementService.getDefaultConfig(StarboardFeatureConfig.STAR_LVL_CONFIG_PREFIX + 1)).thenReturn(config); when(defaultConfigManagementService.getDefaultConfig(StarboardFeatureConfig.STAR_LVL_CONFIG_PREFIX + 1)).thenReturn(config);
when(starboardPostManagementService.findByStarboardPostId(starboardPostId)).thenReturn(Optional.of(post));
when(userService.retrieveUserForId(STARRED_USER_ID)).thenReturn(CompletableFuture.completedFuture(starredJdaUser)); when(userService.retrieveUserForId(STARRED_USER_ID)).thenReturn(CompletableFuture.completedFuture(starredJdaUser));
when(self.sendStarboardPost(eq(oldPostId), eq(message), eq(starboardPostId), any(StarboardPostModel.class))).thenReturn(CompletableFuture.completedFuture(null));
List<AUserInAServer > userExceptAuthor = new ArrayList<>(); List<AUserInAServer > userExceptAuthor = new ArrayList<>();
CompletableFuture<Void> future = testUnit.updateStarboardPost(post, message, userExceptAuthor); CompletableFuture<Void> future = testUnit.updateStarboardPost(post, message, userExceptAuthor);
future.join(); future.join();
Assert.assertFalse(future.isCompletedExceptionally()); Assert.assertFalse(future.isCompletedExceptionally());
verify(postTargetService, times(1)).editOrCreatedInPostTarget(oldPostId, postMessage, StarboardPostTarget.STARBOARD, SERVER_ID);
verify(starboardPostManagementService, times(1)).setStarboardPostMessageId(post, newPostId);
} }
@Test @Test

View File

@@ -1,5 +1,6 @@
package dev.sheldan.abstracto.suggestion.service; package dev.sheldan.abstracto.suggestion.service;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.exception.ChannelNotInGuildException; import dev.sheldan.abstracto.core.exception.ChannelNotInGuildException;
import dev.sheldan.abstracto.core.models.ServerSpecificId; import dev.sheldan.abstracto.core.models.ServerSpecificId;
import dev.sheldan.abstracto.core.models.database.AChannel; import dev.sheldan.abstracto.core.models.database.AChannel;

View File

@@ -22,6 +22,11 @@ public class ListenerExecutorConfig {
return executorService.setupExecutorFor("leaveListener"); return executorService.setupExecutorFor("leaveListener");
} }
@Bean(name = "boostTimeUpdateExecutor")
public TaskExecutor boostTimeUpdateExecutor() {
return executorService.setupExecutorFor("boostTimeUpdateListener");
}
@Bean(name = "messageReceivedExecutor") @Bean(name = "messageReceivedExecutor")
public TaskExecutor messageReceivedExecutor() { public TaskExecutor messageReceivedExecutor() {
return executorService.setupExecutorFor("messageReceivedListener"); return executorService.setupExecutorFor("messageReceivedListener");

View File

@@ -0,0 +1,44 @@
package dev.sheldan.abstracto.core.listener.async.jda;
import dev.sheldan.abstracto.core.listener.ListenerService;
import dev.sheldan.abstracto.core.models.listener.BoostTimeUpdatedModel;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateBoostTimeEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@Slf4j
public class AsyncMemberBoostTimeUpdateListenerBean extends ListenerAdapter {
@Autowired(required = false)
private List<AsyncMemberBoostTimeUpdateListener> listenerList;
@Autowired
@Qualifier("boostTimeUpdateExecutor")
private TaskExecutor boostTimeUpdateListener;
@Autowired
private ListenerService listenerService;
@Override
public void onGuildMemberUpdateBoostTime(@NotNull GuildMemberUpdateBoostTimeEvent event) {
if(listenerList == null) return;
BoostTimeUpdatedModel model = getModel(event);
listenerList.forEach(boostListener -> listenerService.executeFeatureAwareListener(boostListener, model, boostTimeUpdateListener));
}
private BoostTimeUpdatedModel getModel(GuildMemberUpdateBoostTimeEvent event) {
return BoostTimeUpdatedModel
.builder()
.member(event.getMember())
.oldTime(event.getOldTimeBoosted())
.newTime(event.getNewTimeBoosted())
.build();
}
}

View File

@@ -0,0 +1,8 @@
package dev.sheldan.abstracto.core.listener.async.jda;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.FeatureAwareListener;
import dev.sheldan.abstracto.core.models.listener.BoostTimeUpdatedModel;
public interface AsyncMemberBoostTimeUpdateListener extends FeatureAwareListener<BoostTimeUpdatedModel, DefaultListenerResult> {
}

View File

@@ -0,0 +1,23 @@
package dev.sheldan.abstracto.core.models.listener;
import dev.sheldan.abstracto.core.listener.FeatureAwareListenerModel;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.entities.Member;
import java.time.OffsetDateTime;
@Getter
@Setter
@Builder
public class BoostTimeUpdatedModel implements FeatureAwareListenerModel {
private Member member;
private OffsetDateTime oldTime;
private OffsetDateTime newTime;
@Override
public Long getServerId() {
return member.getGuild().getIdLong();
}
}

View File

@@ -3,16 +3,18 @@
This feature enables creating and maintaining so-called 'assignable role places'. These places are messages at which buttons are added, and when a member clicks such a button, a configured role, is assigned to the user. This feature enables creating and maintaining so-called 'assignable role places'. These places are messages at which buttons are added, and when a member clicks such a button, a configured role, is assigned to the user.
A place can be disabled, which causes the buttons to become disabled. Such places can be made 'unique', which means that users can only have one role available in the place. A place can be disabled, which causes the buttons to become disabled. Such places can be made 'unique', which means that users can only have one role available in the place.
Deleting the actual role behind an assignable role causes the assignable role place to become non-functional: the button will remain and can still be clicked, but users will receive and error message. An assignable role place can have a type. This type can either be `DEFAULT`, or `BOOSTER´. The `BOOSTER` type comes with special functionalities: if a member clicked a button, it will evaluate whether the member has boosted the server and reject if not. There is also functionality to automatically remove the assigned roles for members who stopped boosting.
Deleting the actual role behind an assignable role causes the assignable role place to become non-functional: the button will remain and can still be clicked, but users will receive an error message.
Feature key: `assignableRole` Feature key: `assignableRole`
==== Commands ==== Commands
Create a new assignable role place:: Create a new assignable role place::
* Usage: `createAssignableRolePlace <name> <channel> <text>` * Usage: `createAssignableRolePlace <name> <channel> <text> [type]`
* Description: Creates a new assignable role place with the key `name`. The `text` will be shown in the description of the first message. * Description: Creates a new assignable role place with the key `name`. The `text` will be shown in the description of the first message.
When the place is setup, it will be posted in the `channel`. The created place is active and inline by default. When the place is setup, it will be posted in the `channel`. If `type` is not provided, it will be `DEFAULT`. Possible values for are `booster` and `default`.
Add a role to an assignable role place:: Add a role to an assignable role place::
* Usage: `addRoleToAssignableRolePlace <name> <role> <text> [emote]` * Usage: `addRoleToAssignableRolePlace <name> <role> <text> [emote]`