[AB-52] upgrading to alpha 12

adding anonymous reporting
reworking message context commands
refactoring interaction packages
adding post execution handling for message context commands and modals
reworking feature mode response
fixing setup using component ids
storing infraction parameters, for example mute duration, with every infraction
adding infractions for more moderation actions
creating general method to format a duration string
adding infractions command
reworking muting to use built-in functionality of discord
enabling chunking of members
removing manual unmuting feature mode
adding ability to update infractions with a command
implemented infraction listeners for ban and warn
refactored infraction notifications
storing log messages to the infraction for editing said log messages
This commit is contained in:
Sheldan
2022-06-25 12:00:20 +02:00
parent 1a1fde0800
commit 68cae74819
363 changed files with 4306 additions and 3388 deletions

View File

@@ -3,6 +3,7 @@ package dev.sheldan.abstracto.moderation.config;
public class ModerationSlashCommandNames {
public static final String MODERATION = "moderation";
public static final String WARNINGS = "warnings";
public static final String INFRACTIONS = "infractions";
public static final String USER_NOTES = "usernotes";
public static final String MUTE = "mute";
public static final String WARN_DECAY = "warningdecay";

View File

@@ -12,6 +12,9 @@ import java.util.List;
@Component
public class ModerationFeatureConfig implements FeatureConfig {
public static final String BAN_INFRACTION_POINTS = "banInfractionPoints";
public static final String KICK_INFRACTION_POINTS = "kickInfractionPoints";
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.MODERATION;
@@ -22,4 +25,8 @@ public class ModerationFeatureConfig implements FeatureConfig {
return Arrays.asList(ModerationPostTarget.BAN_LOG, ModerationPostTarget.KICK_LOG, ModerationPostTarget.UN_BAN_LOG);
}
@Override
public List<String> getRequiredSystemConfigKeys() {
return Arrays.asList(KICK_INFRACTION_POINTS, BAN_INFRACTION_POINTS);
}
}

View File

@@ -2,9 +2,7 @@ package dev.sheldan.abstracto.moderation.config.feature;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.config.PostTargetEnum;
import dev.sheldan.abstracto.moderation.config.feature.mode.MutingMode;
import dev.sheldan.abstracto.moderation.config.posttarget.MutingPostTarget;
import org.springframework.stereotype.Component;
@@ -14,6 +12,8 @@ import java.util.List;
@Component
public class MutingFeatureConfig implements FeatureConfig {
public static final String MUTE_INFRACTION_POINTS = "muteInfractionPoints";
@Override
public FeatureDefinition getFeature() {
return ModerationFeatureDefinition.MUTING;
@@ -25,7 +25,7 @@ public class MutingFeatureConfig implements FeatureConfig {
}
@Override
public List<FeatureMode> getAvailableModes() {
return Arrays.asList(MutingMode.MANUAL_UN_MUTE_LOGGING);
public List<String> getRequiredSystemConfigKeys() {
return Arrays.asList(MUTE_INFRACTION_POINTS);
}
}

View File

@@ -2,7 +2,9 @@ package dev.sheldan.abstracto.moderation.config.feature;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.config.PostTargetEnum;
import dev.sheldan.abstracto.moderation.config.feature.mode.ReportReactionMode;
import dev.sheldan.abstracto.moderation.config.posttarget.ReactionReportPostTarget;
import dev.sheldan.abstracto.moderation.service.ReactionReportService;
import org.springframework.stereotype.Component;
@@ -33,4 +35,9 @@ public class ReportReactionFeatureConfig implements FeatureConfig {
public List<String> getRequiredSystemConfigKeys() {
return Arrays.asList(ReactionReportService.REACTION_REPORT_COOLDOWN);
}
@Override
public List<FeatureMode> getAvailableModes() {
return Arrays.asList(ReportReactionMode.SINGULAR_MESSAGE, ReportReactionMode.ANONYMOUS);
}
}

View File

@@ -39,4 +39,9 @@ public class WarningFeatureConfig implements FeatureConfig {
public List<FeatureMode> getAvailableModes() {
return Arrays.asList(WarningMode.WARN_DECAY_LOG);
}
@Override
public List<String> getRequiredSystemConfigKeys() {
return Arrays.asList(WARN_INFRACTION_POINTS);
}
}

View File

@@ -4,13 +4,12 @@ import dev.sheldan.abstracto.core.config.FeatureMode;
import lombok.Getter;
@Getter
public enum MutingMode implements FeatureMode {
MANUAL_UN_MUTE_LOGGING("manualUnMuteLogging");
public enum ReportReactionMode implements FeatureMode {
SINGULAR_MESSAGE("singularReportReactions"), ANONYMOUS("anonymousReportReactions");
private final String key;
MutingMode(String key) {
ReportReactionMode(String key) {
this.key = key;
}
}

View File

@@ -1,22 +0,0 @@
package dev.sheldan.abstracto.moderation.exception;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.templating.Templatable;
public class MuteRoleNotSetupException extends AbstractoRunTimeException implements Templatable {
public MuteRoleNotSetupException() {
super("Mute role for server has not been setup");
}
@Override
public String getTemplateName() {
return "mute_role_has_not_been_setup_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -0,0 +1,10 @@
package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.Prioritized;
import dev.sheldan.abstracto.core.listener.AsyncFeatureAwareListener;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.moderation.model.listener.InfractionDescriptionEventModel;
public interface InfractionUpdatedDescriptionListener extends AsyncFeatureAwareListener<InfractionDescriptionEventModel, DefaultListenerResult>, Prioritized {
Boolean handlesEvent(InfractionDescriptionEventModel model);
}

View File

@@ -0,0 +1,8 @@
package dev.sheldan.abstracto.moderation.listener;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.FeatureAwareListener;
import dev.sheldan.abstracto.moderation.model.listener.ReportMessageCreatedModel;
public interface ReportMessageCreatedListener extends FeatureAwareListener<ReportMessageCreatedModel, DefaultListenerResult> {
}

View File

@@ -1,11 +1,14 @@
package dev.sheldan.abstracto.moderation.model.database;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import lombok.*;
import javax.persistence.*;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name="infraction")
@@ -39,6 +42,33 @@ public class Infraction {
@Column(name = "decayed_date")
private Instant decayedDate;
@Column(name = "type")
private String type;
@Column(name = "description")
private String description;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "log_channel_id")
private AChannel logChannel;
@Column(name = "log_message_id")
private Long logMessageId;
@OneToMany(
fetch = FetchType.LAZY,
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
orphanRemoval = true,
mappedBy = "infraction")
@Builder.Default
private List<InfractionParameter> parameters = new ArrayList<>();
@Getter
@Setter
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "infraction_creator_user_in_server_id", nullable = false)
private AUserInAServer infractionCreator;
@Column(name = "created", nullable = false, insertable = false, updatable = false)
private Instant created;

View File

@@ -0,0 +1,35 @@
package dev.sheldan.abstracto.moderation.model.database;
import dev.sheldan.abstracto.moderation.model.database.embedded.InfractionParameterId;
import lombok.*;
import javax.persistence.*;
import java.time.Instant;
@Entity
@Table(name="infraction_parameter")
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@EqualsAndHashCode
public class InfractionParameter {
@EmbeddedId
private InfractionParameterId infractionParameterId;
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
@MapsId("infractionId")
@JoinColumn(name = "infraction_id", nullable = false)
private Infraction infraction;
@Column(name = "value")
private String value;
@Column(name = "created", nullable = false, insertable = false, updatable = false)
private Instant created;
@Column(name = "updated", insertable = false, updatable = false)
private Instant updated;
}

View File

@@ -105,4 +105,10 @@ public class Mute implements Serializable {
@Column(name = "updated", insertable = false, updatable = false)
private Instant updated;
@Getter
@Setter
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "infraction_id")
private Infraction infraction;
}

View File

@@ -0,0 +1,21 @@
package dev.sheldan.abstracto.moderation.model.database.embedded;
import lombok.*;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.io.Serializable;
@Embeddable
@Getter
@Setter
@Builder
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
public class InfractionParameterId implements Serializable {
@Column(name = "infraction_id")
private Long infractionId;
@Column(name = "key")
private String name;
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.moderation.model.interaction;
import dev.sheldan.abstracto.core.interaction.modal.ModalPayload;
import lombok.Builder;
import lombok.Getter;
@Builder
@Getter
public class MessageReportModalPayload implements ModalPayload {
private String modalId;
private String textInputId;
private Long messageId;
private Long channelId;
private Long serverId;
}

View File

@@ -0,0 +1,20 @@
package dev.sheldan.abstracto.moderation.model.listener;
import dev.sheldan.abstracto.core.listener.FeatureAwareListenerModel;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class InfractionDescriptionEventModel implements FeatureAwareListenerModel {
private Long userId;
private Long serverId;
private Long infractionId;
private String newDescription;
private String type;
@Override
public Long getServerId() {
return serverId;
}
}

View File

@@ -0,0 +1,20 @@
package dev.sheldan.abstracto.moderation.model.listener;
import dev.sheldan.abstracto.core.listener.FeatureAwareListenerModel;
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import dev.sheldan.abstracto.core.models.ServerUser;
import lombok.Builder;
import lombok.Getter;
@Builder
@Getter
public class ReportMessageCreatedModel implements FeatureAwareListenerModel {
private ServerChannelMessage reportMessage;
private ServerChannelMessage reportedMessage;
private ServerUser reporter;
@Override
public Long getServerId() {
return reportMessage.getServerId();
}
}

View File

@@ -11,5 +11,7 @@ public class InfractionLevelChangeModel {
private Long oldPoints;
private Integer newLevel;
private Integer oldLevel;
private String type;
private String description;
private MemberDisplay member;
}

View File

@@ -0,0 +1,23 @@
package dev.sheldan.abstracto.moderation.model.template.command;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import lombok.Builder;
import lombok.Getter;
import java.time.Instant;
import java.util.Map;
@Getter
@Builder
public class InfractionEntry {
private String reason;
private Long infractionId;
private Long serverId;
private Boolean decayed;
private Instant creationDate;
private Instant decayDate;
private MemberDisplay infractionUser;
private MemberDisplay infractionCreationUser;
private String type;
private Map<String, String> parameters;
}

View File

@@ -0,0 +1,12 @@
package dev.sheldan.abstracto.moderation.model.template.command;
import lombok.Builder;
import lombok.Getter;
import java.util.List;
@Getter
@Builder
public class InfractionsModel {
private List<InfractionEntry> entries;
}

View File

@@ -1,14 +1,10 @@
package dev.sheldan.abstracto.moderation.model.template.command;
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageChannel;
import java.time.Duration;
import java.time.Instant;
@@ -19,30 +15,11 @@ import java.time.Instant;
@SuperBuilder
@Setter
public class MuteContext {
/**
* The {@link Member} being muted
*/
private Member mutedUser;
/**
* The {@link Member} executing the mute
*/
private Member mutingUser;
/**
* The persisted mute object from the database containing the information about the mute
*/
private Long muteId;
private Instant muteDate;
private Instant muteTargetDate;
private String reason;
private ServerChannelMessage context;
private MessageChannel contextChannel;
private Message message;
private Long channelId;
/**
* The {@link Duration} of the mute between the mute was cast and and the date it should end
* @return The {@link Duration} between start and target date
*/
public Duration getMuteDuration() {
return Duration.between(muteDate, muteTargetDate);
}
}

View File

@@ -0,0 +1,51 @@
package dev.sheldan.abstracto.moderation.model.template.command;
import lombok.Getter;
import lombok.experimental.SuperBuilder;
import net.dv8tion.jda.api.entities.Member;
import java.time.Duration;
import java.time.Instant;
@Getter
@SuperBuilder
public class MuteListenerModel {
/**
* The {@link Member} being muted
*/
private Member mutedUser;
/**
* The {@link Member} executing the mute
*/
private Member mutingUser;
/**
* The persisted mute object from the database containing the information about the mute
*/
private Long muteId;
private Instant muteTargetDate;
private Instant oldMuteTargetDate;
private String reason;
private Long channelId;
/**
* The {@link Duration} of the mute between the mute was cast and and the date it should end
* @return The {@link Duration} between start and target date
*/
public Duration getMuteDuration() {
return Duration.between(Instant.now(), muteTargetDate);
}
public boolean getMuteEnded() {
return oldMuteTargetDate != null && muteTargetDate == null || oldMuteTargetDate == null && muteTargetDate == null;
}
public boolean getMuted() {
return oldMuteTargetDate == null && muteTargetDate != null;
}
public boolean getDurationChanged() {
return oldMuteTargetDate != null
&& muteTargetDate != null
&& !muteTargetDate.equals(oldMuteTargetDate);
}
}

View File

@@ -0,0 +1,11 @@
package dev.sheldan.abstracto.moderation.model.template.listener;
import lombok.Builder;
import lombok.Getter;
@Builder
@Getter
public class ReportInputModalModel {
private String modalId;
private String inputComponentId;
}

View File

@@ -13,4 +13,6 @@ public class ReportReactionNotificationModel {
private CachedMessage reportedMessage;
private ServerUser reporter;
private Integer reportCount;
private String context;
private Boolean singularMessage;
}

View File

@@ -8,6 +8,8 @@ import java.util.concurrent.CompletableFuture;
public interface BanService {
String BAN_EFFECT_KEY = "ban";
String BAN_INFRACTION_TYPE = "ban";
String INFRACTION_PARAMETER_DELETION_DAYS_KEY = "DELETION_DAYS";
CompletableFuture<BanResult> banUserWithNotification(User user, String reason, Member banningMember, Integer deletionDays);
CompletableFuture<Void> unBanUserWithNotification(User user, Member unBanningUser);
CompletableFuture<Void> banUser(Guild guild, User user, Integer deletionDays, String reason);

View File

@@ -2,12 +2,18 @@ package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.moderation.model.database.Infraction;
import net.dv8tion.jda.api.entities.Message;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
public interface InfractionService {
void decayInfraction(Infraction infraction);
Long getActiveInfractionPointsForUser(AUserInAServer aUserInAServer);
CompletableFuture<Infraction> createInfractionWithNotification(AUserInAServer aUserInAServer, Long points);
CompletableFuture<Void> createInfractionNotification(AUserInAServer aUserInAServer, Long points);
CompletableFuture<Infraction> createInfractionWithNotification(AUserInAServer target, Long points, String type, String description, AUserInAServer creator, Map<String, String> parameters, Message logMessage);
CompletableFuture<Infraction> createInfractionWithNotification(AUserInAServer target, Long points, String type, String description, AUserInAServer creator, Map<String, String> parameters);
CompletableFuture<Infraction> createInfractionWithNotification(AUserInAServer target, Long points, String type, String description, AUserInAServer creator, Message logMessage);
CompletableFuture<Infraction> createInfractionWithNotification(AUserInAServer target, Long points, String type, String description, AUserInAServer creator);
CompletableFuture<Void> createInfractionNotification(AUserInAServer aUserInAServer, Long points, String type, String description);
CompletableFuture<Void> editInfraction(Long infractionId, String newReason, Long serverId);
}

View File

@@ -7,5 +7,6 @@ import java.util.concurrent.CompletableFuture;
public interface KickService {
String KICK_EFFECT_KEY = "kick";
String KICK_INFRACTION_TYPE = "kick";
CompletableFuture<Void> kickMember(Member member, String reason, KickLogModel kickLogModel);
}

View File

@@ -1,7 +1,6 @@
package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.models.FullUserInServer;
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.moderation.model.database.Mute;
import dev.sheldan.abstracto.moderation.model.template.command.MuteContext;
@@ -12,16 +11,16 @@ import java.util.concurrent.CompletableFuture;
public interface MuteService {
String MUTE_EFFECT_KEY = "mute";
CompletableFuture<Void> muteMember(Member memberToMute, Member userMuting, String reason, Instant unMuteDate, ServerChannelMessage message);
CompletableFuture<Void> muteUserInServer(FullUserInServer userToMute, FullUserInServer userMuting, String reason, Instant unMuteDate, ServerChannelMessage message);
CompletableFuture<Void> applyMuteRole(AUserInAServer aUserInAServer);
String MUTE_INFRACTION_TYPE = "mute";
String INFRACTION_PARAMETER_DURATION_KEY = "DURATION";
CompletableFuture<Void> muteMember(Member memberToMute, String reason, Instant unMuteDate, Long channelId);
CompletableFuture<Void> muteUserInServer(FullUserInServer userToMute, String reason, Instant unMuteDate, Long channelId);
CompletableFuture<Void> muteMemberWithLog(MuteContext context);
String startUnMuteJobFor(Instant unMuteDate, Long muteId, Long serverId);
void cancelUnMuteJob(Mute mute);
CompletableFuture<Void> unMuteUser(AUserInAServer aUserInAServer);
CompletableFuture<Void> endMute(Mute mute, Boolean sendNotification);
CompletableFuture<Void> unMuteUser(AUserInAServer userToUnmute, Member memberUnMuting);
CompletableFuture<Void> endMute(Mute mute);
CompletableFuture<Void> endMute(Long muteId, Long serverId);
void completelyUnMuteUser(AUserInAServer aUserInAServer);
void completelyUnMuteMember(Member member);
CompletableFuture<Void> muteMemberWithoutContext(Member member);
}

View File

@@ -2,11 +2,14 @@ package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.cache.CachedMessage;
import net.dv8tion.jda.api.entities.Message;
import java.util.concurrent.CompletableFuture;
public interface ReactionReportService {
String REACTION_REPORT_EMOTE_KEY = "reactionReport";
String REACTION_REPORT_COOLDOWN = "reactionReportCooldownSeconds";
CompletableFuture<Void> createReactionReport(CachedMessage reportedMessage, ServerUser reporter);
CompletableFuture<Void> createReactionReport(CachedMessage reportedMessage, ServerUser reporter, String context);
CompletableFuture<Void> createReactionReport(Message message, ServerUser reporter, String context);
boolean allowedToReport(ServerUser reporter);
}

View File

@@ -10,9 +10,10 @@ import java.util.concurrent.CompletableFuture;
public interface WarnService {
String WARN_EFFECT_KEY = "warn";
String WARN_INFRACTION_TYPE = "warn";
CompletableFuture<Void> notifyAndLogFullUserWarning(WarnContext context);
CompletableFuture<Void> warnUserWithLog(WarnContext context);
void decayWarning(Warning warning, Instant decayDate);
CompletableFuture<Void> decayWarningsForServer(AServer server);
CompletableFuture<Void> decayWarningsForServer(AServer server);
CompletableFuture<Void> decayAllWarningsForServer(AServer server);
}

View File

@@ -1,12 +1,16 @@
package dev.sheldan.abstracto.moderation.service.management;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.moderation.model.database.Infraction;
import net.dv8tion.jda.api.entities.Message;
import java.util.List;
public interface InfractionManagementService {
Infraction createInfraction(AUserInAServer aUserInAServer, Long points);
Infraction createInfraction(AUserInAServer aUserInAServer, Long points, String type, String description, AUserInAServer creator, Message message);
List<Infraction> getActiveInfractionsForUser(AUserInAServer aUserInAServer);
List<Infraction> getInfractionsForUser(AUserInAServer aUserInAServer);
List<Infraction> getInfractionsForServer(AServer server);
Infraction loadInfraction(Long infraction);
}

View File

@@ -0,0 +1,8 @@
package dev.sheldan.abstracto.moderation.service.management;
import dev.sheldan.abstracto.moderation.model.database.Infraction;
import dev.sheldan.abstracto.moderation.model.database.InfractionParameter;
public interface InfractionParameterManagementService {
InfractionParameter createInfractionParameter(Infraction infraction, String key, String value);
}

View File

@@ -23,9 +23,10 @@ public interface MuteManagementService {
* @param muteMessage The message containing the command which caused the mute
* @param triggerKey The key of the trigger in quartz, if any
* @param muteId The id of the mute to create
* @param infractionId The ID of the matching infraction
* @return The created mute object containing the mute ID
*/
Mute createMute(AUserInAServer mutedUser, AUserInAServer mutingUser, String reason, Instant unMuteDate, AServerAChannelMessage muteMessage, String triggerKey, Long muteId);
Mute createMute(AUserInAServer mutedUser, AUserInAServer mutingUser, String reason, Instant unMuteDate, AServerAChannelMessage muteMessage, String triggerKey, Long muteId, Long infractionId);
/**
* Finds the mute from the database by the given ID.
@@ -63,6 +64,7 @@ public interface MuteManagementService {
* @return The found {@link Mute}, and null if none was found
*/
Mute getAMuteOf(AUserInAServer userInAServer);
Optional<Mute> getAMuteOfOptional(AUserInAServer userInAServer);
/**
* Returns any active {@link Mute} of this {@link Member} in the database
@@ -70,6 +72,7 @@ public interface MuteManagementService {
* @return The found {@link Mute}, and null if none was found
*/
Mute getAMuteOf(Member member);
Optional<Mute> getAMuteOfOptional(Member member);
/**
* Retrieves all active mutes of the given {@link AUserInAServer} in a collection

View File

@@ -1,15 +0,0 @@
package dev.sheldan.abstracto.moderation.service.management;
import dev.sheldan.abstracto.core.models.database.ARole;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.moderation.model.database.MuteRole;
import java.util.List;
public interface MuteRoleManagementService {
MuteRole retrieveMuteRoleForServer(AServer server);
MuteRole createMuteRoleForServer(AServer server, ARole role);
List<MuteRole> retrieveMuteRolesForServer(AServer server);
MuteRole setMuteRoleForServer(AServer server, ARole role);
boolean muteRoleForServerExists(AServer server);
}

View File

@@ -22,4 +22,5 @@ public interface WarnManagementService {
Warning findById(Long id, Long serverId);
List<Warning> getWarningsViaId(List<Long> warnIds, Long serverId);
void deleteWarning(Warning warn);
Optional<Warning> findWarnByInfraction(Long infractionId);
}