diff --git a/abstracto-application/abstracto-modules/pom.xml b/abstracto-application/abstracto-modules/pom.xml index a51754428..142d428bd 100644 --- a/abstracto-application/abstracto-modules/pom.xml +++ b/abstracto-application/abstracto-modules/pom.xml @@ -23,6 +23,7 @@ remind suggestion repost-detection + sticky-roles webservices logging invite-filter diff --git a/abstracto-application/abstracto-modules/remind/remind-impl/pom.xml b/abstracto-application/abstracto-modules/remind/remind-impl/pom.xml index c4a74d405..5665c814f 100644 --- a/abstracto-application/abstracto-modules/remind/remind-impl/pom.xml +++ b/abstracto-application/abstracto-modules/remind/remind-impl/pom.xml @@ -9,11 +9,6 @@ remind-impl - - 8 - 8 - - diff --git a/abstracto-application/abstracto-modules/repost-detection/pom.xml b/abstracto-application/abstracto-modules/repost-detection/pom.xml index 09680b667..660bf306b 100644 --- a/abstracto-application/abstracto-modules/repost-detection/pom.xml +++ b/abstracto-application/abstracto-modules/repost-detection/pom.xml @@ -14,11 +14,6 @@ repost-detection-impl - - 8 - 8 - - dev.sheldan.abstracto.core diff --git a/abstracto-application/abstracto-modules/repost-detection/repost-detection-int/pom.xml b/abstracto-application/abstracto-modules/repost-detection/repost-detection-int/pom.xml index d7e023542..f9dfa823e 100644 --- a/abstracto-application/abstracto-modules/repost-detection/repost-detection-int/pom.xml +++ b/abstracto-application/abstracto-modules/repost-detection/repost-detection-int/pom.xml @@ -9,9 +9,4 @@ repost-detection-int - - 8 - 8 - - \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/sticky-roles/pom.xml b/abstracto-application/abstracto-modules/sticky-roles/pom.xml new file mode 100644 index 000000000..5f9441af2 --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/pom.xml @@ -0,0 +1,26 @@ + + + + abstracto-modules + dev.sheldan.abstracto.modules + 1.5.25-SNAPSHOT + + 4.0.0 + + sticky-roles + pom + + sticky-roles-int + sticky-roles-impl + + + + + dev.sheldan.abstracto.core + core-int + ${project.version} + compile + + + + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/pom.xml b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/pom.xml new file mode 100644 index 000000000..98841adca --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + dev.sheldan.abstracto.modules + sticky-roles + 1.5.25-SNAPSHOT + + + sticky-roles-impl + + + + + maven-assembly-plugin + + + src/main/assembly/liquibase.xml + + + + + make-assembly + package + + single + + + + + + + + + + dev.sheldan.abstracto.modules + sticky-roles-int + ${project.version} + + + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/assembly/liquibase.xml b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/assembly/liquibase.xml new file mode 100644 index 000000000..8b4774fa0 --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/assembly/liquibase.xml @@ -0,0 +1,18 @@ + + liquibase + + zip + + false + + + . + ${project.basedir}/src/main/resources/migrations + + **/* + + + + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/command/ConfigureStickyRole.java b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/command/ConfigureStickyRole.java new file mode 100644 index 000000000..ecc905112 --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/command/ConfigureStickyRole.java @@ -0,0 +1,103 @@ +package dev.sheldan.abstracto.stickyroles.command; + +import dev.sheldan.abstracto.core.command.UtilityModuleDefinition; +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.CommandResult; +import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.interaction.InteractionService; +import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig; +import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService; +import dev.sheldan.abstracto.stickyroles.config.StickyRolesFeatureDefinition; +import dev.sheldan.abstracto.stickyroles.config.StickyRolesSlashCommandNames; +import dev.sheldan.abstracto.stickyroles.service.StickyRoleService; +import lombok.extern.slf4j.Slf4j; +import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +@Component +@Slf4j +public class ConfigureStickyRole extends AbstractConditionableCommand { + + private static final String COMMAND_NAME = "configureStickyRole"; + private static final String STICKY_PARAMETER_NAME = "sticky"; + private static final String ROLE_PARAMETER_NAME = "role"; + private static final String RESPONSE_TEMPLATE = "configureStickyRole_response"; + + @Autowired + private SlashCommandParameterService slashCommandParameterService; + + @Autowired + private InteractionService interactionService; + + @Autowired + private StickyRoleService stickyRoleService; + + @Override + public CompletableFuture executeSlash(SlashCommandInteractionEvent event) { + Boolean newState = slashCommandParameterService.getCommandOption(STICKY_PARAMETER_NAME, event, Boolean.class); + Role role = slashCommandParameterService.getCommandOption(ROLE_PARAMETER_NAME, event, Role.class); + stickyRoleService.setRoleStickiness(role, newState); + return interactionService.replyEmbed(RESPONSE_TEMPLATE, event) + .thenApply(interactionHook -> CommandResult.fromSuccess()); + } + + @Override + public CommandConfiguration getConfiguration() { + + Parameter roleParameter = Parameter + .builder() + .name(ROLE_PARAMETER_NAME) + .type(Role.class) + .optional(false) + .templated(true) + .build(); + + Parameter stateParameter = Parameter + .builder() + .name(STICKY_PARAMETER_NAME) + .type(Boolean.class) + .optional(false) + .templated(true) + .build(); + + List parameters = Arrays.asList(roleParameter, stateParameter); + + SlashCommandConfig slashCommandConfig = SlashCommandConfig + .builder() + .enabled(true) + .rootCommandName(StickyRolesSlashCommandNames.STICKY_ROLES) + .commandName("configure") + .build(); + + HelpInfo helpInfo = HelpInfo + .builder() + .templated(true) + .build(); + return CommandConfiguration.builder() + .name(COMMAND_NAME) + .module(UtilityModuleDefinition.UTILITY) + .parameters(parameters) + .templated(true) + .slashCommandConfig(slashCommandConfig) + .async(true) + .slashCommandOnly(true) + .supportsEmbedException(true) + .help(helpInfo) + .causesReaction(true) + .build(); + } + + @Override + public FeatureDefinition getFeature() { + return StickyRolesFeatureDefinition.STICKY_ROLES; + } +} diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/command/ShowStickyRoles.java b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/command/ShowStickyRoles.java new file mode 100644 index 000000000..0ffaf6730 --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/command/ShowStickyRoles.java @@ -0,0 +1,94 @@ +package dev.sheldan.abstracto.stickyroles.command; + +import dev.sheldan.abstracto.core.command.UtilityModuleDefinition; +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.execution.CommandResult; +import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.interaction.InteractionService; +import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig; +import dev.sheldan.abstracto.core.models.template.display.RoleDisplay; +import dev.sheldan.abstracto.stickyroles.config.StickyRolesFeatureDefinition; +import dev.sheldan.abstracto.stickyroles.config.StickyRolesSlashCommandNames; +import dev.sheldan.abstracto.stickyroles.model.database.StickyRole; +import dev.sheldan.abstracto.stickyroles.model.template.StickyRoleDisplayModel; +import dev.sheldan.abstracto.stickyroles.model.template.StickyRolesDisplayModel; +import dev.sheldan.abstracto.stickyroles.service.StickyRoleService; +import lombok.extern.slf4j.Slf4j; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +@Component +@Slf4j +public class ShowStickyRoles extends AbstractConditionableCommand { + + private static final String COMMAND_NAME = "showStickyRoles"; + private static final String RESPONSE_TEMPLATE = "showStickyRoles_response"; + + @Autowired + private StickyRoleService stickyRoleService; + + @Autowired + private InteractionService interactionService; + + @Override + public CompletableFuture executeSlash(SlashCommandInteractionEvent event) { + List stickyRoles = stickyRoleService.getStickyRolesForServer(event.getGuild()); + log.info("Showing sticky role config for {} roles in server {}.", stickyRoles.size(), event.getGuild().getIdLong()); + StickyRolesDisplayModel model = getModel(stickyRoles); + return interactionService.replyEmbed(RESPONSE_TEMPLATE, model, event) + .thenApply(interactionHook -> CommandResult.fromSuccess()); + } + + private StickyRolesDisplayModel getModel(List stickyRoles) { + List displayRoles = stickyRoles + .stream() + .map(stickyRole -> StickyRoleDisplayModel + .builder() + .roleDisplay(RoleDisplay.fromARole(stickyRole.getRole())) + .sticky(stickyRole.getSticky()) + .build()) + .toList(); + return StickyRolesDisplayModel + .builder() + .roles(displayRoles) + .build(); + } + + @Override + public CommandConfiguration getConfiguration() { + + SlashCommandConfig slashCommandConfig = SlashCommandConfig + .builder() + .enabled(true) + .rootCommandName(StickyRolesSlashCommandNames.STICKY_ROLES) + .commandName("show") + .build(); + + HelpInfo helpInfo = HelpInfo + .builder() + .templated(true) + .build(); + return CommandConfiguration.builder() + .name(COMMAND_NAME) + .module(UtilityModuleDefinition.UTILITY) + .templated(true) + .slashCommandConfig(slashCommandConfig) + .async(true) + .slashCommandOnly(true) + .supportsEmbedException(true) + .help(helpInfo) + .causesReaction(true) + .build(); + } + + @Override + public FeatureDefinition getFeature() { + return StickyRolesFeatureDefinition.STICKY_ROLES; + } +} diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/command/ToggleStickiness.java b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/command/ToggleStickiness.java new file mode 100644 index 000000000..aa0076fe6 --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/command/ToggleStickiness.java @@ -0,0 +1,103 @@ +package dev.sheldan.abstracto.stickyroles.command; + +import dev.sheldan.abstracto.core.command.UtilityModuleDefinition; +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.CommandResult; +import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.config.FeatureMode; +import dev.sheldan.abstracto.core.interaction.InteractionService; +import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig; +import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService; +import dev.sheldan.abstracto.stickyroles.config.StickyRoleFeatureMode; +import dev.sheldan.abstracto.stickyroles.config.StickyRolesFeatureDefinition; +import dev.sheldan.abstracto.stickyroles.config.StickyRolesSlashCommandNames; +import dev.sheldan.abstracto.stickyroles.service.StickyRoleService; +import lombok.extern.slf4j.Slf4j; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +@Component +@Slf4j +public class ToggleStickiness extends AbstractConditionableCommand { + + private static final String COMMAND_NAME = "toggleStickiness"; + private static final String RESPONSE_TEMPLATE = "toggleStickiness_response"; + + private static final String STICKY_PARAMETER_NAME = "sticky"; + + @Autowired + private StickyRoleService stickyRoleService; + + @Autowired + private InteractionService interactionService; + + @Autowired + private SlashCommandParameterService slashCommandParameterService; + + + @Override + public CompletableFuture executeSlash(SlashCommandInteractionEvent event) { + Member targetMember = event.getMember(); + Boolean newState = slashCommandParameterService.getCommandOption(STICKY_PARAMETER_NAME, event, Boolean.class); + stickyRoleService.setStickiness(targetMember, newState); + return interactionService.replyEmbed(RESPONSE_TEMPLATE, event) + .thenApply(interactionHook -> CommandResult.fromSuccess()); + } + + @Override + public CommandConfiguration getConfiguration() { + + Parameter stateParameter = Parameter + .builder() + .name(STICKY_PARAMETER_NAME) + .type(Boolean.class) + .optional(false) + .templated(true) + .build(); + + SlashCommandConfig slashCommandConfig = SlashCommandConfig + .builder() + .enabled(true) + .rootCommandName(StickyRolesSlashCommandNames.STICKY_ROLES_PUBLIC) + .commandName("toggle") + .build(); + + List parameters = Arrays.asList(stateParameter); + + HelpInfo helpInfo = HelpInfo + .builder() + .templated(true) + .build(); + return CommandConfiguration.builder() + .name(COMMAND_NAME) + .module(UtilityModuleDefinition.UTILITY) + .templated(true) + .slashCommandConfig(slashCommandConfig) + .async(true) + .parameters(parameters) + .slashCommandOnly(true) + .supportsEmbedException(true) + .help(helpInfo) + .causesReaction(true) + .build(); + } + + @Override + public FeatureDefinition getFeature() { + return StickyRolesFeatureDefinition.STICKY_ROLES; + } + + @Override + public List getFeatureModeLimitations() { + return Arrays.asList(StickyRoleFeatureMode.ALLOW_SELF_MANAGEMENT); + } +} diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/command/ToggleStickinessManagement.java b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/command/ToggleStickinessManagement.java new file mode 100644 index 000000000..fee6c6b31 --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/command/ToggleStickinessManagement.java @@ -0,0 +1,129 @@ +package dev.sheldan.abstracto.stickyroles.command; + +import dev.sheldan.abstracto.core.command.UtilityModuleDefinition; +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.CommandResult; +import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.interaction.InteractionService; +import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig; +import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService; +import dev.sheldan.abstracto.core.service.UserService; +import dev.sheldan.abstracto.stickyroles.config.StickyRolesFeatureDefinition; +import dev.sheldan.abstracto.stickyroles.config.StickyRolesSlashCommandNames; +import dev.sheldan.abstracto.stickyroles.service.StickyRoleService; +import lombok.extern.slf4j.Slf4j; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +@Component +@Slf4j +public class ToggleStickinessManagement extends AbstractConditionableCommand { + + private static final String COMMAND_NAME = "toggleStickinessManagement"; + private static final String RESPONSE_TEMPLATE = "toggleStickinessManagement_response"; + + private static final String MEMBER_PARAMETER_NAME = "member"; + private static final String STICKY_PARAMETER_NAME = "sticky"; + + @Autowired + private SlashCommandParameterService slashCommandParameterService; + + @Autowired + private InteractionService interactionService; + + @Autowired + private StickyRoleService stickyRoleService; + + @Autowired + private UserService userService; + + @Autowired + private ToggleStickinessManagement self; + + @Override + public CompletableFuture executeSlash(SlashCommandInteractionEvent event) { + Boolean newState = slashCommandParameterService.getCommandOption(STICKY_PARAMETER_NAME, event, Boolean.class); + if(slashCommandParameterService.hasCommandOptionWithFullType(MEMBER_PARAMETER_NAME, event, OptionType.USER)) { + Member targetMember = slashCommandParameterService.getCommandOption(MEMBER_PARAMETER_NAME, event, User.class, Member.class); + stickyRoleService.setStickiness(targetMember, newState); + return interactionService.replyEmbed(RESPONSE_TEMPLATE, event) + .thenApply(interactionHook -> CommandResult.fromSuccess()); + } else { + String userIdStr = slashCommandParameterService.getCommandOption(MEMBER_PARAMETER_NAME, event, User.class, String.class); + Long userId = Long.parseLong(userIdStr); + return userService.retrieveUserForId(userId).thenCompose(user -> { + self.callService(event, user, newState); + return interactionService.replyEmbed(RESPONSE_TEMPLATE, event); + }).thenApply(interactionHook -> CommandResult.fromSuccess()); + } + } + + + @Transactional + public void callService(SlashCommandInteractionEvent event, User user, Boolean newState) { + stickyRoleService.setStickiness(user, event.getGuild(), newState); + } + + @Override + public CommandConfiguration getConfiguration() { + Parameter memberParameter = Parameter + .builder() + .name(MEMBER_PARAMETER_NAME) + .type(User.class) + .optional(false) + .templated(true) + .build(); + + + Parameter stateParameter = Parameter + .builder() + .name(STICKY_PARAMETER_NAME) + .type(Boolean.class) + .optional(false) + .templated(true) + .build(); + + SlashCommandConfig slashCommandConfig = SlashCommandConfig + .builder() + .enabled(true) + .rootCommandName(StickyRolesSlashCommandNames.STICKY_ROLES) + .commandName("manage") + .build(); + + List parameters = Arrays.asList(memberParameter, stateParameter); + + HelpInfo helpInfo = HelpInfo + .builder() + .templated(true) + .build(); + return CommandConfiguration.builder() + .name(COMMAND_NAME) + .module(UtilityModuleDefinition.UTILITY) + .templated(true) + .parameters(parameters) + .slashCommandConfig(slashCommandConfig) + .async(true) + .slashCommandOnly(true) + .supportsEmbedException(true) + .help(helpInfo) + .causesReaction(true) + .build(); + } + + @Override + public FeatureDefinition getFeature() { + return StickyRolesFeatureDefinition.STICKY_ROLES; + } +} diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/config/StickyRolesConfig.java b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/config/StickyRolesConfig.java new file mode 100644 index 000000000..f6b960c53 --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/config/StickyRolesConfig.java @@ -0,0 +1,9 @@ +package dev.sheldan.abstracto.stickyroles.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +@Configuration +@PropertySource("classpath:stickyRoles-config.properties") +public class StickyRolesConfig { +} diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/listener/StickyRolesJoinListener.java b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/listener/StickyRolesJoinListener.java new file mode 100644 index 000000000..a1e4355af --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/listener/StickyRolesJoinListener.java @@ -0,0 +1,28 @@ +package dev.sheldan.abstracto.stickyroles.listener; + +import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.listener.DefaultListenerResult; +import dev.sheldan.abstracto.core.listener.async.jda.AsyncUpdatePendingListener; +import dev.sheldan.abstracto.core.models.listener.MemberUpdatePendingModel; +import dev.sheldan.abstracto.stickyroles.config.StickyRolesFeatureDefinition; +import dev.sheldan.abstracto.stickyroles.service.StickyRoleService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class StickyRolesJoinListener implements AsyncUpdatePendingListener { + + @Autowired + private StickyRoleService stickyRoleService; + + @Override + public DefaultListenerResult execute(MemberUpdatePendingModel model) { + stickyRoleService.handleJoin(model.getMember()); + return DefaultListenerResult.PROCESSED; + } + + @Override + public FeatureDefinition getFeature() { + return StickyRolesFeatureDefinition.STICKY_ROLES; + } +} diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/listener/StickyRolesLeaveListener.java b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/listener/StickyRolesLeaveListener.java new file mode 100644 index 000000000..760088644 --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/listener/StickyRolesLeaveListener.java @@ -0,0 +1,36 @@ +package dev.sheldan.abstracto.stickyroles.listener; + +import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.listener.DefaultListenerResult; +import dev.sheldan.abstracto.core.listener.async.jda.AsyncLeaveListener; +import dev.sheldan.abstracto.core.models.listener.MemberLeaveModel; +import dev.sheldan.abstracto.stickyroles.config.StickyRolesFeatureDefinition; +import dev.sheldan.abstracto.stickyroles.service.StickyRoleService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class StickyRolesLeaveListener implements AsyncLeaveListener { + + @Autowired + private StickyRoleService stickyRoleService; + + @Override + public DefaultListenerResult execute(MemberLeaveModel model) { + if(model.getMember() != null) { + stickyRoleService.handleLeave(model.getMember()); + return DefaultListenerResult.PROCESSED; + } else { + log.warn("Member object was not found for storing sticky roles for user {} in server {}.", model.getLeavingUser().getUserId(), model.getServerId()); + } + + return DefaultListenerResult.IGNORED; + } + + @Override + public FeatureDefinition getFeature() { + return StickyRolesFeatureDefinition.STICKY_ROLES; + } +} diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/repository/StickyRoleRepository.java b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/repository/StickyRoleRepository.java new file mode 100644 index 000000000..1d9cf55df --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/repository/StickyRoleRepository.java @@ -0,0 +1,13 @@ +package dev.sheldan.abstracto.stickyroles.repository; + +import dev.sheldan.abstracto.core.models.database.AServer; +import dev.sheldan.abstracto.stickyroles.model.database.StickyRole; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface StickyRoleRepository extends JpaRepository { + List findStickyRoleByServer(AServer server); +} diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/repository/StickyRoleUserRepository.java b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/repository/StickyRoleUserRepository.java new file mode 100644 index 000000000..5b9179606 --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/repository/StickyRoleUserRepository.java @@ -0,0 +1,9 @@ +package dev.sheldan.abstracto.stickyroles.repository; + +import dev.sheldan.abstracto.stickyroles.model.database.StickyRoleUser; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface StickyRoleUserRepository extends JpaRepository { +} diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/service/StickyRoleServiceBean.java b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/service/StickyRoleServiceBean.java new file mode 100644 index 000000000..d5f6ea70a --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/service/StickyRoleServiceBean.java @@ -0,0 +1,132 @@ +package dev.sheldan.abstracto.stickyroles.service; + +import dev.sheldan.abstracto.core.models.database.AServer; +import dev.sheldan.abstracto.core.service.RoleService; +import dev.sheldan.abstracto.core.service.management.ServerManagementService; +import dev.sheldan.abstracto.stickyroles.model.database.StickyRole; +import dev.sheldan.abstracto.stickyroles.model.database.StickyRoleUser; +import dev.sheldan.abstracto.stickyroles.service.management.StickyRoleManagementService; +import dev.sheldan.abstracto.stickyroles.service.management.StickyRoleUserManagementService; +import lombok.extern.slf4j.Slf4j; +import net.dv8tion.jda.api.entities.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@Component +@Slf4j +public class StickyRoleServiceBean implements StickyRoleService { + + @Autowired + private StickyRoleManagementService stickyRoleManagementService; + + @Autowired + private StickyRoleUserManagementService stickyRoleUserManagementService; + + @Autowired + private RoleService roleService; + + @Autowired + private ServerManagementService serverManagementService; + + @Override + public void setRoleStickiness(Role role, Boolean stickiness) { + StickyRole stickyRole = stickyRoleManagementService.getOrCreateStickyRole(role); + log.info("Setting stickiness of role {} in server {} to {}", role.getIdLong(), role.getGuild().getIdLong(), stickiness); + stickyRole.setSticky(stickiness); + } + + @Override + public void setStickiness(Member member, Boolean stickiness) { + StickyRoleUser user = stickyRoleUserManagementService.getOrCreateStickyRoleUser(member); + user.setSticky(stickiness); + log.info("Setting stickiness of member {} in server {} to {}", member.getIdLong(), member.getGuild().getIdLong(), stickiness); + if(!stickiness) { + clearStickyRolesForUser(user); + } + } + + @Override + public void setStickiness(User user, Guild guild, Boolean stickiness) { + StickyRoleUser stickyUser = stickyRoleUserManagementService.getOrCreateStickyRoleUser(guild.getIdLong(), user.getIdLong()); + stickyUser.setSticky(stickiness); + log.info("Setting stickiness of member {} in server {} to {}", user.getIdLong(), guild.getIdLong(), stickiness); + if(!stickiness) { + clearStickyRolesForUser(stickyUser); + } + } + + @Override + public void handleLeave(Member member) { + log.info("Handling user leave of member {} from server {} regarding sticky roles.", member.getIdLong(), member.getGuild().getIdLong()); + StickyRoleUser user = stickyRoleUserManagementService.getOrCreateStickyRoleUser(member); + clearStickyRolesForUser(user); + if(user.getSticky()) { + List memberRoles = member.getRoles(); + log.info("Member was marked as sticky - storing {} roles.", memberRoles.size()); + Set memberRoleIds = memberRoles + .stream() + .map(ISnowflake::getIdLong) + .collect(Collectors.toSet()); + List existingStickyRolesOfUser = stickyRoleManagementService.getRoles(new ArrayList<>(memberRoleIds)); + Set existingStickyRoleIdsOfUser = existingStickyRolesOfUser + .stream() + .map(StickyRole::getId) + .collect(Collectors.toSet()); + memberRoleIds.removeAll(existingStickyRoleIdsOfUser); + List newStickyRoles = memberRoleIds + .stream() + .map(rid -> stickyRoleManagementService.createStickyRole(rid)) + .toList(); + log.debug("Creating {} new roles.", newStickyRoles.size()); + List stickyRolesOfUser = new ArrayList<>(existingStickyRolesOfUser); + stickyRolesOfUser.addAll(newStickyRoles); + stickyRolesOfUser.forEach(stickyRole -> { + stickyRole.getUsers().add(user); + }); + user.setRoles(stickyRolesOfUser); + } + } + + private static void clearStickyRolesForUser(StickyRoleUser user) { + log.debug("Clearing sticky roles for user {}", user.getId()); + user.getRoles().forEach(stickyRole -> { + stickyRole.getUsers().remove(user); + }); + user.getRoles().clear(); + } + + @Override + public void handleJoin(Member member) { + log.info("Handling server join for member {} in server {} regarding sticky roles.", member.getIdLong(), member.getGuild().getIdLong()); + StickyRoleUser user = stickyRoleUserManagementService.getOrCreateStickyRoleUser(member); + if(user.getSticky()) { + List rolesToAdd = user + .getRoles() + .stream() + .filter(StickyRole::getSticky) + .filter(r -> Boolean.FALSE.equals(r.getRole().getDeleted())) + .map(StickyRole::getId) + .toList(); + log.info("Adding {} roles to user {} in server {}", rolesToAdd.size(), member.getIdLong(), member.getGuild().getIdLong()); + roleService.updateRolesIds(member, new ArrayList<>(), rolesToAdd).thenAccept(unused -> { + log.info("Successfully added {} roles to user {} in server {}", rolesToAdd.size(), member.getIdLong(), member.getGuild().getIdLong()); + }).exceptionally(throwable -> { + log.warn("Failed to add {} roles to user {} in server {}", rolesToAdd.size(), member.getIdLong(), member.getGuild().getIdLong(), throwable); + return null; + }); + } else { + log.info("Not re-applying roles for member {} in server {} as they opted out.", member.getIdLong(), member.getGuild().getIdLong()); + } + } + + @Override + public List getStickyRolesForServer(Guild guild) { + AServer server = serverManagementService.loadServer(guild); + return stickyRoleManagementService.getStickyRolesForServer(server); + } +} diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/service/management/StickyRoleManagementServieBean.java b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/service/management/StickyRoleManagementServieBean.java new file mode 100644 index 000000000..1815ef754 --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/service/management/StickyRoleManagementServieBean.java @@ -0,0 +1,60 @@ +package dev.sheldan.abstracto.stickyroles.service.management; + +import dev.sheldan.abstracto.core.models.database.ARole; +import dev.sheldan.abstracto.core.models.database.AServer; +import dev.sheldan.abstracto.core.service.management.RoleManagementService; +import dev.sheldan.abstracto.stickyroles.model.database.StickyRole; +import dev.sheldan.abstracto.stickyroles.repository.StickyRoleRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Optional; + +@Component +public class StickyRoleManagementServieBean implements StickyRoleManagementService { + + @Autowired + private StickyRoleRepository stickyRoleRepository; + + @Autowired + private RoleManagementService roleManagementService; + + + @Override + public StickyRole getOrCreateStickyRole(Long roleId) { + Optional existingRole = stickyRoleRepository.findById(roleId); + return existingRole.orElseGet(() -> createStickyRole(roleId)); + } + + @Override + public StickyRole createStickyRole(Long roleId) { + ARole aRole = roleManagementService.findRole(roleId); + StickyRole role = StickyRole + .builder() + .id(roleId) + .sticky(StickyRoleManagementService.DEFAULT_STICKINESS) + .role(aRole) + .server(aRole.getServer()) + .build(); + return stickyRoleRepository.save(role); + } + + @Override + public List createStickyRoles(List roleIds) { + return roleIds + .stream() + .map(this::createStickyRole) + .toList(); + } + + @Override + public List getRoles(List roleIds) { + return stickyRoleRepository.findAllById(roleIds); + } + + @Override + public List getStickyRolesForServer(AServer server) { + return stickyRoleRepository.findStickyRoleByServer(server); + } +} diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/service/management/StickyRoleUserManagementServiceBean.java b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/service/management/StickyRoleUserManagementServiceBean.java new file mode 100644 index 000000000..da9e9ca92 --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/java/dev/sheldan/abstracto/stickyroles/service/management/StickyRoleUserManagementServiceBean.java @@ -0,0 +1,53 @@ +package dev.sheldan.abstracto.stickyroles.service.management; + +import dev.sheldan.abstracto.core.models.ServerUser; +import dev.sheldan.abstracto.core.models.database.AUserInAServer; +import dev.sheldan.abstracto.core.service.management.UserInServerManagementService; +import dev.sheldan.abstracto.stickyroles.model.database.StickyRoleUser; +import dev.sheldan.abstracto.stickyroles.repository.StickyRoleUserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class StickyRoleUserManagementServiceBean implements StickyRoleUserManagementService { + + @Autowired + private StickyRoleUserRepository repository; + + @Autowired + private UserInServerManagementService userInServerManagementService; + + @Override + public StickyRoleUser getOrCreateStickyRoleUser(Long serverId, Long userId) { + ServerUser serverUser = ServerUser + .builder() + .userId(userId) + .serverId(serverId) + .build(); + AUserInAServer userInAServer = userInServerManagementService.loadOrCreateUser(serverUser); + return repository.findById(userInAServer.getUserInServerId()).orElseGet(() -> createStickyroleUser(userInAServer)); + } + + @Override + public StickyRoleUser createStickyroleUser(Long serverId, Long userId) { + ServerUser serverUser = ServerUser + .builder() + .userId(userId) + .serverId(serverId) + .build(); + AUserInAServer userInAServer = userInServerManagementService.loadOrCreateUser(serverUser); + return createStickyroleUser(userInAServer); + } + + @Override + public StickyRoleUser createStickyroleUser(AUserInAServer userInAServer) { + StickyRoleUser stickyRoleUser = StickyRoleUser + .builder() + .user(userInAServer) + .server(userInAServer.getServerReference()) + .id(userInAServer.getUserInServerId()) + .sticky(true) + .build(); + return repository.save(stickyRoleUser); + } +} diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/resources/migrations/1.5.25/collection.xml b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/resources/migrations/1.5.25/collection.xml new file mode 100644 index 000000000..81ce369db --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/resources/migrations/1.5.25/collection.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/resources/migrations/1.5.25/seedData/command.xml b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/resources/migrations/1.5.25/seedData/command.xml new file mode 100644 index 000000000..e5487bed2 --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/resources/migrations/1.5.25/seedData/command.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/resources/migrations/1.5.25/seedData/data.xml b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/resources/migrations/1.5.25/seedData/data.xml new file mode 100644 index 000000000..2bf581917 --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/resources/migrations/1.5.25/seedData/data.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/resources/migrations/1.5.25/seedData/feature.xml b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/resources/migrations/1.5.25/seedData/feature.xml new file mode 100644 index 000000000..b16cf9fe7 --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/resources/migrations/1.5.25/seedData/feature.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/resources/migrations/1.5.25/tables/sticky_role.xml b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/resources/migrations/1.5.25/tables/sticky_role.xml new file mode 100644 index 000000000..85365fac5 --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/resources/migrations/1.5.25/tables/sticky_role.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + DROP TRIGGER IF EXISTS sticky_role_update_trigger ON sticky_role; + CREATE TRIGGER sticky_role_update_trigger BEFORE UPDATE ON sticky_role FOR EACH ROW EXECUTE PROCEDURE update_trigger_procedure(); + + + DROP TRIGGER IF EXISTS sticky_role_insert_trigger ON sticky_role; + CREATE TRIGGER sticky_role_insert_trigger BEFORE INSERT ON sticky_role FOR EACH ROW EXECUTE PROCEDURE insert_trigger_procedure(); + + + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/resources/migrations/1.5.25/tables/sticky_role_user.xml b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/resources/migrations/1.5.25/tables/sticky_role_user.xml new file mode 100644 index 000000000..457cd3f55 --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/resources/migrations/1.5.25/tables/sticky_role_user.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + DROP TRIGGER IF EXISTS sticky_role_user_update_trigger ON sticky_role_user; + CREATE TRIGGER sticky_role_user_update_trigger BEFORE UPDATE ON sticky_role_user FOR EACH ROW EXECUTE PROCEDURE update_trigger_procedure(); + + + DROP TRIGGER IF EXISTS sticky_role_user_insert_trigger ON sticky_role_user; + CREATE TRIGGER sticky_role_user_insert_trigger BEFORE INSERT ON sticky_role_user FOR EACH ROW EXECUTE PROCEDURE insert_trigger_procedure(); + + + + + + + + + + + + + + + + + + + + DROP TRIGGER IF EXISTS sticky_role_user_mapping_update_trigger ON sticky_role_user_mapping; + CREATE TRIGGER sticky_role_user_mapping_update_trigger BEFORE UPDATE ON sticky_role_user_mapping FOR EACH ROW EXECUTE PROCEDURE update_trigger_procedure(); + + + DROP TRIGGER IF EXISTS sticky_role_user_mapping_insert_trigger ON sticky_role_user_mapping; + CREATE TRIGGER sticky_role_user_mapping_insert_trigger BEFORE INSERT ON sticky_role_user_mapping FOR EACH ROW EXECUTE PROCEDURE insert_trigger_procedure(); + + + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/resources/migrations/1.5.25/tables/tables.xml b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/resources/migrations/1.5.25/tables/tables.xml new file mode 100644 index 000000000..d4bf3635b --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/resources/migrations/1.5.25/tables/tables.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/resources/migrations/stickyRoles-changeLog.xml b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/resources/migrations/stickyRoles-changeLog.xml new file mode 100644 index 000000000..392ad33ad --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/resources/migrations/stickyRoles-changeLog.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/resources/stickyRoles-config.properties b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/resources/stickyRoles-config.properties new file mode 100644 index 000000000..ace614423 --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-impl/src/main/resources/stickyRoles-config.properties @@ -0,0 +1,6 @@ +abstracto.featureFlags.stickyRoles.featureName=stickyRoles +abstracto.featureFlags.stickyRoles.enabled=false + +abstracto.featureModes.allowSelfManagement.featureName=stickyRoles +abstracto.featureModes.allowSelfManagement.mode=allowSelfManagement +abstracto.featureModes.allowSelfManagement.enabled=false \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/pom.xml b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/pom.xml new file mode 100644 index 000000000..7799a888f --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/pom.xml @@ -0,0 +1,15 @@ + + + 4.0.0 + + dev.sheldan.abstracto.modules + sticky-roles + 1.5.25-SNAPSHOT + + + sticky-roles-int + + + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/config/StickyRoleFeatureMode.java b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/config/StickyRoleFeatureMode.java new file mode 100644 index 000000000..443957a8b --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/config/StickyRoleFeatureMode.java @@ -0,0 +1,15 @@ +package dev.sheldan.abstracto.stickyroles.config; + +import dev.sheldan.abstracto.core.config.FeatureMode; +import lombok.Getter; + +@Getter +public enum StickyRoleFeatureMode implements FeatureMode { + ALLOW_SELF_MANAGEMENT("allowSelfManagement"); + + private final String key; + + StickyRoleFeatureMode(String key) { + this.key = key; + } +} diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/config/StickyRolesFeatureConfig.java b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/config/StickyRolesFeatureConfig.java new file mode 100644 index 000000000..7f3fd8177 --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/config/StickyRolesFeatureConfig.java @@ -0,0 +1,23 @@ +package dev.sheldan.abstracto.stickyroles.config; + +import dev.sheldan.abstracto.core.config.FeatureConfig; +import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.core.config.FeatureMode; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.List; + +@Component +public class StickyRolesFeatureConfig implements FeatureConfig { + + @Override + public FeatureDefinition getFeature() { + return StickyRolesFeatureDefinition.STICKY_ROLES; + } + + @Override + public List getAvailableModes() { + return Arrays.asList(StickyRoleFeatureMode.ALLOW_SELF_MANAGEMENT); + } +} diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/config/StickyRolesFeatureDefinition.java b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/config/StickyRolesFeatureDefinition.java new file mode 100644 index 000000000..d864ea1a4 --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/config/StickyRolesFeatureDefinition.java @@ -0,0 +1,15 @@ +package dev.sheldan.abstracto.stickyroles.config; + +import dev.sheldan.abstracto.core.config.FeatureDefinition; +import lombok.Getter; + +@Getter +public enum StickyRolesFeatureDefinition implements FeatureDefinition { + STICKY_ROLES("stickyRoles"); + + private String key; + + StickyRolesFeatureDefinition(String key) { + this.key = key; + } +} diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/config/StickyRolesSlashCommandNames.java b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/config/StickyRolesSlashCommandNames.java new file mode 100644 index 000000000..3ea1e149a --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/config/StickyRolesSlashCommandNames.java @@ -0,0 +1,6 @@ +package dev.sheldan.abstracto.stickyroles.config; + +public class StickyRolesSlashCommandNames { + public static final String STICKY_ROLES = "stickyroles"; + public static final String STICKY_ROLES_PUBLIC = "stickyrolespublic"; +} diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/model/database/StickyRole.java b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/model/database/StickyRole.java new file mode 100644 index 000000000..2b052ca43 --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/model/database/StickyRole.java @@ -0,0 +1,52 @@ +package dev.sheldan.abstracto.stickyroles.model.database; + +import dev.sheldan.abstracto.core.models.database.ARole; +import dev.sheldan.abstracto.core.models.database.AServer; +import jakarta.persistence.*; +import lombok.*; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Table(name="sticky_role") +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +@EqualsAndHashCode +public class StickyRole { + @Id + @Column(name = "id", nullable = false) + private Long id; + + /** + * Reference to the actual {@link ARole} being maintained + */ + @OneToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE}) + @PrimaryKeyJoinColumn + private ARole role; + + @Column(name = "sticky", nullable = false) + private Boolean sticky; + + @ManyToMany + @JoinTable( + name = "sticky_role_user_mapping", + joinColumns = @JoinColumn(name = "role_id"), + inverseJoinColumns = @JoinColumn(name = "user_id")) + @Builder.Default + private List users = new ArrayList<>(); + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "server_id", nullable = false) + private AServer server; + + @Column(name = "created", nullable = false, insertable = false, updatable = false) + private Instant created; + + @Column(name = "updated", insertable = false, updatable = false) + private Instant updated; +} diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/model/database/StickyRoleUser.java b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/model/database/StickyRoleUser.java new file mode 100644 index 000000000..3254f0c11 --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/model/database/StickyRoleUser.java @@ -0,0 +1,51 @@ +package dev.sheldan.abstracto.stickyroles.model.database; + +import dev.sheldan.abstracto.core.models.database.AServer; +import dev.sheldan.abstracto.core.models.database.AUserInAServer; +import jakarta.persistence.*; +import lombok.*; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; + +@Builder +@Entity +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "sticky_role_user") +@Getter +@Setter +@EqualsAndHashCode +public class StickyRoleUser { + /** + * The ID of the {@link AUserInAServer user} which is represented by this object + */ + @Id + @Column(name = "id", nullable = false) + private Long id; + + /** + * The {@link AUserInAServer user} which is represented by this object + */ + @OneToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE}) + @PrimaryKeyJoinColumn + private AUserInAServer user; + + @Column(name = "sticky") + private Boolean sticky; + + @ManyToMany(mappedBy = "users") + @Builder.Default + private List roles = new ArrayList<>(); + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "server_id", nullable = false) + private AServer server; + + @Column(name = "created", nullable = false, insertable = false, updatable = false) + private Instant created; + + @Column(name = "updated", insertable = false, updatable = false) + private Instant updated; +} diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/model/template/StickyRoleDisplayModel.java b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/model/template/StickyRoleDisplayModel.java new file mode 100644 index 000000000..006d9dc55 --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/model/template/StickyRoleDisplayModel.java @@ -0,0 +1,12 @@ +package dev.sheldan.abstracto.stickyroles.model.template; + +import dev.sheldan.abstracto.core.models.template.display.RoleDisplay; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class StickyRoleDisplayModel { + private RoleDisplay roleDisplay; + private Boolean sticky; +} diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/model/template/StickyRolesDisplayModel.java b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/model/template/StickyRolesDisplayModel.java new file mode 100644 index 000000000..2f8f29494 --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/model/template/StickyRolesDisplayModel.java @@ -0,0 +1,12 @@ +package dev.sheldan.abstracto.stickyroles.model.template; + +import lombok.Builder; +import lombok.Getter; + +import java.util.List; + +@Getter +@Builder +public class StickyRolesDisplayModel { + private List roles; +} diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/service/StickyRoleService.java b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/service/StickyRoleService.java new file mode 100644 index 000000000..965639e5a --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/service/StickyRoleService.java @@ -0,0 +1,28 @@ +package dev.sheldan.abstracto.stickyroles.service; + +import dev.sheldan.abstracto.stickyroles.model.database.StickyRole; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.entities.User; + +import java.util.List; + +public interface StickyRoleService { + default void ignoreRoleFromStickyRoles(Role role) { + setRoleStickiness(role, false); + } + default void addRoleToStickyRoles(Role role) { + setRoleStickiness(role, true); + } + void setRoleStickiness(Role role, Boolean stickiness); + + void setStickiness(Member member, Boolean newState); + void setStickiness(User user, Guild guild, Boolean newState); + + void handleLeave(Member member); + + void handleJoin(Member member); + + List getStickyRolesForServer(Guild guild); +} diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/service/management/StickyRoleManagementService.java b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/service/management/StickyRoleManagementService.java new file mode 100644 index 000000000..f9da7590f --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/service/management/StickyRoleManagementService.java @@ -0,0 +1,22 @@ +package dev.sheldan.abstracto.stickyroles.service.management; + +import dev.sheldan.abstracto.core.models.database.AServer; +import dev.sheldan.abstracto.stickyroles.model.database.StickyRole; +import net.dv8tion.jda.api.entities.Role; + +import java.util.List; + +public interface StickyRoleManagementService { + default StickyRole getOrCreateStickyRole(Role role) { + return getOrCreateStickyRole(role.getIdLong()); + } + StickyRole getOrCreateStickyRole(Long roleId); + + StickyRole createStickyRole(Long roleId); + List createStickyRoles(List roleIds); + List getRoles(List roleIds); + + List getStickyRolesForServer(AServer server); + + Boolean DEFAULT_STICKINESS = true; +} diff --git a/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/service/management/StickyRoleUserManagementService.java b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/service/management/StickyRoleUserManagementService.java new file mode 100644 index 000000000..d8dd023c0 --- /dev/null +++ b/abstracto-application/abstracto-modules/sticky-roles/sticky-roles-int/src/main/java/dev/sheldan/abstracto/stickyroles/service/management/StickyRoleUserManagementService.java @@ -0,0 +1,14 @@ +package dev.sheldan.abstracto.stickyroles.service.management; + +import dev.sheldan.abstracto.core.models.database.AUserInAServer; +import dev.sheldan.abstracto.stickyroles.model.database.StickyRoleUser; +import net.dv8tion.jda.api.entities.Member; + +public interface StickyRoleUserManagementService { + default StickyRoleUser getOrCreateStickyRoleUser(Member member) { + return getOrCreateStickyRoleUser(member.getGuild().getIdLong(), member.getIdLong()); + } + StickyRoleUser getOrCreateStickyRoleUser(Long serverId, Long userId); + StickyRoleUser createStickyroleUser(Long serverId, Long userId); + StickyRoleUser createStickyroleUser(AUserInAServer userInAServer); +} diff --git a/abstracto-application/bundle/pom.xml b/abstracto-application/bundle/pom.xml index 4ff3412e0..e39ac4fa1 100644 --- a/abstracto-application/bundle/pom.xml +++ b/abstracto-application/bundle/pom.xml @@ -30,6 +30,12 @@ ${project.version} + + dev.sheldan.abstracto.modules + sticky-roles-impl + ${project.version} + + dev.sheldan.abstracto.modules moderation-int diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/command/service/CommandManager.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/command/service/CommandManager.java index cc4fa4fca..dd12148af 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/command/service/CommandManager.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/command/service/CommandManager.java @@ -24,7 +24,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.function.Predicate; -import java.util.stream.Collectors; @Service public class CommandManager implements CommandRegistry { @@ -54,7 +53,7 @@ public class CommandManager implements CommandRegistry { @Override public Optional findCommandByParameters(String name, UnParsedCommandParameter unParsedCommandParameter, Long serverId) { Optional commandOptional = commands.stream().filter(getCommandByNameAndParameterPredicate(name, unParsedCommandParameter, serverId)).findFirst(); - if(!commandOptional.isPresent()) { + if(commandOptional.isEmpty()) { commandOptional = getCommandViaAliasAndParameter(name, unParsedCommandParameter, serverId); } return commandOptional; @@ -139,7 +138,7 @@ public class CommandManager implements CommandRegistry { .getDependentFeatures() .stream() .map(s -> featureConfigService.getFeatureEnum(s)) - .collect(Collectors.toList()); + .toList(); boolean required = false; for (FeatureDefinition featureDefinition : featureDefinitions) { if(featureFlagService.getFeatureFlagValue(featureDefinition, serverId)) { diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/listener/async/jda/AsyncLeaveListenerBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/listener/async/jda/AsyncLeaveListenerBean.java index 973c4fa65..28694c190 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/listener/async/jda/AsyncLeaveListenerBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/listener/async/jda/AsyncLeaveListenerBean.java @@ -46,6 +46,7 @@ public class AsyncLeaveListenerBean extends ListenerAdapter { .build(); return MemberLeaveModel .builder() + .member(event.getMember()) .leavingUser(serverUser) .user(event.getUser()) .build(); diff --git a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/exception/FeatureNotFoundException.java b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/exception/FeatureNotFoundException.java index a15208cd0..4090b21ec 100644 --- a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/exception/FeatureNotFoundException.java +++ b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/exception/FeatureNotFoundException.java @@ -10,7 +10,7 @@ public class FeatureNotFoundException extends AbstractoRunTimeException implemen private final FeatureNotFoundExceptionModel model; public FeatureNotFoundException(String feature, List availableFeatures) { - super("Feature not found."); + super(String.format("Feature %s not found.", feature)); this.model = FeatureNotFoundExceptionModel .builder() .featureName(feature) diff --git a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/models/listener/MemberLeaveModel.java b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/models/listener/MemberLeaveModel.java index daa4385d6..fd1628305 100644 --- a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/models/listener/MemberLeaveModel.java +++ b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/models/listener/MemberLeaveModel.java @@ -5,6 +5,7 @@ import dev.sheldan.abstracto.core.models.ServerUser; import lombok.Builder; import lombok.Getter; import lombok.Setter; +import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.User; @Getter @@ -13,6 +14,7 @@ import net.dv8tion.jda.api.entities.User; public class MemberLeaveModel implements FeatureAwareListenerModel { private ServerUser leavingUser; private User user; + private Member member; @Override public Long getServerId() { return leavingUser.getServerId();