moved post targets to an enum, in order to have more type safety, this might be changed in the future

added validation check when a feature is enabled, in order to notify which configuration is missing for this feature to function properly
This commit is contained in:
Sheldan
2020-05-15 17:43:39 +02:00
parent e4efc26740
commit f11232de05
60 changed files with 514 additions and 62 deletions

View File

@@ -13,7 +13,6 @@ import dev.sheldan.abstracto.core.command.service.management.FeatureManagementSe
import dev.sheldan.abstracto.core.commands.config.ConfigModuleInterface;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.models.database.AFeature;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.service.management.RoleManagementService;

View File

@@ -12,6 +12,7 @@ import dev.sheldan.abstracto.core.commands.config.ConfigModuleInterface;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.models.FeatureValidationResult;
import dev.sheldan.abstracto.core.models.template.commands.EnableModel;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
@@ -44,6 +45,10 @@ public class Enable extends AbstractConditionableCommand {
} else {
String flagKey = (String) commandContext.getParameters().getParameters().get(0);
FeatureConfig feature = featureFlagService.getFeatureDisplayForFeature(flagKey);
FeatureValidationResult featureSetup = featureFlagService.validateFeatureSetup(feature, commandContext.getUserInitiatedContext().getServer());
if(!featureSetup.getValidationResult()) {
channelService.sendTextToChannelNoFuture(templateService.renderTemplatable(featureSetup), commandContext.getChannel());
}
featureFlagService.enableFeature(feature, commandContext.getUserInitiatedContext().getServer());
if(feature.getRequiredFeatures() != null) {
feature.getRequiredFeatures().forEach(featureDisplay -> {

View File

@@ -1,16 +1,12 @@
package dev.sheldan.abstracto.core.commands.help;
import dev.sheldan.abstracto.core.command.*;
import dev.sheldan.abstracto.core.command.condition.CommandCondition;
import dev.sheldan.abstracto.core.command.condition.ConditionResult;
import dev.sheldan.abstracto.core.command.condition.ConditionalCommand;
import dev.sheldan.abstracto.core.command.config.*;
import dev.sheldan.abstracto.core.command.execution.*;
import dev.sheldan.abstracto.core.command.models.database.ACommand;
import dev.sheldan.abstracto.core.command.models.database.ACommandInAServer;
import dev.sheldan.abstracto.core.command.service.CommandRegistry;
import dev.sheldan.abstracto.core.command.service.CommandService;
import dev.sheldan.abstracto.core.command.service.CommandServiceBean;
import dev.sheldan.abstracto.core.command.service.ModuleRegistry;
import dev.sheldan.abstracto.core.command.service.management.CommandInServerManagementService;
import dev.sheldan.abstracto.core.command.service.management.CommandManagementService;

View File

@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.core.repository;
import dev.sheldan.abstracto.core.models.database.AConfig;
import dev.sheldan.abstracto.core.models.database.AServer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.QueryHints;
@@ -10,4 +11,7 @@ public interface ConfigRepository extends JpaRepository<AConfig, Long> {
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
AConfig findAConfigByServerIdAndName(Long serverId, String name);
boolean existsAConfigByServerIdAndName(Long serverId, String name);
boolean existsAConfigByServerAndName(AServer server, String name);
}

View File

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

View File

@@ -3,8 +3,10 @@ package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.command.service.management.FeatureManagementService;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.config.PostTargetEnum;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.exception.FeatureNotFoundException;
import dev.sheldan.abstracto.core.models.FeatureValidationResult;
import dev.sheldan.abstracto.core.models.database.AFeature;
import dev.sheldan.abstracto.core.models.database.AFeatureFlag;
import dev.sheldan.abstracto.core.models.database.AServer;
@@ -15,6 +17,7 @@ import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@Component
@@ -32,6 +35,9 @@ public class FeatureFlagServiceBean implements FeatureFlagService {
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private FeatureValidatorService featureValidatorService;
@Override
public boolean isFeatureEnabled(FeatureConfig name, Long serverId) {
@@ -121,6 +127,17 @@ public class FeatureFlagServiceBean implements FeatureFlagService {
}
throw new AbstractoRunTimeException(String.format("Feature %s not found.", key));
}
@Override
public PostTargetEnum getPostTargetEnumByKey(String key) {
Predicate<PostTargetEnum> postTargetComparison = postTargetEnum -> postTargetEnum.getKey().equals(key);
Optional<FeatureConfig> foundFeature = availableFeatures.stream().filter(featureDisplay -> featureDisplay.getRequiredPostTargets().stream().anyMatch(postTargetComparison)).findAny();
if(foundFeature.isPresent()) {
return foundFeature.get().getRequiredPostTargets().stream().filter(postTargetComparison).findAny().get();
}
throw new AbstractoRunTimeException(String.format("Post target %s not found.", key));
}
@Override
public boolean getFeatureFlagValue(FeatureEnum key, Long serverId) {
AServer server = serverManagementService.loadOrCreate(serverId);
@@ -145,4 +162,19 @@ public class FeatureFlagServiceBean implements FeatureFlagService {
AFeature feature = featureManagementService.getFeature(key.getKey());
return managementService.setFeatureFlagValue(feature, server, newValue);
}
@Override
public FeatureValidationResult validateFeatureSetup(FeatureConfig featureConfig, AServer server) {
FeatureValidationResult featureValidationResult = FeatureValidationResult.validationSuccessful(featureConfig);
featureConfig.getRequiredPostTargets().forEach(s -> {
featureValidatorService.checkPostTarget(s, server, featureValidationResult);
});
featureConfig.getRequiredSystemConfigKeys().forEach(s -> {
featureValidatorService.checkSystemConfig(s, server, featureValidationResult);
});
featureConfig.getAdditionalFeatureValidators().forEach(featureValidator -> {
featureValidator.featureIsSetup(featureConfig, server, featureValidationResult);
});
return featureValidationResult;
}
}

View File

@@ -0,0 +1,42 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.config.PostTargetEnum;
import dev.sheldan.abstracto.core.models.FeatureValidationResult;
import dev.sheldan.abstracto.core.models.PostTargetValidationError;
import dev.sheldan.abstracto.core.models.SystemConfigValidationError;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.management.ConfigManagementService;
import dev.sheldan.abstracto.core.service.management.PostTargetManagement;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class FeatureValidationServiceBean implements FeatureValidatorService {
@Autowired
private PostTargetManagement postTargetManagement;
@Autowired
private ConfigManagementService configService;
@Override
public void checkPostTarget(PostTargetEnum name, AServer server, FeatureValidationResult featureValidationResult) {
if(!postTargetManagement.postTargetExists(name.getKey(), server)) {
PostTargetValidationError validationError = PostTargetValidationError.builder().postTargetName(name.getKey()).build();
featureValidationResult.setValidationResult(false);
featureValidationResult.getValidationErrors().add(validationError);
}
}
@Override
public void checkSystemConfig(String name, AServer server, FeatureValidationResult featureValidationResult) {
if(!configService.configExists(server, name)) {
SystemConfigValidationError validationError = SystemConfigValidationError.builder().configKey(name).build();
featureValidationResult.setValidationResult(false);
featureValidationResult.getValidationErrors().add(validationError);
}
}
}

View File

@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.config.DynamicKeyLoader;
import dev.sheldan.abstracto.core.config.PostTargetEnum;
import dev.sheldan.abstracto.core.exception.ChannelNotFoundException;
import dev.sheldan.abstracto.core.exception.GuildException;
import dev.sheldan.abstracto.core.exception.PostTargetNotFoundException;
@@ -65,30 +66,30 @@ public class PostTargetServiceBean implements PostTargetService {
}
}
private PostTarget getPostTarget(String postTargetName, Long serverId) {
PostTarget postTarget = postTargetManagement.getPostTarget(postTargetName, serverId);
private PostTarget getPostTarget(PostTargetEnum postTargetName, Long serverId) {
PostTarget postTarget = postTargetManagement.getPostTarget(postTargetName.getKey(), serverId);
if(postTarget != null) {
return postTarget;
} else {
log.error("PostTarget {} in server {} was not found!", postTargetName, serverId);
throw new PostTargetNotFoundException(postTargetName);
throw new PostTargetNotFoundException(postTargetName.getKey());
}
}
@Override
public CompletableFuture<Message> sendTextInPostTarget(String text, String postTargetName, Long serverId) {
PostTarget postTarget = this.getPostTarget(postTargetName, serverId);
public CompletableFuture<Message> sendTextInPostTarget(String text, PostTargetEnum postTargetEnum, Long serverId) {
PostTarget postTarget = this.getPostTarget(postTargetEnum, serverId);
return this.sendTextInPostTarget(text, postTarget);
}
@Override
public CompletableFuture<Message> sendEmbedInPostTarget(MessageEmbed embed, String postTargetName, Long serverId) {
public CompletableFuture<Message> sendEmbedInPostTarget(MessageEmbed embed, PostTargetEnum postTargetName, Long serverId) {
PostTarget postTarget = this.getPostTarget(postTargetName, serverId);
return this.sendEmbedInPostTarget(embed, postTarget);
}
@Override
public CompletableFuture<Message> sendMessageInPostTarget(Message message, String postTargetName, Long serverId) {
public CompletableFuture<Message> sendMessageInPostTarget(Message message, PostTargetEnum postTargetName, Long serverId) {
PostTarget postTarget = this.getPostTarget(postTargetName, serverId);
return sendMessageInPostTarget(message, postTarget);
}
@@ -99,7 +100,7 @@ public class PostTargetServiceBean implements PostTargetService {
}
@Override
public List<CompletableFuture<Message>> sendEmbedInPostTarget(MessageToSend message, String postTargetName, Long serverId) {
public List<CompletableFuture<Message>> sendEmbedInPostTarget(MessageToSend message, PostTargetEnum postTargetName, Long serverId) {
PostTarget postTarget = this.getPostTarget(postTargetName, serverId);
return this.sendEmbedInPostTarget(message, postTarget);
}
@@ -163,21 +164,21 @@ public class PostTargetServiceBean implements PostTargetService {
}
@Override
public void editOrCreatedInPostTarget(Long messageId, MessageToSend messageToSend, String postTargetName, Long serverId, List<CompletableFuture<Message>> future) {
public void editOrCreatedInPostTarget(Long messageId, MessageToSend messageToSend, PostTargetEnum postTargetName, Long serverId, List<CompletableFuture<Message>> future) {
PostTarget postTarget = this.getPostTarget(postTargetName, serverId);
this.editOrCreatedInPostTarget(messageId, messageToSend, postTarget, future);
}
@Override
public void throwIfPostTargetIsNotDefined(String name, Long serverId) {
PostTarget postTarget = postTargetManagement.getPostTarget(name, serverId);
public void throwIfPostTargetIsNotDefined(PostTargetEnum name, Long serverId) {
PostTarget postTarget = postTargetManagement.getPostTarget(name.getKey(), serverId);
if(postTarget == null) {
throw new PostTargetNotValidException(name, dynamicKeyLoader.getPostTargetsAsList());
throw new PostTargetNotValidException(name.getKey(), dynamicKeyLoader.getPostTargetsAsList());
}
}
@Override
public List<CompletableFuture<Message>> editEmbedInPostTarget(Long messageId, MessageToSend message, String postTargetName, Long serverId) {
public List<CompletableFuture<Message>> editEmbedInPostTarget(Long messageId, MessageToSend message, PostTargetEnum postTargetName, Long serverId) {
PostTarget postTarget = this.getPostTarget(postTargetName, serverId);
return editEmbedInPostTarget(messageId, message, postTarget);
}

View File

@@ -112,7 +112,12 @@ public class ConfigManagementServiceBean implements ConfigManagementService {
@Override
public boolean configExists(Long serverId, String name) {
return loadConfig(serverId, name) != null;
return configRepository.existsAConfigByServerIdAndName(serverId, name);
}
@Override
public boolean configExists(AServer server, String name) {
return configRepository.existsAConfigByServerAndName(server, name);
}
@Override

View File

@@ -80,6 +80,17 @@ public class PostTargetManagementBean implements PostTargetManagement {
return getPostTarget(name, server);
}
@Override
public Boolean postTargetExists(String name, AServer server) {
return postTargetRepository.existsByNameAndServerReference(name, server);
}
@Override
public Boolean postTargetExists(String name, Long serverId) {
AServer dbServer = serverManagementService.loadOrCreate(serverId);
return postTargetExists(name, dbServer);
}
@Override
public PostTarget updatePostTarget(PostTarget target, AServer server, AChannel newTargetChannel) {
target.setChannelReference(newTargetChannel);

View File

@@ -0,0 +1,6 @@
<#assign featureKey><#include "${featureTemplate}"></#assign><#include "feature_not_setup_message_text">
<#list errors as error>
<#include "${error.templateName}">
</#list>

View File

@@ -1,5 +1,7 @@
package dev.sheldan.abstracto.core.config;
import dev.sheldan.abstracto.core.service.FeatureValidator;
import java.util.Collections;
import java.util.List;
@@ -11,4 +13,7 @@ public interface FeatureConfig {
default List<FeatureConfig> getDependantFeatures() {
return Collections.emptyList();
}
default List<PostTargetEnum> getRequiredPostTargets() { return Collections.emptyList();}
default List<String> getRequiredSystemConfigKeys() { return Collections.emptyList();}
default List<FeatureValidator> getAdditionalFeatureValidators() { return Collections.emptyList(); }
}

View File

@@ -0,0 +1,5 @@
package dev.sheldan.abstracto.core.config;
public interface PostTargetEnum {
String getKey();
}

View File

@@ -0,0 +1,46 @@
package dev.sheldan.abstracto.core.models;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.templating.Templatable;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.GeneratedValue;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@Builder
@Getter
@Setter
public class FeatureValidationResult implements Templatable {
private FeatureConfig feature;
private Boolean validationResult;
@Builder.Default
private List<ValidationError> validationErrors = new ArrayList<>();
public static FeatureValidationResult validationSuccessful(FeatureConfig featureConfig) {
return FeatureValidationResult
.builder()
.feature(featureConfig)
.validationResult(true)
.build();
}
@Override
public String getTemplateName() {
return "feature_not_setup_message";
}
@Override
public Object getTemplateModel() {
HashMap<String, Object> params = new HashMap<>();
params.put("featureTemplate", this.feature.getFeature().getKey() + "_feature");
params.put("errors", this.validationErrors);
return params;
}
}

View File

@@ -0,0 +1,27 @@
package dev.sheldan.abstracto.core.models;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.HashMap;
@Getter
@Setter
@Builder
public class PostTargetValidationError implements ValidationError {
private String postTargetName;
@Override
public String getTemplateName() {
return "post_target_not_setup";
}
@Override
public Object getTemplateModel() {
HashMap<String, String> params = new HashMap<>();
params.put("postTargetKey", postTargetName);
return params;
}
}

View File

@@ -0,0 +1,27 @@
package dev.sheldan.abstracto.core.models;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.HashMap;
@Getter
@Setter
@Builder
public class SystemConfigValidationError implements ValidationError {
private String configKey;
@Override
public String getTemplateName() {
return "config_key_not_setup";
}
@Override
public Object getTemplateModel() {
HashMap<String, String> params = new HashMap<>();
params.put("configKey", configKey);
return params;
}
}

View File

@@ -0,0 +1,6 @@
package dev.sheldan.abstracto.core.models;
import dev.sheldan.abstracto.templating.Templatable;
public interface ValidationError extends Templatable {
}

View File

@@ -2,6 +2,8 @@ package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.config.PostTargetEnum;
import dev.sheldan.abstracto.core.models.FeatureValidationResult;
import dev.sheldan.abstracto.core.models.database.AFeatureFlag;
import dev.sheldan.abstracto.core.models.database.AServer;
@@ -21,8 +23,10 @@ public interface FeatureFlagService {
boolean doesFeatureExist(FeatureConfig name);
List<String> getFeaturesAsList();
FeatureEnum getFeatureEnum(String key);
PostTargetEnum getPostTargetEnumByKey(String key);
boolean getFeatureFlagValue(FeatureEnum key, Long serverId);
boolean getFeatureFlagValue(FeatureEnum key, AServer server);
AFeatureFlag updateFeatureFlag(FeatureEnum key, Long serverId, Boolean newValue);
AFeatureFlag updateFeatureFlag(FeatureEnum key, AServer server, Boolean newValue);
FeatureValidationResult validateFeatureSetup(FeatureConfig featureConfig, AServer server);
}

View File

@@ -0,0 +1,9 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.models.FeatureValidationResult;
import dev.sheldan.abstracto.core.models.database.AServer;
public interface FeatureValidator {
void featureIsSetup(FeatureConfig featureConfig, AServer server, FeatureValidationResult validationResult);
}

View File

@@ -0,0 +1,10 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.config.PostTargetEnum;
import dev.sheldan.abstracto.core.models.FeatureValidationResult;
import dev.sheldan.abstracto.core.models.database.AServer;
public interface FeatureValidatorService {
void checkPostTarget(PostTargetEnum postTargetEnum, AServer server, FeatureValidationResult featureValidationResult);
void checkSystemConfig(String name, AServer server, FeatureValidationResult featureValidationResult);
}

View File

@@ -1,5 +1,6 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.config.PostTargetEnum;
import dev.sheldan.abstracto.core.models.database.PostTarget;
import dev.sheldan.abstracto.templating.model.MessageToSend;
import net.dv8tion.jda.api.entities.Message;
@@ -11,17 +12,17 @@ import java.util.concurrent.CompletableFuture;
public interface PostTargetService {
CompletableFuture<Message> sendTextInPostTarget(String text, PostTarget target);
CompletableFuture<Message> sendEmbedInPostTarget(MessageEmbed embed, PostTarget target);
CompletableFuture<Message> sendTextInPostTarget(String text, String postTargetName, Long serverId);
CompletableFuture<Message> sendEmbedInPostTarget(MessageEmbed embed, String postTargetName, Long serverId);
CompletableFuture<Message> sendMessageInPostTarget(Message message, String postTargetName, Long serverId);
CompletableFuture<Message> sendTextInPostTarget(String text, PostTargetEnum postTargetName, Long serverId);
CompletableFuture<Message> sendEmbedInPostTarget(MessageEmbed embed, PostTargetEnum postTargetName, Long serverId);
CompletableFuture<Message> sendMessageInPostTarget(Message message, PostTargetEnum postTargetName, Long serverId);
CompletableFuture<Message> sendMessageInPostTarget(Message message, PostTarget target);
List<CompletableFuture<Message>> sendEmbedInPostTarget(MessageToSend message, String postTargetName, Long serverId);
List<CompletableFuture<Message>> sendEmbedInPostTarget(MessageToSend message, PostTargetEnum postTargetName, Long serverId);
List<CompletableFuture<Message>> sendEmbedInPostTarget(MessageToSend message, PostTarget target);
List<CompletableFuture<Message>> editEmbedInPostTarget(Long messageId, MessageToSend message, PostTarget target);
List<CompletableFuture<Message>> editEmbedInPostTarget(Long messageId, MessageToSend message, String postTargetName, Long serverId);
List<CompletableFuture<Message>> editEmbedInPostTarget(Long messageId, MessageToSend message, PostTargetEnum postTargetName, Long serverId);
void editOrCreatedInPostTarget(Long messageId, MessageToSend messageToSend, PostTarget target, List<CompletableFuture<Message>> future);
void editOrCreatedInPostTarget(Long messageId, MessageToSend messageToSend, String postTarget, Long serverId, List<CompletableFuture<Message>> future);
void throwIfPostTargetIsNotDefined(String name, Long serverId);
void editOrCreatedInPostTarget(Long messageId, MessageToSend messageToSend, PostTargetEnum postTarget, Long serverId, List<CompletableFuture<Message>> future);
void throwIfPostTargetIsNotDefined(PostTargetEnum name, Long serverId);
boolean validPostTarget(String name);
List<String> getAvailablePostTargets();
}

View File

@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.core.service.management;
import dev.sheldan.abstracto.core.models.database.AConfig;
import dev.sheldan.abstracto.core.models.database.AServer;
public interface ConfigManagementService {
AConfig setOrCreateStringValue(Long serverId, String name, String value);
@@ -13,6 +14,7 @@ public interface ConfigManagementService {
AConfig createIfNotExists(Long serverId, String name, Double value);
AConfig loadConfig(Long serverId, String name);
boolean configExists(Long serverId, String name);
boolean configExists(AServer server, String name);
AConfig setDoubleValue(Long serverId, String name, Double value);
AConfig setLongValue(Long serverId, String name, Long value);
AConfig setStringValue(Long serverId, String name, String value);

View File

@@ -11,5 +11,7 @@ public interface PostTargetManagement {
PostTarget createOrUpdate(String name, Long serverId, Long channelId);
PostTarget getPostTarget(String name, AServer server);
PostTarget getPostTarget(String name, Long serverId);
Boolean postTargetExists(String name, AServer server);
Boolean postTargetExists(String name, Long serverId);
PostTarget updatePostTarget(PostTarget target, AServer server, AChannel newTargetChannel);
}