From 020cc58c4a7b41c762a5a41a1e0d737308feea82 Mon Sep 17 00:00:00 2001 From: Sheldan <5037282+Sheldan@users.noreply.github.com> Date: Thu, 29 Apr 2021 01:00:27 +0200 Subject: [PATCH] [AB-242] refactoring suggestions adding veto and unsuggest command adding support for configuration whether or not a reply mentions the message adding support to reply to a message via template changed default mention config to exclude role mentions --- .../service/ModMailThreadServiceBean.java | 2 +- .../suggestion/suggestion-impl/pom.xml | 6 + .../abstracto/suggestion/command/Accept.java | 5 +- .../abstracto/suggestion/command/Reject.java | 5 +- .../abstracto/suggestion/command/Suggest.java | 6 +- .../suggestion/command/UnSuggest.java | 59 ++++++ .../abstracto/suggestion/command/Veto.java | 65 +++++++ .../suggestion/job/SuggestionCleanUpJob.java | 31 +++ .../repository/SuggestionRepository.java | 5 + .../service/SuggestionServiceBean.java | 181 ++++++++++++------ .../SuggestionManagementServiceBean.java | 46 ++++- .../suggestion-seedData/command.xml | 10 + .../suggestion-seedData/data.xml | 1 + .../suggestion_cleanup_job.xml | 19 ++ .../suggestion-tables/suggestion.xml | 29 ++- .../resources/suggestion-config.properties | 2 + .../suggestion/command/AcceptTest.java | 4 +- .../suggestion/command/RejectTest.java | 4 +- .../suggestion/command/SuggestTest.java | 4 +- .../service/SuggestionServiceBeanTest.java | 151 +++++---------- .../SuggestionManagementServiceBeanTest.java | 12 +- ...ava => UnSuggestNotPossibleException.java} | 8 +- .../suggestion/model/database/Suggestion.java | 15 +- .../model/database/SuggestionState.java | 2 +- .../model/template/SuggestionLog.java | 19 +- .../suggestion/service/SuggestionService.java | 11 +- .../SuggestionManagementService.java | 13 +- .../interactive/InteractiveServiceBean.java | 2 +- .../core/service/ChannelServiceBean.java | 19 +- .../core/service/MessageServiceBean.java | 2 +- .../templating/model/EmbedConfiguration.java | 1 + .../model/MetaEmbedConfiguration.java | 2 + .../service/TemplateServiceBean.java | 3 + .../src/main/resources/abstracto.properties | 2 +- .../core/service/ChannelService.java | 4 +- .../core/templating/model/MessageConfig.java | 2 + .../core/templating/model/MessageToSend.java | 1 + .../main/docs/asciidoc/modules/utility.adoc | 14 +- 38 files changed, 524 insertions(+), 243 deletions(-) create mode 100644 abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/command/UnSuggest.java create mode 100644 abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/command/Veto.java create mode 100644 abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/job/SuggestionCleanUpJob.java create mode 100644 abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/resources/migrations/1.0-suggestion/suggestion-seedData/suggestion_cleanup_job.xml rename abstracto-application/abstracto-modules/suggestion/suggestion-int/src/main/java/dev/sheldan/abstracto/suggestion/exception/{SuggestionUpdateException.java => UnSuggestNotPossibleException.java} (57%) diff --git a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/service/ModMailThreadServiceBean.java b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/service/ModMailThreadServiceBean.java index 9ed403bcd..2fd484a74 100644 --- a/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/service/ModMailThreadServiceBean.java +++ b/abstracto-application/abstracto-modules/modmail/modmail-impl/src/main/java/dev/sheldan/abstracto/modmail/service/ModMailThreadServiceBean.java @@ -504,7 +504,7 @@ public class ModMailThreadServiceBean implements ModMailThreadService { CompletableFuture future = messageService.sendMessageToSendToUser(targetMember.getUser(), messageToSend); CompletableFuture sameThreadMessageFuture; if(featureModeService.featureModeActive(ModMailFeatureDefinition.MOD_MAIL, modMailThread.getServer(), ModMailMode.SEPARATE_MESSAGE)) { - sameThreadMessageFuture = channelService.sendMessageToSendToAChannel(messageToSend, modMailThread.getChannel()).get(0); + sameThreadMessageFuture = channelService.sendMessageEmbedToSendToAChannel(messageToSend, modMailThread.getChannel()).get(0); } else { sameThreadMessageFuture = CompletableFuture.completedFuture(null); } diff --git a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/pom.xml b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/pom.xml index cd2f5c6fe..2dcb7a03e 100644 --- a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/pom.xml +++ b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/pom.xml @@ -51,6 +51,12 @@ test + + dev.sheldan.abstracto.scheduling + scheduling-int + ${project.version} + + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/command/Accept.java b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/command/Accept.java index 826665410..f51755144 100644 --- a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/command/Accept.java +++ b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/command/Accept.java @@ -9,10 +9,8 @@ import dev.sheldan.abstracto.core.command.config.ParameterValidator; import dev.sheldan.abstracto.core.command.config.validator.MinIntegerValueValidator; import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandResult; -import dev.sheldan.abstracto.core.command.execution.ContextConverter; import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.suggestion.config.SuggestionFeatureDefinition; -import dev.sheldan.abstracto.suggestion.model.template.SuggestionLog; import dev.sheldan.abstracto.suggestion.service.SuggestionService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -36,8 +34,7 @@ public class Accept extends AbstractConditionableCommand { Long suggestionId = (Long) parameters.get(0); String text = parameters.size() == 2 ? (String) parameters.get(1) : ""; log.debug("Using default reason for accept: {}.", parameters.size() != 2); - SuggestionLog suggestionModel = (SuggestionLog) ContextConverter.fromCommandContext(commandContext, SuggestionLog.class); - return suggestionService.acceptSuggestion(suggestionId, text, suggestionModel) + return suggestionService.acceptSuggestion(suggestionId, commandContext.getMessage(), text) .thenApply(aVoid -> CommandResult.fromSuccess()); } diff --git a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/command/Reject.java b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/command/Reject.java index c06523879..a0b8ac56a 100644 --- a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/command/Reject.java +++ b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/command/Reject.java @@ -9,10 +9,8 @@ import dev.sheldan.abstracto.core.command.config.ParameterValidator; import dev.sheldan.abstracto.core.command.config.validator.MinIntegerValueValidator; import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandResult; -import dev.sheldan.abstracto.core.command.execution.ContextConverter; import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.suggestion.config.SuggestionFeatureDefinition; -import dev.sheldan.abstracto.suggestion.model.template.SuggestionLog; import dev.sheldan.abstracto.suggestion.service.SuggestionService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -36,8 +34,7 @@ public class Reject extends AbstractConditionableCommand { Long suggestionId = (Long) parameters.get(0); String text = parameters.size() == 2 ? (String) parameters.get(1) : ""; log.debug("Using default reason for accept: {}.", parameters.size() != 2); - SuggestionLog suggestionModel = (SuggestionLog) ContextConverter.fromCommandContext(commandContext, SuggestionLog.class); - return suggestionService.rejectSuggestion(suggestionId, text, suggestionModel) + return suggestionService.rejectSuggestion(suggestionId, commandContext.getMessage(), text) .thenApply(aVoid -> CommandResult.fromSuccess()); } diff --git a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/command/Suggest.java b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/command/Suggest.java index b53108ef5..4e27121cd 100644 --- a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/command/Suggest.java +++ b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/command/Suggest.java @@ -7,10 +7,8 @@ import dev.sheldan.abstracto.core.command.config.HelpInfo; import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.execution.CommandContext; import dev.sheldan.abstracto.core.command.execution.CommandResult; -import dev.sheldan.abstracto.core.command.execution.ContextConverter; import dev.sheldan.abstracto.core.config.FeatureDefinition; import dev.sheldan.abstracto.suggestion.config.SuggestionFeatureDefinition; -import dev.sheldan.abstracto.suggestion.model.template.SuggestionLog; import dev.sheldan.abstracto.suggestion.service.SuggestionService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -29,9 +27,7 @@ public class Suggest extends AbstractConditionableCommand { public CompletableFuture executeAsync(CommandContext commandContext) { List parameters = commandContext.getParameters().getParameters(); String text = (String) parameters.get(0); - SuggestionLog suggestLogModel = (SuggestionLog) ContextConverter.fromCommandContext(commandContext, SuggestionLog.class); - suggestLogModel.setSuggester(commandContext.getAuthor()); - return suggestionService.createSuggestionMessage(commandContext.getAuthor(), text, suggestLogModel) + return suggestionService.createSuggestionMessage(commandContext.getMessage(), text) .thenApply(aVoid -> CommandResult.fromSuccess()); } diff --git a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/command/UnSuggest.java b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/command/UnSuggest.java new file mode 100644 index 000000000..9591bf499 --- /dev/null +++ b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/command/UnSuggest.java @@ -0,0 +1,59 @@ +package dev.sheldan.abstracto.suggestion.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.config.ParameterValidator; +import dev.sheldan.abstracto.core.command.config.validator.MinIntegerValueValidator; +import dev.sheldan.abstracto.core.command.execution.CommandContext; +import dev.sheldan.abstracto.core.command.execution.CommandResult; +import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.suggestion.config.SuggestionFeatureDefinition; +import dev.sheldan.abstracto.suggestion.service.SuggestionService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +@Component +public class UnSuggest extends AbstractConditionableCommand { + + @Autowired + private SuggestionService suggestionService; + + @Override + public CompletableFuture executeAsync(CommandContext commandContext) { + List parameters = commandContext.getParameters().getParameters(); + Long suggestionId = (Long) parameters.get(0); + return suggestionService.removeSuggestion(suggestionId, commandContext.getAuthor()) + .thenApply(aVoid -> CommandResult.fromSuccess()); + } + + @Override + public CommandConfiguration getConfiguration() { + List parameters = new ArrayList<>(); + List suggestionIdValidator = Arrays.asList(MinIntegerValueValidator.min(1L)); + parameters.add(Parameter.builder().name("suggestionId").validators(suggestionIdValidator).type(Long.class).templated(true).build()); + HelpInfo helpInfo = HelpInfo.builder().templated(true).build(); + return CommandConfiguration.builder() + .name("unSuggest") + .module(UtilityModuleDefinition.UTILITY) + .templated(true) + .async(true) + .supportsEmbedException(true) + .causesReaction(true) + .parameters(parameters) + .help(helpInfo) + .build(); + } + + @Override + public FeatureDefinition getFeature() { + return SuggestionFeatureDefinition.SUGGEST; + } +} diff --git a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/command/Veto.java b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/command/Veto.java new file mode 100644 index 000000000..e192c6065 --- /dev/null +++ b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/command/Veto.java @@ -0,0 +1,65 @@ +package dev.sheldan.abstracto.suggestion.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.config.ParameterValidator; +import dev.sheldan.abstracto.core.command.config.validator.MinIntegerValueValidator; +import dev.sheldan.abstracto.core.command.execution.CommandContext; +import dev.sheldan.abstracto.core.command.execution.CommandResult; +import dev.sheldan.abstracto.core.config.FeatureDefinition; +import dev.sheldan.abstracto.suggestion.config.SuggestionFeatureDefinition; +import dev.sheldan.abstracto.suggestion.service.SuggestionService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +@Slf4j +@Component +public class Veto extends AbstractConditionableCommand { + + @Autowired + private SuggestionService suggestionService; + + @Override + public CompletableFuture executeAsync(CommandContext commandContext) { + List parameters = commandContext.getParameters().getParameters(); + Long suggestionId = (Long) parameters.get(0); + String text = parameters.size() == 2 ? (String) parameters.get(1) : ""; + log.debug("Using default reason for veto: {}.", parameters.size() != 2); + return suggestionService.vetoSuggestion(suggestionId, commandContext.getMessage(), text) + .thenApply(aVoid -> CommandResult.fromSuccess()); + } + + @Override + public CommandConfiguration getConfiguration() { + List parameters = new ArrayList<>(); + + List suggestionIdValidator = Arrays.asList(MinIntegerValueValidator.min(1L)); + parameters.add(Parameter.builder().name("suggestionId").validators(suggestionIdValidator).type(Long.class).templated(true).build()); + parameters.add(Parameter.builder().name("text").type(String.class).optional(true).remainder(true).templated(true).build()); + HelpInfo helpInfo = HelpInfo.builder().templated(true).hasExample(true).build(); + return CommandConfiguration.builder() + .name("veto") + .module(UtilityModuleDefinition.UTILITY) + .templated(true) + .async(true) + .supportsEmbedException(true) + .causesReaction(true) + .parameters(parameters) + .help(helpInfo) + .build(); + } + + @Override + public FeatureDefinition getFeature() { + return SuggestionFeatureDefinition.SUGGEST; + } +} diff --git a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/job/SuggestionCleanUpJob.java b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/job/SuggestionCleanUpJob.java new file mode 100644 index 000000000..3bd6268a5 --- /dev/null +++ b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/job/SuggestionCleanUpJob.java @@ -0,0 +1,31 @@ +package dev.sheldan.abstracto.suggestion.job; + +import dev.sheldan.abstracto.suggestion.service.SuggestionService; +import lombok.extern.slf4j.Slf4j; +import org.quartz.DisallowConcurrentExecution; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.quartz.PersistJobDataAfterExecution; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.quartz.QuartzJobBean; +import org.springframework.stereotype.Component; + +@Slf4j +@DisallowConcurrentExecution +@Component +@PersistJobDataAfterExecution +public class SuggestionCleanUpJob extends QuartzJobBean { + + @Autowired + private SuggestionService suggestionService; + + @Override + protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException { + log.info("Executing suggestion clean up job."); + try { + suggestionService.cleanUpSuggestions(); + } catch (Exception exception) { + log.error("Suggestion clean up job failed.", exception); + } + } +} diff --git a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/repository/SuggestionRepository.java b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/repository/SuggestionRepository.java index 57e87d5d3..9bb7f439d 100644 --- a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/repository/SuggestionRepository.java +++ b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/repository/SuggestionRepository.java @@ -2,10 +2,15 @@ package dev.sheldan.abstracto.suggestion.repository; import dev.sheldan.abstracto.core.models.ServerSpecificId; import dev.sheldan.abstracto.suggestion.model.database.Suggestion; +import dev.sheldan.abstracto.suggestion.model.database.SuggestionState; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.time.Instant; +import java.util.List; + @Repository public interface SuggestionRepository extends JpaRepository { + List findByUpdatedLessThanAndStateNot(Instant start, SuggestionState state); } diff --git a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/service/SuggestionServiceBean.java b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/service/SuggestionServiceBean.java index 16091d545..96527f897 100644 --- a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/service/SuggestionServiceBean.java +++ b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/service/SuggestionServiceBean.java @@ -6,34 +6,33 @@ import dev.sheldan.abstracto.core.service.*; import dev.sheldan.abstracto.core.service.management.ServerManagementService; import dev.sheldan.abstracto.core.service.management.UserInServerManagementService; import dev.sheldan.abstracto.core.utils.FutureUtils; -import dev.sheldan.abstracto.core.utils.MessageUtils; import dev.sheldan.abstracto.core.templating.model.MessageToSend; import dev.sheldan.abstracto.core.templating.service.TemplateService; import dev.sheldan.abstracto.suggestion.config.SuggestionPostTarget; -import dev.sheldan.abstracto.suggestion.exception.SuggestionNotFoundException; -import dev.sheldan.abstracto.suggestion.exception.SuggestionUpdateException; +import dev.sheldan.abstracto.suggestion.exception.UnSuggestNotPossibleException; import dev.sheldan.abstracto.suggestion.model.database.Suggestion; import dev.sheldan.abstracto.suggestion.model.database.SuggestionState; import dev.sheldan.abstracto.suggestion.model.template.SuggestionLog; import dev.sheldan.abstracto.suggestion.service.management.SuggestionManagementService; import lombok.extern.slf4j.Slf4j; -import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.Message; -import net.dv8tion.jda.api.entities.MessageEmbed; -import net.dv8tion.jda.api.entities.TextChannel; +import net.dv8tion.jda.api.entities.*; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.List; -import java.util.Optional; import java.util.concurrent.CompletableFuture; @Component @Slf4j public class SuggestionServiceBean implements SuggestionService { - public static final String SUGGESTION_LOG_TEMPLATE = "suggest_log"; + public static final String SUGGESTION_CREATION_TEMPLATE = "suggest_initial"; + public static final String SUGGESTION_UPDATE_TEMPLATE = "suggest_update"; public static final String SUGGESTION_YES_EMOTE = "suggestionYes"; public static final String SUGGESTION_NO_EMOTE = "suggestionNo"; public static final String SUGGESTION_COUNTER_KEY = "suggestion"; @@ -68,68 +67,105 @@ public class SuggestionServiceBean implements SuggestionService { @Autowired private ServerManagementService serverManagementService; + @Autowired + private MessageService messageService; + + @Autowired + private UserService userService; + + @Value("${abstracto.feature.suggestion.removalMaxAge}") + private Long removalMaxAgeSeconds; + + @Value("${abstracto.feature.suggestion.removalDays}") + private Long autoRemovalMaxDays; + @Override - public CompletableFuture createSuggestionMessage(Member member, String text, SuggestionLog suggestionLog) { - AServer server = serverManagementService.loadServer(member.getGuild()); - AUserInAServer suggester = userInServerManagementService.loadOrCreateUser(member); + public CompletableFuture createSuggestionMessage(Message commandMessage, String text) { + Member suggester = commandMessage.getMember(); + Long serverId = suggester.getGuild().getIdLong(); + AServer server = serverManagementService.loadServer(serverId); + AUserInAServer userSuggester = userInServerManagementService.loadOrCreateUser(suggester); Long newSuggestionId = counterService.getNextCounterValue(server, SUGGESTION_COUNTER_KEY); - suggestionLog.setSuggestionId(newSuggestionId); - suggestionLog.setState(SuggestionState.NEW); - suggestionLog.setSuggesterUser(suggester); - suggestionLog.setText(text); - MessageToSend messageToSend = templateService.renderEmbedTemplate(SUGGESTION_LOG_TEMPLATE, suggestionLog, member.getGuild().getIdLong()); - long guildId = member.getGuild().getIdLong(); - log.info("Creating suggestion with id {} in server {} from member {}.", newSuggestionId, member.getGuild().getId(), member.getId()); - List> completableFutures = postTargetService.sendEmbedInPostTarget(messageToSend, SuggestionPostTarget.SUGGESTION, guildId); + SuggestionLog model = SuggestionLog + .builder() + .suggestionId(newSuggestionId) + .state(SuggestionState.NEW) + .serverId(serverId) + .message(commandMessage) + .member(commandMessage.getMember()) + .suggesterUser(userSuggester) + .suggester(suggester.getUser()) + .text(text) + .build(); + MessageToSend messageToSend = templateService.renderEmbedTemplate(SUGGESTION_CREATION_TEMPLATE, model, serverId); + log.info("Creating suggestion with id {} in server {} from member {}.", newSuggestionId, serverId, suggester.getIdLong()); + List> completableFutures = postTargetService.sendEmbedInPostTarget(messageToSend, SuggestionPostTarget.SUGGESTION, serverId); return FutureUtils.toSingleFutureGeneric(completableFutures).thenCompose(aVoid -> { Message message = completableFutures.get(0).join(); log.debug("Posted message, adding reaction for suggestion {} to message {}.", newSuggestionId, message.getId()); - CompletableFuture firstReaction = reactionService.addReactionToMessageAsync(SUGGESTION_YES_EMOTE, guildId, message); - CompletableFuture secondReaction = reactionService.addReactionToMessageAsync(SUGGESTION_NO_EMOTE, guildId, message); + CompletableFuture firstReaction = reactionService.addReactionToMessageAsync(SUGGESTION_YES_EMOTE, serverId, message); + CompletableFuture secondReaction = reactionService.addReactionToMessageAsync(SUGGESTION_NO_EMOTE, serverId, message); return CompletableFuture.allOf(firstReaction, secondReaction).thenAccept(aVoid1 -> { log.debug("Reaction added to message {} for suggestion {}.", message.getId(), newSuggestionId); - self.persistSuggestionInDatabase(member, text, message, newSuggestionId); + self.persistSuggestionInDatabase(suggester, text, message, newSuggestionId, commandMessage); }); }); } @Transactional - public void persistSuggestionInDatabase(Member member, String text, Message message, Long suggestionId) { + public void persistSuggestionInDatabase(Member member, String text, Message message, Long suggestionId, Message commandMessage) { log.info("Persisting suggestion {} for server {} in database.", suggestionId, member.getGuild().getId()); - suggestionManagementService.createSuggestion(member, text, message, suggestionId); + suggestionManagementService.createSuggestion(member, text, message, suggestionId, commandMessage); } @Override - public CompletableFuture acceptSuggestion(Long suggestionId, String text, SuggestionLog suggestionLog) { - Suggestion suggestion = suggestionManagementService.getSuggestion(suggestionId, suggestionLog.getGuild().getIdLong()).orElseThrow(() -> new SuggestionNotFoundException(suggestionId)); + public CompletableFuture acceptSuggestion(Long suggestionId, Message commandMessage, String text) { + Long serverId = commandMessage.getGuild().getIdLong(); + Suggestion suggestion = suggestionManagementService.getSuggestion(serverId, suggestionId); suggestionManagementService.setSuggestionState(suggestion, SuggestionState.ACCEPTED); log.info("Accepting suggestion {} in server {}.", suggestionId, suggestion.getServer().getId()); - return updateSuggestion(text, suggestionLog, suggestion); + return updateSuggestion(commandMessage.getMember(), text, suggestion); } - private CompletableFuture updateSuggestion(String text, SuggestionLog suggestionLog, Suggestion suggestion) { - suggestionLog.setSuggesterUser(suggestion.getSuggester()); + @Override + public CompletableFuture vetoSuggestion(Long suggestionId, Message commandMessage, String text) { + Long serverId = commandMessage.getGuild().getIdLong(); + Suggestion suggestion = suggestionManagementService.getSuggestion(serverId, suggestionId); + suggestionManagementService.setSuggestionState(suggestion, SuggestionState.VETOED); + log.info("Vetoing suggestion {} in server {}.", suggestionId, suggestion.getServer().getId()); + return updateSuggestion(commandMessage.getMember(), text, suggestion); + } + + private CompletableFuture updateSuggestion(Member memberExecutingCommand, String reason, Suggestion suggestion) { + Long serverId = suggestion.getServer().getId(); Long channelId = suggestion.getChannel().getId(); Long originalMessageId = suggestion.getMessageId(); - Long serverId = suggestion.getServer().getId(); + SuggestionLog model = SuggestionLog + .builder() + .suggestionId(suggestion.getSuggestionId().getId()) + .state(suggestion.getState()) + .suggesterUser(suggestion.getSuggester()) + .serverId(serverId) + .member(memberExecutingCommand) + .originalMessageId(originalMessageId) + .text(suggestion.getSuggestionText()) + .originalChannelId(channelId) + .reason(reason) + .build(); log.info("Updated posted suggestion {} in server {}.", suggestion.getSuggestionId().getId(), suggestion.getServer().getId()); - - suggestionLog.setOriginalChannelId(channelId); - suggestionLog.setOriginalMessageId(originalMessageId); - suggestionLog.setOriginalMessageUrl(MessageUtils.buildMessageUrl(serverId, channelId, originalMessageId)); - AUserInAServer suggester = suggestion.getSuggester(); - TextChannel textChannelById = channelService.getTextChannelFromServer(serverId, channelId); - CompletableFuture memberById = memberService.getMemberInServerAsync(serverId, suggester.getUserReference().getId()); - suggestionLog.setState(suggestion.getState()); - suggestionLog.setSuggestionId(suggestion.getSuggestionId().getId()); + CompletableFuture memberById = userService.retrieveUserForId(suggestion.getSuggester().getUserReference().getId()); CompletableFuture finalFuture = new CompletableFuture<>(); - memberById.whenComplete((member, throwable) -> { + memberById.whenComplete((user, throwable) -> { if(throwable == null) { - suggestionLog.setSuggester(member); + model.setSuggester(user); } - channelService.retrieveMessageInChannel(textChannelById, originalMessageId).thenCompose(message -> - self.updateSuggestionMessageText(text, suggestionLog, message) - ).thenAccept(aVoid -> finalFuture.complete(null)); + self.updateSuggestionMessageText(reason, model).thenAccept(unused -> finalFuture.complete(null)).exceptionally(throwable1 -> { + finalFuture.completeExceptionally(throwable1); + return null; + }); + }).exceptionally(throwable -> { + finalFuture.completeExceptionally(throwable); + return null; }); return finalFuture; @@ -137,27 +173,48 @@ public class SuggestionServiceBean implements SuggestionService { } @Transactional - public CompletableFuture updateSuggestionMessageText(String text, SuggestionLog suggestionLog, Message message) { - Optional embedOptional = message.getEmbeds().stream().filter(embed -> embed.getDescription() != null).findFirst(); - if(embedOptional.isPresent()) { - log.info("Updating the text of the suggestion {} in server {}.", suggestionLog.getSuggestionId(), message.getGuild().getId()); - MessageEmbed suggestionEmbed = embedOptional.get(); - suggestionLog.setReason(text); - suggestionLog.setText(suggestionEmbed.getDescription()); - MessageToSend messageToSend = templateService.renderEmbedTemplate(SUGGESTION_LOG_TEMPLATE, suggestionLog, suggestionLog.getGuild().getIdLong()); - List> completableFutures = postTargetService.sendEmbedInPostTarget(messageToSend, SuggestionPostTarget.SUGGESTION, suggestionLog.getGuild().getIdLong()); - return CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0])); - } else { - log.warn("The message to update the suggestion for, did not contain an embed to update. Suggestions require an embed with a description as a container. MessageURL: {}", message.getJumpUrl()); - throw new SuggestionUpdateException(); - } + public CompletableFuture updateSuggestionMessageText(String text, SuggestionLog suggestionLog) { + suggestionLog.setReason(text); + Long serverId = suggestionLog.getServerId(); + MessageToSend messageToSend = templateService.renderEmbedTemplate(SUGGESTION_UPDATE_TEMPLATE, suggestionLog, serverId); + List> completableFutures = postTargetService.sendEmbedInPostTarget(messageToSend, SuggestionPostTarget.SUGGESTION, serverId); + return CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0])); } @Override - public CompletableFuture rejectSuggestion(Long suggestionId, String text, SuggestionLog suggestionLog) { - Suggestion suggestion = suggestionManagementService.getSuggestion(suggestionId, suggestionLog.getGuild().getIdLong()).orElseThrow(() -> new SuggestionNotFoundException(suggestionId)); + public CompletableFuture rejectSuggestion(Long suggestionId, Message commandMessage, String text) { + Long serverId = commandMessage.getGuild().getIdLong(); + Suggestion suggestion = suggestionManagementService.getSuggestion(serverId, suggestionId); suggestionManagementService.setSuggestionState(suggestion, SuggestionState.REJECTED); log.info("Rejecting suggestion {} in server {}.", suggestionId, suggestion.getServer().getId()); - return updateSuggestion(text, suggestionLog, suggestion); + return updateSuggestion(commandMessage.getMember(), text, suggestion); + } + + @Override + public CompletableFuture removeSuggestion(Long suggestionId, Member member) { + Long serverId = member.getGuild().getIdLong(); + Suggestion suggestion = suggestionManagementService.getSuggestion(serverId, suggestionId); + if(member.getIdLong() != suggestion.getSuggester().getUserReference().getId() || + suggestion.getCreated().isBefore(Instant.now().minus(Duration.ofSeconds(removalMaxAgeSeconds)))) { + throw new UnSuggestNotPossibleException(); + } + return messageService.deleteMessageInChannelInServer(suggestion.getServer().getId(), suggestion.getChannel().getId(), suggestion.getMessageId()) + .thenAccept(unused -> self.deleteSuggestion(suggestionId, serverId)); + } + + @Override + @Transactional + public void cleanUpSuggestions() { + Instant pointInTime = Instant.now().minus(Duration.ofDays(autoRemovalMaxDays)).truncatedTo(ChronoUnit.DAYS); + List suggestionsToRemove = suggestionManagementService.getSuggestionsUpdatedBeforeNotNew(pointInTime); + log.info("Removing {} suggestions older than {}.", suggestionsToRemove.size(), pointInTime); + suggestionsToRemove.forEach(suggestion -> log.info("Deleting suggestion {} in server {}.", + suggestion.getSuggestionId().getId(), suggestion.getSuggestionId().getServerId())); + suggestionManagementService.deleteSuggestion(suggestionsToRemove); + } + + @Transactional + public void deleteSuggestion(Long suggestionId, Long serverId) { + suggestionManagementService.deleteSuggestion(serverId, suggestionId); } } diff --git a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/service/management/SuggestionManagementServiceBean.java b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/service/management/SuggestionManagementServiceBean.java index d21bf60f9..5e1f15765 100644 --- a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/service/management/SuggestionManagementServiceBean.java +++ b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/java/dev/sheldan/abstracto/suggestion/service/management/SuggestionManagementServiceBean.java @@ -6,6 +6,7 @@ import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.service.management.ChannelManagementService; import dev.sheldan.abstracto.core.service.management.ServerManagementService; import dev.sheldan.abstracto.core.service.management.UserInServerManagementService; +import dev.sheldan.abstracto.suggestion.exception.SuggestionNotFoundException; import dev.sheldan.abstracto.suggestion.model.database.Suggestion; import dev.sheldan.abstracto.suggestion.model.database.SuggestionState; import dev.sheldan.abstracto.suggestion.repository.SuggestionRepository; @@ -16,6 +17,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.time.Instant; +import java.util.List; import java.util.Optional; @Component @@ -35,35 +37,42 @@ public class SuggestionManagementServiceBean implements SuggestionManagementServ private ServerManagementService serverManagementService; @Override - public Suggestion createSuggestion(Member suggester, String text, Message message, Long suggestionId) { + public Suggestion createSuggestion(Member suggester, String text, Message message, Long suggestionId, Message commandMessage) { AUserInAServer user = userInServerManagementService.loadOrCreateUser(suggester); - return this.createSuggestion(user, text, message, suggestionId); + return this.createSuggestion(user, text, message, suggestionId, commandMessage); } @Override - public Suggestion createSuggestion(AUserInAServer suggester, String text, Message message, Long suggestionId) { - long channelId = message.getChannel().getIdLong(); + public Suggestion createSuggestion(AUserInAServer suggester, String text, Message createdMessage, Long suggestionId, Message commandMessage) { + long channelId = createdMessage.getChannel().getIdLong(); AChannel channel = channelManagementService.loadChannel(channelId); + AChannel commandChannel = channelManagementService.loadChannel(commandMessage.getChannel().getIdLong()); Suggestion suggestion = Suggestion .builder() .state(SuggestionState.NEW) .suggester(suggester) + .suggestionText(text) .suggestionId(new ServerSpecificId(suggester.getServerReference().getId(), suggestionId)) .server(suggester.getServerReference()) - .suggestionDate(Instant.now()) .channel(channel) - .messageId(message.getIdLong()) + .commandChannel(commandChannel) + .commandMessageId(commandMessage.getIdLong()) + .messageId(createdMessage.getIdLong()) .build(); log.info("Persisting suggestion {} at message {} in channel {} on server {} from user {}.", - suggestionId, message.getId(), channelId, message.getGuild().getId(), suggester.getUserReference().getId()); + suggestionId, createdMessage.getId(), channelId, createdMessage.getGuild().getId(), suggester.getUserReference().getId()); return suggestionRepository.save(suggestion); } @Override - public Optional getSuggestion(Long suggestionId, Long serverId) { + public Optional getSuggestionOptional(Long serverId, Long suggestionId) { return suggestionRepository.findById(new ServerSpecificId(serverId, suggestionId)); } + @Override + public Suggestion getSuggestion(Long serverId, Long suggestionId) { + return getSuggestionOptional(serverId, suggestionId).orElseThrow(() -> new SuggestionNotFoundException(suggestionId)); + } @Override public void setSuggestionState(Suggestion suggestion, SuggestionState newState) { @@ -71,4 +80,25 @@ public class SuggestionManagementServiceBean implements SuggestionManagementServ log.info("Setting suggestion {} in server {} to state {}.", suggestion.getSuggestionId().getId(), suggestion.getSuggestionId().getServerId(), newState); suggestionRepository.save(suggestion); } + + @Override + public void deleteSuggestion(Long serverId, Long suggestionId) { + deleteSuggestion(getSuggestion(serverId, suggestionId)); + } + + @Override + public void deleteSuggestion(List suggestions) { + suggestionRepository.deleteAll(suggestions); + } + + @Override + public void deleteSuggestion(Suggestion suggestion) { + log.info("Deleting suggestion {} in server {}.", suggestion.getSuggestionId().getId(), suggestion.getSuggestionId().getServerId()); + suggestionRepository.delete(suggestion); + } + + @Override + public List getSuggestionsUpdatedBeforeNotNew(Instant date) { + return suggestionRepository.findByUpdatedLessThanAndStateNot(date, SuggestionState.NEW); + } } diff --git a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/resources/migrations/1.0-suggestion/suggestion-seedData/command.xml b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/resources/migrations/1.0-suggestion/suggestion-seedData/command.xml index 5f9864537..37a7ad2df 100644 --- a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/resources/migrations/1.0-suggestion/suggestion-seedData/command.xml +++ b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/resources/migrations/1.0-suggestion/suggestion-seedData/command.xml @@ -25,6 +25,16 @@ + + + + + + + + + + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/resources/migrations/1.0-suggestion/suggestion-seedData/data.xml b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/resources/migrations/1.0-suggestion/suggestion-seedData/data.xml index b1b4a568f..9747b7927 100644 --- a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/resources/migrations/1.0-suggestion/suggestion-seedData/data.xml +++ b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/resources/migrations/1.0-suggestion/suggestion-seedData/data.xml @@ -9,4 +9,5 @@ + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/resources/migrations/1.0-suggestion/suggestion-seedData/suggestion_cleanup_job.xml b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/resources/migrations/1.0-suggestion/suggestion-seedData/suggestion_cleanup_job.xml new file mode 100644 index 000000000..65f926176 --- /dev/null +++ b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/resources/migrations/1.0-suggestion/suggestion-seedData/suggestion_cleanup_job.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/resources/migrations/1.0-suggestion/suggestion-tables/suggestion.xml b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/resources/migrations/1.0-suggestion/suggestion-tables/suggestion.xml index 83988f39f..b0ff36944 100644 --- a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/resources/migrations/1.0-suggestion/suggestion-tables/suggestion.xml +++ b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/resources/migrations/1.0-suggestion/suggestion-tables/suggestion.xml @@ -17,9 +17,6 @@ - - - @@ -33,11 +30,29 @@ + + + + + + + + + - - - + + + + DROP TRIGGER IF EXISTS suggestion_update_trigger ON suggestion; CREATE TRIGGER suggestion_update_trigger BEFORE UPDATE ON suggestion FOR EACH ROW EXECUTE PROCEDURE update_trigger_procedure(); @@ -47,7 +62,7 @@ CREATE TRIGGER suggestion_insert_trigger BEFORE INSERT ON suggestion FOR EACH ROW EXECUTE PROCEDURE insert_trigger_procedure(); - ALTER TABLE suggestion ADD CONSTRAINT check_suggestion_state CHECK (state IN ('NEW','ACCEPTED', 'REJECTED')); + ALTER TABLE suggestion ADD CONSTRAINT check_suggestion_state CHECK (state IN ('NEW','ACCEPTED', 'REJECTED', 'VETOED')); \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/resources/suggestion-config.properties b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/resources/suggestion-config.properties index f56555a91..8bafbd043 100644 --- a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/resources/suggestion-config.properties +++ b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/main/resources/suggestion-config.properties @@ -3,3 +3,5 @@ abstracto.featureFlags.suggestion.enabled=false abstracto.postTargets.suggestions.name=suggestions +abstracto.feature.suggestion.removalMaxAge=3600 +abstracto.feature.suggestion.removalDays=2 \ No newline at end of file diff --git a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/test/java/dev/sheldan/abstracto/suggestion/command/AcceptTest.java b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/test/java/dev/sheldan/abstracto/suggestion/command/AcceptTest.java index e9e558ff0..137f332bc 100644 --- a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/test/java/dev/sheldan/abstracto/suggestion/command/AcceptTest.java +++ b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/test/java/dev/sheldan/abstracto/suggestion/command/AcceptTest.java @@ -34,9 +34,9 @@ public class AcceptTest { String text = "text"; Long suggestionId = 5L; CommandContext context = CommandTestUtilities.getWithParameters(Arrays.asList(suggestionId, text)); - when(suggestionService.acceptSuggestion(eq(suggestionId), eq(text), any(SuggestionLog.class))).thenReturn(CompletableFuture.completedFuture(null)); + when(suggestionService.acceptSuggestion(eq(suggestionId), eq(context.getMessage()), eq(text))).thenReturn(CompletableFuture.completedFuture(null)); CompletableFuture result = testUnit.executeAsync(context); - verify(suggestionService, times(1)).acceptSuggestion(eq(suggestionId), eq(text), any(SuggestionLog.class)); + verify(suggestionService, times(1)).acceptSuggestion(eq(suggestionId), eq(context.getMessage()), eq(text)); CommandTestUtilities.checkSuccessfulCompletion(result.get()); } diff --git a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/test/java/dev/sheldan/abstracto/suggestion/command/RejectTest.java b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/test/java/dev/sheldan/abstracto/suggestion/command/RejectTest.java index fb74b87ed..43b4e56c5 100644 --- a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/test/java/dev/sheldan/abstracto/suggestion/command/RejectTest.java +++ b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/test/java/dev/sheldan/abstracto/suggestion/command/RejectTest.java @@ -34,9 +34,9 @@ public class RejectTest { String text = "text"; Long suggestionId = 5L; CommandContext context = CommandTestUtilities.getWithParameters(Arrays.asList(suggestionId, text)); - when(suggestionService.rejectSuggestion(eq(suggestionId), eq(text), any(SuggestionLog.class))).thenReturn(CompletableFuture.completedFuture(null)); + when(suggestionService.rejectSuggestion(eq(suggestionId), eq(context.getMessage()), eq(text))).thenReturn(CompletableFuture.completedFuture(null)); CompletableFuture result = testUnit.executeAsync(context); - verify(suggestionService, times(1)).rejectSuggestion(eq(suggestionId), eq(text), any(SuggestionLog.class)); + verify(suggestionService, times(1)).rejectSuggestion(eq(suggestionId), eq(context.getMessage()), eq(text)); CommandTestUtilities.checkSuccessfulCompletion(result.get()); } diff --git a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/test/java/dev/sheldan/abstracto/suggestion/command/SuggestTest.java b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/test/java/dev/sheldan/abstracto/suggestion/command/SuggestTest.java index 546695f8d..0477c2282 100644 --- a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/test/java/dev/sheldan/abstracto/suggestion/command/SuggestTest.java +++ b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/test/java/dev/sheldan/abstracto/suggestion/command/SuggestTest.java @@ -31,9 +31,9 @@ public class SuggestTest { public void testExecuteCommand() throws ExecutionException, InterruptedException { String text = "text"; CommandContext context = CommandTestUtilities.getWithParameters(Arrays.asList(text)); - when(suggestionService.createSuggestionMessage(eq(context.getAuthor()), eq(text), any(SuggestionLog.class))).thenReturn(CompletableFuture.completedFuture(null)); + when(suggestionService.createSuggestionMessage(eq(context.getMessage()), eq(text))).thenReturn(CompletableFuture.completedFuture(null)); CompletableFuture result = testUnit.executeAsync(context); - verify(suggestionService, times(1)).createSuggestionMessage(eq(context.getAuthor()), eq(text), any(SuggestionLog.class)); + verify(suggestionService, times(1)).createSuggestionMessage(eq(context.getMessage()), eq(text)); CommandTestUtilities.checkSuccessfulCompletion(result.get()); } diff --git a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/test/java/dev/sheldan/abstracto/suggestion/service/SuggestionServiceBeanTest.java b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/test/java/dev/sheldan/abstracto/suggestion/service/SuggestionServiceBeanTest.java index 0f3f7e02c..644044121 100644 --- a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/test/java/dev/sheldan/abstracto/suggestion/service/SuggestionServiceBeanTest.java +++ b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/test/java/dev/sheldan/abstracto/suggestion/service/SuggestionServiceBeanTest.java @@ -13,7 +13,7 @@ import dev.sheldan.abstracto.core.templating.model.MessageToSend; import dev.sheldan.abstracto.core.templating.service.TemplateService; import dev.sheldan.abstracto.suggestion.config.SuggestionPostTarget; import dev.sheldan.abstracto.suggestion.exception.SuggestionNotFoundException; -import dev.sheldan.abstracto.suggestion.exception.SuggestionUpdateException; +import dev.sheldan.abstracto.suggestion.exception.UnSuggestNotPossibleException; import dev.sheldan.abstracto.suggestion.model.database.Suggestion; import dev.sheldan.abstracto.suggestion.model.database.SuggestionState; import dev.sheldan.abstracto.suggestion.model.template.SuggestionLog; @@ -28,7 +28,6 @@ import org.mockito.junit.MockitoJUnitRunner; import java.util.Arrays; import java.util.List; -import java.util.Optional; import java.util.concurrent.CompletableFuture; import static org.mockito.Mockito.*; @@ -62,9 +61,6 @@ public class SuggestionServiceBeanTest { @Mock private SuggestionServiceBean self; - @Mock - private Member suggestionCreator; - @Mock private Guild guild; @@ -80,6 +76,9 @@ public class SuggestionServiceBeanTest { @Mock private ServerManagementService serverManagementService; + @Mock + private UserService userService; + @Mock private AServer server; @@ -90,164 +89,103 @@ public class SuggestionServiceBeanTest { private AUserInAServer suggester; @Mock - private AUser suggesterUser; + private User suggesterUser; @Mock - private Member suggesterMember; + private Member member; + + @Mock + private Message message; private static final Long SUGGESTER_ID = 8L; private static final Long SERVER_ID = 3L; private static final Long CHANNEL_ID = 7L; private static final Long SUGGESTION_ID = 5L; + private static final Long USER_ID = 6L; @Test public void testCreateSuggestionMessage() { String suggestionText = "text"; - SuggestionLog log = Mockito.mock(SuggestionLog.class); - when(suggestionCreator.getGuild()).thenReturn(guild); when(guild.getIdLong()).thenReturn(SERVER_ID); - when(serverManagementService.loadServer(suggestionCreator.getGuild())).thenReturn(server); + when(serverManagementService.loadServer(SERVER_ID)).thenReturn(server); MessageToSend messageToSend = Mockito.mock(MessageToSend.class); - when(templateService.renderEmbedTemplate(eq(SuggestionServiceBean.SUGGESTION_LOG_TEMPLATE), any(SuggestionLog.class), eq(SERVER_ID))).thenReturn(messageToSend); + when(templateService.renderEmbedTemplate(eq(SuggestionServiceBean.SUGGESTION_CREATION_TEMPLATE), any(SuggestionLog.class), eq(SERVER_ID))).thenReturn(messageToSend); Message suggestionMessage = Mockito.mock(Message.class); when(counterService.getNextCounterValue(server, SuggestionServiceBean.SUGGESTION_COUNTER_KEY)).thenReturn(SUGGESTION_ID); - AUserInAServer aUserInAServer = Mockito.mock(AUserInAServer.class); - when(userInServerManagementService.loadOrCreateUser(suggestionCreator)).thenReturn(aUserInAServer); List> postingFutures = Arrays.asList(CompletableFuture.completedFuture(suggestionMessage)); when(postTargetService.sendEmbedInPostTarget(messageToSend, SuggestionPostTarget.SUGGESTION, SERVER_ID)).thenReturn(postingFutures); - testUnit.createSuggestionMessage(suggestionCreator, suggestionText, log); + when(message.getMember()).thenReturn(member); + when(member.getGuild()).thenReturn(guild); + when(member.getIdLong()).thenReturn(SUGGESTER_ID); + testUnit.createSuggestionMessage(message, suggestionText); verify(reactionService, times(1)).addReactionToMessageAsync(SuggestionServiceBean.SUGGESTION_YES_EMOTE, SERVER_ID, suggestionMessage); verify(reactionService, times(1)).addReactionToMessageAsync(SuggestionServiceBean.SUGGESTION_NO_EMOTE, SERVER_ID, suggestionMessage); } @Test public void testCreateSuggestion() { - when(suggesterMember.getGuild()).thenReturn(guild); + when(member.getGuild()).thenReturn(guild); when(guild.getId()).thenReturn("5"); String text = "text"; Message message = Mockito.mock(Message.class); - testUnit.persistSuggestionInDatabase(suggesterMember, text, message, SUGGESTION_ID); - verify(suggestionManagementService, times(1)).createSuggestion(suggesterMember, text, message, SUGGESTION_ID); + Message commandMessage = Mockito.mock(Message.class); + testUnit.persistSuggestionInDatabase(member, text, message, SUGGESTION_ID, commandMessage); + verify(suggestionManagementService, times(1)).createSuggestion(member, text, message, SUGGESTION_ID, commandMessage); } @Test public void testAcceptExistingSuggestion() { - executeAcceptWithMember(suggesterMember); + executeAcceptWithMember(); } @Test(expected = SuggestionNotFoundException.class) public void testAcceptNotExistingSuggestion() { - when(suggestionManagementService.getSuggestion(SUGGESTION_ID, SERVER_ID)).thenReturn(Optional.empty()); - SuggestionLog log = Mockito.mock(SuggestionLog.class); - when(log.getGuild()).thenReturn(guild); + when(suggestionManagementService.getSuggestion(SERVER_ID, SUGGESTION_ID)).thenThrow(new SuggestionNotFoundException(SUGGESTION_ID)); when(guild.getIdLong()).thenReturn(SERVER_ID); - testUnit.acceptSuggestion(SUGGESTION_ID, CLOSING_TEXT, log); + when(message.getGuild()).thenReturn(guild); + testUnit.acceptSuggestion(SUGGESTION_ID, message, CLOSING_TEXT); } @Test - public void testAcceptSuggestionWithMemberLeavingGuild() { - executeAcceptWithMember(null); - } - - @Test(expected = ChannelNotInGuildException.class) - public void testAcceptSuggestionInNoTextChannel() { - setupForNoTextChannel(); + public void testUpdateSuggestionMessage() { SuggestionLog log = Mockito.mock(SuggestionLog.class); - when(log.getGuild()).thenReturn(guild); - when(guild.getIdLong()).thenReturn(SERVER_ID); - testUnit.acceptSuggestion(SUGGESTION_ID, CLOSING_TEXT, log); - } - - private void setupForNoTextChannel() { - Long messageId = 7L; - Suggestion suggestionToAccept = Mockito.mock(Suggestion.class); - when(suggestionToAccept.getChannel()).thenReturn(channel); - when(suggestionToAccept.getServer()).thenReturn(server); - when(suggestionToAccept.getSuggester()).thenReturn(suggester); - ServerSpecificId suggestionId = Mockito.mock(ServerSpecificId.class); - when(suggestionId.getId()).thenReturn(SUGGESTION_ID); - when(suggestionToAccept.getSuggestionId()).thenReturn(suggestionId); - when(suggestionToAccept.getMessageId()).thenReturn(messageId); - when(server.getId()).thenReturn(SERVER_ID); - when(channel.getId()).thenReturn(CHANNEL_ID); - when(channelService.getTextChannelFromServer(SERVER_ID, CHANNEL_ID)).thenThrow(new ChannelNotInGuildException(CHANNEL_ID)); - when(suggestionManagementService.getSuggestion(SUGGESTION_ID, SERVER_ID)).thenReturn(Optional.of(suggestionToAccept)); - } - - @Test(expected = SuggestionUpdateException.class) - public void testUpdateSuggestionTextWithoutEmbed() { - SuggestionLog log = Mockito.mock(SuggestionLog.class); - Message suggestionMessage = Mockito.mock(Message.class); - testUnit.updateSuggestionMessageText(CLOSING_TEXT, log, suggestionMessage); - } - - @Test - public void testUpdateSuggestionMessageWithEmbed() { - SuggestionLog log = Mockito.mock(SuggestionLog.class); - when(log.getGuild()).thenReturn(guild); - MessageEmbed embed = Mockito.mock(MessageEmbed.class); - when(embed.getDescription()).thenReturn("description"); - Message suggestionMessage = Mockito.mock(Message.class); - when(suggestionMessage.getGuild()).thenReturn(guild); - when(guild.getIdLong()).thenReturn(SERVER_ID); - when(suggestionMessage.getEmbeds()).thenReturn(Arrays.asList(embed)); + when(log.getServerId()).thenReturn(SERVER_ID); MessageToSend updatedMessage = Mockito.mock(MessageToSend.class); - when(templateService.renderEmbedTemplate(eq(SuggestionServiceBean.SUGGESTION_LOG_TEMPLATE), any(SuggestionLog.class), eq(SERVER_ID))).thenReturn(updatedMessage); - testUnit.updateSuggestionMessageText(CLOSING_TEXT, log, suggestionMessage); + when(templateService.renderEmbedTemplate(eq(SuggestionServiceBean.SUGGESTION_UPDATE_TEMPLATE), any(SuggestionLog.class), eq(SERVER_ID))).thenReturn(updatedMessage); + testUnit.updateSuggestionMessageText(CLOSING_TEXT, log); verify(postTargetService, times(1)).sendEmbedInPostTarget(updatedMessage, SuggestionPostTarget.SUGGESTION, SERVER_ID); } @Test public void testRejectExistingSuggestion() { - executeRejectWithMember(suggesterMember); + executeRejectWithMember(); } @Test(expected = SuggestionNotFoundException.class) public void testRejectNotExistingSuggestion() { - when(suggestionManagementService.getSuggestion(SUGGESTION_ID, SERVER_ID)).thenReturn(Optional.empty()); - SuggestionLog log = Mockito.mock(SuggestionLog.class); - when(log.getGuild()).thenReturn(guild); + when(suggestionManagementService.getSuggestion(SERVER_ID, SUGGESTION_ID)).thenThrow(new SuggestionNotFoundException(SUGGESTION_ID)); when(guild.getIdLong()).thenReturn(SERVER_ID); - testUnit.rejectSuggestion(SUGGESTION_ID, CLOSING_TEXT, log); + when(message.getGuild()).thenReturn(guild); + testUnit.rejectSuggestion(SUGGESTION_ID, message, CLOSING_TEXT); } - @Test - public void testRejectSuggestionWithMemberLeavingGuild() { - executeRejectWithMember(null); - } - - @Test(expected = ChannelNotInGuildException.class) - public void testRejectSuggestionInNoTextChannel() { - setupForNoTextChannel(); - SuggestionLog log = Mockito.mock(SuggestionLog.class); - when(log.getGuild()).thenReturn(guild); - when(guild.getIdLong()).thenReturn(SERVER_ID); - testUnit.rejectSuggestion(SUGGESTION_ID, CLOSING_TEXT, log); - } - - private void executeAcceptWithMember(Member actualMember) { + private void executeAcceptWithMember() { Long messageId = 7L; - SuggestionLog log = Mockito.mock(SuggestionLog.class); - when(log.getGuild()).thenReturn(guild); when(guild.getIdLong()).thenReturn(SERVER_ID); Suggestion suggestionToAccept = setupClosing(messageId); - Message suggestionMessage = Mockito.mock(Message.class); - when(channelService.retrieveMessageInChannel(textChannel, messageId)).thenReturn(CompletableFuture.completedFuture(suggestionMessage)); - when(memberService.getMemberInServerAsync(SERVER_ID, SUGGESTER_ID)).thenReturn(CompletableFuture.completedFuture(actualMember)); - testUnit.acceptSuggestion(SUGGESTION_ID, CLOSING_TEXT, log); + when(message.getGuild()).thenReturn(guild); + when(message.getMember()).thenReturn(member); + testUnit.acceptSuggestion(SUGGESTION_ID, message, CLOSING_TEXT); verify(suggestionManagementService, times(1)).setSuggestionState(suggestionToAccept, SuggestionState.ACCEPTED); } - private void executeRejectWithMember(Member actualMember) { + private void executeRejectWithMember() { Long messageId = 7L; - SuggestionLog log = Mockito.mock(SuggestionLog.class); - when(log.getGuild()).thenReturn(guild); when(guild.getIdLong()).thenReturn(SERVER_ID); Suggestion suggestionToAccept = setupClosing(messageId); - Message suggestionMessage = Mockito.mock(Message.class); - when(channelService.retrieveMessageInChannel(textChannel, messageId)).thenReturn(CompletableFuture.completedFuture(suggestionMessage)); - when(memberService.getMemberInServerAsync(SERVER_ID, SUGGESTER_ID)).thenReturn(CompletableFuture.completedFuture(actualMember)); - testUnit.rejectSuggestion(SUGGESTION_ID, CLOSING_TEXT, log); + when(message.getGuild()).thenReturn(guild); + when(message.getMember()).thenReturn(member); + testUnit.rejectSuggestion(SUGGESTION_ID, message, CLOSING_TEXT); verify(suggestionManagementService, times(1)).setSuggestionState(suggestionToAccept, SuggestionState.REJECTED); } @@ -256,16 +194,17 @@ public class SuggestionServiceBeanTest { when(suggestionToAccept.getChannel()).thenReturn(channel); when(suggestionToAccept.getServer()).thenReturn(server); when(suggestionToAccept.getSuggester()).thenReturn(suggester); + AUser aUser = Mockito.mock(AUser.class); + when(aUser.getId()).thenReturn(USER_ID); + when(suggester.getUserReference()).thenReturn(aUser); ServerSpecificId suggestionId = Mockito.mock(ServerSpecificId.class); when(suggestionId.getId()).thenReturn(SUGGESTION_ID); when(suggestionToAccept.getSuggestionId()).thenReturn(suggestionId); when(suggestionToAccept.getMessageId()).thenReturn(messageId); when(server.getId()).thenReturn(SERVER_ID); when(channel.getId()).thenReturn(CHANNEL_ID); - when(suggester.getUserReference()).thenReturn(suggesterUser); - when(suggesterUser.getId()).thenReturn(SUGGESTER_ID); - when(suggestionManagementService.getSuggestion(SUGGESTION_ID, SERVER_ID)).thenReturn(Optional.of(suggestionToAccept)); - when(channelService.getTextChannelFromServer(SERVER_ID, CHANNEL_ID)).thenReturn(textChannel); + when(userService.retrieveUserForId(USER_ID)).thenReturn(CompletableFuture.completedFuture(suggesterUser)); + when(suggestionManagementService.getSuggestion(SERVER_ID, SUGGESTION_ID)).thenReturn(suggestionToAccept); return suggestionToAccept; } } diff --git a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/test/java/dev/sheldan/abstracto/suggestion/service/management/SuggestionManagementServiceBeanTest.java b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/test/java/dev/sheldan/abstracto/suggestion/service/management/SuggestionManagementServiceBeanTest.java index 5190ad0e8..e9f1d8767 100644 --- a/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/test/java/dev/sheldan/abstracto/suggestion/service/management/SuggestionManagementServiceBeanTest.java +++ b/abstracto-application/abstracto-modules/suggestion/suggestion-impl/src/test/java/dev/sheldan/abstracto/suggestion/service/management/SuggestionManagementServiceBeanTest.java @@ -74,7 +74,9 @@ public class SuggestionManagementServiceBeanTest { ArgumentCaptor suggestionArgumentCaptor = ArgumentCaptor.forClass(Suggestion.class); Suggestion savedSuggestion = Mockito.mock(Suggestion.class); when(suggestionRepository.save(suggestionArgumentCaptor.capture())).thenReturn(savedSuggestion); - Suggestion createdSuggestion = testUnit.createSuggestion(aUserInAServer, text, message, suggestionId); + Message commandMessage = Mockito.mock(Message.class); + when(commandMessage.getChannel()).thenReturn(messageChannel); + Suggestion createdSuggestion = testUnit.createSuggestion(aUserInAServer, text, message, suggestionId, commandMessage); Assert.assertEquals(savedSuggestion, createdSuggestion); Suggestion capturedSuggestion = suggestionArgumentCaptor.getValue(); Assert.assertEquals(SuggestionState.NEW, capturedSuggestion.getState()); @@ -100,7 +102,9 @@ public class SuggestionManagementServiceBeanTest { ArgumentCaptor suggestionArgumentCaptor = ArgumentCaptor.forClass(Suggestion.class); Suggestion savedSuggestion = Mockito.mock(Suggestion.class); when(suggestionRepository.save(suggestionArgumentCaptor.capture())).thenReturn(savedSuggestion); - Suggestion createdSuggestion = testUnit.createSuggestion(member, text, message, suggestionId); + Message commandMessage = Mockito.mock(Message.class); + when(commandMessage.getChannel()).thenReturn(messageChannel); + Suggestion createdSuggestion = testUnit.createSuggestion(member, text, message, suggestionId, commandMessage); Assert.assertEquals(savedSuggestion, createdSuggestion); Suggestion capturedSuggestion = suggestionArgumentCaptor.getValue(); Assert.assertEquals(SuggestionState.NEW, capturedSuggestion.getState()); @@ -112,7 +116,7 @@ public class SuggestionManagementServiceBeanTest { public void testGetSuggestion() { Suggestion foundSuggestion = createSuggestion(); when(suggestionRepository.findById(new ServerSpecificId(SERVER_ID, SUGGESTION_ID))).thenReturn(Optional.of(foundSuggestion)); - Optional suggestionOptional = testUnit.getSuggestion(SUGGESTION_ID, SERVER_ID); + Optional suggestionOptional = testUnit.getSuggestionOptional(SERVER_ID, SUGGESTION_ID); Assert.assertTrue(suggestionOptional.isPresent()); suggestionOptional.ifPresent(suggestion -> Assert.assertEquals(SUGGESTION_ID, suggestion.getSuggestionId().getId().longValue())); } @@ -120,7 +124,7 @@ public class SuggestionManagementServiceBeanTest { @Test public void testGetSuggestionNotFound() { when(suggestionRepository.findById(new ServerSpecificId(SERVER_ID, SUGGESTION_ID))).thenReturn(Optional.empty()); - Optional suggestionOptional = testUnit.getSuggestion(SUGGESTION_ID, SERVER_ID); + Optional suggestionOptional = testUnit.getSuggestionOptional(SERVER_ID, SUGGESTION_ID); Assert.assertFalse(suggestionOptional.isPresent()); } diff --git a/abstracto-application/abstracto-modules/suggestion/suggestion-int/src/main/java/dev/sheldan/abstracto/suggestion/exception/SuggestionUpdateException.java b/abstracto-application/abstracto-modules/suggestion/suggestion-int/src/main/java/dev/sheldan/abstracto/suggestion/exception/UnSuggestNotPossibleException.java similarity index 57% rename from abstracto-application/abstracto-modules/suggestion/suggestion-int/src/main/java/dev/sheldan/abstracto/suggestion/exception/SuggestionUpdateException.java rename to abstracto-application/abstracto-modules/suggestion/suggestion-int/src/main/java/dev/sheldan/abstracto/suggestion/exception/UnSuggestNotPossibleException.java index bb3f5ca89..b3bd56e58 100644 --- a/abstracto-application/abstracto-modules/suggestion/suggestion-int/src/main/java/dev/sheldan/abstracto/suggestion/exception/SuggestionUpdateException.java +++ b/abstracto-application/abstracto-modules/suggestion/suggestion-int/src/main/java/dev/sheldan/abstracto/suggestion/exception/UnSuggestNotPossibleException.java @@ -3,14 +3,14 @@ package dev.sheldan.abstracto.suggestion.exception; import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException; import dev.sheldan.abstracto.core.templating.Templatable; -public class SuggestionUpdateException extends AbstractoRunTimeException implements Templatable { - public SuggestionUpdateException() { - super("Not possible to update suggestion."); +public class UnSuggestNotPossibleException extends AbstractoRunTimeException implements Templatable { + public UnSuggestNotPossibleException() { + super("Not possible to remove suggestion."); } @Override public String getTemplateName() { - return "suggestion_update_exception"; + return "un_suggest_not_possible_exception"; } @Override diff --git a/abstracto-application/abstracto-modules/suggestion/suggestion-int/src/main/java/dev/sheldan/abstracto/suggestion/model/database/Suggestion.java b/abstracto-application/abstracto-modules/suggestion/suggestion-int/src/main/java/dev/sheldan/abstracto/suggestion/model/database/Suggestion.java index f7ef383f0..673e270d5 100644 --- a/abstracto-application/abstracto-modules/suggestion/suggestion-int/src/main/java/dev/sheldan/abstracto/suggestion/model/database/Suggestion.java +++ b/abstracto-application/abstracto-modules/suggestion/suggestion-int/src/main/java/dev/sheldan/abstracto/suggestion/model/database/Suggestion.java @@ -44,10 +44,6 @@ public class Suggestion implements Serializable { @JoinColumn(name = "server_id", referencedColumnName = "id", nullable = false) private AServer server; - @Getter - @Column(name = "suggestion_date") - private Instant suggestionDate; - @Getter @Enumerated(EnumType.STRING) @Column(name = "state") @@ -59,4 +55,15 @@ public class Suggestion implements Serializable { @Column(name = "updated") private Instant updated; + @Column(name = "suggestion_text") + private String suggestionText; + + @Getter + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "command_channel_id") + private AChannel commandChannel; + + @Column(name = "command_message_id") + private Long commandMessageId; + } diff --git a/abstracto-application/abstracto-modules/suggestion/suggestion-int/src/main/java/dev/sheldan/abstracto/suggestion/model/database/SuggestionState.java b/abstracto-application/abstracto-modules/suggestion/suggestion-int/src/main/java/dev/sheldan/abstracto/suggestion/model/database/SuggestionState.java index 443f2d9ab..9e004f617 100644 --- a/abstracto-application/abstracto-modules/suggestion/suggestion-int/src/main/java/dev/sheldan/abstracto/suggestion/model/database/SuggestionState.java +++ b/abstracto-application/abstracto-modules/suggestion/suggestion-int/src/main/java/dev/sheldan/abstracto/suggestion/model/database/SuggestionState.java @@ -1,5 +1,5 @@ package dev.sheldan.abstracto.suggestion.model.database; public enum SuggestionState { - NEW, ACCEPTED, REJECTED + NEW, ACCEPTED, REJECTED, VETOED } diff --git a/abstracto-application/abstracto-modules/suggestion/suggestion-int/src/main/java/dev/sheldan/abstracto/suggestion/model/template/SuggestionLog.java b/abstracto-application/abstracto-modules/suggestion/suggestion-int/src/main/java/dev/sheldan/abstracto/suggestion/model/template/SuggestionLog.java index 7a1ea2f4c..64b4a75a4 100644 --- a/abstracto-application/abstracto-modules/suggestion/suggestion-int/src/main/java/dev/sheldan/abstracto/suggestion/model/template/SuggestionLog.java +++ b/abstracto-application/abstracto-modules/suggestion/suggestion-int/src/main/java/dev/sheldan/abstracto/suggestion/model/template/SuggestionLog.java @@ -1,25 +1,32 @@ package dev.sheldan.abstracto.suggestion.model.template; -import dev.sheldan.abstracto.core.models.context.UserInitiatedServerContext; import dev.sheldan.abstracto.core.models.database.AUserInAServer; +import dev.sheldan.abstracto.core.utils.MessageUtils; import dev.sheldan.abstracto.suggestion.model.database.SuggestionState; import lombok.Getter; import lombok.Setter; import lombok.experimental.SuperBuilder; import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.User; @Getter @Setter @SuperBuilder -// TODO change user initiated context so slim context, and remove database entities referenced -public class SuggestionLog extends UserInitiatedServerContext { +public class SuggestionLog { private Long suggestionId; private SuggestionState state; - private Member suggester; + private User suggester; + private Member member; private AUserInAServer suggesterUser; private String text; + private Message message; private String reason; - private Long originalMessageId; + private Long serverId; private Long originalChannelId; - private String originalMessageUrl; + private Long originalMessageId; + + public String getOriginalMessageUrl() { + return MessageUtils.buildMessageUrl(serverId, originalChannelId , originalMessageId); + } } diff --git a/abstracto-application/abstracto-modules/suggestion/suggestion-int/src/main/java/dev/sheldan/abstracto/suggestion/service/SuggestionService.java b/abstracto-application/abstracto-modules/suggestion/suggestion-int/src/main/java/dev/sheldan/abstracto/suggestion/service/SuggestionService.java index e37f998db..0f0a90dae 100644 --- a/abstracto-application/abstracto-modules/suggestion/suggestion-int/src/main/java/dev/sheldan/abstracto/suggestion/service/SuggestionService.java +++ b/abstracto-application/abstracto-modules/suggestion/suggestion-int/src/main/java/dev/sheldan/abstracto/suggestion/service/SuggestionService.java @@ -1,12 +1,15 @@ package dev.sheldan.abstracto.suggestion.service; -import dev.sheldan.abstracto.suggestion.model.template.SuggestionLog; import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Message; import java.util.concurrent.CompletableFuture; public interface SuggestionService { - CompletableFuture createSuggestionMessage(Member member, String text, SuggestionLog log); - CompletableFuture acceptSuggestion(Long suggestionId, String text, SuggestionLog log); - CompletableFuture rejectSuggestion(Long suggestionId, String text, SuggestionLog log); + CompletableFuture createSuggestionMessage(Message commandMessage, String text); + CompletableFuture acceptSuggestion(Long suggestionId, Message commandMessage, String text); + CompletableFuture vetoSuggestion(Long suggestionId, Message commandMessage, String text); + CompletableFuture rejectSuggestion(Long suggestionId, Message commandMessage, String text); + CompletableFuture removeSuggestion(Long suggestionId, Member member); + void cleanUpSuggestions(); } diff --git a/abstracto-application/abstracto-modules/suggestion/suggestion-int/src/main/java/dev/sheldan/abstracto/suggestion/service/management/SuggestionManagementService.java b/abstracto-application/abstracto-modules/suggestion/suggestion-int/src/main/java/dev/sheldan/abstracto/suggestion/service/management/SuggestionManagementService.java index 07ae70866..14303873a 100644 --- a/abstracto-application/abstracto-modules/suggestion/suggestion-int/src/main/java/dev/sheldan/abstracto/suggestion/service/management/SuggestionManagementService.java +++ b/abstracto-application/abstracto-modules/suggestion/suggestion-int/src/main/java/dev/sheldan/abstracto/suggestion/service/management/SuggestionManagementService.java @@ -6,11 +6,18 @@ import dev.sheldan.abstracto.suggestion.model.database.SuggestionState; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Message; +import java.time.Instant; +import java.util.List; import java.util.Optional; public interface SuggestionManagementService { - Suggestion createSuggestion(Member suggester, String text, Message message, Long suggestionId); - Suggestion createSuggestion(AUserInAServer suggester, String text, Message message, Long suggestionId); - Optional getSuggestion(Long suggestionId, Long serverId); + Suggestion createSuggestion(Member suggester, String text, Message message, Long suggestionId, Message commandMessage); + Suggestion createSuggestion(AUserInAServer suggester, String text, Message message, Long suggestionId, Message commandMessage); + Optional getSuggestionOptional(Long serverId, Long suggestionId); + Suggestion getSuggestion(Long serverId, Long suggestionId); void setSuggestionState(Suggestion suggestion, SuggestionState newState); + void deleteSuggestion(Long serverId, Long suggestionId); + void deleteSuggestion(List suggestions); + void deleteSuggestion(Suggestion suggestion); + List getSuggestionsUpdatedBeforeNotNew(Instant date); } diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interactive/InteractiveServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interactive/InteractiveServiceBean.java index 801e77f10..0e44ad0f4 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interactive/InteractiveServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/interactive/InteractiveServiceBean.java @@ -51,7 +51,7 @@ public class InteractiveServiceBean implements InteractiveService { @Override public void createMessageWithResponse(MessageToSend messageToSend, AUserInAServer responder, AChannel channel, Long messageId, Consumer action, Runnable finalAction) { - channelService.sendMessageToSendToAChannel(messageToSend, channel); + channelService.sendMessageEmbedToSendToAChannel(messageToSend, channel); Long userId = responder.getUserReference().getId(); eventWaiter.waitForEvent(MessageReceivedEvent.class, event -> { if(event != null) { diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/ChannelServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/ChannelServiceBean.java index 9eb05bc73..8e9012e61 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/ChannelServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/ChannelServiceBean.java @@ -184,7 +184,7 @@ public class ChannelServiceBean implements ChannelService { } @Override - public List> sendMessageToSendToAChannel(MessageToSend messageToSend, AChannel channel) { + public List> sendMessageEmbedToSendToAChannel(MessageToSend messageToSend, AChannel channel) { Optional textChannelFromServer = getTextChannelFromServerOptional(channel.getServer().getId(), channel.getId()); if(textChannelFromServer.isPresent()) { return sendMessageToSendToChannel(messageToSend, textChannelFromServer.get()); @@ -193,7 +193,7 @@ public class ChannelServiceBean implements ChannelService { } @Override - public CompletableFuture sendMessageToSendToAChannel(MessageToSend messageToSend, AChannel channel, Integer embedIndex) { + public CompletableFuture sendMessageEmbedToSendToAChannel(MessageToSend messageToSend, AChannel channel, Integer embedIndex) { return sendEmbedToAChannel(messageToSend.getEmbeds().get(embedIndex), channel); } @@ -244,9 +244,15 @@ public class ChannelServiceBean implements ChannelService { } } Set allowedMentions = getAllowedMentionsFor(textChannel, messageToSend); - allMessageActions.forEach(messageAction -> - futures.add(messageAction.allowedMentions(allowedMentions).submit()) - ); + allMessageActions.forEach(messageAction -> { + if(messageToSend.getReferencedMessageId() != null) { + messageAction = messageAction.referenceById(messageToSend.getReferencedMessageId()); + if(messageToSend.getMessageConfig() != null && !messageToSend.getMessageConfig().isMentionsReferencedMessage()) { + messageAction = messageAction.mentionRepliedUser(false); + } + } + futures.add(messageAction.allowedMentions(allowedMentions).submit()); + }); return futures; } @@ -284,6 +290,9 @@ public class ChannelServiceBean implements ChannelService { throw new IllegalArgumentException("Message to send did not contain anything to send."); } } + if(messageToSend.getReferencedMessageId() != null) { + messageAction = messageAction.referenceById(messageToSend.getReferencedMessageId()); + } metricService.incrementCounter(MESSAGE_EDIT_METRIC); return messageAction.submit(); } diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/MessageServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/MessageServiceBean.java index 1dbe5fd9d..64aba5d62 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/MessageServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/service/MessageServiceBean.java @@ -80,7 +80,7 @@ public class MessageServiceBean implements MessageService { @Override public CompletableFuture createStatusMessage(MessageToSend messageToSend, AChannel channel) { - return channelService.sendMessageToSendToAChannel(messageToSend, channel).get(0); + return channelService.sendMessageEmbedToSendToAChannel(messageToSend, channel).get(0); } @Override diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/EmbedConfiguration.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/EmbedConfiguration.java index 4f97d7cf3..a23eabf17 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/EmbedConfiguration.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/EmbedConfiguration.java @@ -51,6 +51,7 @@ public class EmbedConfiguration { * The {@link OffsetDateTime} object used as the time stamp in the {@link net.dv8tion.jda.api.entities.MessageEmbed} */ private OffsetDateTime timeStamp; + private Long referencedMessageId; /** * The message which is posted along the {@link net.dv8tion.jda.api.entities.MessageEmbed} as a normal message. */ diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/MetaEmbedConfiguration.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/MetaEmbedConfiguration.java index 6e1b7b166..fe605687d 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/MetaEmbedConfiguration.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/model/MetaEmbedConfiguration.java @@ -16,4 +16,6 @@ public class MetaEmbedConfiguration { private boolean allowsEveryoneMention; @Builder.Default private boolean allowsUserMention = true; + @Builder.Default + private boolean mentionsReferencedMessage = true; } diff --git a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/service/TemplateServiceBean.java b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/service/TemplateServiceBean.java index 9e88a638a..021ee977d 100644 --- a/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/service/TemplateServiceBean.java +++ b/abstracto-application/core/core-impl/src/main/java/dev/sheldan/abstracto/core/templating/service/TemplateServiceBean.java @@ -164,11 +164,13 @@ public class TemplateServiceBean implements TemplateService { log.info("Limiting size of messages. Max allowed: {}, currently: {}.", messageLimit, messages.size()); messages.subList(messageLimit.intValue(), messages.size()).clear(); } + Long referencedMessageId = embedConfiguration.getReferencedMessageId(); return MessageToSend.builder() .embeds(embeds) .messageConfig(createMessageConfig(embedConfiguration.getMetaConfig())) .messages(messages) + .referencedMessageId(referencedMessageId) .build(); } @@ -181,6 +183,7 @@ public class TemplateServiceBean implements TemplateService { .allowsEveryoneMention(metaEmbedConfiguration.isAllowsEveryoneMention()) .allowsUserMention(metaEmbedConfiguration.isAllowsUserMention()) .allowsRoleMention(metaEmbedConfiguration.isAllowsRoleMention()) + .mentionsReferencedMessage(metaEmbedConfiguration.isMentionsReferencedMessage()) .build(); } diff --git a/abstracto-application/core/core-impl/src/main/resources/abstracto.properties b/abstracto-application/core/core-impl/src/main/resources/abstracto.properties index d0c55cb35..54c5b93ba 100644 --- a/abstracto-application/core/core-impl/src/main/resources/abstracto.properties +++ b/abstracto-application/core/core-impl/src/main/resources/abstracto.properties @@ -4,7 +4,7 @@ abstracto.eventWaiter.threads=10 server.port=8080 abstracto.allowedmention.everyone=false -abstracto.allowedmention.role=true +abstracto.allowedmention.role=false abstracto.allowedmention.user=true abstracto.systemConfigs.prefix.name=prefix diff --git a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/service/ChannelService.java b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/service/ChannelService.java index df55ad6c3..7f6ea8543 100644 --- a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/service/ChannelService.java +++ b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/service/ChannelService.java @@ -21,8 +21,8 @@ public interface ChannelService { CompletableFuture sendEmbedToAChannel(MessageEmbed embed, AChannel channel); CompletableFuture sendEmbedToChannel(MessageEmbed embed, MessageChannel channel); MessageAction sendEmbedToChannelInComplete(MessageEmbed embed, MessageChannel channel); - List> sendMessageToSendToAChannel(MessageToSend messageToSend, AChannel channel); - CompletableFuture sendMessageToSendToAChannel(MessageToSend messageToSend, AChannel channel, Integer embedIndex); + List> sendMessageEmbedToSendToAChannel(MessageToSend messageToSend, AChannel channel); + CompletableFuture sendMessageEmbedToSendToAChannel(MessageToSend messageToSend, AChannel channel, Integer embedIndex); CompletableFuture retrieveMessageInChannel(Long serverId, Long channelId, Long messageId); CompletableFuture retrieveMessageInChannel(MessageChannel channel, Long messageId); diff --git a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/templating/model/MessageConfig.java b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/templating/model/MessageConfig.java index 9acbcab82..de649dc0d 100644 --- a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/templating/model/MessageConfig.java +++ b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/templating/model/MessageConfig.java @@ -12,4 +12,6 @@ public class MessageConfig { private boolean allowsEveryoneMention; @Builder.Default private boolean allowsUserMention = true; + @Builder.Default + private boolean mentionsReferencedMessage = true; } diff --git a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/templating/model/MessageToSend.java b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/templating/model/MessageToSend.java index 2cc11d905..82bf8f04c 100644 --- a/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/templating/model/MessageToSend.java +++ b/abstracto-application/core/core-int/src/main/java/dev/sheldan/abstracto/core/templating/model/MessageToSend.java @@ -30,6 +30,7 @@ public class MessageToSend { */ private File fileToSend; private MessageConfig messageConfig; + private Long referencedMessageId; public boolean hasFileToSend() { return fileToSend != null; diff --git a/abstracto-application/documentation/src/main/docs/asciidoc/modules/utility.adoc b/abstracto-application/documentation/src/main/docs/asciidoc/modules/utility.adoc index 4601d7538..ef003ea50 100644 --- a/abstracto-application/documentation/src/main/docs/asciidoc/modules/utility.adoc +++ b/abstracto-application/documentation/src/main/docs/asciidoc/modules/utility.adoc @@ -76,13 +76,19 @@ Creating a suggestion:: * Usage: `suggest ` * Description: Posts the text to the `suggest` post target and places the emotes for up and down voting. Accepting a suggestion:: -* Usage: `accept [note]` -* Description: Re-posts the suggestion identified by `suggestionId` and marks the suggestion as accepted. The optional `note` will be used in this re-post, if provided. +* Usage: `accept [reason]` +* Description: Re-posts the suggestion identified by `suggestionId` and marks the suggestion as accepted. The optional `reason` will be used in this re-post, if provided. * Example: `accept 1 okay` in order to accept the suggestion `1` with the reason `okay` Rejecting a suggestion:: -* Usage: `reject [note]` -* Description: Re-posts the suggestion identified by `suggestionId` and marks the suggestion as denied. The optional `note` will be used in this re-post, if provided. +* Usage: `reject [reason]` +* Description: Re-posts the suggestion identified by `suggestionId` and marks the suggestion as denied. The optional `reason` will be used in this re-post, if provided. * Example: `deny 1 not okay` in order to reject the suggestion `1` with the reason `not okay` +Removing a suggestion you created:: +* Usage: `unSuggest ` +* Description: This will delete the suggestion identified by `suggestionId` from the channel and the database, but this is only possible within a specicied time range. +Vetoing a suggestion:: +* Usage : `veto [reason]` +* Description: This command will veto the suggestion, this means, it should be indicated that the suggestion was not rejected by votes, but because it was not acceptable on a fundamental level. This is basically just a different state of the suggestion. === Miscellaneous