[AB-68] adding invite filter with commands to allow/disallow invites, remove stored filtered invite links and show filtered invite links

removing database entities from command context
This commit is contained in:
Sheldan
2021-01-23 15:33:00 +01:00
parent fb3ed69650
commit 2a2a3aea70
182 changed files with 2571 additions and 325 deletions

View File

@@ -7,6 +7,8 @@ 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.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.moderation.config.ModerationModule;
import dev.sheldan.abstracto.moderation.config.features.ModerationFeatures;
import dev.sheldan.abstracto.moderation.service.WarnService;
@@ -23,9 +25,13 @@ public class DecayAllWarnings extends AbstractConditionableCommand {
@Autowired
private WarnService warnService;
@Autowired
private ServerManagementService serverManagementService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
return warnService.decayAllWarningsForServer(commandContext.getUserInitiatedContext().getServer())
AServer server = serverManagementService.loadServer(commandContext.getGuild());
return warnService.decayAllWarningsForServer(server)
.thenApply(aVoid -> CommandResult.fromSuccess());
}

View File

@@ -7,6 +7,8 @@ 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.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.moderation.config.ModerationModule;
import dev.sheldan.abstracto.moderation.config.features.ModerationFeatures;
import dev.sheldan.abstracto.moderation.service.WarnService;
@@ -23,9 +25,13 @@ public class DecayWarnings extends AbstractConditionableCommand {
@Autowired
private WarnService warnService;
@Autowired
private ServerManagementService serverManagementService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
return warnService.decayWarningsForServer(commandContext.getUserInitiatedContext().getServer())
AServer server = serverManagementService.loadServer(commandContext.getGuild());
return warnService.decayWarningsForServer(server)
.thenApply(aVoid -> CommandResult.fromSuccess());
}

View File

@@ -9,6 +9,8 @@ import dev.sheldan.abstracto.core.command.config.validator.MinIntegerValueValida
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.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.moderation.config.ModerationModule;
import dev.sheldan.abstracto.moderation.config.features.ModerationFeatures;
import dev.sheldan.abstracto.moderation.service.management.UserNoteManagementService;
@@ -30,13 +32,15 @@ public class DeleteNote extends AbstractConditionableCommand {
@Autowired
private TemplateService templateService;
@Autowired
private ServerManagementService serverManagementService;
@Override
public CommandResult execute(CommandContext commandContext) {
Long id = (Long) commandContext.getParameters().getParameters().get(0);
if(userNoteManagementService.noteExists(id, commandContext.getUserInitiatedContext().getServer())) {
userNoteManagementService.deleteNote(id, commandContext.getUserInitiatedContext().getServer());
AServer server = serverManagementService.loadServer(commandContext.getGuild());
if(userNoteManagementService.noteExists(id, server)) {
userNoteManagementService.deleteNote(id, server);
} else {
// TODO replace with exception
return CommandResult.fromError(templateService.renderSimpleTemplate(NOTE_NOT_FOUND_EXCEPTION_TEMPLATE));

View File

@@ -8,7 +8,9 @@ import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.execution.ContextConverter;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.moderation.config.ModerationModule;
import dev.sheldan.abstracto.moderation.config.features.ModerationFeatures;
import dev.sheldan.abstracto.moderation.models.template.commands.MyWarningsModel;
@@ -30,12 +32,16 @@ public class MyWarnings extends AbstractConditionableCommand {
@Autowired
private WarnManagementService warnManagementService;
@Autowired
private UserInServerManagementService userInServerManagementService;
@Override
public CommandResult execute(CommandContext commandContext) {
MyWarningsModel model = (MyWarningsModel) ContextConverter.fromCommandContext(commandContext, MyWarningsModel.class);
Long currentWarnCount = warnManagementService.getActiveWarnsForUser(commandContext.getUserInitiatedContext().getAUserInAServer());
AUserInAServer userInAServer = userInServerManagementService.loadUser(commandContext.getAuthor());
Long currentWarnCount = warnManagementService.getActiveWarnsForUser(userInAServer);
model.setCurrentWarnCount(currentWarnCount);
Long totalWarnCount = warnManagementService.getTotalWarnsForUser(commandContext.getUserInitiatedContext().getAUserInAServer());
Long totalWarnCount = warnManagementService.getTotalWarnsForUser(userInAServer);
model.setTotalWarnCount(totalWarnCount);
channelService.sendEmbedTemplateInChannel(MY_WARNINGS_RESPONSE_EMBED_TEMPLATE, model, commandContext.getChannel());
return CommandResult.fromIgnored();

View File

@@ -9,8 +9,10 @@ import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.execution.ContextConverter;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.models.FullUserInServer;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.moderation.config.ModerationModule;
@@ -47,6 +49,9 @@ public class UserNotes extends AbstractConditionableCommand {
@Autowired
private UserNotesConverter userNotesConverter;
@Autowired
private ServerManagementService serverManagementService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
@@ -64,7 +69,8 @@ public class UserNotes extends AbstractConditionableCommand {
.build();
model.setSpecifiedUser(specifiedUser);
} else {
userNotes = userNoteManagementService.loadNotesForServer(commandContext.getUserInitiatedContext().getServer());
AServer server = serverManagementService.loadServer(commandContext.getGuild());
userNotes = userNoteManagementService.loadNotesForServer(server);
}
CompletableFuture<List<NoteEntryModel>> listCompletableFuture = userNotesConverter.fromNotes(userNotes);
return listCompletableFuture.thenCompose(noteEntryModels -> {

View File

@@ -10,7 +10,9 @@ import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.execution.ContextConverter;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.PaginatorService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.moderation.config.ModerationModule;
import dev.sheldan.abstracto.moderation.config.features.ModerationFeatures;
@@ -47,6 +49,9 @@ public class Warnings extends AbstractConditionableCommand {
@Autowired
private EventWaiter eventWaiter;
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private Warnings self;
@@ -57,7 +62,8 @@ public class Warnings extends AbstractConditionableCommand {
Member member = (Member) commandContext.getParameters().getParameters().get(0);
warnsToDisplay = warnManagementService.getAllWarnsForUser(userInServerManagementService.loadUser(member));
} else {
warnsToDisplay = warnManagementService.getAllWarningsOfServer(commandContext.getUserInitiatedContext().getServer());
AServer server = serverManagementService.loadServer(commandContext.getGuild());
warnsToDisplay = warnManagementService.getAllWarningsOfServer(server);
}
return warnEntryConverter.fromWarnings(warnsToDisplay).thenApply(warnEntries -> {
self.renderWarnings(commandContext, warnEntries);

View File

@@ -0,0 +1,52 @@
package dev.sheldan.abstracto.moderation.commands.invite;
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.InviteLinkFilterServiceBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class AllowInvite extends AbstractConditionableCommand {
@Autowired
private InviteLinkFilterServiceBean inviteLinkFilterServiceBean;
@Override
public CommandResult execute(CommandContext commandContext) {
String inviteLink = (String) commandContext.getParameters().getParameters().get(0);
inviteLinkFilterServiceBean.allowInvite(inviteLink, commandContext.getGuild().getIdLong());
return CommandResult.fromSuccess();
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
parameters.add(Parameter.builder().name("invite").type(String.class).templated(true).build());
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("allowInvite")
.module(ModerationModule.MODERATION)
.templated(true)
.supportsEmbedException(true)
.causesReaction(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureEnum getFeature() {
return ModerationFeatures.INVITE_FILTER;
}
}

View File

@@ -0,0 +1,52 @@
package dev.sheldan.abstracto.moderation.commands.invite;
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.InviteLinkFilterServiceBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class DisAllowInvite extends AbstractConditionableCommand {
@Autowired
private InviteLinkFilterServiceBean inviteLinkFilterServiceBean;
@Override
public CommandResult execute(CommandContext commandContext) {
String inviteLink = (String) commandContext.getParameters().getParameters().get(0);
inviteLinkFilterServiceBean.disAllowInvite(inviteLink, commandContext.getGuild().getIdLong());
return CommandResult.fromSuccess();
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
parameters.add(Parameter.builder().name("invite").type(String.class).templated(true).build());
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("disAllowInvite")
.module(ModerationModule.MODERATION)
.templated(true)
.supportsEmbedException(true)
.causesReaction(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureEnum getFeature() {
return ModerationFeatures.INVITE_FILTER;
}
}

View File

@@ -0,0 +1,65 @@
package dev.sheldan.abstracto.moderation.commands.invite;
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.core.config.FeatureMode;
import dev.sheldan.abstracto.moderation.config.ModerationModule;
import dev.sheldan.abstracto.moderation.config.features.ModerationFeatures;
import dev.sheldan.abstracto.moderation.config.features.mode.InviteFilterMode;
import dev.sheldan.abstracto.moderation.service.InviteLinkFilterService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Component
public class RemoveTrackedInviteLinks extends AbstractConditionableCommand {
@Autowired
private InviteLinkFilterService inviteLinkFilterService;
@Override
public CommandResult execute(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
if(!parameters.isEmpty()) {
String invite = (String) parameters.get(0);
inviteLinkFilterService.clearAllUses(invite, commandContext.getGuild().getIdLong());
} else {
inviteLinkFilterService.clearAllTrackedInviteCodes(commandContext.getGuild().getIdLong());
}
return CommandResult.fromSuccess();
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
parameters.add(Parameter.builder().name("invite").type(String.class).optional(true).templated(true).build());
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("removeTrackedInviteLinks")
.module(ModerationModule.MODERATION)
.templated(true)
.supportsEmbedException(true)
.causesReaction(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureEnum getFeature() {
return ModerationFeatures.INVITE_FILTER;
}
@Override
public List<FeatureMode> getFeatureModeLimitations() {
return Arrays.asList(InviteFilterMode.TRACK_USES);
}
}

View File

@@ -0,0 +1,86 @@
package dev.sheldan.abstracto.moderation.commands.invite;
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.command.execution.ContextConverter;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.moderation.config.ModerationModule;
import dev.sheldan.abstracto.moderation.config.features.ModerationFeatures;
import dev.sheldan.abstracto.moderation.config.features.mode.InviteFilterMode;
import dev.sheldan.abstracto.moderation.models.database.FilteredInviteLink;
import dev.sheldan.abstracto.moderation.models.template.commands.TrackedInviteLinksModel;
import dev.sheldan.abstracto.moderation.service.InviteLinkFilterService;
import dev.sheldan.abstracto.templating.model.MessageToSend;
import dev.sheldan.abstracto.templating.service.TemplateService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
public class ShowTrackedInviteLinks extends AbstractConditionableCommand {
@Autowired
private InviteLinkFilterService inviteLinkFilterService;
@Autowired
private ChannelService channelService;
@Autowired
private TemplateService templateService;
public static final String TRACKED_INVITE_LINKS_EMBED_TEMPLATE_KEY = "showTrackedInviteLinks_response";
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
TrackedInviteLinksModel model = (TrackedInviteLinksModel) ContextConverter.slimFromCommandContext(commandContext, TrackedInviteLinksModel.class);
List<Object> parameters = commandContext.getParameters().getParameters();
List<FilteredInviteLink> inviteLinks;
if(!parameters.isEmpty()) {
Integer count = (Integer) parameters.get(0);
inviteLinks = inviteLinkFilterService.getTopFilteredInviteLinks(commandContext.getGuild().getIdLong(), count);
} else {
inviteLinks = inviteLinkFilterService.getTopFilteredInviteLinks(commandContext.getGuild().getIdLong());
}
model.setInviteLinks(inviteLinks);
MessageToSend messageToSend = templateService.renderEmbedTemplate(TRACKED_INVITE_LINKS_EMBED_TEMPLATE_KEY, model);
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
parameters.add(Parameter.builder().name("amount").type(Integer.class).optional(true).templated(true).build());
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("showTrackedInviteLinks")
.module(ModerationModule.MODERATION)
.templated(true)
.async(true)
.supportsEmbedException(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureEnum getFeature() {
return ModerationFeatures.INVITE_FILTER;
}
@Override
public List<FeatureMode> getFeatureModeLimitations() {
return Arrays.asList(InviteFilterMode.TRACK_USES);
}
}

View File

@@ -8,7 +8,9 @@ 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.core.models.database.ARole;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.management.RoleManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.moderation.config.ModerationModule;
import dev.sheldan.abstracto.moderation.config.features.ModerationFeatures;
import dev.sheldan.abstracto.moderation.service.management.MuteRoleManagementService;
@@ -28,11 +30,15 @@ public class SetMuteRole extends AbstractConditionableCommand {
@Autowired
private RoleManagementService roleManagementService;
@Autowired
private ServerManagementService serverManagementService;
@Override
public CommandResult execute(CommandContext commandContext) {
Role jdaRole = (Role) commandContext.getParameters().getParameters().get(0);
ARole role = roleManagementService.findRole(jdaRole.getIdLong());
muteRoleManagementService.setMuteRoleForServer(commandContext.getUserInitiatedContext().getServer(), role);
AServer server = serverManagementService.loadServer(commandContext.getGuild());
muteRoleManagementService.setMuteRoleForServer(server, role);
return CommandResult.fromSuccess();
}

View File

@@ -0,0 +1,114 @@
package dev.sheldan.abstracto.moderation.listener.async;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.config.ListenerPriority;
import dev.sheldan.abstracto.core.execution.result.MessageReceivedListenerResult;
import dev.sheldan.abstracto.core.listener.sync.jda.MessageReceivedListener;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.service.FeatureModeService;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.moderation.config.features.ModerationFeatures;
import dev.sheldan.abstracto.moderation.config.features.mode.InviteFilterMode;
import dev.sheldan.abstracto.moderation.config.posttargets.InviteFilterPostTarget;
import dev.sheldan.abstracto.moderation.models.template.listener.InviteDeletedModel;
import dev.sheldan.abstracto.moderation.service.InviteLinkFilterService;
import dev.sheldan.abstracto.templating.model.MessageToSend;
import dev.sheldan.abstracto.templating.service.TemplateService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.regex.Matcher;
@Component
@Slf4j
public class InviteLinkFilterListener implements MessageReceivedListener {
@Autowired
private InviteLinkFilterService inviteLinkFilterService;
@Autowired
private FeatureModeService featureModeService;
@Autowired
private PostTargetService postTargetService;
@Autowired
private TemplateService templateService;
public static final String INVITE_LINK_DELETED_NOTIFICATION_EMBED_TEMPLATE_KEY = "invite_link_deleted_notification";
@Override
public MessageReceivedListenerResult execute(Message message) {
Long serverId = message.getGuild().getIdLong();
Matcher matcher = Message.INVITE_PATTERN.matcher(message.getContentRaw());
ServerUser author = ServerUser.builder().userId(message.getAuthor().getIdLong()).serverId(message.getGuild().getIdLong()).build();
boolean toDelete = false;
List<String> codesToTrack = new ArrayList<>();
while(matcher.find()) {
String code = matcher.group("code");
boolean codeFiltered = inviteLinkFilterService.isCodeFiltered(code, author);
if(codeFiltered) {
codesToTrack.add(code);
toDelete = true;
}
}
if(toDelete) {
message.delete().queue();
boolean trackUsages = featureModeService.featureModeActive(ModerationFeatures.INVITE_FILTER, serverId, InviteFilterMode.TRACK_USES);
if(trackUsages) {
codesToTrack.forEach(s -> inviteLinkFilterService.storeFilteredInviteLinkUsage(s, author));
}
boolean sendNotification = featureModeService.featureModeActive(ModerationFeatures.INVITE_FILTER, serverId, InviteFilterMode.FILTER_NOTIFICATIONS);
if(sendNotification) {
sendDeletionNotification(codesToTrack, message);
}
return MessageReceivedListenerResult.DELETED;
} else {
return MessageReceivedListenerResult.PROCESSED;
}
}
private void sendDeletionNotification(List<String> codes, Message message) {
Long serverId = message.getGuild().getIdLong();
if(!postTargetService.postTargetDefinedInServer(InviteFilterPostTarget.INVITE_DELETE_LOG, serverId)) {
log.info("Post target {} not defined for server {} - not sending invite link deletion notification.", InviteFilterPostTarget.INVITE_DELETE_LOG.getKey(), serverId);
return;
}
InviteDeletedModel model = InviteDeletedModel
.builder()
.author(message.getMember())
.guild(message.getGuild())
.message(message)
.channel(message.getTextChannel())
.invites(codes)
.build();
log.info("Sending notification about {} deleted invite links in guild {} from user {} in channel {} in message {}.",
codes.size(), serverId, message.getAuthor().getIdLong(), message.getTextChannel().getIdLong(), message.getIdLong());
MessageToSend messageToSend = templateService.renderEmbedTemplate(INVITE_LINK_DELETED_NOTIFICATION_EMBED_TEMPLATE_KEY, model);
List<CompletableFuture<Message>> messageFutures = postTargetService.sendEmbedInPostTarget(messageToSend, InviteFilterPostTarget.INVITE_DELETE_LOG, serverId);
FutureUtils.toSingleFutureGeneric(messageFutures).thenAccept(unused ->
log.trace("Successfully send notification about deleted invite link in message {}.", message.getIdLong())
).exceptionally(throwable -> {
log.error("Failed to send notification about deleted invite link in message {}.", message.getIdLong());
return null;
});
}
@Override
public FeatureEnum getFeature() {
return ModerationFeatures.INVITE_FILTER;
}
@Override
public Integer getPriority() {
return ListenerPriority.HIGH;
}
}

View File

@@ -1,4 +1,4 @@
package dev.sheldan.abstracto.moderation.listener;
package dev.sheldan.abstracto.moderation.listener.async;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncMessageDeletedListener;

View File

@@ -1,4 +1,4 @@
package dev.sheldan.abstracto.moderation.listener;
package dev.sheldan.abstracto.moderation.listener.async;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.listener.async.jda.AsyncMessageTextUpdatedListener;

View File

@@ -0,0 +1,20 @@
package dev.sheldan.abstracto.moderation.repository;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.moderation.models.database.AllowedInviteLink;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.stereotype.Repository;
import javax.persistence.QueryHint;
import java.util.Optional;
@Repository
public interface AllowedInviteLinkRepository extends JpaRepository<AllowedInviteLink, Long> {
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
Optional<AllowedInviteLink> findByCodeAndServer(String code, AServer server);
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
Optional<AllowedInviteLink> findByCodeAndServer_Id(String code, Long serverId);
}

View File

@@ -0,0 +1,27 @@
package dev.sheldan.abstracto.moderation.repository;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.moderation.models.database.FilteredInviteLink;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.stereotype.Repository;
import javax.persistence.QueryHint;
import java.util.List;
import java.util.Optional;
@Repository
public interface FilteredInviteLinkRepository extends JpaRepository<FilteredInviteLink, Long> {
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
Optional<FilteredInviteLink> findByCodeAndServer(String code, AServer server);
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
Optional<FilteredInviteLink> findByCodeAndServer_Id(String code, Long serverId);
void deleteByServer_Id(Long serverId);
void deleteByCodeAndServer_Id(String code, Long serverId);
List<FilteredInviteLink> findAllByServer_IdOrderByUsesDesc(Long serverId, Pageable pageable);
}

View File

@@ -7,7 +7,7 @@ import dev.sheldan.abstracto.core.service.FeatureModeService;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.moderation.config.features.ModerationFeatures;
import dev.sheldan.abstracto.moderation.config.features.ModerationMode;
import dev.sheldan.abstracto.moderation.config.features.mode.ModerationMode;
import dev.sheldan.abstracto.moderation.config.posttargets.ModerationPostTarget;
import dev.sheldan.abstracto.templating.model.MessageToSend;
import dev.sheldan.abstracto.templating.service.TemplateService;

View File

@@ -0,0 +1,112 @@
package dev.sheldan.abstracto.moderation.service;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.moderation.exception.InvalidInviteException;
import dev.sheldan.abstracto.moderation.models.database.FilteredInviteLink;
import dev.sheldan.abstracto.moderation.service.management.AllowedInviteLinkManagement;
import dev.sheldan.abstracto.moderation.service.management.FilteredInviteLinkManagement;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Component
public class InviteLinkFilterServiceBean implements InviteLinkFilterService {
@Autowired
private AllowedInviteLinkManagement allowedInviteLinkManagement;
@Autowired
private FilteredInviteLinkManagement filteredInviteLinkManagement;
@Autowired
private ServerManagementService serverManagementService;
private static final Pattern INVITE_CODE_PATTERN = Pattern.compile("(?<code>[a-z0-9-]+)", Pattern.CASE_INSENSITIVE);
@Override
public boolean isCodeFiltered(String code, ServerUser serverUser) {
return !isCodeAllowed(code, serverUser);
}
@Override
public boolean isCodeAllowed(String code, ServerUser serverUser) {
return allowedInviteLinkManagement.allowedInviteLinkExists(serverUser, code);
}
@Override
public boolean isCodeAllowed(String code, Long serverId) {
return allowedInviteLinkManagement.allowedInviteLinkExists(serverId, code);
}
@Override
public void storeFilteredInviteLinkUsage(String code, ServerUser serverUser) {
Optional<FilteredInviteLink> inviteLinkOptional = filteredInviteLinkManagement.findInviteLinkViaCode(serverUser.getServerId(), code);
if(inviteLinkOptional.isPresent()) {
inviteLinkOptional.ifPresent(filteredInviteLink -> filteredInviteLink.setUses(filteredInviteLink.getUses() + 1));
} else {
AServer server = serverManagementService.loadServer(serverUser.getServerId());
filteredInviteLinkManagement.createFilteredInviteLink(server, code);
}
}
@Override
public void allowInvite(String invite, Long serverId) {
String inviteCode = extractCode(invite);
if(isCodeAllowed(inviteCode, serverId)) {
return;
}
AServer server = serverManagementService.loadServer(serverId);
allowedInviteLinkManagement.createAllowedInviteLink(server, inviteCode);
}
private String extractCode(String invite) {
Matcher matcher = Message.INVITE_PATTERN.matcher(invite);
String inviteCode;
if(matcher.find()) {
inviteCode = matcher.group("code");
} else {
Matcher codeOnlyMatcher = INVITE_CODE_PATTERN.matcher(invite);
if(codeOnlyMatcher.find()) {
inviteCode = codeOnlyMatcher.group("code");
} else {
throw new InvalidInviteException("Invalid invite was provided.");
}
}
return inviteCode;
}
@Override
public void disAllowInvite(String invite, Long serverId) {
String inviteCode = extractCode(invite);
AServer server = serverManagementService.loadServer(serverId);
allowedInviteLinkManagement.removeAllowedInviteLink(server, inviteCode);
}
@Override
public void clearAllTrackedInviteCodes(Long serverId) {
filteredInviteLinkManagement.clearFilteredInviteLinks(serverId);
}
@Override
public void clearAllUses(String code, Long serverId) {
String inviteCode = extractCode(code);
filteredInviteLinkManagement.clearFilteredInviteLink(inviteCode, serverId);
}
@Override
public List<FilteredInviteLink> getTopFilteredInviteLinks(Long serverId, Integer count) {
return filteredInviteLinkManagement.getTopFilteredInviteLink(serverId, count);
}
@Override
public List<FilteredInviteLink> getTopFilteredInviteLinks(Long serverId) {
return getTopFilteredInviteLinks(serverId, 5);
}
}

View File

@@ -4,7 +4,7 @@ import dev.sheldan.abstracto.core.service.FeatureModeService;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.moderation.config.features.ModerationFeatures;
import dev.sheldan.abstracto.moderation.config.features.ModerationMode;
import dev.sheldan.abstracto.moderation.config.features.mode.ModerationMode;
import dev.sheldan.abstracto.moderation.config.posttargets.ModerationPostTarget;
import dev.sheldan.abstracto.moderation.models.template.commands.KickLogModel;
import dev.sheldan.abstracto.templating.model.MessageToSend;

View File

@@ -12,7 +12,7 @@ import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.moderation.config.features.ModerationFeatures;
import dev.sheldan.abstracto.moderation.config.features.MutingMode;
import dev.sheldan.abstracto.moderation.config.features.mode.MutingMode;
import dev.sheldan.abstracto.moderation.config.posttargets.MutingPostTarget;
import dev.sheldan.abstracto.moderation.exception.MuteRoleNotSetupException;
import dev.sheldan.abstracto.moderation.exception.NoMuteFoundException;
@@ -252,7 +252,7 @@ public class MuteServiceBean implements MuteService {
if(featureModeService.featureModeActive(ModerationFeatures.MUTING, server, MutingMode.MUTE_LOGGING)) {
log.trace("Sending unMute log for mute {} to the mute posttarget in server {}", muteLogModel.getMute().getMuteId().getId(), server.getId());
MessageToSend message = templateService.renderEmbedTemplate(UN_MUTE_LOG_TEMPLATE, muteLogModel);
List<CompletableFuture<Message>> completableFutures = postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, muteLogModel.getServer().getId());
List<CompletableFuture<Message>> completableFutures = postTargetService.sendEmbedInPostTarget(message, MutingPostTarget.MUTE_LOG, server.getId());
completableFuture = FutureUtils.toSingleFutureGeneric(completableFutures);
} else {
completableFuture = CompletableFuture.completedFuture(null);
@@ -338,7 +338,6 @@ public class MuteServiceBean implements MuteService {
.mutingUser(mutingMemberFuture.join())
.unMutedUser(mutedMemberFuture.join())
.guild(guild)
.server(mutingServer)
.build();
CompletableFuture<Void> notificationFuture = sendUnMuteLogMessage(unMuteLog, mutingServer);
return CompletableFuture.allOf(notificationFuture).thenAccept(aVoid ->

View File

@@ -7,9 +7,9 @@ import dev.sheldan.abstracto.core.service.*;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.moderation.config.features.ModerationFeatures;
import dev.sheldan.abstracto.moderation.config.features.WarnDecayMode;
import dev.sheldan.abstracto.moderation.config.features.mode.WarnDecayMode;
import dev.sheldan.abstracto.moderation.config.features.WarningDecayFeature;
import dev.sheldan.abstracto.moderation.config.features.WarningMode;
import dev.sheldan.abstracto.moderation.config.features.mode.WarningMode;
import dev.sheldan.abstracto.moderation.config.posttargets.WarnDecayPostTarget;
import dev.sheldan.abstracto.moderation.config.posttargets.WarningPostTarget;
import dev.sheldan.abstracto.moderation.models.template.job.WarnDecayLogModel;
@@ -215,7 +215,6 @@ public class WarnServiceBean implements WarnService {
WarnDecayLogModel warnDecayLogModel = WarnDecayLogModel
.builder()
.guild(botService.getGuildById(server.getId()))
.server(server)
.warnings(warnDecayWarnings)
.build();
MessageToSend messageToSend = templateService.renderEmbedTemplate(WARN_DECAY_LOG_TEMPLATE_KEY, warnDecayLogModel);

View File

@@ -0,0 +1,48 @@
package dev.sheldan.abstracto.moderation.service.management;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.moderation.exception.AllowedInviteLinkNotFound;
import dev.sheldan.abstracto.moderation.models.database.AllowedInviteLink;
import dev.sheldan.abstracto.moderation.repository.AllowedInviteLinkRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class AllowedInviteLinkManagementBean implements AllowedInviteLinkManagement {
@Autowired
private AllowedInviteLinkRepository repository;
@Override
public AllowedInviteLink createAllowedInviteLink(AServer server, String code) {
AllowedInviteLink inviteLink = AllowedInviteLink.builder().code(code).server(server).build();
return repository.save(inviteLink);
}
@Override
public void removeAllowedInviteLink(AServer server, String code) {
AllowedInviteLink existingCode = findAllowedInviteLinkByCode(server, code);
repository.delete(existingCode);
}
@Override
public AllowedInviteLink findAllowedInviteLinkByCode(AServer server, String code) {
return repository.findByCodeAndServer(code, server).orElseThrow(() -> new AllowedInviteLinkNotFound("Allowed invite code not found."));
}
@Override
public boolean allowedInviteLinkExists(AServer server, String code) {
return repository.findByCodeAndServer(code, server).isPresent();
}
@Override
public boolean allowedInviteLinkExists(Long serverId, String code) {
return repository.findByCodeAndServer_Id(code, serverId).isPresent();
}
@Override
public boolean allowedInviteLinkExists(ServerUser serverUser, String code) {
return repository.findByCodeAndServer_Id(code, serverUser.getServerId()).isPresent();
}
}

View File

@@ -0,0 +1,64 @@
package dev.sheldan.abstracto.moderation.service.management;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.moderation.models.database.FilteredInviteLink;
import dev.sheldan.abstracto.moderation.repository.FilteredInviteLinkRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Optional;
@Component
public class FilteredInviteLinkManagementBean implements FilteredInviteLinkManagement {
@Autowired
private FilteredInviteLinkRepository repository;
@Override
public FilteredInviteLink createFilteredInviteLink(AServer server, String code) {
FilteredInviteLink inviteLink = FilteredInviteLink
.builder()
.code(code)
.server(server)
.uses(1L)
.build();
return repository.save(inviteLink);
}
@Override
public Optional<FilteredInviteLink> findInviteLinkViaCode(AServer server, String code) {
return repository.findByCodeAndServer(code, server);
}
@Override
public Optional<FilteredInviteLink> findInviteLinkViaCode(Long serverId, String code) {
return repository.findByCodeAndServer_Id(code, serverId);
}
@Override
public void clearFilteredInviteLinks(Long serverId) {
repository.deleteByServer_Id(serverId);
}
@Override
public void clearFilteredInviteLinks(AServer server) {
clearFilteredInviteLinks(server.getId());
}
@Override
public void clearFilteredInviteLink(String code, Long serverId) {
repository.deleteByCodeAndServer_Id(code, serverId);
}
@Override
public void clearFilteredInviteLink(String code, AServer server) {
clearFilteredInviteLink(code, server.getId());
}
@Override
public List<FilteredInviteLink> getTopFilteredInviteLink(Long serverId, Integer count) {
return repository.findAllByServer_IdOrderByUsesDesc(serverId, PageRequest.of(0, count));
}
}

View File

@@ -13,6 +13,7 @@
<property name="mutingFeature" value="(SELECT id FROM feature WHERE key = 'muting')"/>
<property name="warnDecayFeature" value="(SELECT id FROM feature WHERE key = 'warnDecay')"/>
<property name="userNotesFeature" value="(SELECT id FROM feature WHERE key = 'userNotes')"/>
<property name="inviteLinkFilteringFeature" value="(SELECT id FROM feature WHERE key = 'inviteFilter')"/>
<property name="today" value="(SELECT NOW())"/>
<changeSet author="Sheldan" id="moderation_moderation-commands">
@@ -131,4 +132,31 @@
</insert>
</changeSet>
<changeSet author="Sheldan" id="moderation_inviteFilter-commands">
<insert tableName="command">
<column name="name" value="allowInvite"/>
<column name="module_id" valueComputed="${moderationModule}"/>
<column name="feature_id" valueComputed="${inviteLinkFilteringFeature}"/>
<column name="created" valueComputed="${today}"/>
</insert>
<insert tableName="command">
<column name="name" value="disAllowInvite"/>
<column name="module_id" valueComputed="${moderationModule}"/>
<column name="feature_id" valueComputed="${inviteLinkFilteringFeature}"/>
<column name="created" valueComputed="${today}"/>
</insert>
<insert tableName="command">
<column name="name" value="removeTrackedInviteLinks"/>
<column name="module_id" valueComputed="${moderationModule}"/>
<column name="feature_id" valueComputed="${inviteLinkFilteringFeature}"/>
<column name="created" valueComputed="${today}"/>
</insert>
<insert tableName="command">
<column name="name" value="showTrackedInviteLinks"/>
<column name="module_id" valueComputed="${moderationModule}"/>
<column name="feature_id" valueComputed="${inviteLinkFilteringFeature}"/>
<column name="created" valueComputed="${today}"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -13,7 +13,6 @@
<column name="long_value" value="90"/>
<column name="created" valueComputed="${today}"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -12,6 +12,7 @@
<property name="loggingFeature" value="(SELECT id FROM feature WHERE key = 'logging')"/>
<property name="mutingFeature" value="(SELECT id FROM feature WHERE key = 'muting')"/>
<property name="userNotesFeature" value="(SELECT id FROM feature WHERE key = 'userNotes')"/>
<property name="inviteLinkFilteringFeature" value="(SELECT id FROM feature WHERE key = 'inviteFilter')"/>
<property name="today" value="(SELECT NOW())"/>
<changeSet author="Sheldan" id="moderation_default_feature_flag-insertion">
<insert tableName="default_feature_flag">
@@ -44,5 +45,10 @@
<column name="feature_id" valueComputed="${userNotesFeature}" />
<column name="created" valueComputed="${today}"/>
</insert>
<insert tableName="default_feature_flag">
<column name="enabled" value="false"/>
<column name="feature_id" valueComputed="${inviteLinkFilteringFeature}" />
<column name="created" valueComputed="${today}"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -10,6 +10,7 @@
<property name="warningsFeature" value="(SELECT id FROM feature WHERE key = 'warnings')"/>
<property name="warnDecayFeature" value="(SELECT id FROM feature WHERE key = 'warnDecay')"/>
<property name="mutingFeature" value="(SELECT id FROM feature WHERE key = 'muting')"/>
<property name="inviteLinkFilteringFeature" value="(SELECT id FROM feature WHERE key = 'inviteFilter')"/>
<property name="today" value="(SELECT NOW())"/>
<changeSet author="Sheldan" id="moderation_default_feature_mode-insertion">
<insert tableName="default_feature_mode">
@@ -60,5 +61,17 @@
<column name="feature_id" valueComputed="${mutingFeature}" />
<column name="created" valueComputed="${today}"/>
</insert>
<insert tableName="default_feature_mode">
<column name="enabled" value="true"/>
<column name="mode" value="trackUses"/>
<column name="feature_id" valueComputed="${inviteLinkFilteringFeature}" />
<column name="created" valueComputed="${today}"/>
</insert>
<insert tableName="default_feature_mode">
<column name="enabled" value="true"/>
<column name="mode" value="filterNotifications"/>
<column name="feature_id" valueComputed="${inviteLinkFilteringFeature}" />
<column name="created" valueComputed="${today}"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -44,5 +44,9 @@
<column name="name" value="decayLog"/>
<column name="created" valueComputed="${today}"/>
</insert>
<insert tableName="default_posttarget">
<column name="name" value="inviteDeleteLog"/>
<column name="created" valueComputed="${today}"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -32,5 +32,9 @@
<column name="key" value="userNotes"/>
<column name="created" valueComputed="${today}"/>
</insert>
<insert tableName="feature">
<column name="key" value="inviteFilter"/>
<column name="created" valueComputed="${today}"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,30 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.xsd" >
<changeSet author="Sheldan" id="allowed_invite_link-table">
<createTable tableName="allowed_invite_link">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="allowed_invite_link_pkey"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="code" type="VARCHAR(32)">
<constraints nullable="false"/>
</column>
<column name="server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
<changeSet author="Sheldan" id="allowed_invite_link-fk_allowed_invite_link_server">
<addForeignKeyConstraint baseColumnNames="server_id" baseTableName="allowed_invite_link" constraintName="fk_allowed_invite_link_server" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id" referencedTableName="server" validate="true"/>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,34 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.xsd" >
<changeSet author="Sheldan" id="filtered_invite_link-table">
<createTable tableName="filtered_invite_link">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="filtered_invite_link_pkey"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="uses" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="code" type="VARCHAR(32)">
<constraints nullable="false"/>
</column>
<column name="server_id" type="BIGINT">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
<changeSet author="Sheldan" id="filtered_invite_link-fk_filtered_invite_link_server">
<addForeignKeyConstraint baseColumnNames="server_id" baseTableName="filtered_invite_link" constraintName="fk_filtered_invite_link_server" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id" referencedTableName="server" validate="true"/>
</changeSet>
</databaseChangeLog>

View File

@@ -10,4 +10,6 @@
<include file="mute_role.xml" relativeToChangelogFile="true"/>
<include file="user_note.xml" relativeToChangelogFile="true"/>
<include file="warning.xml" relativeToChangelogFile="true"/>
<include file="allowed_invite_link.xml" relativeToChangelogFile="true"/>
<include file="filtered_invite_link.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>