renamed feature display to feature config

added concept of feature dependencies, if one feature depends on another feature, and it gets enabled, the other feature is enabled as well
changed some feature related apis
added ability to automatically decay warnings (separate feature)
added command to trigger the warn decay manually
added command to decay all active warnings
added method to scheduler service to directly start a cron job
This commit is contained in:
Sheldan
2020-04-29 21:25:26 +02:00
parent 85c47db5ed
commit d5482fabd4
60 changed files with 571 additions and 97 deletions

View File

@@ -1,11 +1,11 @@
package dev.sheldan.abstracto.experience.config.features; package dev.sheldan.abstracto.experience.config.features;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureEnum; import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.config.FeatureDisplay;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
public class ExperienceFeatureDisplay implements FeatureDisplay { public class ExperienceFeatureConfig implements FeatureConfig {
@Override @Override
public FeatureEnum getFeature() { public FeatureEnum getFeature() {
return ExperienceFeature.EXPERIENCE; return ExperienceFeature.EXPERIENCE;

View File

@@ -0,0 +1,53 @@
package dev.sheldan.abstracto.moderation.commands;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.moderation.config.ModerationModule;
import dev.sheldan.abstracto.moderation.config.features.ModerationFeatures;
import dev.sheldan.abstracto.moderation.service.WarnService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class DecayAllWarnings extends AbstractConditionableCommand {
@Autowired
private WarnService warnService;
@Override
public CommandResult execute(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
Boolean logWarnings = !parameters.isEmpty() ? (Boolean) parameters.get(0) : Boolean.FALSE;
warnService.decayAllWarningsForServer(commandContext.getUserInitiatedContext().getServer(), logWarnings);
return CommandResult.fromSuccess();
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
Parameter logWarnings = Parameter.builder().optional(true).type(Boolean.class).build();
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
parameters.add(logWarnings);
return CommandConfiguration.builder()
.name("decayAllWarnings")
.module(ModerationModule.MODERATION)
.templated(true)
.causesReaction(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureEnum getFeature() {
return ModerationFeatures.WARNING;
}
}

View File

@@ -0,0 +1,49 @@
package dev.sheldan.abstracto.moderation.commands;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.moderation.config.ModerationModule;
import dev.sheldan.abstracto.moderation.config.features.ModerationFeatures;
import dev.sheldan.abstracto.moderation.service.WarnService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class DecayWarnings extends AbstractConditionableCommand {
@Autowired
private WarnService warnService;
@Override
public CommandResult execute(CommandContext commandContext) {
warnService.decayWarningsForServer(commandContext.getUserInitiatedContext().getServer());
return CommandResult.fromSuccess();
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("decayWarnings")
.module(ModerationModule.MODERATION)
.templated(true)
.causesReaction(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureEnum getFeature() {
return ModerationFeatures.AUTOMATIC_WARN_DECAY;
}
}

View File

@@ -0,0 +1,53 @@
package dev.sheldan.abstracto.moderation.job;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.service.management.FeatureFlagManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.moderation.config.features.WarningDecayFeature;
import dev.sheldan.abstracto.moderation.service.WarnService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Slf4j
@DisallowConcurrentExecution
@Component
@PersistJobDataAfterExecution
public class WarnDecayJob extends QuartzJobBean {
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private FeatureFlagManagementService featureFlagManagementService;
@Autowired
private FeatureFlagService featureFlagService;
@Autowired
private WarningDecayFeature warningDecayFeature;
@Autowired
private WarnService warnService;
@Override
@Transactional
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
List<AServer> allServers = serverManagementService.getAllServers();
allServers.forEach(server -> {
boolean featureEnabled = featureFlagService.isFeatureEnabled(warningDecayFeature, server);
if(featureEnabled) {
warnService.decayWarningsForServer(server);
}
});
}
}

View File

@@ -0,0 +1,23 @@
package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.listener.ServerConfigListener;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.management.ConfigManagementService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class WarnDecayConfigListener implements ServerConfigListener {
@Autowired
private ConfigManagementService configManagementService;
@Value("${abstracto.warnings.warnDecay.days}")
private Long decayDays;
@Override
public void updateServerConfig(AServer server) {
configManagementService.createIfNotExists(server.getId(), "decayDays", decayDays.doubleValue());
}
}

View File

@@ -1,7 +1,14 @@
package dev.sheldan.abstracto.moderation.repository; package dev.sheldan.abstracto.moderation.repository;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.moderation.models.database.Warning; import dev.sheldan.abstracto.moderation.models.database.Warning;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.time.Instant;
import java.util.List;
@Repository
public interface WarnRepository extends JpaRepository<Warning, Long> { public interface WarnRepository extends JpaRepository<Warning, Long> {
List<Warning> findAllByWarnedUser_ServerReferenceAndDecayedFalseAndWarnDateLessThan(AServer server, Instant cutOffDate);
} }

View File

@@ -2,7 +2,11 @@ package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.models.FullUser; import dev.sheldan.abstracto.core.models.FullUser;
import dev.sheldan.abstracto.core.models.context.ServerContext; import dev.sheldan.abstracto.core.models.context.ServerContext;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.service.MessageService; import dev.sheldan.abstracto.core.service.MessageService;
import dev.sheldan.abstracto.moderation.models.template.job.WarnDecayLogModel;
import dev.sheldan.abstracto.moderation.models.template.job.WarnDecayWarning;
import dev.sheldan.abstracto.templating.model.MessageToSend; import dev.sheldan.abstracto.templating.model.MessageToSend;
import dev.sheldan.abstracto.moderation.models.template.commands.WarnLog; import dev.sheldan.abstracto.moderation.models.template.commands.WarnLog;
import dev.sheldan.abstracto.moderation.models.template.commands.WarnNotification; import dev.sheldan.abstracto.moderation.models.template.commands.WarnNotification;
@@ -17,6 +21,12 @@ import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.entities.*;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
@Slf4j @Slf4j
@Component @Component
@@ -42,6 +52,9 @@ public class WarnServiceBean implements WarnService {
@Autowired @Autowired
private MessageService messageService; private MessageService messageService;
@Autowired
private ConfigService configService;
private static final String WARN_LOG_TEMPLATE = "warn_log"; private static final String WARN_LOG_TEMPLATE = "warn_log";
private static final String WARN_NOTIFICATION_TEMPLATE = "warn_notification"; private static final String WARN_NOTIFICATION_TEMPLATE = "warn_notification";
@@ -95,6 +108,54 @@ public class WarnServiceBean implements WarnService {
this.sendWarnLog(warnLog); this.sendWarnLog(warnLog);
} }
@Override
@Transactional
public void decayWarningsForServer(AServer server) {
Double days = configService.getDoubleValue("decayDays", server.getId());
Instant cutOffDay = Instant.now().minus(days.longValue(), ChronoUnit.DAYS);
List<Warning> warningsToDecay = warnManagementService.getActiveWarningsInServerOlderThan(server, cutOffDay);
decayWarnings(warningsToDecay);
logDecayedWarnings(server, warningsToDecay);
}
private void decayWarnings(List<Warning> warningsToDecay) {
Instant now = Instant.now();
warningsToDecay.forEach(warning -> {
warning.setDecayDate(now);
warning.setDecayed(true);
});
}
private void logDecayedWarnings(AServer server, List<Warning> warningsToDecay) {
List<WarnDecayWarning> warnDecayWarnings = new ArrayList<>();
warningsToDecay.forEach(warning -> {
WarnDecayWarning warnDecayWarning = WarnDecayWarning
.builder()
.warnedMember(botService.getMemberInServer(warning.getWarnedUser()))
.warningMember(botService.getMemberInServer(warning.getWarningUser()))
.warning(warning)
.build();
warnDecayWarnings.add(warnDecayWarning);
});
WarnDecayLogModel warnDecayLogModel = WarnDecayLogModel
.builder()
.guild(botService.getGuildByIdNullable(server.getId()))
.server(server)
.warnings(warnDecayWarnings)
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate("warn_decay_log", warnDecayLogModel);
postTargetService.sendEmbedInPostTarget(messageToSend, "decayLog", server.getId());
}
@Override
public void decayAllWarningsForServer(AServer server, Boolean logWarnings) {
List<Warning> warningsToDecay = warnManagementService.getActiveWarningsInServerOlderThan(server, Instant.now());
decayWarnings(warningsToDecay);
if(logWarnings) {
logDecayedWarnings(server, warningsToDecay);
}
}
private void sendWarnLog(ServerContext warnLogModel) { private void sendWarnLog(ServerContext warnLogModel) {
MessageToSend message = templateService.renderEmbedTemplate(WARN_LOG_TEMPLATE, warnLogModel); MessageToSend message = templateService.renderEmbedTemplate(WARN_LOG_TEMPLATE, warnLogModel);
postTargetService.sendEmbedInPostTarget(message, WARN_LOG_TARGET, warnLogModel.getServer().getId()); postTargetService.sendEmbedInPostTarget(message, WARN_LOG_TARGET, warnLogModel.getServer().getId());

View File

@@ -1,5 +1,6 @@
package dev.sheldan.abstracto.moderation.service.management; package dev.sheldan.abstracto.moderation.service.management;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.moderation.models.database.Warning; import dev.sheldan.abstracto.moderation.models.database.Warning;
import dev.sheldan.abstracto.moderation.repository.WarnRepository; import dev.sheldan.abstracto.moderation.repository.WarnRepository;
import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer;
@@ -7,6 +8,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.time.Instant; import java.time.Instant;
import java.util.List;
@Component @Component
public class WarnManagementServiceBean implements WarnManagementService { public class WarnManagementServiceBean implements WarnManagementService {
@@ -21,8 +23,14 @@ public class WarnManagementServiceBean implements WarnManagementService {
.warnedUser(warnedAUser) .warnedUser(warnedAUser)
.warningUser(warningAUser) .warningUser(warningAUser)
.warnDate(Instant.now()) .warnDate(Instant.now())
.decayed(false)
.build(); .build();
warnRepository.save(warning); warnRepository.save(warning);
return warning; return warning;
} }
@Override
public List<Warning> getActiveWarningsInServerOlderThan(AServer server, Instant date) {
return warnRepository.findAllByWarnedUser_ServerReferenceAndDecayedFalseAndWarnDateLessThan(server, date);
}
} }

View File

@@ -1,12 +1,21 @@
abstracto.postTargets.moderation=joinLog,leaveLog,warnLog,kickLog,banLog,editLog,deleteLog,muteLog abstracto.postTargets.moderation=joinLog,leaveLog,warnLog,kickLog,banLog,editLog,deleteLog,muteLog,decayLog
abstracto.features.moderation=false abstracto.features.moderation=false
abstracto.features.warning=false abstracto.features.warning=false
abstracto.features.logging=true abstracto.features.logging=true
abstracto.features.mutes=true abstracto.features.mutes=true
abstracto.warnings.warnDecay.days=90
abstracto.scheduling.jobs.unMuteJob.name=unMuteJob abstracto.scheduling.jobs.unMuteJob.name=unMuteJob
abstracto.scheduling.jobs.unMuteJob.group=moderation abstracto.scheduling.jobs.unMuteJob.group=moderation
abstracto.scheduling.jobs.unMuteJob.clazz=dev.sheldan.abstracto.moderation.job.UnMuteJob abstracto.scheduling.jobs.unMuteJob.clazz=dev.sheldan.abstracto.moderation.job.UnMuteJob
abstracto.scheduling.jobs.unMuteJob.standAlone=false abstracto.scheduling.jobs.unMuteJob.standAlone=false
abstracto.scheduling.jobs.unMuteJob.active=true abstracto.scheduling.jobs.unMuteJob.active=true
abstracto.scheduling.jobs.unMuteJob.recovery=false abstracto.scheduling.jobs.unMuteJob.recovery=false
abstracto.scheduling.jobs.warnDecayJob.name=warnDecayJob
abstracto.scheduling.jobs.warnDecayJob.group=moderation
abstracto.scheduling.jobs.warnDecayJob.clazz=dev.sheldan.abstracto.moderation.job.WarnDecayJob
abstracto.scheduling.jobs.warnDecayJob.standAlone=true
abstracto.scheduling.jobs.warnDecayJob.active=true
abstracto.scheduling.jobs.warnDecayJob.cronExpression=0 0 * * * ?
abstracto.scheduling.jobs.warnDecayJob.recovery=false

View File

@@ -0,0 +1 @@
Decays all warnings which are currently active on this server, and logs them to the `decayLog` post target, if the parameter was true.

View File

@@ -0,0 +1 @@
Causes the warnings older than the configured threshold in days to be decayed. They are still stored but there is an indication, that they are now longer active.

View File

@@ -0,0 +1,17 @@
{
"title": {
"title": "Warnings have been decayed"
},
"color" : {
"r": 200,
"g": 0,
"b": 255
},
"description": "<#list warnings as warning>
<#if warning.warnedMember??>${warning.warnedMember.asMention} (${warning.warnedMember.id})<#else>${warning.warning.warnedUser.userReference.id?c}</#if> was warned on ${formatInstant(warning.warning.warnDate, "yyyy-MM-dd HH:mm:ss")}
with reason `${warning.warning.reason}` by <#if warning.warningMember??>${warning.warningMember.asMention} (${warning.warningMember.id})<#else>${warning.warning.warningUser.userReference.id?c}</#if>
<#else>
No warnings to decay.
</#list>"
}

View File

@@ -1,11 +1,11 @@
package dev.sheldan.abstracto.moderation.config.features; package dev.sheldan.abstracto.moderation.config.features;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureEnum; import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.config.FeatureDisplay;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
public class LoggingFeature implements FeatureDisplay { public class LoggingFeature implements FeatureConfig {
@Override @Override
public FeatureEnum getFeature() { public FeatureEnum getFeature() {

View File

@@ -1,11 +1,11 @@
package dev.sheldan.abstracto.moderation.config.features; package dev.sheldan.abstracto.moderation.config.features;
import dev.sheldan.abstracto.core.config.FeatureEnum; import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.config.FeatureDisplay; import dev.sheldan.abstracto.core.config.FeatureConfig;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
public class ModerationFeature implements FeatureDisplay { public class ModerationFeature implements FeatureConfig {
@Override @Override
public FeatureEnum getFeature() { public FeatureEnum getFeature() {

View File

@@ -5,7 +5,7 @@ import lombok.Getter;
@Getter @Getter
public enum ModerationFeatures implements FeatureEnum { public enum ModerationFeatures implements FeatureEnum {
MODERATION("moderation"), WARNING("warnings"), LOGGING("logging"), MUTING("muting"); MODERATION("moderation"), WARNING("warnings"), LOGGING("logging"), MUTING("muting"), AUTOMATIC_WARN_DECAY("warnDecay");
private String key; private String key;

View File

@@ -1,11 +1,11 @@
package dev.sheldan.abstracto.moderation.config.features; package dev.sheldan.abstracto.moderation.config.features;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureEnum; import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.config.FeatureDisplay;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
public class MutingFeature implements FeatureDisplay { public class MutingFeature implements FeatureConfig {
@Override @Override
public FeatureEnum getFeature() { public FeatureEnum getFeature() {

View File

@@ -0,0 +1,26 @@
package dev.sheldan.abstracto.moderation.config.features;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class WarningDecayFeature implements FeatureConfig {
@Autowired
private WarningFeature warningFeature;
@Override
public FeatureEnum getFeature() {
return ModerationFeatures.AUTOMATIC_WARN_DECAY;
}
@Override
public List<FeatureConfig> getRequiredFeatures() {
return Arrays.asList(warningFeature);
}
}

View File

@@ -1,15 +1,26 @@
package dev.sheldan.abstracto.moderation.config.features; package dev.sheldan.abstracto.moderation.config.features;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureEnum; import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.config.FeatureDisplay; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component @Component
public class WarningFeature implements FeatureDisplay { public class WarningFeature implements FeatureConfig {
@Autowired
private WarningDecayFeature warningDecayFeature;
@Override @Override
public FeatureEnum getFeature() { public FeatureEnum getFeature() {
return ModerationFeatures.WARNING; return ModerationFeatures.WARNING;
} }
@Override
public List<FeatureConfig> getDependantFeatures() {
return Arrays.asList(warningDecayFeature);
}
} }

View File

@@ -1,10 +1,7 @@
package dev.sheldan.abstracto.moderation.models.database; package dev.sheldan.abstracto.moderation.models.database;
import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import lombok.AllArgsConstructor; import lombok.*;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*; import javax.persistence.*;
import java.time.Instant; import java.time.Instant;
@@ -38,9 +35,11 @@ public class Warning {
private Instant warnDate; private Instant warnDate;
@Getter @Getter
@Setter
private Boolean decayed; private Boolean decayed;
@Getter @Getter
@Setter
private Instant decayDate; private Instant decayDate;

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.moderation.models.template.job;
import dev.sheldan.abstracto.core.models.context.ServerContext;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import java.util.List;
@Getter
@Setter
@SuperBuilder
public class WarnDecayLogModel extends ServerContext {
private List<WarnDecayWarning> warnings;
}

View File

@@ -0,0 +1,16 @@
package dev.sheldan.abstracto.moderation.models.template.job;
import dev.sheldan.abstracto.moderation.models.database.Warning;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.entities.Member;
@Getter
@Setter
@Builder
public class WarnDecayWarning {
private Warning warning;
private Member warnedMember;
private Member warningMember;
}

View File

@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.moderation.service; package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.models.FullUser; import dev.sheldan.abstracto.core.models.FullUser;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.moderation.models.database.Warning; import dev.sheldan.abstracto.moderation.models.database.Warning;
import dev.sheldan.abstracto.moderation.models.template.commands.WarnLog; import dev.sheldan.abstracto.moderation.models.template.commands.WarnLog;
import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer;
@@ -13,4 +14,6 @@ public interface WarnService {
Warning warnUser(Member warnedMember, Member warningMember, String reason, TextChannel feedbackChannel); Warning warnUser(Member warnedMember, Member warningMember, String reason, TextChannel feedbackChannel);
Warning warnUser(FullUser warnedUser, FullUser warningUser, String reason, TextChannel feedbackChannel); Warning warnUser(FullUser warnedUser, FullUser warningUser, String reason, TextChannel feedbackChannel);
void warnUserWithLog(Member warnedMember, Member warningMember, String reason, WarnLog warnLog, TextChannel feedbackChannel); void warnUserWithLog(Member warnedMember, Member warningMember, String reason, WarnLog warnLog, TextChannel feedbackChannel);
void decayWarningsForServer(AServer server);
void decayAllWarningsForServer(AServer server, Boolean logWarnings);
} }

View File

@@ -1,8 +1,13 @@
package dev.sheldan.abstracto.moderation.service.management; package dev.sheldan.abstracto.moderation.service.management;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.moderation.models.database.Warning; import dev.sheldan.abstracto.moderation.models.database.Warning;
import dev.sheldan.abstracto.core.models.database.AUserInAServer; import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import java.time.Instant;
import java.util.List;
public interface WarnManagementService { public interface WarnManagementService {
Warning createWarning(AUserInAServer warnedAUser, AUserInAServer warningAUser, String reason); Warning createWarning(AUserInAServer warnedAUser, AUserInAServer warningAUser, String reason);
List<Warning> getActiveWarningsInServerOlderThan(AServer server, Instant date);
} }

View File

@@ -1,11 +1,11 @@
package dev.sheldan.abstracto.utility.config.features; package dev.sheldan.abstracto.utility.config.features;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureEnum; import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.config.FeatureDisplay;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
public class LinkEmbedFeature implements FeatureDisplay { public class LinkEmbedFeature implements FeatureConfig {
@Override @Override
public FeatureEnum getFeature() { public FeatureEnum getFeature() {

View File

@@ -1,11 +1,11 @@
package dev.sheldan.abstracto.utility.config.features; package dev.sheldan.abstracto.utility.config.features;
import dev.sheldan.abstracto.core.config.FeatureEnum; import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.config.FeatureDisplay; import dev.sheldan.abstracto.core.config.FeatureConfig;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
public class RemindFeature implements FeatureDisplay { public class RemindFeature implements FeatureConfig {
@Override @Override
public FeatureEnum getFeature() { public FeatureEnum getFeature() {

View File

@@ -1,11 +1,11 @@
package dev.sheldan.abstracto.utility.config.features; package dev.sheldan.abstracto.utility.config.features;
import dev.sheldan.abstracto.core.config.FeatureEnum; import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.config.FeatureDisplay; import dev.sheldan.abstracto.core.config.FeatureConfig;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
public class StarboardFeature implements FeatureDisplay { public class StarboardFeature implements FeatureConfig {
@Override @Override
public FeatureEnum getFeature() { public FeatureEnum getFeature() {

View File

@@ -1,11 +1,11 @@
package dev.sheldan.abstracto.utility.config.features; package dev.sheldan.abstracto.utility.config.features;
import dev.sheldan.abstracto.core.config.FeatureDisplay; import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureEnum; import dev.sheldan.abstracto.core.config.FeatureEnum;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
public class SuggestionFeature implements FeatureDisplay { public class SuggestionFeature implements FeatureConfig {
@Override @Override
public FeatureEnum getFeature() { public FeatureEnum getFeature() {

View File

@@ -1,11 +1,11 @@
package dev.sheldan.abstracto.utility.config.features; package dev.sheldan.abstracto.utility.config.features;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureEnum; import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.config.FeatureDisplay;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
public class UtilitiesFeature implements FeatureDisplay { public class UtilitiesFeature implements FeatureConfig {
@Override @Override
public FeatureEnum getFeature() { public FeatureEnum getFeature() {

View File

@@ -31,7 +31,7 @@ public class CommandCreationListener {
@EventListener @EventListener
@Transactional @Transactional
public void handleContextRefreshEvent(ContextRefreshedEvent ctxStartEvt) { public void handleContextRefreshEvent(ContextRefreshedEvent ctxStartEvt) {
featureFlagService.getAllFeatureDisplays().forEach((featureFlagKey) -> { featureFlagService.getAllFeatureConfigs().forEach((featureFlagKey) -> {
String featureKey = featureFlagKey.getFeature().getKey(); String featureKey = featureFlagKey.getFeature().getKey();
if(!featureManagementService.featureExists(featureKey)) { if(!featureManagementService.featureExists(featureKey)) {
featureManagementService.createFeature(featureKey); featureManagementService.createFeature(featureKey);

View File

@@ -9,6 +9,7 @@ import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.ContextConverter; import dev.sheldan.abstracto.core.command.execution.ContextConverter;
import dev.sheldan.abstracto.core.commands.config.ConfigModuleInterface; 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.FeatureEnum;
import dev.sheldan.abstracto.core.config.features.CoreFeatures; import dev.sheldan.abstracto.core.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.models.template.commands.EnableModel; import dev.sheldan.abstracto.core.models.template.commands.EnableModel;
@@ -43,8 +44,13 @@ public class Disable extends AbstractConditionableCommand {
channelService.sendTextInAChannel(response, commandContext.getChannel()); channelService.sendTextInAChannel(response, commandContext.getChannel());
} else { } else {
String flagKey = (String) commandContext.getParameters().getParameters().get(0); String flagKey = (String) commandContext.getParameters().getParameters().get(0);
FeatureEnum feature = featureFlagService.getFeatureEnum(flagKey); FeatureConfig feature = featureFlagService.getFeatureDisplayForFeature(flagKey);
featureFlagService.disableFeature(feature, commandContext.getGuild().getIdLong()); featureFlagService.disableFeature(feature, commandContext.getGuild().getIdLong());
if(feature.getDependantFeatures() != null) {
feature.getDependantFeatures().forEach(featureDisplay -> {
featureFlagService.disableFeature(featureDisplay, commandContext.getUserInitiatedContext().getServer());
});
}
} }
return CommandResult.fromSuccess(); return CommandResult.fromSuccess();
} }

View File

@@ -9,6 +9,7 @@ import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.config.Parameter; import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.ContextConverter; import dev.sheldan.abstracto.core.command.execution.ContextConverter;
import dev.sheldan.abstracto.core.commands.config.ConfigModuleInterface; 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.FeatureEnum;
import dev.sheldan.abstracto.core.config.features.CoreFeatures; import dev.sheldan.abstracto.core.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.models.template.commands.EnableModel; import dev.sheldan.abstracto.core.models.template.commands.EnableModel;
@@ -42,8 +43,13 @@ public class Enable extends AbstractConditionableCommand {
channelService.sendTextInAChannel(response, commandContext.getChannel()); channelService.sendTextInAChannel(response, commandContext.getChannel());
} else { } else {
String flagKey = (String) commandContext.getParameters().getParameters().get(0); String flagKey = (String) commandContext.getParameters().getParameters().get(0);
FeatureEnum feature = featureFlagService.getFeatureEnum(flagKey); FeatureConfig feature = featureFlagService.getFeatureDisplayForFeature(flagKey);
featureFlagService.enableFeature(feature, commandContext.getGuild().getIdLong()); featureFlagService.enableFeature(feature, commandContext.getUserInitiatedContext().getServer());
if(feature.getRequiredFeatures() != null) {
feature.getRequiredFeatures().forEach(featureDisplay -> {
featureFlagService.enableFeature(featureDisplay, commandContext.getUserInitiatedContext().getServer());
});
}
} }
return CommandResult.fromSuccess(); return CommandResult.fromSuccess();
} }

View File

@@ -31,7 +31,7 @@ public class FeatureFlagListener implements ServerConfigListener {
@Override @Override
public void updateServerConfig(AServer server) { public void updateServerConfig(AServer server) {
log.info("Setting up feature flags if necessary."); log.info("Setting up feature flags if necessary.");
featureFlagService.getAllFeatureDisplays().forEach((featureFlagKey) -> { featureFlagService.getAllFeatureConfigs().forEach((featureFlagKey) -> {
String featureKey = featureFlagKey.getFeature().getKey(); String featureKey = featureFlagKey.getFeature().getKey();
AFeature feature = featureManagementService.getFeature(featureKey); AFeature feature = featureManagementService.getFeature(featureKey);
boolean featureFlagValue = BooleanUtils.toBoolean(environment.getProperty("abstracto.features." + featureKey, "false")); boolean featureFlagValue = BooleanUtils.toBoolean(environment.getProperty("abstracto.features." + featureKey, "false"));

View File

@@ -1,11 +1,11 @@
package dev.sheldan.abstracto.core.config.features; package dev.sheldan.abstracto.core.config.features;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureEnum; import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.config.FeatureDisplay;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
public class CoreFeature implements FeatureDisplay { public class CoreFeature implements FeatureConfig {
@Override @Override
public FeatureEnum getFeature() { public FeatureEnum getFeature() {

View File

@@ -1,6 +1,6 @@
package dev.sheldan.abstracto.core.converter; package dev.sheldan.abstracto.core.converter;
import dev.sheldan.abstracto.core.config.FeatureDisplay; import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureEnum; import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.models.database.AFeatureFlag; import dev.sheldan.abstracto.core.models.database.AFeatureFlag;
import dev.sheldan.abstracto.core.models.template.commands.FeatureFlagDisplay; import dev.sheldan.abstracto.core.models.template.commands.FeatureFlagDisplay;
@@ -19,10 +19,10 @@ public class FeatureFlagConverter {
public FeatureFlagDisplay fromAFeatureFlag(AFeatureFlag featureFlag) { public FeatureFlagDisplay fromAFeatureFlag(AFeatureFlag featureFlag) {
FeatureEnum featureEnum = featureFlagService.getFeatureEnum(featureFlag.getFeature().getKey()); FeatureEnum featureEnum = featureFlagService.getFeatureEnum(featureFlag.getFeature().getKey());
FeatureDisplay forFeature = featureFlagService.getFeatureDisplayforFeature(featureEnum); FeatureConfig forFeature = featureFlagService.getFeatureDisplayForFeature(featureEnum);
return FeatureFlagDisplay return FeatureFlagDisplay
.builder() .builder()
.featureDisplay(forFeature) .featureConfig(forFeature)
.featureFlag(featureFlag) .featureFlag(featureFlag)
.build(); .build();
} }

View File

@@ -1,5 +1,6 @@
package dev.sheldan.abstracto.core.listener; 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.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.FeatureFlagService; import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.service.management.UserManagementService; import dev.sheldan.abstracto.core.service.management.UserManagementService;
@@ -31,7 +32,8 @@ public class JoinListenerBean extends ListenerAdapter {
@Transactional @Transactional
public void onGuildMemberJoin(@Nonnull GuildMemberJoinEvent event) { public void onGuildMemberJoin(@Nonnull GuildMemberJoinEvent event) {
listenerList.forEach(joinListener -> { listenerList.forEach(joinListener -> {
if (!featureFlagService.isFeatureEnabled(joinListener.getFeature(), event.getGuild().getIdLong())) { FeatureConfig feature = featureFlagService.getFeatureDisplayForFeature(joinListener.getFeature());
if (!featureFlagService.isFeatureEnabled(feature, event.getGuild().getIdLong())) {
return; return;
} }
try { try {

View File

@@ -1,5 +1,6 @@
package dev.sheldan.abstracto.core.listener; 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.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.service.FeatureFlagService; import dev.sheldan.abstracto.core.service.FeatureFlagService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -26,7 +27,8 @@ public class LeaveListenerBean extends ListenerAdapter {
@Transactional @Transactional
public void onGuildMemberLeave(@Nonnull GuildMemberLeaveEvent event) { public void onGuildMemberLeave(@Nonnull GuildMemberLeaveEvent event) {
listenerList.forEach(leaveListener -> { listenerList.forEach(leaveListener -> {
if(!featureFlagService.isFeatureEnabled(leaveListener.getFeature(), event.getGuild().getIdLong())) { FeatureConfig feature = featureFlagService.getFeatureDisplayForFeature(leaveListener.getFeature());
if(!featureFlagService.isFeatureEnabled(feature, event.getGuild().getIdLong())) {
return; return;
} }
try { try {

View File

@@ -1,5 +1,6 @@
package dev.sheldan.abstracto.core.listener; 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.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.models.cache.CachedMessage; import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.service.FeatureFlagService; import dev.sheldan.abstracto.core.service.FeatureFlagService;
@@ -40,7 +41,8 @@ public class MessageDeletedListenerBean extends ListenerAdapter {
@Transactional @Transactional
public void executeListener(CachedMessage cachedMessage) { public void executeListener(CachedMessage cachedMessage) {
listener.forEach(messageDeletedListener -> { listener.forEach(messageDeletedListener -> {
if(!featureFlagService.isFeatureEnabled(messageDeletedListener.getFeature(), cachedMessage.getServerId())) { FeatureConfig feature = featureFlagService.getFeatureDisplayForFeature(messageDeletedListener.getFeature());
if(!featureFlagService.isFeatureEnabled(feature, cachedMessage.getServerId())) {
return; return;
} }
try { try {

View File

@@ -1,5 +1,6 @@
package dev.sheldan.abstracto.core.listener; package dev.sheldan.abstracto.core.listener;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.service.FeatureFlagService; import dev.sheldan.abstracto.core.service.FeatureFlagService;
import dev.sheldan.abstracto.core.service.MessageCache; import dev.sheldan.abstracto.core.service.MessageCache;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -30,7 +31,8 @@ public class MessageReceivedListenerBean extends ListenerAdapter {
public void onGuildMessageReceived(@Nonnull GuildMessageReceivedEvent event) { public void onGuildMessageReceived(@Nonnull GuildMessageReceivedEvent event) {
messageCache.putMessageInCache(event.getMessage()); messageCache.putMessageInCache(event.getMessage());
listenerList.forEach(messageReceivedListener -> { listenerList.forEach(messageReceivedListener -> {
if(!featureFlagService.isFeatureEnabled(messageReceivedListener.getFeature(), event.getGuild().getIdLong())) { FeatureConfig feature = featureFlagService.getFeatureDisplayForFeature(messageReceivedListener.getFeature());
if(!featureFlagService.isFeatureEnabled(feature, event.getGuild().getIdLong())) {
return; return;
} }
messageReceivedListener.execute(event.getMessage()); messageReceivedListener.execute(event.getMessage());

View File

@@ -1,5 +1,6 @@
package dev.sheldan.abstracto.core.listener; 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.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.models.cache.CachedMessage; import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import dev.sheldan.abstracto.core.service.FeatureFlagService; import dev.sheldan.abstracto.core.service.FeatureFlagService;
@@ -44,7 +45,8 @@ public class MessageUpdatedListener extends ListenerAdapter {
@Transactional @Transactional
public void executeListener(Message message, CachedMessage cachedMessage) { public void executeListener(Message message, CachedMessage cachedMessage) {
listener.forEach(messageTextUpdatedListener -> { listener.forEach(messageTextUpdatedListener -> {
if(!featureFlagService.isFeatureEnabled(messageTextUpdatedListener.getFeature(), message.getGuild().getIdLong())) { FeatureConfig feature = featureFlagService.getFeatureDisplayForFeature(messageTextUpdatedListener.getFeature());
if(!featureFlagService.isFeatureEnabled(feature, message.getGuild().getIdLong())) {
return; return;
} }
try { try {

View File

@@ -1,5 +1,6 @@
package dev.sheldan.abstracto.core.listener; 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.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.service.BotService; import dev.sheldan.abstracto.core.service.BotService;
import dev.sheldan.abstracto.core.service.FeatureFlagService; import dev.sheldan.abstracto.core.service.FeatureFlagService;
@@ -97,7 +98,8 @@ public class ReactionUpdatedListener extends ListenerAdapter {
AUserInAServer userInAServer = userManagementService.loadUser(event.getGuild().getIdLong(), event.getUserIdLong()); AUserInAServer userInAServer = userManagementService.loadUser(event.getGuild().getIdLong(), event.getUserIdLong());
addReactionIfNotThere(cachedMessage, reaction, userInAServer.getUserReference()); addReactionIfNotThere(cachedMessage, reaction, userInAServer.getUserReference());
addedListenerList.forEach(reactedAddedListener -> { addedListenerList.forEach(reactedAddedListener -> {
if(!featureFlagService.isFeatureEnabled(reactedAddedListener.getFeature(), event.getGuild().getIdLong())) { FeatureConfig feature = featureFlagService.getFeatureDisplayForFeature(reactedAddedListener.getFeature());
if(!featureFlagService.isFeatureEnabled(feature, event.getGuild().getIdLong())) {
return; return;
} }
try { try {
@@ -130,14 +132,15 @@ public class ReactionUpdatedListener extends ListenerAdapter {
public void callRemoveListeners(@Nonnull GuildMessageReactionRemoveEvent event, CachedMessage cachedMessage, CachedReaction reaction) { public void callRemoveListeners(@Nonnull GuildMessageReactionRemoveEvent event, CachedMessage cachedMessage, CachedReaction reaction) {
AUserInAServer userInAServer = userManagementService.loadUser(event.getGuild().getIdLong(), event.getUserIdLong()); AUserInAServer userInAServer = userManagementService.loadUser(event.getGuild().getIdLong(), event.getUserIdLong());
removeReactionIfThere(cachedMessage, reaction, userInAServer.getUserReference()); removeReactionIfThere(cachedMessage, reaction, userInAServer.getUserReference());
reactionRemovedListener.forEach(reactedAddedListener -> { reactionRemovedListener.forEach(reactionRemovedListener -> {
if(!featureFlagService.isFeatureEnabled(reactedAddedListener.getFeature(), event.getGuild().getIdLong())) { FeatureConfig feature = featureFlagService.getFeatureDisplayForFeature(reactionRemovedListener.getFeature());
if(!featureFlagService.isFeatureEnabled(feature, event.getGuild().getIdLong())) {
return; return;
} }
try { try {
reactedAddedListener.executeReactionRemoved(cachedMessage, event.getReaction(), userInAServer); reactionRemovedListener.executeReactionRemoved(cachedMessage, event.getReaction(), userInAServer);
} catch (AbstractoRunTimeException e) { } catch (AbstractoRunTimeException e) {
log.warn(String.format("Failed to execute reaction removed listener %s.", reactedAddedListener.getClass().getName()), e); log.warn(String.format("Failed to execute reaction removed listener %s.", reactionRemovedListener.getClass().getName()), e);
} }
}); });
} }

View File

@@ -0,0 +1,12 @@
package dev.sheldan.abstracto.core.repository;
import dev.sheldan.abstracto.core.models.database.AFeatureFlag;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface FeatureJobTriggerRepository extends JpaRepository<AFeatureJobTrigger, Long> {
List<AFeatureJobTrigger> findByFeature(AFeatureFlag featureFlag);
}

View File

@@ -1,10 +1,12 @@
package dev.sheldan.abstracto.core.service; package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.config.FeatureDisplay; import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureEnum; import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException; import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.exception.FeatureNotFoundException; import dev.sheldan.abstracto.core.exception.FeatureNotFoundException;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.management.FeatureFlagManagementService; import dev.sheldan.abstracto.core.service.management.FeatureFlagManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -19,28 +21,50 @@ public class FeatureFlagServiceBean implements FeatureFlagService {
private FeatureFlagManagementService managementService; private FeatureFlagManagementService managementService;
@Autowired @Autowired
private List<FeatureDisplay> availableFeatures; private List<FeatureConfig> availableFeatures;
@Autowired
private ServerManagementService serverManagementService;
@Override @Override
public boolean isFeatureEnabled(FeatureEnum name, Long serverId) { public boolean isFeatureEnabled(FeatureConfig name, Long serverId) {
return managementService.getFeatureFlagValue(name, serverId); return managementService.getFeatureFlagValue(name.getFeature(), serverId);
} }
@Override @Override
public void enableFeature(FeatureEnum name, Long serverId) { public boolean isFeatureEnabled(FeatureConfig name, AServer server) {
if(!doesFeatureExist(name)) { return managementService.getFeatureFlagValue(name.getFeature(), server);
throw new FeatureNotFoundException("Feature not found.", name.getKey(), getFeaturesAsList());
}
managementService.updateFeatureFlag(name, serverId, true);
} }
@Override @Override
public void disableFeature(FeatureEnum name, Long serverId) { public void enableFeature(FeatureConfig name, Long serverId) {
AServer server = serverManagementService.loadOrCreate(serverId);
enableFeature(name, server);
}
@Override
public void enableFeature(FeatureConfig name, AServer server) {
FeatureEnum feature = name.getFeature();
if(!doesFeatureExist(name)) { if(!doesFeatureExist(name)) {
throw new FeatureNotFoundException("Feature not found.", name.getKey(), getFeaturesAsList()); throw new FeatureNotFoundException("Feature not found.", feature.getKey(), getFeaturesAsList());
} }
managementService.updateFeatureFlag(name, serverId, false); managementService.updateFeatureFlag(feature, server, true);
}
@Override
public void disableFeature(FeatureConfig name, Long serverId) {
AServer server = serverManagementService.loadOrCreate(serverId);
disableFeature(name, server);
}
@Override
public void disableFeature(FeatureConfig name, AServer server) {
FeatureEnum feature = name.getFeature();
if(!doesFeatureExist(name)) {
throw new FeatureNotFoundException("Feature not found.", feature.getKey(), getFeaturesAsList());
}
managementService.updateFeatureFlag(feature, server, false);
} }
@Override @Override
@@ -52,13 +76,13 @@ public class FeatureFlagServiceBean implements FeatureFlagService {
} }
@Override @Override
public List<FeatureDisplay> getAllFeatureDisplays() { public List<FeatureConfig> getAllFeatureConfigs() {
return availableFeatures; return availableFeatures;
} }
@Override @Override
public FeatureDisplay getFeatureDisplayforFeature(FeatureEnum featureEnum) { public FeatureConfig getFeatureDisplayForFeature(FeatureEnum featureEnum) {
Optional<FeatureDisplay> any = getAllFeatureDisplays().stream().filter(featureDisplay -> featureDisplay.getFeature().equals(featureEnum)).findAny(); Optional<FeatureConfig> any = getAllFeatureConfigs().stream().filter(featureDisplay -> featureDisplay.getFeature().equals(featureEnum)).findAny();
if(any.isPresent()) { if(any.isPresent()) {
return any.get(); return any.get();
} }
@@ -66,8 +90,13 @@ public class FeatureFlagServiceBean implements FeatureFlagService {
} }
@Override @Override
public boolean doesFeatureExist(FeatureEnum name) { public FeatureConfig getFeatureDisplayForFeature(String key) {
return availableFeatures.stream().anyMatch(featureDisplay -> featureDisplay.getFeature().equals(name)); return getFeatureDisplayForFeature(getFeatureEnum(key));
}
@Override
public boolean doesFeatureExist(FeatureConfig name) {
return availableFeatures.stream().anyMatch(featureDisplay -> featureDisplay.getFeature().equals(name.getFeature()));
} }
@Override @Override
@@ -80,7 +109,7 @@ public class FeatureFlagServiceBean implements FeatureFlagService {
@Override @Override
public FeatureEnum getFeatureEnum(String key) { public FeatureEnum getFeatureEnum(String key) {
Optional<FeatureDisplay> foundFeature = availableFeatures.stream().filter(featureDisplay -> featureDisplay.getFeature().getKey().equals(key)).findAny(); Optional<FeatureConfig> foundFeature = availableFeatures.stream().filter(featureDisplay -> featureDisplay.getFeature().getKey().equals(key)).findAny();
if(foundFeature.isPresent()) { if(foundFeature.isPresent()) {
return foundFeature.get().getFeature(); return foundFeature.get().getFeature();
} }

View File

@@ -44,15 +44,27 @@ public class FeatureFlagManagementServiceBean implements FeatureFlagManagementSe
@Override @Override
public boolean getFeatureFlagValue(FeatureEnum key, Long serverId) { public boolean getFeatureFlagValue(FeatureEnum key, Long serverId) {
AServer server = serverManagementService.loadOrCreate(serverId);
return getFeatureFlagValue(key, server);
}
@Override
public boolean getFeatureFlagValue(FeatureEnum key, AServer server) {
AFeature feature = featureManagementService.getFeature(key.getKey()); AFeature feature = featureManagementService.getFeature(key.getKey());
Optional<AFeatureFlag> featureFlag = getFeatureFlag(feature, serverId); Optional<AFeatureFlag> featureFlag = getFeatureFlag(feature, server);
return featureFlag.isPresent() && featureFlag.get().isEnabled(); return featureFlag.isPresent() && featureFlag.get().isEnabled();
} }
@Override @Override
public void updateFeatureFlag(FeatureEnum key, Long serverId, Boolean newValue) { public void updateFeatureFlag(FeatureEnum key, Long serverId, Boolean newValue) {
AServer server = serverManagementService.loadOrCreate(serverId);
updateFeatureFlag(key, server, newValue);
}
@Override
public void updateFeatureFlag(FeatureEnum key, AServer server, Boolean newValue) {
AFeature feature = featureManagementService.getFeature(key.getKey()); AFeature feature = featureManagementService.getFeature(key.getKey());
Optional<AFeatureFlag> existing = getFeatureFlag(feature, serverId); Optional<AFeatureFlag> existing = getFeatureFlag(feature, server);
if(existing.isPresent()) { if(existing.isPresent()) {
AFeatureFlag flag = existing.get(); AFeatureFlag flag = existing.get();
flag.setEnabled(newValue); flag.setEnabled(newValue);
@@ -63,7 +75,12 @@ public class FeatureFlagManagementServiceBean implements FeatureFlagManagementSe
@Override @Override
public Optional<AFeatureFlag> getFeatureFlag(AFeature feature, Long serverId) { public Optional<AFeatureFlag> getFeatureFlag(AFeature feature, Long serverId) {
AServer server = serverManagementService.loadOrCreate(serverId); AServer server = serverManagementService.loadOrCreate(serverId);
return Optional.ofNullable(repository.findByServerAndFeature(server, feature)); return getFeatureFlag(feature, server);
}
@Override
public Optional<AFeatureFlag> getFeatureFlag(AFeature key, AServer server) {
return Optional.ofNullable(repository.findByServerAndFeature(server, key));
} }
@Override @Override

View File

@@ -9,7 +9,7 @@
}, },
"description": " "description": "
<#list features as feature> <#list features as feature>
${feature.featureFlag.enabled?string('✅', '❌')} **<#include "${feature.featureDisplay.feature.key}_feature">** Key: `${feature.featureDisplay.feature.key}` ${feature.featureFlag.enabled?string('✅', '❌')} **<#include "${feature.featureConfig.feature.key}_feature">** Key: `${feature.featureConfig.feature.key}`
</#list> </#list>
" "
} }

View File

@@ -1 +1 @@
Feature has been disabled. Necessary feature is: <#include "${featureDisplay.feature.key}_feature">, you can enable it by executing `enable ${featureDisplay.feature.key}`. Feature has been disabled. Necessary feature is: <#include "${featureConfig.feature.key}_feature">, you can enable it by executing `enable ${featureConfig.feature.key}`.

View File

@@ -31,7 +31,7 @@ public class FeatureEnabledCondition implements CommandCondition {
featureFlagValue = featureFlagManagementService.getFeatureFlagValue(feature, context.getGuild().getIdLong()); featureFlagValue = featureFlagManagementService.getFeatureFlagValue(feature, context.getGuild().getIdLong());
FeatureDisabledMessage featureDisabledMessage = FeatureDisabledMessage FeatureDisabledMessage featureDisabledMessage = FeatureDisabledMessage
.builder() .builder()
.featureDisplay(featureFlagService.getFeatureDisplayforFeature(feature)) .featureConfig(featureFlagService.getFeatureDisplayForFeature(feature))
.build(); .build();
reason = templateService.renderTemplate("feature_disabled_message", featureDisabledMessage); reason = templateService.renderTemplate("feature_disabled_message", featureDisabledMessage);
} }

View File

@@ -1,6 +1,6 @@
package dev.sheldan.abstracto.core.command.models; package dev.sheldan.abstracto.core.command.models;
import dev.sheldan.abstracto.core.config.FeatureDisplay; import dev.sheldan.abstracto.core.config.FeatureConfig;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@@ -9,5 +9,5 @@ import lombok.Setter;
@Getter @Getter
@Builder @Builder
public class FeatureDisabledMessage { public class FeatureDisabledMessage {
private FeatureDisplay featureDisplay; private FeatureConfig featureConfig;
} }

View File

@@ -0,0 +1,14 @@
package dev.sheldan.abstracto.core.config;
import java.util.Collections;
import java.util.List;
public interface FeatureConfig {
FeatureEnum getFeature();
default List<FeatureConfig> getRequiredFeatures() {
return Collections.emptyList();
}
default List<FeatureConfig> getDependantFeatures() {
return Collections.emptyList();
}
}

View File

@@ -1,5 +0,0 @@
package dev.sheldan.abstracto.core.config;
public interface FeatureDisplay {
FeatureEnum getFeature();
}

View File

@@ -1,6 +1,6 @@
package dev.sheldan.abstracto.core.models.template.commands; package dev.sheldan.abstracto.core.models.template.commands;
import dev.sheldan.abstracto.core.config.FeatureDisplay; import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.models.database.AFeatureFlag; import dev.sheldan.abstracto.core.models.database.AFeatureFlag;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
@@ -11,5 +11,5 @@ import lombok.Setter;
@Builder @Builder
public class FeatureFlagDisplay { public class FeatureFlagDisplay {
private AFeatureFlag featureFlag; private AFeatureFlag featureFlag;
private FeatureDisplay featureDisplay; private FeatureConfig featureConfig;
} }

View File

@@ -1,18 +1,23 @@
package dev.sheldan.abstracto.core.service; package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.config.FeatureDisplay; import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureEnum; import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.models.database.AServer;
import java.util.List; import java.util.List;
public interface FeatureFlagService { public interface FeatureFlagService {
boolean isFeatureEnabled(FeatureEnum name, Long serverId); boolean isFeatureEnabled(FeatureConfig name, Long serverId);
void enableFeature(FeatureEnum name, Long serverId); boolean isFeatureEnabled(FeatureConfig name, AServer server);
void disableFeature(FeatureEnum name, Long serverId); void enableFeature(FeatureConfig name, Long serverId);
void enableFeature(FeatureConfig name, AServer server);
void disableFeature(FeatureConfig name, Long serverId);
void disableFeature(FeatureConfig name, AServer server);
List<String> getAllFeatures(); List<String> getAllFeatures();
List<FeatureDisplay> getAllFeatureDisplays(); List<FeatureConfig> getAllFeatureConfigs();
FeatureDisplay getFeatureDisplayforFeature(FeatureEnum featureEnum); FeatureConfig getFeatureDisplayForFeature(FeatureEnum featureEnum);
boolean doesFeatureExist(FeatureEnum name); FeatureConfig getFeatureDisplayForFeature(String key);
boolean doesFeatureExist(FeatureConfig name);
List<String> getFeaturesAsList(); List<String> getFeaturesAsList();
FeatureEnum getFeatureEnum(String key); FeatureEnum getFeatureEnum(String key);
} }

View File

@@ -12,7 +12,10 @@ public interface FeatureFlagManagementService {
void createFeatureFlag(AFeature feature, Long serverId, Boolean newValue); void createFeatureFlag(AFeature feature, Long serverId, Boolean newValue);
void createFeatureFlag(AFeature feature, AServer server, Boolean newValue); void createFeatureFlag(AFeature feature, AServer server, Boolean newValue);
boolean getFeatureFlagValue(FeatureEnum key, Long serverId); boolean getFeatureFlagValue(FeatureEnum key, Long serverId);
boolean getFeatureFlagValue(FeatureEnum key, AServer server);
void updateFeatureFlag(FeatureEnum key, Long serverId, Boolean newValue); void updateFeatureFlag(FeatureEnum key, Long serverId, Boolean newValue);
void updateFeatureFlag(FeatureEnum key, AServer server, Boolean newValue);
Optional<AFeatureFlag> getFeatureFlag(AFeature key, Long serverId); Optional<AFeatureFlag> getFeatureFlag(AFeature key, Long serverId);
Optional<AFeatureFlag> getFeatureFlag(AFeature key, AServer server);
List<AFeatureFlag> getFeatureFlagsOfServer(AServer server); List<AFeatureFlag> getFeatureFlagsOfServer(AServer server);
} }

View File

@@ -44,6 +44,15 @@ public class QuartzConfigFactory {
.build(); .build();
} }
public CronTrigger createBasicCronTrigger(String jobName, String jobGroup, Date startTime, String cronExpression, JobDataMap jobDataMap) {
return newTrigger()
.withSchedule(cronSchedule(cronExpression).inTimeZone(TimeZone.getTimeZone("UTC")).withMisfireHandlingInstructionDoNothing())
.startAt(startTime)
.usingJobData(jobDataMap)
.forJob(jobName, jobGroup)
.build();
}
public Trigger createSimpleOnceOnlyTrigger(Date startTime) { public Trigger createSimpleOnceOnlyTrigger(Date startTime) {
return newTrigger() return newTrigger()
.startAt(startTime) .startAt(startTime)

View File

@@ -159,6 +159,18 @@ public class SchedulerServiceBean implements SchedulerService {
} }
} }
@Override
public String startCronJobWithParameters(String name, String group, JobDataMap dataMap, String cronExpression) {
Trigger cronTrigger = scheduleCreator.createBasicCronTrigger(name, group, new Date(), cronExpression, dataMap);
try {
schedulerFactoryBean.getScheduler().scheduleJob(cronTrigger);
return cronTrigger.getKey().getName();
} catch (SchedulerException e) {
log.error("Failed to start new job - {}", name, e);
return null;
}
}
@Override @Override
public void stopTrigger(String triggerKey) { public void stopTrigger(String triggerKey) {
try { try {

View File

@@ -7,24 +7,15 @@ import java.util.Date;
public interface SchedulerService { public interface SchedulerService {
void startScheduledJobs(); void startScheduledJobs();
void scheduleJob(SchedulerJob job); void scheduleJob(SchedulerJob job);
void updateJob(SchedulerJob job, Date startDate); void updateJob(SchedulerJob job, Date startDate);
boolean unScheduleJob(String jobName); boolean unScheduleJob(String jobName);
boolean deleteJob(SchedulerJob job); boolean deleteJob(SchedulerJob job);
boolean pauseJob(SchedulerJob job); boolean pauseJob(SchedulerJob job);
boolean continueJob(SchedulerJob job); boolean continueJob(SchedulerJob job);
boolean executeJob(SchedulerJob job); boolean executeJob(SchedulerJob job);
String executeJobWithParametersOnce(String name, String group, JobDataMap dataMap, Date date); String executeJobWithParametersOnce(String name, String group, JobDataMap dataMap, Date date);
String startCronJobWithParameters(String name, String group, JobDataMap dataMap, String cronExpression);
void stopTrigger(String triggerKey); void stopTrigger(String triggerKey);
void startScheduler(); void startScheduler();
} }