[AB-57] [AB-61] reworked commands and services to work with completable futures and moved the database operations to the very last operation so we have transaction safety in more areas

added some cache annotations to the default repository functions
reworked how the undo cations are processed within commands, they are executed in a post command listener when the state is error
added a counter id to generate ids to be unique within servers, changed a few tables to be unique within a server
added future utils class for wrapping a list of futures into one
moved abstracto tables to separate schema in the installer
refactored experience gain to work with more futures and delayed database access
This commit is contained in:
Sheldan
2020-09-20 11:11:20 +02:00
parent 552ecc26b8
commit 76adda90a3
220 changed files with 2691 additions and 1498 deletions

View File

@@ -90,6 +90,7 @@ public class CommandReceivedHandler extends ListenerAdapter {
CommandContext.CommandContextBuilder commandContextBuilder = CommandContext.builder()
.author(event.getMember())
.guild(event.getGuild())
.undoActions(new ArrayList<>())
.channel(event.getTextChannel())
.message(event.getMessage())
.jda(event.getJDA())
@@ -130,6 +131,7 @@ public class CommandReceivedHandler extends ListenerAdapter {
.channel(event.getTextChannel())
.message(event.getMessage())
.jda(event.getJDA())
.undoActions(commandContext.getUndoActions()) // TODO really do this? it would need to guarantee that its available and usable
.userInitiatedContext(rebuildUserContext)
.parameters(parsedParameters).build();
CommandResult failedResult = CommandResult.fromError(throwable.getMessage(), throwable);
@@ -140,7 +142,10 @@ public class CommandReceivedHandler extends ListenerAdapter {
commandResult = self.executeCommand(foundCommand, commandContext);
}
} else {
commandResult = CommandResult.fromCondition(conditionResult);
// TODO can it be done nicer?
if(conditionResult.getException() != null) {
throw conditionResult.getException();
}
}
if(commandResult != null) {
self.executePostCommandListener(foundCommand, commandContext, commandResult);

View File

@@ -0,0 +1,33 @@
package dev.sheldan.abstracto.core.command.post;
import dev.sheldan.abstracto.core.command.Command;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.execution.ResultState;
import dev.sheldan.abstracto.core.command.service.PostCommandExecution;
import dev.sheldan.abstracto.core.service.UndoActionService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class UndoActionPostExecution implements PostCommandExecution {
@Autowired
private UndoActionService undoActionService;
@Override
public void execute(CommandContext commandContext, CommandResult commandResult, Command command) {
ResultState result = commandResult.getResult();
if(result.equals(ResultState.ERROR) && commandContext.getUndoActions() != null && !commandContext.getUndoActions().isEmpty()) {
undoActionService.performActionsFuture(commandContext.getUndoActions()).whenComplete((aVoid, undoThrowable) -> {
if(undoThrowable != null) {
log.warn("Undo actions failed.", undoThrowable);
} else {
log.info("Successfully executed undo actions.");
}
});
}
}
}

View File

@@ -14,5 +14,6 @@ public interface CommandRepository extends JpaRepository<ACommand, Long> {
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
Optional<ACommand> findByNameIgnoreCase(String name);
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
boolean existsByNameIgnoreCase(String name);
}

View File

@@ -15,6 +15,7 @@ import dev.sheldan.abstracto.core.models.template.commands.PostTargetModelEntry;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.service.management.PostTargetManagement;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.templating.service.TemplateService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild;
@@ -27,6 +28,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Service
@Slf4j
@@ -47,7 +49,7 @@ public class PostTargetCommand extends AbstractConditionableCommand {
private ChannelService channelService;
@Override
public CommandResult execute(CommandContext commandContext) {
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
if(commandContext.getParameters().getParameters().isEmpty()) {
PostTargetDisplayModel posttargetDisplayModel = (PostTargetDisplayModel) ContextConverter.fromCommandContext(commandContext, PostTargetDisplayModel.class);
AServer server = commandContext.getUserInitiatedContext().getServer();
@@ -67,8 +69,8 @@ public class PostTargetCommand extends AbstractConditionableCommand {
postTargetEntries.add(postTargetEntry);
}
});
channelService.sendEmbedTemplateInChannel(POST_TARGET_SHOW_TARGETS, posttargetDisplayModel, commandContext.getChannel());
return CommandResult.fromSuccess();
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInChannel(POST_TARGET_SHOW_TARGETS, posttargetDisplayModel, commandContext.getChannel()))
.thenApply(aVoid -> CommandResult.fromSuccess());
}
String targetName = (String) commandContext.getParameters().getParameters().get(0);
if(!postTargetService.validPostTarget(targetName)) {
@@ -78,7 +80,7 @@ public class PostTargetCommand extends AbstractConditionableCommand {
Guild guild = channel.getGuild();
postTargetManagement.createOrUpdate(targetName, guild.getIdLong(), channel.getIdLong());
log.info("Setting posttarget {} in {} to {}", targetName, guild.getIdLong(), channel.getId());
return CommandResult.fromSuccess();
return CompletableFuture.completedFuture(CommandResult.fromSuccess());
}
@Override
@@ -91,6 +93,7 @@ public class PostTargetCommand extends AbstractConditionableCommand {
.name("posttarget")
.module(ChannelsModuleInterface.CHANNELS)
.parameters(parameters)
.async(true)
.supportsEmbedException(true)
.help(helpInfo)
.templated(true)

View File

@@ -22,6 +22,7 @@ import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
public class Disable extends AbstractConditionableCommand {
@@ -40,12 +41,13 @@ public class Disable extends AbstractConditionableCommand {
@Override
public CommandResult execute(CommandContext commandContext) {
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
if(commandContext.getParameters().getParameters().isEmpty()) {
EnableModel model = (EnableModel) ContextConverter.fromCommandContext(commandContext, EnableModel.class);
model.setFeatures(featureConfigService.getAllFeatures());
String response = templateService.renderTemplate("disable_features_response", model);
channelService.sendTextToChannelNoFuture(response, commandContext.getChannel());
return channelService.sendTextToChannel(response, commandContext.getChannel())
.thenApply(aVoid -> CommandResult.fromSuccess());
} else {
String flagKey = (String) commandContext.getParameters().getParameters().get(0);
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(flagKey);
@@ -55,8 +57,8 @@ public class Disable extends AbstractConditionableCommand {
featureFlagService.disableFeature(featureDisplay, commandContext.getUserInitiatedContext().getServer())
);
}
return CompletableFuture.completedFuture(CommandResult.fromSuccess());
}
return CommandResult.fromSuccess();
}
@Override
@@ -68,6 +70,7 @@ public class Disable extends AbstractConditionableCommand {
.name("disable")
.module(ConfigModuleInterface.CONFIG)
.parameters(parameters)
.async(true)
.help(helpInfo)
.templated(true)
.supportsEmbedException(true)

View File

@@ -23,6 +23,7 @@ import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
public class Enable extends AbstractConditionableCommand {
@@ -40,18 +41,19 @@ public class Enable extends AbstractConditionableCommand {
private TemplateService templateService;
@Override
public CommandResult execute(CommandContext commandContext) {
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
if(commandContext.getParameters().getParameters().isEmpty()) {
EnableModel model = (EnableModel) ContextConverter.fromCommandContext(commandContext, EnableModel.class);
model.setFeatures(featureConfigService.getAllFeatures());
String response = templateService.renderTemplate("enable_features_response", model);
channelService.sendTextToChannelNoFuture(response, commandContext.getChannel());
return channelService.sendTextToChannel(response, commandContext.getChannel())
.thenApply(message -> CommandResult.fromSuccess());
} else {
String flagKey = (String) commandContext.getParameters().getParameters().get(0);
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(flagKey);
FeatureValidationResult featureSetup = featureConfigService.validateFeatureSetup(feature, commandContext.getUserInitiatedContext().getServer());
if(Boolean.FALSE.equals(featureSetup.getValidationResult())) {
channelService.sendTextToChannelNoFuture(templateService.renderTemplatable(featureSetup), commandContext.getChannel());
channelService.sendTextToChannelNotAsync(templateService.renderTemplatable(featureSetup), commandContext.getChannel());
}
featureFlagService.enableFeature(feature, commandContext.getUserInitiatedContext().getServer());
if(feature.getRequiredFeatures() != null) {
@@ -59,8 +61,8 @@ public class Enable extends AbstractConditionableCommand {
featureFlagService.enableFeature(featureDisplay, commandContext.getUserInitiatedContext().getServer());
});
}
return CompletableFuture.completedFuture(CommandResult.fromSuccess());
}
return CommandResult.fromSuccess();
}
@Override
@@ -72,6 +74,7 @@ public class Enable extends AbstractConditionableCommand {
.name("enable")
.module(ConfigModuleInterface.CONFIG)
.parameters(parameters)
.async(true)
.supportsEmbedException(true)
.templated(true)
.help(helpInfo)

View File

@@ -9,10 +9,14 @@ import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.models.template.commands.PingModel;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.templating.service.TemplateService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;
@Service
public class Ping implements Command {
@@ -21,13 +25,18 @@ public class Ping implements Command {
@Autowired
private TemplateService templateService;
@Autowired
private MessageService messageService;
@Autowired
private ChannelService channelService;
@Override
public CommandResult execute(CommandContext commandContext) {
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
long ping = commandContext.getJda().getGatewayPing();
PingModel model = PingModel.builder().latency(ping).build();
String text = templateService.renderTemplate(PING_TEMPLATE, model);
commandContext.getChannel().sendMessage(text).queue();
return CommandResult.fromSuccess();
return channelService.sendTextTemplateInChannel(PING_TEMPLATE, model, commandContext.getChannel())
.thenApply(message -> CommandResult.fromSuccess());
}
@Override
@@ -37,6 +46,7 @@ public class Ping implements Command {
.name("ping")
.module(UtilityModuleInterface.UTILITY)
.templated(true)
.async(true)
.help(helpInfo)
.causesReaction(false)
.build();

View File

@@ -16,5 +16,6 @@ public interface ChannelGroupRepository extends JpaRepository<AChannelGroup, Lon
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
List<AChannelGroup> findByServer(AServer server);
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
boolean existsByGroupNameAndServer(String name, AServer server);
}

View File

@@ -1,16 +1,24 @@
package dev.sheldan.abstracto.core.repository;
import dev.sheldan.abstracto.core.models.database.AChannel;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Repository;
import javax.persistence.QueryHint;
import java.util.List;
import java.util.Optional;
@Repository
public interface ChannelRepository extends JpaRepository<AChannel, Long> {
@NotNull
@Override
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
List<AChannel> findAll();
Optional<AChannel> findById(@NonNull Long aLong);
@Override
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
boolean existsById(@NonNull Long aLong);
}

View File

@@ -14,6 +14,9 @@ public interface ConfigRepository extends JpaRepository<AConfig, Long> {
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
AConfig findAConfigByServerIdAndName(Long serverId, String name);
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
boolean existsAConfigByServerIdAndName(Long serverId, String name);
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
boolean existsAConfigByServerAndName(AServer server, String name);
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.abstracto.core.repository;
import dev.sheldan.abstracto.core.models.CounterId;
import dev.sheldan.abstracto.core.models.database.Counter;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface CounterRepository extends JpaRepository<Counter, CounterId> {
@Query(value = "SELECT next_counter(:counterKey, :serverId)", nativeQuery = true)
Long getNewCounterForKey(@Param("serverId") Long serverId, @Param("counterKey") String counterKey);
}

View File

@@ -2,10 +2,16 @@ package dev.sheldan.abstracto.core.repository;
import dev.sheldan.abstracto.core.models.database.ADefaultConfig;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.stereotype.Repository;
import javax.persistence.QueryHint;
@Repository
public interface DefaultConfigRepository extends JpaRepository<ADefaultConfig, Long> {
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
ADefaultConfig findByName(String name);
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
boolean existsByName(String name);
}

View File

@@ -1,10 +1,21 @@
package dev.sheldan.abstracto.core.repository;
import dev.sheldan.abstracto.core.models.database.DefaultEmote;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.stereotype.Repository;
import javax.persistence.QueryHint;
import java.util.List;
@Repository
public interface DefaultEmoteRepository extends JpaRepository<DefaultEmote, Long> {
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
DefaultEmote getByEmoteKey(String emoteKey);
@NotNull
@Override
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
List<DefaultEmote> findAll();
}

View File

@@ -1,9 +1,18 @@
package dev.sheldan.abstracto.core.repository;
import dev.sheldan.abstracto.core.models.database.DefaultFeatureFlag;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.stereotype.Repository;
import javax.persistence.QueryHint;
import java.util.List;
@Repository
public interface DefaultFeatureFlagRepository extends JpaRepository<DefaultFeatureFlag, Long> {
@NotNull
@Override
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
List<DefaultFeatureFlag> findAll();
}

View File

@@ -1,9 +1,18 @@
package dev.sheldan.abstracto.core.repository;
import dev.sheldan.abstracto.core.models.database.DefaultPostTarget;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.stereotype.Repository;
import javax.persistence.QueryHint;
import java.util.List;
@Repository
public interface DefaultPostTargetRepository extends JpaRepository<DefaultPostTarget, Long> {
@NotNull
@Override
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
List<DefaultPostTarget> findAll();
}

View File

@@ -2,8 +2,10 @@ package dev.sheldan.abstracto.core.repository;
import dev.sheldan.abstracto.core.models.database.AEmote;
import dev.sheldan.abstracto.core.models.database.AServer;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Repository;
import javax.persistence.QueryHint;
@@ -26,4 +28,9 @@ public interface EmoteRepository extends JpaRepository<AEmote, Integer> {
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
boolean existsByEmoteIdAndServerRef(String emoteId, AServer server);
@NotNull
@Override
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
Optional<AEmote> findById(@NonNull Integer aLong);
}

View File

@@ -5,10 +5,16 @@ import dev.sheldan.abstracto.core.models.database.AFeatureFlag;
import dev.sheldan.abstracto.core.models.database.AFeatureMode;
import dev.sheldan.abstracto.core.models.database.AServer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.stereotype.Repository;
import javax.persistence.QueryHint;
@Repository
public interface FeatureModeRepository extends JpaRepository<AFeatureMode, Long> {
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
AFeatureMode findByFeatureFlag(AFeatureFlag featureFlag);
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
boolean existsByFeatureFlag_ServerAndFeatureFlag_Feature(AServer server, AFeature feature);
}

View File

@@ -15,8 +15,10 @@ public interface PostTargetRepository extends JpaRepository<PostTarget, Long> {
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
PostTarget findPostTargetByNameAndServerReference(String name, AServer server);
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
boolean existsByNameAndServerReference(String name, AServer server);
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
List<PostTarget> findByServerReference(AServer server);
}

View File

@@ -1,9 +1,19 @@
package dev.sheldan.abstracto.core.repository;
import dev.sheldan.abstracto.core.models.database.ARole;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Repository;
import javax.persistence.QueryHint;
import java.util.Optional;
@Repository
public interface RoleRepository extends JpaRepository<ARole, Long> {
@NotNull
@Override
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
Optional<ARole> findById(@NonNull Long aLong);
}

View File

@@ -1,9 +1,30 @@
package dev.sheldan.abstracto.core.repository;
import dev.sheldan.abstracto.core.models.database.AServer;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Repository;
import javax.persistence.QueryHint;
import java.util.List;
import java.util.Optional;
@Repository
public interface ServerRepository extends JpaRepository<AServer, Long> {
@NotNull
@Override
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
Optional<AServer> findById(@NonNull Long aLong);
@Override
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
boolean existsById(@NonNull Long aLong);
@NotNull
@Override
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
List<AServer> findAll();
}

View File

@@ -1,9 +1,23 @@
package dev.sheldan.abstracto.core.repository;
import dev.sheldan.abstracto.core.models.database.AUser;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Repository;
import javax.persistence.QueryHint;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<AUser, Long> {
@NotNull
@Override
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
Optional<AUser> findById(@NonNull Long aLong);
@Override
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
boolean existsById(@NonNull Long aLong);
}

View File

@@ -32,12 +32,12 @@ public class ChannelServiceBean implements ChannelService {
private TemplateService templateService;
@Override
public void sendTextToAChannelNoFuture(String text, AChannel channel) {
public void sendTextToAChannelNotAsync(String text, AChannel channel) {
sendTextToAChannel(text, channel);
}
@Override
public void sendTextToChannelNoFuture(String text, MessageChannel channel) {
public void sendTextToChannelNotAsync(String text, MessageChannel channel) {
sendTextToChannel(text, channel);
}
@@ -232,6 +232,12 @@ public class ChannelServiceBean implements ChannelService {
return sendMessageToSendToChannel(messageToSend, channel);
}
@Override
public CompletableFuture<Message> sendTextTemplateInChannel(String templateKey, Object model, MessageChannel channel) {
String text = templateService.renderTemplate(templateKey, model);
return sendTextToChannel(text, channel);
}
@Override
public CompletableFuture<TextChannel> createTextChannel(String name, AServer server, Long categoryId) {
Optional<Guild> guildById = botService.getGuildById(server.getId());

View File

@@ -0,0 +1,18 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.repository.CounterRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class CounterServiceBean implements CounterService {
@Autowired
private CounterRepository counterRepository;
@Override
public Long getNextCounterValue(AServer server, String key) {
return counterRepository.getNewCounterForKey(server.getId(), key);
}
}

View File

@@ -2,21 +2,20 @@ package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.exception.ConfiguredEmoteNotUsableException;
import dev.sheldan.abstracto.core.exception.EmoteNotInServerException;
import dev.sheldan.abstracto.core.exception.GuildNotFoundException;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.management.EmoteManagementService;
import dev.sheldan.abstracto.core.models.database.AEmote;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.templating.model.MessageToSend;
import dev.sheldan.abstracto.templating.service.TemplateService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Component
@@ -38,6 +37,9 @@ public class MessageServiceBean implements MessageService {
@Autowired
private MessageServiceBean self;
@Autowired
private TemplateService templateService;
@Override
public void addReactionToMessage(String emoteKey, Long serverId, Message message) {
addReactionToMessageWithFuture(emoteKey, serverId, message);
@@ -45,25 +47,14 @@ public class MessageServiceBean implements MessageService {
@Override
public CompletableFuture<Void> addReactionToMessageWithFuture(String emoteKey, Long serverId, Message message) {
AEmote emote = emoteService.getEmoteOrDefaultEmote(emoteKey, serverId);
Optional<Guild> guildByIdOptional = botService.getGuildById(serverId);
if(guildByIdOptional.isPresent()) {
Guild guild = guildByIdOptional.get();
if(Boolean.TRUE.equals(emote.getCustom())) {
Emote emoteById = botService.getInstance().getEmoteById(emote.getEmoteId());
if(emoteById != null) {
return message.addReaction(emoteById).submit();
} else {
log.error("Emote with key {} and id {} for guild {} was not found.", emoteKey, emote.getEmoteId(), guild.getId());
throw new ConfiguredEmoteNotUsableException(emote);
}
} else {
return message.addReaction(emote.getEmoteKey()).submit();
}
} else {
log.error("Cannot add reaction, guild not found {}", serverId);
throw new GuildNotFoundException(serverId);
}
Guild guild = botService.getGuildByIdNullable(serverId);
return addReactionToMessageWithFuture(emoteKey, guild, message);
}
@Override
public CompletableFuture<Void> addReactionToMessageWithFuture(String emoteKey, Guild guild, Message message) {
AEmote emote = emoteService.getEmoteOrDefaultEmote(emoteKey, guild.getIdLong());
return addReactionToMessageWithFuture(emote, guild, message);
}
@Override
@@ -75,6 +66,21 @@ public class MessageServiceBean implements MessageService {
}
}
@Override
public CompletableFuture<Void> addReactionToMessageWithFuture(AEmote emote, Guild guild, Message message) {
if(Boolean.TRUE.equals(emote.getCustom())) {
Emote emoteById = botService.getInstance().getEmoteById(emote.getEmoteId());
if(emoteById != null) {
return message.addReaction(emoteById).submit();
} else {
log.error("Emote with key {} and id {} for guild {} was not found.", emote.getName() , emote.getEmoteId(), guild.getId());
throw new ConfiguredEmoteNotUsableException(emote);
}
} else {
return message.addReaction(emote.getEmoteKey()).submit();
}
}
@Override
public CompletableFuture<Void> addReactionToMessageWithFuture(Long emoteId, Long serverId, Message message) {
Emote emoteById = botService.getInstance().getEmoteById(emoteId);
@@ -224,30 +230,31 @@ public class MessageServiceBean implements MessageService {
}
@Override
public void sendMessageToUser(AUserInAServer userInAServer, String text, MessageChannel feedbackChannel) {
public CompletableFuture<Message> sendMessageToUser(AUserInAServer userInAServer, String text) {
Member memberInServer = botService.getMemberInServer(userInAServer);
sendMessageToUser(memberInServer.getUser(), text, feedbackChannel);
return sendMessageToUser(memberInServer.getUser(), text);
}
@Override
public void sendMessageToUser(User user, String text, MessageChannel feedbackChannel) {
CompletableFuture<Message> messageFuture = new CompletableFuture<>();
user.openPrivateChannel().queue(privateChannel ->
privateChannel.sendMessage(text).queue(messageFuture::complete, messageFuture::completeExceptionally)
);
messageFuture.exceptionally(e -> {
log.warn("Failed to send message. ", e);
if(feedbackChannel != null){
self.sendFeedbackAboutException(e, feedbackChannel);
}
return null;
});
public CompletableFuture<Message> sendTemplateToUser(User user, String template, Object model) {
String message = templateService.renderTemplate(template, model);
return sendMessageToUser(user, message);
}
@Transactional
public void sendFeedbackAboutException(Throwable e, MessageChannel feedbackChannel) {
channelService.sendTextToChannelNoFuture(String.format("Failed to send message: %s", e.getMessage()), feedbackChannel);
@Override
public CompletableFuture<Void> sendEmbedToUser(User user, String template, Object model) {
return user.openPrivateChannel().submit().thenCompose(privateChannel ->
FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInChannel(template, model, privateChannel)));
}
@Override
public CompletableFuture<Message> sendEmbedToUserWithMessage(User user, String template, Object model) {
return user.openPrivateChannel().submit().thenCompose(privateChannel ->
channelService.sendEmbedTemplateInChannel(template, model, privateChannel).get(0));
}
@Override
public CompletableFuture<Message> sendMessageToUser(User user, String text) {
return user.openPrivateChannel().flatMap(privateChannel -> privateChannel.sendMessage(text)).submit();
}
}

View File

@@ -2,11 +2,14 @@ package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.models.UndoAction;
import dev.sheldan.abstracto.core.models.UndoActionInstance;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
@@ -20,6 +23,12 @@ public class UndoActionServiceBean implements UndoActionService {
@Override
public void performActions(List<UndoActionInstance> actionsToPerform) {
performActionsFuture(actionsToPerform);
}
@Override
public CompletableFuture<Void> performActionsFuture(List<UndoActionInstance> actionsToPerform) {
List<CompletableFuture<Void>> futures = new ArrayList<>();
actionsToPerform.forEach(undoActionInstance -> {
UndoAction action = undoActionInstance.getAction();
List<Long> ids = undoActionInstance.getIds();
@@ -28,19 +37,20 @@ public class UndoActionServiceBean implements UndoActionService {
log.error("Not the correct amount of ids provided for the channel deletion undo action.");
return;
}
deleteChannel(ids.get(0), ids.get(1));
futures.add(deleteChannel(ids.get(0), ids.get(1)));
} else if(action.equals(UndoAction.DELETE_MESSAGE)) {
if(ids.size() != 2) {
log.error("Not the correct amount of ids provided for the message deletion undo action.");
return;
}
botService.deleteMessage(ids.get(0), ids.get(1));
futures.add(botService.deleteMessage(ids.get(0), ids.get(1)));
}
});
return FutureUtils.toSingleFutureGeneric(futures);
}
private void deleteChannel(Long serverId, Long channelId) {
channelService.deleteTextChannel(serverId, channelId).exceptionally(throwable -> {
private CompletableFuture<Void> deleteChannel(Long serverId, Long channelId) {
return channelService.deleteTextChannel(serverId, channelId).exceptionally(throwable -> {
log.error("Failed to execute undo action channel delete for channel {} in server {}", channelId, serverId, throwable);
return null;
});

View File

@@ -1,5 +1,6 @@
package dev.sheldan.abstracto.core.service.management;
import dev.sheldan.abstracto.core.exception.UserInServerNotFoundException;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUser;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
@@ -48,10 +49,15 @@ public class UserInServerManagementServiceBean implements UserInServerManagement
}
@Override
public Optional<AUserInAServer> loadUser(Long userInServerId) {
public Optional<AUserInAServer> loadUserConditional(Long userInServerId) {
return userInServerRepository.findById(userInServerId);
}
@Override
public AUserInAServer loadUser(Long userInServerId) {
return loadUserConditional(userInServerId).orElseThrow(() -> new UserInServerNotFoundException(userInServerId));
}
@Override
public AUserInAServer createUserInServer(Member member) {
return this.createUserInServer(member.getGuild().getIdLong(), member.getIdLong());

View File

@@ -8,4 +8,5 @@
http://www.liquibase.org/xml/ns/pro ../dbchangelog-3.8.xsd" >
<include file="core-tables/tables.xml" relativeToChangelogFile="true"/>
<include file="core-seedData/data.xml" relativeToChangelogFile="true"/>
<include file="core-functions/functions.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,14 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.xsd" >
<changeSet author="me" id="counter_function" dbms="postgres">
<sqlFile encoding="utf8" path="sql/counter_function.sql"
relativeToChangelogFile="true"
stripComments="false"/>
</changeSet>
</databaseChangeLog>

View File

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

View File

@@ -0,0 +1,30 @@
CREATE OR REPLACE FUNCTION next_counter(p_counter_key VARCHAR(255), p_server_id BIGINT)
RETURNS bigint as $new_count$
DECLARE v_next_count bigint;
v_exists int;
BEGIN
SELECT count(1)
FROM COUNTER
INTO v_exists
WHERE server_reference = p_server_id
AND counter_key = p_counter_key;
IF v_exists >= 1 THEN
SELECT MAX(counter) + 1
INTO v_next_count
FROM counter
WHERE server_reference = p_server_id
AND counter_key = p_counter_key;
UPDATE counter
SET counter = v_next_count
WHERE server_reference = p_server_id
AND counter_key = p_counter_key;
ELSE
v_next_count := 1;
INSERT INTO counter (counter_key, server_reference, counter)
VALUES (p_counter_key, p_server_id, v_next_count);
END IF;
RETURN v_next_count;
END;
$new_count$ LANGUAGE plpgsql;

View File

@@ -0,0 +1,29 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.xsd" >
<changeSet author="Sheldan" id="counters-table">
<createTable tableName="counter">
<column autoIncrement="true" name="counter" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="counter_key" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
</createTable>
<addPrimaryKey
columnNames="counter_key, server_id"
constraintName="pk_counter"
tableName="counter"/>
</changeSet>
<changeSet author="Sheldan" id="counter-counter_server">
<addForeignKeyConstraint baseColumnNames="server_id" baseTableName="counter" constraintName="fk_counter_server" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id" referencedTableName="server" validate="true"/>
</changeSet>
</databaseChangeLog>

View File

@@ -23,5 +23,6 @@
<include file="feature_mode.xml" relativeToChangelogFile="true"/>
<include file="lock.xml" relativeToChangelogFile="true"/>
<include file="system_config.xml" relativeToChangelogFile="true"/>
<include file="users.xml" relativeToChangelogFile="true"/>
<include file="auser.xml" relativeToChangelogFile="true"/>
<include file="counter.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>