added the concept of feature modes, these are the modes in which a feature can operate, its basically a separate way how the feature does it jobs, for example mod mail might log or it might not. bot of these are different modes

refactored services for feature flag and feature config
This commit is contained in:
Sheldan
2020-05-16 20:55:13 +02:00
parent cd80d27795
commit d2264937b2
35 changed files with 656 additions and 138 deletions

View File

@@ -3,7 +3,7 @@ package dev.sheldan.abstracto.core.command.config;
import dev.sheldan.abstracto.core.command.Command;
import dev.sheldan.abstracto.core.command.service.CommandService;
import dev.sheldan.abstracto.core.command.service.management.FeatureManagementService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.service.FeatureConfigService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.ContextRefreshedEvent;
@@ -24,7 +24,7 @@ public class CommandCreationListener {
private CommandService commandService;
@Autowired
private FeatureFlagService featureFlagService;
private FeatureConfigService featureFlagService;
@Autowired
private FeatureManagementService featureManagementService;

View File

@@ -11,9 +11,11 @@ import dev.sheldan.abstracto.core.command.service.CommandService;
import dev.sheldan.abstracto.core.command.service.management.CommandManagementService;
import dev.sheldan.abstracto.core.command.service.management.FeatureManagementService;
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.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.service.FeatureConfigService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.service.management.RoleManagementService;
import dev.sheldan.abstracto.templating.service.TemplateService;
@@ -39,7 +41,7 @@ public class AllowRole extends AbstractConditionableCommand {
private RoleManagementService roleManagementService;
@Autowired
private FeatureFlagService featureFlagService;
private FeatureConfigService featureFlagService;
@Autowired
private TemplateService templateService;

View File

@@ -14,6 +14,7 @@ import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.models.template.commands.EnableModel;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.FeatureConfigService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.templating.service.TemplateService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -25,6 +26,9 @@ import java.util.List;
@Component
public class Disable extends AbstractConditionableCommand {
@Autowired
private FeatureConfigService featureConfigService;
@Autowired
private FeatureFlagService featureFlagService;
@@ -39,12 +43,12 @@ public class Disable extends AbstractConditionableCommand {
public CommandResult execute(CommandContext commandContext) {
if(commandContext.getParameters().getParameters().isEmpty()) {
EnableModel model = (EnableModel) ContextConverter.fromCommandContext(commandContext, EnableModel.class);
model.setFeatures(featureFlagService.getAllFeatures());
model.setFeatures(featureConfigService.getAllFeatures());
String response = templateService.renderTemplate("disable_features_response", model);
channelService.sendTextToChannelNoFuture(response, commandContext.getChannel());
} else {
String flagKey = (String) commandContext.getParameters().getParameters().get(0);
FeatureConfig feature = featureFlagService.getFeatureDisplayForFeature(flagKey);
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(flagKey);
featureFlagService.disableFeature(feature, commandContext.getGuild().getIdLong());
if(feature.getDependantFeatures() != null) {
feature.getDependantFeatures().forEach(featureDisplay -> {

View File

@@ -15,6 +15,7 @@ import dev.sheldan.abstracto.core.command.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.FeatureConfigService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.templating.service.TemplateService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -26,6 +27,9 @@ import java.util.List;
@Component
public class Enable extends AbstractConditionableCommand {
@Autowired
private FeatureConfigService featureConfigService;
@Autowired
private FeatureFlagService featureFlagService;
@@ -39,13 +43,13 @@ public class Enable extends AbstractConditionableCommand {
public CommandResult execute(CommandContext commandContext) {
if(commandContext.getParameters().getParameters().isEmpty()) {
EnableModel model = (EnableModel) ContextConverter.fromCommandContext(commandContext, EnableModel.class);
model.setFeatures(featureFlagService.getAllFeatures());
model.setFeatures(featureConfigService.getAllFeatures());
String response = templateService.renderTemplate("enable_features_response", model);
channelService.sendTextToChannelNoFuture(response, commandContext.getChannel());
} else {
String flagKey = (String) commandContext.getParameters().getParameters().get(0);
FeatureConfig feature = featureFlagService.getFeatureDisplayForFeature(flagKey);
FeatureValidationResult featureSetup = featureFlagService.validateFeatureSetup(feature, commandContext.getUserInitiatedContext().getServer());
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(flagKey);
FeatureValidationResult featureSetup = featureConfigService.validateFeatureSetup(feature, commandContext.getUserInitiatedContext().getServer());
if(!featureSetup.getValidationResult()) {
channelService.sendTextToChannelNoFuture(templateService.renderTemplatable(featureSetup), commandContext.getChannel());
}

View File

@@ -0,0 +1,74 @@
package dev.sheldan.abstracto.core.commands.config.features;
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.features.CoreFeatures;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.service.management.FeatureManagementService;
import dev.sheldan.abstracto.core.commands.config.ConfigModuleInterface;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.service.FeatureConfigService;
import dev.sheldan.abstracto.core.service.FeatureModeService;
import dev.sheldan.abstracto.core.service.management.FeatureFlagManagementService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class SetMode extends AbstractConditionableCommand {
@Autowired
private FeatureManagementService featureManagementService;
@Autowired
private FeatureConfigService featureConfigService;
@Autowired
private FeatureModeService featureModeService;
@Autowired
private FeatureFlagManagementService featureFlagManagementService;
@Override
public CommandResult execute(CommandContext commandContext) {
String featureName = (String) commandContext.getParameters().getParameters().get(0);
String modeName = (String) commandContext.getParameters().getParameters().get(1);
if(featureConfigService.doesFeatureExist(featureName)) {
if(featureConfigService.isModeValid(featureName, modeName)) {
featureModeService.setModeForFeatureTo(featureName, commandContext.getUserInitiatedContext().getServer(), modeName);
} else {
return CommandResult.fromError("Mode not available");
}
} else {
return CommandResult.fromError("Feature does not exist");
}
return CommandResult.fromSuccess();
}
@Override
public CommandConfiguration getConfiguration() {
Parameter featureName = Parameter.builder().name("feature").type(String.class).templated(true).build();
Parameter newMode = Parameter.builder().name("newMode").type(String.class).templated(true).build();
List<Parameter> parameters = Arrays.asList(featureName, newMode);
HelpInfo helpInfo = HelpInfo.builder().templated(true).hasExample(true).build();
return CommandConfiguration.builder()
.name("setMode")
.module(ConfigModuleInterface.CONFIG)
.parameters(parameters)
.templated(true)
.help(helpInfo)
.causesReaction(true)
.build();
}
@Override
public FeatureEnum getFeature() {
return CoreFeatures.CORE_FEATURE;
}
}

View File

@@ -0,0 +1,21 @@
package dev.sheldan.abstracto.core.config;
import dev.sheldan.abstracto.core.models.config.FeaturePropertiesConfig;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.HashMap;
@Component
@Getter
@Setter
@ConfigurationProperties(prefix = "abstracto")
public class FeatureConfigLoader {
private HashMap<String, FeaturePropertiesConfig> features = new HashMap<>();
}

View File

@@ -4,7 +4,7 @@ import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.models.database.AFeatureFlag;
import dev.sheldan.abstracto.core.models.template.commands.FeatureFlagDisplay;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.service.FeatureConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -15,7 +15,7 @@ import java.util.stream.Collectors;
public class FeatureFlagConverter {
@Autowired
private FeatureFlagService featureFlagService;
private FeatureConfigService featureFlagService;
public FeatureFlagDisplay fromAFeatureFlag(AFeatureFlag featureFlag) {
FeatureEnum featureEnum = featureFlagService.getFeatureEnum(featureFlag.getFeature().getKey());

View File

@@ -1,13 +1,15 @@
package dev.sheldan.abstracto.core.config;
package dev.sheldan.abstracto.core.listener;
import dev.sheldan.abstracto.core.command.service.management.FeatureManagementService;
import dev.sheldan.abstracto.core.listener.ServerConfigListener;
import dev.sheldan.abstracto.core.config.FeatureConfigLoader;
import dev.sheldan.abstracto.core.models.config.FeaturePropertiesConfig;
import dev.sheldan.abstracto.core.models.database.AFeature;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.service.FeatureConfigService;
import dev.sheldan.abstracto.core.service.FeatureModeService;
import dev.sheldan.abstracto.core.service.management.FeatureFlagManagementService;
import dev.sheldan.abstracto.core.service.management.FeatureModeManagementService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
@@ -17,26 +19,42 @@ import org.springframework.stereotype.Component;
public class FeatureFlagListener implements ServerConfigListener {
@Autowired
private FeatureFlagService featureFlagService;
private FeatureConfigService featureFlagService;
@Autowired
private FeatureFlagManagementService service;
@Autowired
private FeatureModeService featureModeService;
@Autowired
private FeatureModeManagementService featureModeManagementService;
@Autowired
private FeatureManagementService featureManagementService;
@Autowired
private Environment environment;
@Autowired
private FeatureConfigLoader featureConfigLoader;
@Autowired
private FeatureFlagManagementService featureFlagManagementService;
@Override
public void updateServerConfig(AServer server) {
log.info("Setting up feature flags if necessary.");
featureFlagService.getAllFeatureConfigs().forEach((featureFlagKey) -> {
String featureKey = featureFlagKey.getFeature().getKey();
AFeature feature = featureManagementService.getFeature(featureKey);
boolean featureFlagValue = BooleanUtils.toBoolean(environment.getProperty("abstracto.features." + featureKey, "false"));
FeaturePropertiesConfig featurePropertiesConfig = featureConfigLoader.getFeatures().get(featureKey);
if(service.getFeatureFlag(feature, server.getId()) == null) {
service.createFeatureFlag(feature, server.getId(), featureFlagValue);
service.createFeatureFlag(feature, server.getId(), featurePropertiesConfig.getEnabled());
}
if(featurePropertiesConfig.getDefaultMode() != null && !featureModeManagementService.featureModeSet(feature, server)) {
featureModeService.createMode(feature, server, featurePropertiesConfig.getDefaultMode());
}
});
}

View File

@@ -2,6 +2,7 @@ package dev.sheldan.abstracto.core.listener;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.FeatureConfigService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import lombok.extern.slf4j.Slf4j;
@@ -22,6 +23,9 @@ public class JoinListenerBean extends ListenerAdapter {
@Autowired
private List<JoinListener> listenerList;
@Autowired
private FeatureConfigService featureConfigService;
@Autowired
private FeatureFlagService featureFlagService;
@@ -32,7 +36,7 @@ public class JoinListenerBean extends ListenerAdapter {
@Transactional
public void onGuildMemberJoin(@Nonnull GuildMemberJoinEvent event) {
listenerList.forEach(joinListener -> {
FeatureConfig feature = featureFlagService.getFeatureDisplayForFeature(joinListener.getFeature());
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(joinListener.getFeature());
if (!featureFlagService.isFeatureEnabled(feature, event.getGuild().getIdLong())) {
return;
}

View File

@@ -2,6 +2,7 @@ package dev.sheldan.abstracto.core.listener;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.service.FeatureConfigService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.guild.member.GuildMemberLeaveEvent;
@@ -20,6 +21,9 @@ public class LeaveListenerBean extends ListenerAdapter {
@Autowired
private List<LeaveListener> listenerList;
@Autowired
private FeatureConfigService featureConfigService;
@Autowired
private FeatureFlagService featureFlagService;
@@ -27,7 +31,7 @@ public class LeaveListenerBean extends ListenerAdapter {
@Transactional
public void onGuildMemberLeave(@Nonnull GuildMemberLeaveEvent event) {
listenerList.forEach(leaveListener -> {
FeatureConfig feature = featureFlagService.getFeatureDisplayForFeature(leaveListener.getFeature());
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(leaveListener.getFeature());
if(!featureFlagService.isFeatureEnabled(feature, event.getGuild().getIdLong())) {
return;
}

View File

@@ -3,6 +3,7 @@ package dev.sheldan.abstracto.core.listener;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.service.FeatureConfigService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.service.MessageCache;
import lombok.extern.slf4j.Slf4j;
@@ -28,6 +29,9 @@ public class MessageDeletedListenerBean extends ListenerAdapter {
@Autowired
private MessageDeletedListenerBean self;
@Autowired
private FeatureConfigService featureConfigService;
@Autowired
private FeatureFlagService featureFlagService;
@@ -46,7 +50,7 @@ public class MessageDeletedListenerBean extends ListenerAdapter {
@Transactional
public void executeListener(CachedMessage cachedMessage) {
listener.forEach(messageDeletedListener -> {
FeatureConfig feature = featureFlagService.getFeatureDisplayForFeature(messageDeletedListener.getFeature());
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(messageDeletedListener.getFeature());
if(!featureFlagService.isFeatureEnabled(feature, cachedMessage.getServerId())) {
return;
}

View File

@@ -2,6 +2,7 @@ package dev.sheldan.abstracto.core.listener;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.service.BotService;
import dev.sheldan.abstracto.core.service.FeatureConfigService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.service.MessageCache;
import lombok.extern.slf4j.Slf4j;
@@ -27,6 +28,9 @@ public class MessageReceivedListenerBean extends ListenerAdapter {
@Autowired
private List<PrivateMessageReceivedListener> privateMessageReceivedListeners;
@Autowired
private FeatureConfigService featureConfigService;
@Autowired
private FeatureFlagService featureFlagService;
@@ -38,7 +42,7 @@ public class MessageReceivedListenerBean extends ListenerAdapter {
messageCache.putMessageInCache(event.getMessage());
listenerList.forEach(messageReceivedListener -> {
try {
FeatureConfig feature = featureFlagService.getFeatureDisplayForFeature(messageReceivedListener.getFeature());
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(messageReceivedListener.getFeature());
if(!featureFlagService.isFeatureEnabled(feature, event.getGuild().getIdLong())) {
return;
}

View File

@@ -3,6 +3,7 @@ package dev.sheldan.abstracto.core.listener;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.service.FeatureConfigService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.service.MessageCache;
import lombok.extern.slf4j.Slf4j;
@@ -32,6 +33,9 @@ public class MessageUpdatedListener extends ListenerAdapter {
@Autowired
private FeatureFlagService featureFlagService;
@Autowired
private FeatureConfigService featureConfigService;
@Override
public void onGuildMessageUpdate(@Nonnull GuildMessageUpdateEvent event) {
Message message = event.getMessage();
@@ -48,7 +52,7 @@ public class MessageUpdatedListener extends ListenerAdapter {
@Transactional
public void executeListener(Message message, CachedMessage cachedMessage) {
listener.forEach(messageTextUpdatedListener -> {
FeatureConfig feature = featureFlagService.getFeatureDisplayForFeature(messageTextUpdatedListener.getFeature());
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(messageTextUpdatedListener.getFeature());
if(!featureFlagService.isFeatureEnabled(feature, message.getGuild().getIdLong())) {
return;
}

View File

@@ -3,6 +3,7 @@ package dev.sheldan.abstracto.core.listener;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.service.BotService;
import dev.sheldan.abstracto.core.service.FeatureConfigService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
@@ -43,6 +44,9 @@ public class ReactionUpdatedListener extends ListenerAdapter {
@Autowired
private ReactionUpdatedListener self;
@Autowired
private FeatureConfigService featureConfigService;
@Autowired
private FeatureFlagService featureFlagService;
@@ -101,7 +105,7 @@ public class ReactionUpdatedListener extends ListenerAdapter {
AUserInAServer userInAServer = userInServerManagementService.loadUser(event.getGuild().getIdLong(), event.getUserIdLong());
addReactionIfNotThere(cachedMessage, reaction, userInAServer);
addedListenerList.forEach(reactedAddedListener -> {
FeatureConfig feature = featureFlagService.getFeatureDisplayForFeature(reactedAddedListener.getFeature());
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(reactedAddedListener.getFeature());
if(!featureFlagService.isFeatureEnabled(feature, event.getGuild().getIdLong())) {
return;
}
@@ -140,7 +144,7 @@ public class ReactionUpdatedListener extends ListenerAdapter {
AUserInAServer userInAServer = userInServerManagementService.loadUser(event.getGuild().getIdLong(), event.getUserIdLong());
removeReactionIfThere(cachedMessage, reaction, userInAServer);
reactionRemovedListener.forEach(reactionRemovedListener -> {
FeatureConfig feature = featureFlagService.getFeatureDisplayForFeature(reactionRemovedListener.getFeature());
FeatureConfig feature = featureConfigService.getFeatureDisplayForFeature(reactionRemovedListener.getFeature());
if(!featureFlagService.isFeatureEnabled(feature, event.getGuild().getIdLong())) {
return;
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.abstracto.core.repository;
import dev.sheldan.abstracto.core.models.database.AFeature;
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.stereotype.Repository;
@Repository
public interface FeatureModeRepository extends JpaRepository<AFeatureMode, Long> {
AFeatureMode findByFeatureFlag(AFeatureFlag featureFlag);
boolean existsByFeatureFlag_ServerAndFeatureFlag_Feature(AServer server, AFeature feature);
}

View File

@@ -0,0 +1,130 @@
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.FeatureMode;
import dev.sheldan.abstracto.core.config.PostTargetEnum;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.models.FeatureValidationResult;
import dev.sheldan.abstracto.core.models.database.AServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@Component
public class FeatureConfigServiceBean implements FeatureConfigService {
@Autowired
private List<FeatureConfig> availableFeatures;
@Autowired
private FeatureValidatorService featureValidatorService;
@Override
public List<String> getAllFeatures() {
return availableFeatures
.stream()
.map(featureDisplay -> featureDisplay.getFeature().getKey())
.collect(Collectors.toList());
}
@Override
public List<FeatureConfig> getAllFeatureConfigs() {
return availableFeatures;
}
@Override
public FeatureConfig getFeatureDisplayForFeature(FeatureEnum featureEnum) {
Optional<FeatureConfig> any = getAllFeatureConfigs().stream().filter(featureDisplay -> featureDisplay.getFeature().equals(featureEnum)).findAny();
if(any.isPresent()) {
return any.get();
}
throw new AbstractoRunTimeException(String.format("Feature %s not found in configuration", featureEnum.getKey()));
}
@Override
public FeatureConfig getFeatureDisplayForFeature(String key) {
return getFeatureDisplayForFeature(getFeatureEnum(key));
}
@Override
public boolean doesFeatureExist(FeatureConfig name) {
return availableFeatures.stream().anyMatch(featureDisplay -> featureDisplay.getFeature().equals(name.getFeature()));
}
@Override
public List<String> getFeaturesAsList() {
return availableFeatures
.stream()
.map(featureDisplay -> featureDisplay.getFeature().getKey())
.collect(Collectors.toList());
}
@Override
public boolean doesFeatureExist(String name) {
return availableFeatures.stream().anyMatch(featureDisplay -> featureDisplay.getFeature().getKey().equals(name));
}
@Override
public FeatureEnum getFeatureEnum(String key) {
Optional<FeatureConfig> foundFeature = availableFeatures.stream().filter(featureDisplay -> featureDisplay.getFeature().getKey().equals(key)).findAny();
if(foundFeature.isPresent()) {
return foundFeature.get().getFeature();
}
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 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.getRequiredEmotes().forEach(s -> {
featureValidatorService.checkEmote(s, server, featureValidationResult);
});
featureConfig.getAdditionalFeatureValidators().forEach(featureValidator -> {
featureValidator.featureIsSetup(featureConfig, server, featureValidationResult);
});
return featureValidationResult;
}
@Override
public FeatureMode getFeatureModeByKey(String key) {
Predicate<FeatureMode> postTargetComparison = postTargetEnum -> postTargetEnum.getKey().equals(key);
Optional<FeatureConfig> foundFeature = availableFeatures.stream().filter(featureDisplay -> featureDisplay.getAvailableModes().stream().anyMatch(postTargetComparison)).findAny();
if(foundFeature.isPresent()) {
return foundFeature.get().getAvailableModes().stream().filter(postTargetComparison).findAny().get();
}
throw new AbstractoRunTimeException(String.format("Feature mode %s not found.", key));
}
@Override
public boolean isModeValid(String featureName, String modeName) {
return availableFeatures
.stream()
.filter(featureConfig -> featureConfig.getFeature().getKey().equals(featureName))
.map(FeatureConfig::getAvailableModes)
.anyMatch(featureModes -> featureModes
.stream()
.anyMatch(featureMode -> featureMode.getKey().equals(modeName))
);
}
}

View File

@@ -30,15 +30,11 @@ public class FeatureFlagServiceBean implements FeatureFlagService {
private FeatureManagementService featureManagementService;
@Autowired
private List<FeatureConfig> availableFeatures;
private FeatureConfigService featureConfigService;
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private FeatureValidatorService featureValidatorService;
@Override
public boolean isFeatureEnabled(FeatureConfig name, Long serverId) {
return getFeatureFlagValue(name.getFeature(), serverId);
@@ -58,8 +54,8 @@ public class FeatureFlagServiceBean implements FeatureFlagService {
@Override
public void enableFeature(FeatureConfig name, AServer server) {
FeatureEnum feature = name.getFeature();
if(!doesFeatureExist(name)) {
throw new FeatureNotFoundException("Feature not found.", feature.getKey(), getFeaturesAsList());
if(!featureConfigService.doesFeatureExist(name)) {
throw new FeatureNotFoundException("Feature not found.", feature.getKey(), featureConfigService.getFeaturesAsList());
}
updateFeatureFlag(feature, server, true);
}
@@ -73,71 +69,12 @@ public class FeatureFlagServiceBean implements FeatureFlagService {
@Override
public void disableFeature(FeatureConfig name, AServer server) {
FeatureEnum feature = name.getFeature();
if(!doesFeatureExist(name)) {
throw new FeatureNotFoundException("Feature not found.", feature.getKey(), getFeaturesAsList());
if(!featureConfigService.doesFeatureExist(name)) {
throw new FeatureNotFoundException("Feature not found.", feature.getKey(), featureConfigService.getFeaturesAsList());
}
updateFeatureFlag(feature, server, false);
}
@Override
public List<String> getAllFeatures() {
return availableFeatures
.stream()
.map(featureDisplay -> featureDisplay.getFeature().getKey())
.collect(Collectors.toList());
}
@Override
public List<FeatureConfig> getAllFeatureConfigs() {
return availableFeatures;
}
@Override
public FeatureConfig getFeatureDisplayForFeature(FeatureEnum featureEnum) {
Optional<FeatureConfig> any = getAllFeatureConfigs().stream().filter(featureDisplay -> featureDisplay.getFeature().equals(featureEnum)).findAny();
if(any.isPresent()) {
return any.get();
}
throw new AbstractoRunTimeException(String.format("Feature %s not found in configuration", featureEnum.getKey()));
}
@Override
public FeatureConfig getFeatureDisplayForFeature(String key) {
return getFeatureDisplayForFeature(getFeatureEnum(key));
}
@Override
public boolean doesFeatureExist(FeatureConfig name) {
return availableFeatures.stream().anyMatch(featureDisplay -> featureDisplay.getFeature().equals(name.getFeature()));
}
@Override
public List<String> getFeaturesAsList() {
return availableFeatures
.stream()
.map(featureDisplay -> featureDisplay.getFeature().getKey())
.collect(Collectors.toList());
}
@Override
public FeatureEnum getFeatureEnum(String key) {
Optional<FeatureConfig> foundFeature = availableFeatures.stream().filter(featureDisplay -> featureDisplay.getFeature().getKey().equals(key)).findAny();
if(foundFeature.isPresent()) {
return foundFeature.get().getFeature();
}
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);
@@ -162,22 +99,4 @@ 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.getRequiredEmotes().forEach(s -> {
featureValidatorService.checkEmote(s, server, featureValidationResult);
});
featureConfig.getAdditionalFeatureValidators().forEach(featureValidator -> {
featureValidator.featureIsSetup(featureConfig, server, featureValidationResult);
});
return featureValidationResult;
}
}

View File

@@ -0,0 +1,114 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.command.service.management.FeatureManagementService;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.models.database.AFeature;
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 dev.sheldan.abstracto.core.service.management.FeatureFlagManagementService;
import dev.sheldan.abstracto.core.service.management.FeatureModeManagementService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class FeatureModeServiceBean implements FeatureModeService {
@Autowired
private FeatureConfigService featureConfigService;
@Autowired
private FeatureFlagManagementService featureFlagManagementService;
@Autowired
private FeatureManagementService featureManagementService;
@Autowired
private FeatureModeManagementService featureModeManagementService;
@Override
public AFeatureMode setModeForFeatureTo(String key, AServer server, String newMode) {
FeatureEnum featureEnum = featureConfigService.getFeatureEnum(key);
return setModeForFeatureTo(featureEnum, server, newMode);
}
@Override
public AFeatureMode setModeForFeatureTo(AFeatureFlag flag, String newMode) {
FeatureMode featureMode = featureConfigService.getFeatureModeByKey(newMode);
return setModeForFeatureTo(flag, featureMode);
}
@Override
public AFeatureMode setModeForFeatureTo(FeatureEnum featureEnum, AServer server, String newMode) {
FeatureMode featureMode = featureConfigService.getFeatureModeByKey(newMode);
return setModeForFeatureTo(featureEnum, server, featureMode);
}
@Override
public AFeatureMode setModeForFeatureTo(AFeature feature, AServer server, String newMode) {
FeatureMode featureMode = featureConfigService.getFeatureModeByKey(newMode);
return setModeForFeatureTo(feature, server, featureMode);
}
@Override
public AFeatureMode setModeForFeatureTo(FeatureEnum featureEnum, AServer server, FeatureMode mode) {
AFeature feature = featureManagementService.getFeature(featureEnum.getKey());
return setModeForFeatureTo(feature, server, mode);
}
@Override
public AFeatureMode setModeForFeatureTo(AFeature feature, AServer server, FeatureMode mode) {
AFeatureFlag featureFlag = featureFlagManagementService.getFeatureFlag(feature, server);
return setModeForFeatureTo(featureFlag, mode);
}
@Override
public AFeatureMode setModeForFeatureTo(AFeatureFlag featureFlag, FeatureMode mode) {
return featureModeManagementService.setModeForFeature(featureFlag, mode);
}
@Override
public AFeatureMode createMode(String key, AServer server, String newMode) {
FeatureEnum featureEnum = featureConfigService.getFeatureEnum(key);
return createMode(featureEnum, server, newMode);
}
@Override
public AFeatureMode createMode(AFeatureFlag flag, String newMode) {
FeatureMode featureMode = featureConfigService.getFeatureModeByKey(newMode);
return createMode(flag, featureMode);
}
@Override
public AFeatureMode createMode(FeatureEnum featureEnum, AServer server, String newMode) {
FeatureMode featureMode = featureConfigService.getFeatureModeByKey(newMode);
return createMode(featureEnum, server, featureMode);
}
@Override
public AFeatureMode createMode(AFeature feature, AServer server, String newMode) {
FeatureMode featureMode = featureConfigService.getFeatureModeByKey(newMode);
return createMode(feature, server, featureMode);
}
@Override
public AFeatureMode createMode(FeatureEnum featureEnum, AServer server, FeatureMode mode) {
AFeature feature = featureManagementService.getFeature(featureEnum.getKey());
return createMode(feature, server, mode);
}
@Override
public AFeatureMode createMode(AFeature feature, AServer server, FeatureMode mode) {
AFeatureFlag featureFlag = featureFlagManagementService.getFeatureFlag(feature, server);
return createMode(featureFlag, mode);
}
@Override
public AFeatureMode createMode(AFeatureFlag featureFlag, FeatureMode mode) {
return featureModeManagementService.createMode(featureFlag, mode);
}
}

View File

@@ -0,0 +1,46 @@
package dev.sheldan.abstracto.core.service.management;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.models.database.AFeature;
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 dev.sheldan.abstracto.core.repository.FeatureModeRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class FeatureModeManagementServiceBean implements FeatureModeManagementService {
@Autowired
private FeatureModeRepository featureModeRepository;
@Override
public AFeatureMode createMode(AFeatureFlag featureFlag, FeatureMode mode) {
AFeatureMode aFeatureMode = AFeatureMode
.builder()
.featureFlag(featureFlag)
.mode(mode.getKey())
.build();
featureModeRepository.save(aFeatureMode);
return aFeatureMode;
}
@Override
public AFeatureMode getModeForFeature(AFeatureFlag featureFlag) {
return featureModeRepository.findByFeatureFlag(featureFlag);
}
@Override
public boolean featureModeSet(AFeature aFeature, AServer server) {
return featureModeRepository.existsByFeatureFlag_ServerAndFeatureFlag_Feature(server, aFeature);
}
@Override
public AFeatureMode setModeForFeature(AFeatureFlag featureFlag, FeatureMode featureMode) {
AFeatureMode modeForFeature = getModeForFeature(featureFlag);
modeForFeature.setMode(featureMode.getKey());
return modeForFeature;
}
}

View File

@@ -2,6 +2,6 @@ abstracto.startup.synchronize=true
abstracto.parameter.lowerBound=50
abstracto.features.core=true
abstracto.features.core.enabled=true
abstracto.prefix=!
abstracto.eventWaiter.threads=10

View File

@@ -4,6 +4,7 @@ import dev.sheldan.abstracto.core.command.Command;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.models.FeatureDisabledMessage;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.service.FeatureConfigService;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.templating.service.TemplateService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -15,6 +16,9 @@ public class FeatureEnabledCondition implements CommandCondition {
@Autowired
private TemplateService templateService;
@Autowired
private FeatureConfigService featureConfigService;
@Autowired
private FeatureFlagService featureFlagService;
@@ -28,7 +32,7 @@ public class FeatureEnabledCondition implements CommandCondition {
if(!featureFlagValue) {
FeatureDisabledMessage featureDisabledMessage = FeatureDisabledMessage
.builder()
.featureConfig(featureFlagService.getFeatureDisplayForFeature(feature))
.featureConfig(featureConfigService.getFeatureDisplayForFeature(feature))
.build();
reason = templateService.renderTemplate("feature_disabled_message", featureDisabledMessage);
}

View File

@@ -17,4 +17,5 @@ public interface FeatureConfig {
default List<String> getRequiredSystemConfigKeys() { return Collections.emptyList();}
default List<FeatureValidator> getAdditionalFeatureValidators() { return Collections.emptyList(); }
default List<String> getRequiredEmotes() { return Collections.emptyList(); }
default List<FeatureMode> getAvailableModes() { return Collections.emptyList(); };
}

View File

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

View File

@@ -0,0 +1,13 @@
package dev.sheldan.abstracto.core.models.config;
import lombok.*;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FeaturePropertiesConfig {
private Boolean enabled;
private String defaultMode;
}

View File

@@ -0,0 +1,49 @@
package dev.sheldan.abstracto.core.models.database;
import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.time.Instant;
@Entity
@Table(name="feature_modes")
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class AFeatureMode {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Getter
@Column(name = "id")
public Long id;
@Getter
@Setter
@OneToOne
@JoinColumn(name = "feature_flag_id", nullable = false)
private AFeatureFlag featureFlag;
@Column
@Setter
private String mode;
@Column(name = "created")
private Instant created;
@PrePersist
private void onInsert() {
this.created = Instant.now();
}
@Column(name = "updated")
private Instant updateTimestamp;
@PreUpdate
private void onUpdate() {
this.updateTimestamp = Instant.now();
}
}

View File

@@ -0,0 +1,25 @@
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.FeatureMode;
import dev.sheldan.abstracto.core.config.PostTargetEnum;
import dev.sheldan.abstracto.core.models.FeatureValidationResult;
import dev.sheldan.abstracto.core.models.database.AServer;
import java.util.List;
public interface FeatureConfigService {
List<String> getAllFeatures();
List<FeatureConfig> getAllFeatureConfigs();
FeatureConfig getFeatureDisplayForFeature(FeatureEnum featureEnum);
FeatureConfig getFeatureDisplayForFeature(String key);
boolean doesFeatureExist(FeatureConfig name);
boolean doesFeatureExist(String name);
List<String> getFeaturesAsList();
FeatureEnum getFeatureEnum(String key);
PostTargetEnum getPostTargetEnumByKey(String key);
FeatureValidationResult validateFeatureSetup(FeatureConfig featureConfig, AServer server);
FeatureMode getFeatureModeByKey(String key);
boolean isModeValid(String featureName, String modeName);
}

View File

@@ -2,13 +2,9 @@ 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;
import java.util.List;
public interface FeatureFlagService {
boolean isFeatureEnabled(FeatureConfig name, Long serverId);
boolean isFeatureEnabled(FeatureConfig name, AServer server);
@@ -16,17 +12,8 @@ public interface FeatureFlagService {
void enableFeature(FeatureConfig name, AServer server);
void disableFeature(FeatureConfig name, Long serverId);
void disableFeature(FeatureConfig name, AServer server);
List<String> getAllFeatures();
List<FeatureConfig> getAllFeatureConfigs();
FeatureConfig getFeatureDisplayForFeature(FeatureEnum featureEnum);
FeatureConfig getFeatureDisplayForFeature(String key);
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,26 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.models.database.AFeature;
import dev.sheldan.abstracto.core.models.database.AFeatureFlag;
import dev.sheldan.abstracto.core.models.database.AFeatureMode;
import dev.sheldan.abstracto.core.models.database.AServer;
public interface FeatureModeService {
AFeatureMode setModeForFeatureTo(String key, AServer server, String newMode);
AFeatureMode setModeForFeatureTo(AFeatureFlag flag, String newMode);
AFeatureMode setModeForFeatureTo(FeatureEnum featureEnum, AServer server, String newMode);
AFeatureMode setModeForFeatureTo(AFeature featureEnum, AServer server, String newMode);
AFeatureMode setModeForFeatureTo(FeatureEnum featureEnum, AServer server, FeatureMode mode);
AFeatureMode setModeForFeatureTo(AFeature feature, AServer server, FeatureMode mode);
AFeatureMode setModeForFeatureTo(AFeatureFlag featureFlag, FeatureMode mode);
AFeatureMode createMode(String key, AServer server, String newMode);
AFeatureMode createMode(AFeatureFlag flag, String newMode);
AFeatureMode createMode(FeatureEnum featureEnum, AServer server, String newMode);
AFeatureMode createMode(AFeature featureEnum, AServer server, String newMode);
AFeatureMode createMode(FeatureEnum featureEnum, AServer server, FeatureMode mode);
AFeatureMode createMode(AFeature feature, AServer server, FeatureMode mode);
AFeatureMode createMode(AFeatureFlag featureFlag, FeatureMode mode);
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.abstracto.core.service.management;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.models.database.AFeature;
import dev.sheldan.abstracto.core.models.database.AFeatureFlag;
import dev.sheldan.abstracto.core.models.database.AFeatureMode;
import dev.sheldan.abstracto.core.models.database.AServer;
public interface FeatureModeManagementService {
AFeatureMode createMode(AFeatureFlag featureFlag, FeatureMode mode);
AFeatureMode getModeForFeature(AFeatureFlag featureFlag);
boolean featureModeSet(AFeature aFeature, AServer server);
AFeatureMode setModeForFeature(AFeatureFlag featureFlag, FeatureMode featureMode);
}