From 4dd6770e91895ed95c6e5a5e010951322b4dd1e1 Mon Sep 17 00:00:00 2001 From: Patrick Klaeren Date: Wed, 13 Nov 2024 22:22:51 +0000 Subject: [PATCH 1/2] V3: Refactor channel designations, add relay service --- .../Behaviors/ModerationLoggingBehavior.cs | 23 +- .../Behaviors/PromotionLoggingHandler.cs | 31 +- src/Modix.Bot/Handlers/SendMessageHandler.cs | 25 + ...Module.cs => ChannelDesignationsModule.cs} | 37 +- src/Modix.Bot/Modules/InfractionModule.cs | 1 - .../Responders/StarboardReactionResponder.cs | 6 +- .../Models/Core/ConfigurationActionEntity.cs | 156 ++--- .../DesignatedChannelMappingCreationData.cs | 15 - .../Core/DesignatedChannelMappingEntity.cs | 85 ++- .../DesignatedChannelMappingRepository.cs | 207 ------- src/Modix.Services/Core/CoreSetup.cs | 3 +- .../Core/DesignatedChannelService.cs | 360 ++++------- src/Modix.Services/DiscordRelayService.cs | 20 + .../MessageLogging/MessageLoggingBehavior.cs | 8 +- .../Moderation/AttachmentBlacklistBehavior.cs | 6 +- .../MessageContentCheckBehaviour.cs | 6 +- .../Moderation/ModerationService.cs | 8 +- .../Starboard/StarboardService.cs | 6 +- .../Components/Configuration/Channels.razor | 8 +- .../Extensions/ServiceCollectionExtensions.cs | 1 + src/Modix/appsettings.Development.json | 5 +- ...DesignatedChannelMappingRepositoryTests.cs | 582 ------------------ 22 files changed, 364 insertions(+), 1235 deletions(-) create mode 100644 src/Modix.Bot/Handlers/SendMessageHandler.cs rename src/Modix.Bot/Modules/{DesignatedChannelModule.cs => ChannelDesignationsModule.cs} (79%) delete mode 100644 src/Modix.Data/Repositories/DesignatedChannelMappingRepository.cs create mode 100644 src/Modix.Services/DiscordRelayService.cs delete mode 100644 test/Modix.Data.Test/Repositories/DesignatedChannelMappingRepositoryTests.cs diff --git a/src/Modix.Bot/Behaviors/ModerationLoggingBehavior.cs b/src/Modix.Bot/Behaviors/ModerationLoggingBehavior.cs index 909ac6cd4..0288f6ffd 100644 --- a/src/Modix.Bot/Behaviors/ModerationLoggingBehavior.cs +++ b/src/Modix.Bot/Behaviors/ModerationLoggingBehavior.cs @@ -2,17 +2,14 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; - using Discord; - using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; - -using Modix.Bot.Extensions; using Modix.Common.Extensions; using Modix.Data.Models.Core; using Modix.Data.Models.Moderation; using Modix.Data.Repositories; +using Modix.Services; using Modix.Services.Core; using Modix.Services.Moderation; using Modix.Services.Utilities; @@ -24,15 +21,19 @@ namespace Modix.Behaviors /// public class ModerationLoggingBehavior : IModerationActionEventHandler { + private readonly DiscordRelayService _discordRelayService; + /// /// Constructs a new object, with injected dependencies. /// public ModerationLoggingBehavior( IServiceProvider serviceProvider, IDiscordClient discordClient, - IDesignatedChannelService designatedChannelService, + DesignatedChannelService designatedChannelService, + DiscordRelayService discordRelayService, IOptions config) { + _discordRelayService = discordRelayService; DiscordClient = discordClient; DesignatedChannelService = designatedChannelService; Config = config.Value; @@ -43,7 +44,9 @@ public ModerationLoggingBehavior( /// public async Task OnModerationActionCreatedAsync(long moderationActionId, ModerationActionCreationData data) { - if (!await DesignatedChannelService.AnyDesignatedChannelAsync(data.GuildId, DesignatedChannelType.ModerationLog)) + var designatedChannels = await DesignatedChannelService.GetDesignatedChannelIds(data.GuildId, DesignatedChannelType.ModerationLog); + + if (!designatedChannels.Any()) return; var moderationAction = await ModerationService.GetModerationActionSummaryAsync(moderationActionId); @@ -73,8 +76,10 @@ public async Task OnModerationActionCreatedAsync(long moderationActionId, Modera moderationAction.OriginalInfractionReason, string.IsNullOrEmpty(moderationAction.Infraction?.RescindReason) ? "" : $"for reason: ```\n{moderationAction.Infraction?.RescindReason}```"); - await DesignatedChannelService.SendToDesignatedChannelsAsync( - await DiscordClient.GetGuildAsync(data.GuildId), DesignatedChannelType.ModerationLog, message); + foreach (var channel in designatedChannels) + { + await _discordRelayService.SendMessageToChannel(channel, message); + } } /// @@ -85,7 +90,7 @@ await DesignatedChannelService.SendToDesignatedChannelsAsync( /// /// An for logging moderation actions. /// - internal protected IDesignatedChannelService DesignatedChannelService { get; } + internal protected DesignatedChannelService DesignatedChannelService { get; } /// /// An for performing moderation actions. diff --git a/src/Modix.Bot/Behaviors/PromotionLoggingHandler.cs b/src/Modix.Bot/Behaviors/PromotionLoggingHandler.cs index 4c2a6f307..205f47c31 100644 --- a/src/Modix.Bot/Behaviors/PromotionLoggingHandler.cs +++ b/src/Modix.Bot/Behaviors/PromotionLoggingHandler.cs @@ -12,6 +12,7 @@ using Modix.Common.Messaging; using Modix.Data.Models.Core; using Modix.Data.Models.Promotions; +using Modix.Services; using Modix.Services.Core; using Modix.Services.Promotions; using Modix.Services.Utilities; @@ -24,17 +25,21 @@ namespace Modix.Behaviors public class PromotionLoggingHandler : INotificationHandler { + private readonly DiscordRelayService _discordRelayService; + /// /// Constructs a new object, with injected dependencies. /// public PromotionLoggingHandler( IAuthorizationService authorizationService, DiscordSocketClient discordSocketClient, - IDesignatedChannelService designatedChannelService, + DesignatedChannelService designatedChannelService, IUserService userService, IPromotionsService promotionsService, + DiscordRelayService discordRelayService, IOptions modixConfig) { + _discordRelayService = discordRelayService; AuthorizationService = authorizationService; DiscordSocketClient = discordSocketClient; DesignatedChannelService = designatedChannelService; @@ -49,26 +54,36 @@ public async Task HandleNotificationAsync(PromotionActionCreatedNotification not if (AuthorizationService.CurrentUserId is null) await AuthorizationService.OnAuthenticatedAsync(DiscordSocketClient.CurrentUser); - if (await DesignatedChannelService.AnyDesignatedChannelAsync(notification.Data.GuildId, DesignatedChannelType.PromotionLog)) + if (await DesignatedChannelService.HasDesignatedChannelForType(notification.Data.GuildId, DesignatedChannelType.PromotionLog)) { var message = await FormatPromotionLogEntryAsync(notification.Id); if (message == null) return; - await DesignatedChannelService.SendToDesignatedChannelsAsync( - await DiscordSocketClient.GetGuildAsync(notification.Data.GuildId), DesignatedChannelType.PromotionLog, message); + var designatedChannels = await DesignatedChannelService.GetDesignatedChannelIds(notification.Data.GuildId, + DesignatedChannelType.PromotionLog); + + foreach (var channel in designatedChannels) + { + await _discordRelayService.SendMessageToChannel(channel, message); + } } - if (await DesignatedChannelService.AnyDesignatedChannelAsync(notification.Data.GuildId, DesignatedChannelType.PromotionNotifications)) + if (await DesignatedChannelService.HasDesignatedChannelForType(notification.Data.GuildId, DesignatedChannelType.PromotionNotifications)) { var embed = await FormatPromotionNotificationAsync(notification.Id, notification.Data); if (embed == null) return; - await DesignatedChannelService.SendToDesignatedChannelsAsync( - await DiscordSocketClient.GetGuildAsync(notification.Data.GuildId), DesignatedChannelType.PromotionNotifications, "", embed); + var designatedChannels = await DesignatedChannelService.GetDesignatedChannelIds(notification.Data.GuildId, + DesignatedChannelType.PromotionNotifications); + + foreach (var channel in designatedChannels) + { + await _discordRelayService.SendMessageToChannel(channel, string.Empty, embed); + } } } @@ -144,7 +159,7 @@ private async Task FormatPromotionLogEntryAsync(long promotionActionId) /// /// An for logging moderation actions. /// - internal protected IDesignatedChannelService DesignatedChannelService { get; } + internal protected DesignatedChannelService DesignatedChannelService { get; } /// /// An for retrieving user info diff --git a/src/Modix.Bot/Handlers/SendMessageHandler.cs b/src/Modix.Bot/Handlers/SendMessageHandler.cs new file mode 100644 index 000000000..1daade385 --- /dev/null +++ b/src/Modix.Bot/Handlers/SendMessageHandler.cs @@ -0,0 +1,25 @@ +using System.Threading; +using System.Threading.Tasks; +using Discord; +using Discord.WebSocket; +using MediatR; +using Modix.Services; + +namespace Modix.Bot.Handlers; + +public class SendMessageHandler(DiscordSocketClient discordSocketClient) + : IRequestHandler +{ + public async Task Handle(SendMessageInDiscordRequest request, CancellationToken cancellationToken) + { + var channel = await discordSocketClient.GetChannelAsync(request.ChannelId); + + if (channel is ITextChannel textChannel) + { + await textChannel.SendMessageAsync(request.Message, false, request.Embed, allowedMentions: AllowedMentions.None); + return true; + } + + return false; + } +} diff --git a/src/Modix.Bot/Modules/DesignatedChannelModule.cs b/src/Modix.Bot/Modules/ChannelDesignationsModule.cs similarity index 79% rename from src/Modix.Bot/Modules/DesignatedChannelModule.cs rename to src/Modix.Bot/Modules/ChannelDesignationsModule.cs index e1539ffb3..901d7cff9 100644 --- a/src/Modix.Bot/Modules/DesignatedChannelModule.cs +++ b/src/Modix.Bot/Modules/ChannelDesignationsModule.cs @@ -12,17 +12,17 @@ using Modix.Services.CommandHelp; using Modix.Services.Core; -namespace Modix.Modules +namespace Modix.Bot.Modules { [ModuleHelp("Channel Designations", "Configures channel designation for various bot services.")] [Group("channel-designations", "Configures channel designation for various bot services.")] [DefaultMemberPermissions(GuildPermission.BanMembers)] - public class DesignatedChannelModule : InteractionModuleBase + public class ChannelDesignationsModule : InteractionModuleBase { - private readonly IDesignatedChannelService _designatedChannelService; + private readonly DesignatedChannelService _designatedChannelService; private readonly ModixConfig _config; - public DesignatedChannelModule(IDesignatedChannelService designatedChannelService, IOptions config) + public ChannelDesignationsModule(DesignatedChannelService designatedChannelService, IOptions config) { _designatedChannelService = designatedChannelService; _config = config.Value; @@ -30,24 +30,27 @@ public DesignatedChannelModule(IDesignatedChannelService designatedChannelServic [SlashCommand("list", "Lists all of the channels designated for use by the bot.")] [RequireClaims(AuthorizationClaim.DesignatedChannelMappingRead)] - public async Task ListAsync() + public async Task List() { - var channels = await _designatedChannelService.GetDesignatedChannelsAsync(Context.Guild.Id); - - // https://mod.gg/config/channels - var url = new UriBuilder(_config.WebsiteBaseUrl) - { - Path = "/config/channels" - }.RemoveDefaultPort().ToString(); + var channels = await _designatedChannelService.GetDesignatedChannels(Context.Guild.Id); var builder = new EmbedBuilder() { Title = "Assigned Channel Designations", - Url = url, Color = Color.Gold, Timestamp = DateTimeOffset.UtcNow }; + if (!string.IsNullOrWhiteSpace(_config.WebsiteBaseUrl)) + { + var url = new UriBuilder(_config.WebsiteBaseUrl) + { + Path = "/config/channels" + }.RemoveDefaultPort().ToString(); + + builder.Url = url; + } + foreach (var type in Enum.GetValues()) { var designatedChannels = channels @@ -69,25 +72,25 @@ public async Task ListAsync() [SlashCommand("add", "Assigns a designation to the given channel.")] [RequireClaims(AuthorizationClaim.DesignatedChannelMappingCreate)] - public async Task AddAsync( + public async Task Add( [Summary(description: "The channel to be assigned a designation.")] IMessageChannel channel, [Summary(description: "The designation to assign.")] DesignatedChannelType designation) { - await _designatedChannelService.AddDesignatedChannelAsync(Context.Guild, channel, designation); + await _designatedChannelService.AddDesignatedChannel(Context.Guild, channel, designation); await Context.AddConfirmationAsync(); } [SlashCommand("remove", "Removes a designation from the given channel.")] [RequireClaims(AuthorizationClaim.DesignatedChannelMappingDelete)] - public async Task RemoveAsync( + public async Task Remove( [Summary(description: "The channel whose designation is to be unassigned.")] IMessageChannel channel, [Summary(description: "The designation to be unassigned.")] DesignatedChannelType designation) { - await _designatedChannelService.RemoveDesignatedChannelAsync(Context.Guild, channel, designation); + await _designatedChannelService.RemoveDesignatedChannel(Context.Guild, channel, designation); await Context.AddConfirmationAsync(); } } diff --git a/src/Modix.Bot/Modules/InfractionModule.cs b/src/Modix.Bot/Modules/InfractionModule.cs index b353107d2..bea873291 100644 --- a/src/Modix.Bot/Modules/InfractionModule.cs +++ b/src/Modix.Bot/Modules/InfractionModule.cs @@ -86,7 +86,6 @@ public async Task SearchAsync( var counts = await _moderationService.GetInfractionCountsForUserAsync(user.Id); - // https://modix.gg/infractions?subject=12345 var url = new UriBuilder(_config.WebsiteBaseUrl) { Path = "/infractions", diff --git a/src/Modix.Bot/Responders/StarboardReactionResponder.cs b/src/Modix.Bot/Responders/StarboardReactionResponder.cs index 51965b670..76f1bf549 100644 --- a/src/Modix.Bot/Responders/StarboardReactionResponder.cs +++ b/src/Modix.Bot/Responders/StarboardReactionResponder.cs @@ -11,7 +11,7 @@ namespace Modix.Bot.Responders { - public class StarboardReactionResponder(IStarboardService starboardService, IDesignatedChannelService designatedChannelService) + public class StarboardReactionResponder(IStarboardService starboardService, DesignatedChannelService designatedChannelService) : INotificationHandler, INotificationHandler { public Task Handle(ReactionAddedNotificationV3 notification, CancellationToken cancellationToken) @@ -35,10 +35,10 @@ private async Task HandleReactionAsync(Cacheable cachedMess } var isIgnoredFromStarboard = await designatedChannelService - .ChannelHasDesignationAsync(channel.Guild.Id, channel.Id, DesignatedChannelType.IgnoredFromStarboard, default); + .ChannelHasDesignation(channel.Guild.Id, channel.Id, DesignatedChannelType.IgnoredFromStarboard, default); var starboardExists = await designatedChannelService - .AnyDesignatedChannelAsync(channel.GuildId, DesignatedChannelType.Starboard); + .HasDesignatedChannelForType(channel.GuildId, DesignatedChannelType.Starboard); if (isIgnoredFromStarboard || !starboardExists) { diff --git a/src/Modix.Data/Models/Core/ConfigurationActionEntity.cs b/src/Modix.Data/Models/Core/ConfigurationActionEntity.cs index a613f47f6..c43908efa 100644 --- a/src/Modix.Data/Models/Core/ConfigurationActionEntity.cs +++ b/src/Modix.Data/Models/Core/ConfigurationActionEntity.cs @@ -5,106 +5,66 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace Modix.Data.Models.Core +namespace Modix.Data.Models.Core; + +[Table("ConfigurationActions")] +public class ConfigurationActionEntity { - /// - /// Describes an action that was performed, that somehow changed the application's configuration. - /// - [Table("ConfigurationActions")] - public class ConfigurationActionEntity - { - /// - /// A unique identifier for this configuration action. - /// - [Key, Required, DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public long Id { get; set; } - - /// - /// The snowflake ID, within the Discord API, of the guild to which this configuration action applies. - /// - [Required] - public ulong GuildId { get; set; } - - /// - /// The type of configuration action that was performed. - /// - [Required] - public ConfigurationActionType Type { get; set; } - - /// - /// A timestamp indicating when this configuration action was performed. - /// - [Required] - public DateTimeOffset Created { get; set; } - - /// - /// The value of . - /// - [Required] - public ulong CreatedById { get; set; } - - /// - /// The Discord user that performed this action. - /// - [Required] - public virtual GuildUserEntity CreatedBy { get; set; } = null!; - - /// - /// The value (if any) of . - /// - [ForeignKey(nameof(ClaimMapping))] - public long? ClaimMappingId { get; set; } - - /// - /// The claim mapping that was affected by this action, if any. - /// - public ClaimMappingEntity? ClaimMapping { get; set; } - - /// - /// The value (if any) of . - /// - [ForeignKey(nameof(DesignatedChannelMapping))] - public long? DesignatedChannelMappingId { get; set; } - - /// - /// The designated channel mapping that was affected by this action, if any. - /// - public DesignatedChannelMappingEntity? DesignatedChannelMapping { get; set; } - - /// - /// The value (if any) of . - /// - [ForeignKey(nameof(DesignatedRoleMapping))] - public long? DesignatedRoleMappingId { get; set; } - - /// - /// The designated role mapping that was affected by this action, if any. - /// - public DesignatedRoleMappingEntity? DesignatedRoleMapping { get; set; } - } + [Key, Required, DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long Id { get; set; } + + [Required] + public ulong GuildId { get; set; } + + [Required] + public ConfigurationActionType Type { get; set; } + + [Required] + public DateTimeOffset Created { get; set; } + + [Required] + public ulong CreatedById { get; set; } + + [Required] + public virtual GuildUserEntity CreatedBy { get; set; } = null!; - public class ConfigurationActionEntityConfigurator - : IEntityTypeConfiguration + [ForeignKey(nameof(ClaimMapping))] + public long? ClaimMappingId { get; set; } + + public ClaimMappingEntity? ClaimMapping { get; set; } + + [ForeignKey(nameof(DesignatedChannelMapping))] + public long? DesignatedChannelMappingId { get; set; } + + public DesignatedChannelMappingEntity? DesignatedChannelMapping { get; set; } + + [ForeignKey(nameof(DesignatedRoleMapping))] + public long? DesignatedRoleMappingId { get; set; } + + public DesignatedRoleMappingEntity? DesignatedRoleMapping { get; set; } +} + +public class ConfigurationActionEntityConfigurator + : IEntityTypeConfiguration +{ + public void Configure( + EntityTypeBuilder entityTypeBuilder) { - public void Configure( - EntityTypeBuilder entityTypeBuilder) - { - entityTypeBuilder - .Property(x => x.Type) - .HasConversion(); - - entityTypeBuilder - .Property(x => x.GuildId) - .HasConversion(); - - entityTypeBuilder - .Property(x => x.CreatedById) - .HasConversion(); - - entityTypeBuilder - .HasOne(x => x.CreatedBy) - .WithMany() - .HasForeignKey(x => new { x.GuildId, x.CreatedById }); - } + entityTypeBuilder + .Property(x => x.Type) + .HasConversion(); + + entityTypeBuilder + .Property(x => x.GuildId) + .HasConversion(); + + entityTypeBuilder + .Property(x => x.CreatedById) + .HasConversion(); + + entityTypeBuilder + .HasOne(x => x.CreatedBy) + .WithMany() + .HasForeignKey(x => new { x.GuildId, x.CreatedById }); } } diff --git a/src/Modix.Data/Models/Core/DesignatedChannelMappingCreationData.cs b/src/Modix.Data/Models/Core/DesignatedChannelMappingCreationData.cs index 2d9ad7dec..4a48f3749 100644 --- a/src/Modix.Data/Models/Core/DesignatedChannelMappingCreationData.cs +++ b/src/Modix.Data/Models/Core/DesignatedChannelMappingCreationData.cs @@ -26,20 +26,5 @@ public class DesignatedChannelMappingCreationData /// See . /// public ulong CreatedById { get; set; } - - internal DesignatedChannelMappingEntity ToEntity() - => new DesignatedChannelMappingEntity() - { - GuildId = GuildId, - ChannelId = ChannelId, - Type = Type, - CreateAction = new ConfigurationActionEntity() - { - GuildId = GuildId, - Type = ConfigurationActionType.DesignatedChannelMappingCreated, - Created = DateTimeOffset.UtcNow, - CreatedById = CreatedById - } - }; } } diff --git a/src/Modix.Data/Models/Core/DesignatedChannelMappingEntity.cs b/src/Modix.Data/Models/Core/DesignatedChannelMappingEntity.cs index 65b49e42a..7c6bc6957 100644 --- a/src/Modix.Data/Models/Core/DesignatedChannelMappingEntity.cs +++ b/src/Modix.Data/Models/Core/DesignatedChannelMappingEntity.cs @@ -4,59 +4,58 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace Modix.Data.Models.Core +namespace Modix.Data.Models.Core; + +[Table("DesignatedChannelMappings")] +public class DesignatedChannelMappingEntity { - [Table("DesignatedChannelMappings")] - public class DesignatedChannelMappingEntity - { - [Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public long Id { get; set; } + [Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long Id { get; set; } - public ulong GuildId { get; set; } + public ulong GuildId { get; set; } - [ForeignKey(nameof(Channel))] - public ulong ChannelId { get; set; } + [ForeignKey(nameof(Channel))] + public ulong ChannelId { get; set; } - public virtual GuildChannelEntity Channel { get; set; } = null!; + public virtual GuildChannelEntity Channel { get; set; } = null!; - public DesignatedChannelType Type { get; set; } + public DesignatedChannelType Type { get; set; } - public long CreateActionId { get; set; } + public long CreateActionId { get; set; } - public virtual ConfigurationActionEntity CreateAction { get; set; } = null!; + public virtual ConfigurationActionEntity CreateAction { get; set; } = null!; - public long? DeleteActionId { get; set; } + public long? DeleteActionId { get; set; } - public virtual ConfigurationActionEntity? DeleteAction { get; set; } - } + public virtual ConfigurationActionEntity? DeleteAction { get; set; } +} - public class DesignatedChannelMappingEntityConfiguration - : IEntityTypeConfiguration +public class DesignatedChannelMappingEntityConfiguration + : IEntityTypeConfiguration +{ + public void Configure( + EntityTypeBuilder entityTypeBuilder) { - public void Configure( - EntityTypeBuilder entityTypeBuilder) - { - entityTypeBuilder - .Property(x => x.Type) - .HasConversion(); - - entityTypeBuilder - .Property(x => x.GuildId) - .HasConversion(); - - entityTypeBuilder - .Property(x => x.ChannelId) - .HasConversion(); - - entityTypeBuilder - .HasOne(x => x.CreateAction) - .WithOne() - .HasForeignKey(x => x.CreateActionId); - - entityTypeBuilder - .HasOne(x => x.DeleteAction) - .WithOne() - .HasForeignKey(x => x.DeleteActionId); - } + entityTypeBuilder + .Property(x => x.Type) + .HasConversion(); + + entityTypeBuilder + .Property(x => x.GuildId) + .HasConversion(); + + entityTypeBuilder + .Property(x => x.ChannelId) + .HasConversion(); + + entityTypeBuilder + .HasOne(x => x.CreateAction) + .WithOne() + .HasForeignKey(x => x.CreateActionId); + + entityTypeBuilder + .HasOne(x => x.DeleteAction) + .WithOne() + .HasForeignKey(x => x.DeleteActionId); } } diff --git a/src/Modix.Data/Repositories/DesignatedChannelMappingRepository.cs b/src/Modix.Data/Repositories/DesignatedChannelMappingRepository.cs deleted file mode 100644 index e5ad3d20b..000000000 --- a/src/Modix.Data/Repositories/DesignatedChannelMappingRepository.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -using Microsoft.EntityFrameworkCore; - -using Modix.Data.ExpandableQueries; -using Modix.Data.Models.Core; - -namespace Modix.Data.Repositories -{ - /// - /// Describes a repository for managing entities, within an underlying data storage provider. - /// - public interface IDesignatedChannelMappingRepository - { - /// - /// Begins a new transaction to create mappings within the repository. - /// - /// - /// A that will complete, with the requested transaction object, - /// when no other create transactions are active upon the repository. - /// - Task BeginCreateTransactionAsync(); - - /// - /// Begins a new transaction to delete mappings within the repository. - /// - /// - /// A that will complete, with the requested transaction object, - /// when no other delete transactions are active upon the repository. - /// - Task BeginDeleteTransactionAsync(); - - /// - /// Creates a new mapping withn the repository. - /// This method must not be called outside of the scope of an returned by . - /// - /// The data for the mapping to be created. - /// Throws for . - /// - /// A which will complete when the operation is complete, - /// containing the auto-generated value assigned to the new mapping. - /// - Task CreateAsync(DesignatedChannelMappingCreationData data); - - /// - /// Checks whether any mappings exist within the repository, according to an arbitrary set of criteria. - /// - /// A set of criteria defining the mappings to check for. - /// A token that may be used to cancel the operation. - /// - /// A that will complete when the operation has completed, - /// containing a flag indicating whether any matching mappings were found. - /// - Task AnyAsync( - DesignatedChannelMappingSearchCriteria criteria, - CancellationToken cancellationToken); - - /// - /// Searches the repository for mapped values, based on an arbitrary set of criteria - /// - /// The criteria for selecting ID values to be returned. - /// A which will complete when the matching values have been retrieved. - Task> SearchChannelIdsAsync(DesignatedChannelMappingSearchCriteria searchCriteria); - - /// - /// Searches the repository for mappings, based on an arbitrary set of criteria. - /// - /// The criteria for selecting records to be returned. - /// A which will complete when the matching records have been retrieved. - Task> SearchBriefsAsync(DesignatedChannelMappingSearchCriteria searchCriteria); - - /// - /// Marks mappings within the repository as deleted, based on a given set of search criteria. - /// This method must not be called outside of the scope of an returned by . - /// - /// A set of criteria defining the mappings to be deleted. - /// The value of the user that is deleting the mapping. - /// - /// A which will complete when the operation is complete, - /// containing the total number of mappings that were deleted, based on the given criteria. - /// - Task DeleteAsync(DesignatedChannelMappingSearchCriteria criteria, ulong deletedById); - - /// - /// Marks an existing mapping as deleted, based on its ID. - /// This method must not be called outside of the scope of an returned by . - /// - /// The value of the mapping to be deleted. - /// The value of the user that is deleting the mapping. - /// A which will complete when the operation is complete, - /// containing a flag indicating whether the operation was successful (I.E. whether the specified mapping could be found). - /// - Task TryDeleteAsync(long mappingId, ulong deletedById); - } - - /// - public class DesignatedChannelMappingRepository : RepositoryBase, IDesignatedChannelMappingRepository - { - /// - /// Creates a new . - /// See for details. - /// - public DesignatedChannelMappingRepository(ModixContext modixContext) - : base(modixContext) { } - - /// - public Task BeginCreateTransactionAsync() - => _createTransactionFactory.BeginTransactionAsync(ModixContext.Database); - - /// - public Task BeginDeleteTransactionAsync() - => _deleteTransactionFactory.BeginTransactionAsync(ModixContext.Database); - - /// - public async Task CreateAsync(DesignatedChannelMappingCreationData data) - { - if (data == null) - throw new ArgumentNullException(nameof(data)); - - var entity = data.ToEntity(); - - await ModixContext.Set().AddAsync(entity); - await ModixContext.SaveChangesAsync(); - - entity.CreateAction.DesignatedChannelMappingId = entity.Id; - await ModixContext.SaveChangesAsync(); - - return entity.Id; - } - - /// - public Task AnyAsync( - DesignatedChannelMappingSearchCriteria criteria, - CancellationToken cancellationToken) - => ModixContext.Set().AsNoTracking() - .FilterBy(criteria) - .AnyAsync(cancellationToken); - - /// - public async Task> SearchChannelIdsAsync(DesignatedChannelMappingSearchCriteria searchCriteria) - => await ModixContext.Set().AsNoTracking() - .FilterBy(searchCriteria) - .Select(x => x.ChannelId) - .ToArrayAsync(); - - /// - public async Task> SearchBriefsAsync(DesignatedChannelMappingSearchCriteria searchCriteria) - => await ModixContext.Set().AsNoTracking() - .FilterBy(searchCriteria) - .AsExpandable() - .Select(DesignatedChannelMappingBrief.FromEntityProjection) - .ToArrayAsync(); - - /// - public async Task DeleteAsync(DesignatedChannelMappingSearchCriteria criteria, ulong deletedById) - { - var entities = await ModixContext.Set() - .Where(x => x.DeleteActionId == null) - .FilterBy(criteria) - .ToArrayAsync(); - - foreach (var entity in entities) - DoEntityDelete(entity, deletedById); - - await ModixContext.SaveChangesAsync(); - - return entities.Length; - } - - /// - public async Task TryDeleteAsync(long mappingId, ulong deletedById) - { - var entity = await ModixContext.Set() - .Where(x => x.Id == mappingId) - .FirstOrDefaultAsync(); - - if ((entity == null) || (entity.DeleteActionId != null)) - return false; - - DoEntityDelete(entity, deletedById); - - await ModixContext.SaveChangesAsync(); - - return true; - } - - private static readonly RepositoryTransactionFactory _createTransactionFactory - = new RepositoryTransactionFactory(); - - private static readonly RepositoryTransactionFactory _deleteTransactionFactory - = new RepositoryTransactionFactory(); - - private void DoEntityDelete(DesignatedChannelMappingEntity entity, ulong deletedById) - => entity.DeleteAction = new ConfigurationActionEntity() - { - Type = ConfigurationActionType.DesignatedChannelMappingDeleted, - Created = DateTimeOffset.UtcNow, - CreatedById = deletedById, - DesignatedChannelMappingId = entity.Id, - GuildId = entity.GuildId - }; - } -} diff --git a/src/Modix.Services/Core/CoreSetup.cs b/src/Modix.Services/Core/CoreSetup.cs index 58d465dc3..dfa3f044f 100644 --- a/src/Modix.Services/Core/CoreSetup.cs +++ b/src/Modix.Services/Core/CoreSetup.cs @@ -35,11 +35,10 @@ public static IServiceCollection AddModixCore(this IServiceCollection services) .AddScoped() .AddScoped() .AddScoped() - .AddScoped() + .AddScoped() .AddScoped() .AddScoped() .AddScoped() - .AddScoped() .AddScoped() .AddScoped() .AddScoped(); diff --git a/src/Modix.Services/Core/DesignatedChannelService.cs b/src/Modix.Services/Core/DesignatedChannelService.cs index cc7f62129..380c01c92 100644 --- a/src/Modix.Services/Core/DesignatedChannelService.cs +++ b/src/Modix.Services/Core/DesignatedChannelService.cs @@ -4,262 +4,168 @@ using System.Threading; using System.Threading.Tasks; using Discord; +using Microsoft.EntityFrameworkCore; +using Modix.Data; using Modix.Data.Models.Core; -using Modix.Data.Repositories; -using Serilog; -namespace Modix.Services.Core -{ - /// - /// Provides methods for managing and interacting with Discord channels, designated for use within the application. - /// - public interface IDesignatedChannelService - { - /// - /// Assigns a channel to a given designation, for a given guild. - /// - /// The guild where the exists. - /// The channel to be assigned. - /// The type of designation to be assigned. - /// A that will complete when the operation has completed. - Task AddDesignatedChannelAsync(IGuild guild, IMessageChannel channel, DesignatedChannelType type); - - /// - /// Unassigns a channel's previously given designation, for a given guild. - /// - /// The guild where the exists. - /// The channel to be unassigned. - /// The type of designation to be unassigned. - /// A that will complete when the operation has completed, with the number of records deleted. - Task RemoveDesignatedChannelAsync(IGuild guild, IMessageChannel channel, DesignatedChannelType type); - - /// - /// Removes a channel designation by ID. - /// - /// The ID of the designation to be removed. - /// A that will complete when the operation has completed, with the number of records deleted. - Task RemoveDesignatedChannelByIdAsync(long designationId); - - /// - /// Checks whether any designated channels exist, for an arbitrary set of criteria. - /// - /// The Discord snowflake ID of the guild whose designated channels are to be checked. - /// The type of designated channels to check for. - /// - /// A that will complete when the operation has completed, - /// containing a flag indicating whether any matching channel designations exist. - /// - Task AnyDesignatedChannelAsync(ulong guildId, DesignatedChannelType type); - - /// - /// Retrieves the Discord snowflake ID values of the channels assigned to a given designation, for a given guild. - /// - /// The Discord snowflake ID of the guild for which channel designations are to be retrieved. - /// The type of designation for which channels are to be retrieved. - /// - /// A that will complete when the operation has compelted, - /// containing the requested channel ID values. - /// - Task> GetDesignatedChannelIdsAsync(ulong guildId, DesignatedChannelType type); - - /// - /// Retrieves the s assigned to the given designation, for a given guild. - /// - /// The guild to retrieve designations for - /// The designation to retrieve assigned channels for - /// A that will complete when all s have been retrieved. - Task> GetDesignatedChannelsAsync(IGuild guild, DesignatedChannelType type); - - /// - /// Retrieves a collection of s associated with the given guild. - /// - /// The ID of the guild to retrieve designations for - /// A that will complete when all s have been retrieved. - Task> GetDesignatedChannelsAsync(ulong guildId); +namespace Modix.Services.Core; - /// - /// Checks if the given channel has the given designation - /// - /// The where the channel is located - /// The to check the designation for - /// The to check for - /// A token that may be used to cancel the operation. - /// - Task ChannelHasDesignationAsync( - ulong guildId, - ulong channelId, - DesignatedChannelType designation, - CancellationToken cancellationToken); - - /// - /// Sends the given message (and embed) to the s assigned to the given designation, for a given guild. - /// - /// The to send the message to - /// The of the channels to send the messages to - /// The text content of the message - /// An optional to attach to the message - /// A that, when completed, results in a collection of the messages that were sent. - Task> SendToDesignatedChannelsAsync(IGuild guild, DesignatedChannelType designation, string content, Embed embed = null); - } - - public class DesignatedChannelService : IDesignatedChannelService +public class DesignatedChannelService( + ModixContext db, + IAuthorizationService authorizationService) +{ + public async Task AddDesignatedChannel(IGuild guild, IMessageChannel logChannel, + DesignatedChannelType type) { - internal protected IDesignatedChannelMappingRepository DesignatedChannelMappingRepository { get; } - internal protected IAuthorizationService AuthorizationService { get; } - - public DesignatedChannelService(IDesignatedChannelMappingRepository designatedChannelMappingRepository, IAuthorizationService authorizationService) + authorizationService.RequireAuthenticatedUser(); + authorizationService.RequireClaims(AuthorizationClaim.DesignatedChannelMappingCreate); + + var designationId = await db + .Set() + .Where(x => x.GuildId == guild.Id) + .Where(x => x.ChannelId == logChannel.Id) + .Where(x => x.Type == type) + .Where(x => x.DeleteActionId == null) + .Select(x => (long?)x.Id) + .SingleOrDefaultAsync(); + + if (designationId is not null) { - DesignatedChannelMappingRepository = designatedChannelMappingRepository; - AuthorizationService = authorizationService; + return designationId.Value; } - /// - public async Task AddDesignatedChannelAsync(IGuild guild, IMessageChannel logChannel, DesignatedChannelType type) + var newEntity = new DesignatedChannelMappingEntity { - AuthorizationService.RequireAuthenticatedUser(); - AuthorizationService.RequireClaims(AuthorizationClaim.DesignatedChannelMappingCreate); - - using (var transaction = await DesignatedChannelMappingRepository.BeginCreateTransactionAsync()) + GuildId = guild.Id, + ChannelId = logChannel.Id, + Type = type, + CreateAction = new ConfigurationActionEntity { - if (await DesignatedChannelMappingRepository.AnyAsync(new DesignatedChannelMappingSearchCriteria() - { - GuildId = guild.Id, - ChannelId = logChannel.Id, - IsDeleted = false, - Type = type - }, default)) - { - throw new InvalidOperationException($"{logChannel.Name} in {guild.Name} is already assigned to {type}"); - } - - var id = await DesignatedChannelMappingRepository.CreateAsync(new DesignatedChannelMappingCreationData() - { - GuildId = guild.Id, - ChannelId = logChannel.Id, - CreatedById = AuthorizationService.CurrentUserId.Value, - Type = type - }); + GuildId = guild.Id, + Type = ConfigurationActionType.DesignatedChannelMappingCreated, + Created = DateTimeOffset.UtcNow, + CreatedById = authorizationService.CurrentUserId!.Value, + } + }; - transaction.Commit(); + db.Add(newEntity); + await db.SaveChangesAsync(); - return id; - } - } + return newEntity.Id; + } - /// - public async Task RemoveDesignatedChannelAsync(IGuild guild, IMessageChannel logChannel, DesignatedChannelType type) + public async Task RemoveDesignatedChannel(IGuild guild, IMessageChannel logChannel, DesignatedChannelType type) + { + var designationId = await db + .Set() + .Where(x => x.GuildId == guild.Id) + .Where(x => x.ChannelId == logChannel.Id) + .Where(x => x.Type == type) + .Where(x => x.DeleteActionId == null) + .Select(x => (long?)x.Id) + .SingleOrDefaultAsync(); + + if (designationId is null) { - AuthorizationService.RequireAuthenticatedUser(); - AuthorizationService.RequireClaims(AuthorizationClaim.DesignatedChannelMappingDelete); + return; + } - using (var transaction = await DesignatedChannelMappingRepository.BeginDeleteTransactionAsync()) - { - var deletedCount = await DesignatedChannelMappingRepository.DeleteAsync(new DesignatedChannelMappingSearchCriteria() - { - GuildId = guild.Id, - ChannelId = logChannel.Id, - IsDeleted = false, - Type = type - }, AuthorizationService.CurrentUserId.Value); + await RemoveDesignatedChannelById(designationId.Value); + } - if (deletedCount == 0) - throw new InvalidOperationException($"{logChannel.Name} in {guild.Name} is not assigned to {type}"); + public async Task RemoveDesignatedChannelById(long designationId) + { + authorizationService.RequireAuthenticatedUser(); + authorizationService.RequireClaims(AuthorizationClaim.DesignatedChannelMappingDelete); - transaction.Commit(); - return deletedCount; - } - } + var designation = await db + .Set() + .Where(x => x.Id == designationId) + .SingleAsync(); - /// - public async Task RemoveDesignatedChannelByIdAsync(long designationId) + var deleteAction = new ConfigurationActionEntity { - AuthorizationService.RequireAuthenticatedUser(); - AuthorizationService.RequireClaims(AuthorizationClaim.DesignatedChannelMappingDelete); - - using (var transaction = await DesignatedChannelMappingRepository.BeginDeleteTransactionAsync()) - { - var deletedCount = await DesignatedChannelMappingRepository.DeleteAsync(new DesignatedChannelMappingSearchCriteria() - { - Id = designationId, - IsDeleted = false - }, AuthorizationService.CurrentUserId.Value); - - if (deletedCount == 0) - throw new InvalidOperationException($"No designations with id {designationId} found."); + Type = ConfigurationActionType.DesignatedChannelMappingDeleted, + Created = DateTimeOffset.UtcNow, + CreatedById = authorizationService.CurrentUserId!.Value, + DesignatedChannelMappingId = designation.Id, + GuildId = designation.GuildId, + }; + + db.Add(deleteAction); + await db.SaveChangesAsync(); + } - transaction.Commit(); - return deletedCount; - } - } + public async Task HasDesignatedChannelForType(ulong guildId, DesignatedChannelType type) + { + return await db + .Set() + .Where(x => x.GuildId == guildId) + .Where(x => x.Type == type) + .Where(x => x.DeleteActionId == null) + .AnyAsync(); + } - /// - public Task AnyDesignatedChannelAsync(ulong guildId, DesignatedChannelType type) - => DesignatedChannelMappingRepository.AnyAsync(new DesignatedChannelMappingSearchCriteria() - { - GuildId = guildId, - Type = type, - IsDeleted = false - }, default); + public async Task> GetDesignatedChannelIds(ulong guildId, DesignatedChannelType type) + { + return await db + .Set() + .Where(x => x.GuildId == guildId) + .Where(x => x.Type == type) + .Select(x => x.ChannelId) + .ToListAsync(); + } - /// - public Task> GetDesignatedChannelIdsAsync(ulong guildId, DesignatedChannelType type) - => DesignatedChannelMappingRepository.SearchChannelIdsAsync(new DesignatedChannelMappingSearchCriteria() - { - GuildId = guildId, - Type = type, - IsDeleted = false - }); + public async Task> GetDesignatedChannels(IGuild guild, + DesignatedChannelType type) + { + var channelIds = await GetDesignatedChannelIds(guild.Id, type); - /// - public async Task> GetDesignatedChannelsAsync(IGuild guild, DesignatedChannelType type) + if (!channelIds.Any()) { - var channelIds = await GetDesignatedChannelIdsAsync(guild.Id, type); - - if (!channelIds.Any()) - throw new InvalidOperationException($"{guild.Name} has no channels assigned to {type}"); - - return (await Task.WhenAll(channelIds.Select(d => guild.GetChannelAsync(d)))) - .OfType() - .ToArray(); + return []; } - /// - public async Task> SendToDesignatedChannelsAsync(IGuild guild, DesignatedChannelType designation, string text, Embed embed = null) - { - var channels = await GetDesignatedChannelsAsync(guild, designation); + var channels = await Task.WhenAll(channelIds.Select(d => guild.GetChannelAsync(d))); - if (!channels.Any()) - { - Log.Warning("Warning: Tried to send to channels assigned to designation {designation}, but none were assigned.", new { designation }); - } - - return await Task.WhenAll(channels.Select(channel => channel.SendMessageAsync(text, false, embed))); - } + return channels + .OfType() + .ToArray(); + } - /// - public Task> GetDesignatedChannelsAsync(ulong guildId) - { - AuthorizationService.RequireClaims(AuthorizationClaim.DesignatedChannelMappingRead); + public async Task> GetDesignatedChannels(ulong guildId) + { + authorizationService.RequireClaims(AuthorizationClaim.DesignatedChannelMappingRead); - return DesignatedChannelMappingRepository.SearchBriefsAsync(new DesignatedChannelMappingSearchCriteria() + return await db + .Set() + .Where(x => x.GuildId == guildId) + .Where(x => x.DeleteActionId == null) + .Select(x => new DesignatedChannelMappingBrief { - GuildId = guildId, - IsDeleted = false - }); - } + Id = x.Id, + Channel = new GuildChannelBrief + { + Id = x.ChannelId, + Name = x.Channel.Name, + ParentChannelId = x.Channel.ParentChannelId, + }, + Type = x.Type, + }).ToListAsync(); + } - /// - public Task ChannelHasDesignationAsync( - ulong guildId, - ulong channelId, - DesignatedChannelType designation, - CancellationToken cancellationToken) - => DesignatedChannelMappingRepository.AnyAsync(new DesignatedChannelMappingSearchCriteria() - { - GuildId = guildId, - Type = designation, - ChannelId = channelId, - IsDeleted = false - }, cancellationToken); + public async Task ChannelHasDesignation( + ulong guildId, + ulong channelId, + DesignatedChannelType type, + CancellationToken cancellationToken) + { + return await db + .Set() + .Where(x => x.GuildId == guildId) + .Where(x => x.ChannelId == channelId) + .Where(x => x.Type == type) + .Where(x => x.DeleteActionId == null) + .AnyAsync(cancellationToken: cancellationToken); } } diff --git a/src/Modix.Services/DiscordRelayService.cs b/src/Modix.Services/DiscordRelayService.cs new file mode 100644 index 000000000..a4b493354 --- /dev/null +++ b/src/Modix.Services/DiscordRelayService.cs @@ -0,0 +1,20 @@ +using System.Threading.Tasks; +using Discord; +using MediatR; + +namespace Modix.Services; + +public class DiscordRelayService(IMediator mediator) +{ + public async Task SendMessageToChannel(ulong channelId, string message, Embed embed = null) + { + return await mediator.Send(new SendMessageInDiscordRequest(channelId, message, embed)); + } +} + +public class SendMessageInDiscordRequest(ulong channelId, string message, Embed embed) : IRequest +{ + public ulong ChannelId { get; } = channelId; + public string Message { get; } = message; + public Embed Embed { get; } = embed; +} diff --git a/src/Modix.Services/MessageLogging/MessageLoggingBehavior.cs b/src/Modix.Services/MessageLogging/MessageLoggingBehavior.cs index 664255971..6ad69105a 100644 --- a/src/Modix.Services/MessageLogging/MessageLoggingBehavior.cs +++ b/src/Modix.Services/MessageLogging/MessageLoggingBehavior.cs @@ -24,7 +24,7 @@ public class MessageLoggingBehavior INotificationHandler { public MessageLoggingBehavior( - IDesignatedChannelService designatedChannelService, + DesignatedChannelService designatedChannelService, DiscordSocketClient discordSocketClient, ILogger logger) { @@ -175,7 +175,7 @@ private async Task TryLogAsync( return; } - var channelIsUnmoderated = await _designatedChannelService.ChannelHasDesignationAsync( + var channelIsUnmoderated = await _designatedChannelService.ChannelHasDesignation( guild.Id, channel.Id, DesignatedChannelType.Unmoderated, @@ -187,7 +187,7 @@ private async Task TryLogAsync( } MessageLoggingLogMessages.ModeratedChannelIdentified(_logger); - var messageLogChannels = await _designatedChannelService.GetDesignatedChannelsAsync( + var messageLogChannels = await _designatedChannelService.GetDesignatedChannels( guild, DesignatedChannelType.MessageLog); if (messageLogChannels.Count == 0) @@ -207,7 +207,7 @@ private async Task TryLogAsync( } } - private readonly IDesignatedChannelService _designatedChannelService; + private readonly DesignatedChannelService _designatedChannelService; private readonly DiscordSocketClient _discordSocketClient; private readonly ILogger _logger; } diff --git a/src/Modix.Services/Moderation/AttachmentBlacklistBehavior.cs b/src/Modix.Services/Moderation/AttachmentBlacklistBehavior.cs index bd0438a78..21ffe8783 100644 --- a/src/Modix.Services/Moderation/AttachmentBlacklistBehavior.cs +++ b/src/Modix.Services/Moderation/AttachmentBlacklistBehavior.cs @@ -95,7 +95,7 @@ public class AttachmentBlacklistBehavior }; public AttachmentBlacklistBehavior( - IDesignatedChannelService designatedChannelService, + DesignatedChannelService designatedChannelService, DiscordSocketClient discordSocketClient, ILogger logger, IModerationService moderationService) @@ -136,7 +136,7 @@ public async Task HandleNotificationAsync( } AttachmentBlacklistLogMessages.ChannelModerationStatusFetching(_logger); - var channelIsUnmoderated = await _designatedChannelService.ChannelHasDesignationAsync( + var channelIsUnmoderated = await _designatedChannelService.ChannelHasDesignation( guild.Id, channel.Id, DesignatedChannelType.Unmoderated, @@ -180,7 +180,7 @@ await message.Channel.SendMessageAsync( AttachmentBlacklistLogMessages.ReplySent(_logger); } - private readonly IDesignatedChannelService _designatedChannelService; + private readonly DesignatedChannelService _designatedChannelService; private readonly DiscordSocketClient _discordSocketClient; private readonly ILogger _logger; private readonly IModerationService _moderationService; diff --git a/src/Modix.Services/Moderation/MessageContentCheckBehaviour.cs b/src/Modix.Services/Moderation/MessageContentCheckBehaviour.cs index 3eeff67e8..70129c2fc 100644 --- a/src/Modix.Services/Moderation/MessageContentCheckBehaviour.cs +++ b/src/Modix.Services/Moderation/MessageContentCheckBehaviour.cs @@ -18,14 +18,14 @@ public class MessageContentCheckBehaviour : INotificationHandler, INotificationHandler { - private readonly IDesignatedChannelService _designatedChannelService; + private readonly DesignatedChannelService _designatedChannelService; private readonly IAuthorizationService _authorizationService; private readonly IModerationService _moderationService; private readonly IMessageContentPatternService _messageContentPatternService; private readonly DiscordSocketClient _discordSocketClient; public MessageContentCheckBehaviour( - IDesignatedChannelService designatedChannelService, + DesignatedChannelService designatedChannelService, DiscordSocketClient discordSocketClient, IAuthorizationService authorizationService, IModerationService moderationService, IMessageContentPatternService messageContentPatternService) @@ -91,7 +91,7 @@ private async Task TryCheckMessageAsync(IMessage message) return; } - if (await _designatedChannelService.ChannelHasDesignationAsync(channel.Guild.Id, + if (await _designatedChannelService.ChannelHasDesignation(channel.Guild.Id, channel.Id, DesignatedChannelType.Unmoderated, default)) { return; diff --git a/src/Modix.Services/Moderation/ModerationService.cs b/src/Modix.Services/Moderation/ModerationService.cs index 93efa6a26..006f712fa 100644 --- a/src/Modix.Services/Moderation/ModerationService.cs +++ b/src/Modix.Services/Moderation/ModerationService.cs @@ -82,7 +82,7 @@ public class ModerationService : IModerationService private readonly IDeletedMessageRepository _deletedMessageRepository; private readonly IDeletedMessageBatchRepository _deletedMessageBatchRepository; private readonly IRoleService _roleService; - private readonly IDesignatedChannelService _designatedChannelService; + private readonly DesignatedChannelService _designatedChannelService; // TODO: Push this to a bot-wide config? Or maybe on a per-guild basis, but with a bot-wide default, that's pulled from config? private const string MuteRoleName = "MODiX_Moderation_Mute"; @@ -100,7 +100,7 @@ public ModerationService( IDeletedMessageRepository deletedMessageRepository, IDeletedMessageBatchRepository deletedMessageBatchRepository, IRoleService roleService, - IDesignatedChannelService designatedChannelService) + DesignatedChannelService designatedChannelService) { _discordClient = discordClient; _authorizationService = authorizationService; @@ -128,7 +128,7 @@ private async Task SetUpMuteRole(IGuild guild) var muteRole = await GetOrCreateDesignatedMuteRoleAsync(guild, _authorizationService.CurrentUserId!.Value); var unmoderatedChannels = - await _designatedChannelService.GetDesignatedChannelIdsAsync(guild.Id, + await _designatedChannelService.GetDesignatedChannelIds(guild.Id, DesignatedChannelType.Unmoderated); var nonCategoryChannels = @@ -169,7 +169,7 @@ public async Task AutoConfigureChannelAsync(IChannel channel) if (channel is IGuildChannel guildChannel) { - var isUnmoderated = await _designatedChannelService.ChannelHasDesignationAsync(guildChannel.Guild.Id, + var isUnmoderated = await _designatedChannelService.ChannelHasDesignation(guildChannel.Guild.Id, channel.Id, DesignatedChannelType.Unmoderated, default); if (isUnmoderated) diff --git a/src/Modix.Services/Starboard/StarboardService.cs b/src/Modix.Services/Starboard/StarboardService.cs index 986c43036..1bb97abf0 100644 --- a/src/Modix.Services/Starboard/StarboardService.cs +++ b/src/Modix.Services/Starboard/StarboardService.cs @@ -80,7 +80,7 @@ public interface IStarboardService /// public class StarboardService : IStarboardService { - private readonly IDesignatedChannelService _designatedChannelService; + private readonly DesignatedChannelService _designatedChannelService; private readonly IMessageRepository _messageRepository; private static readonly IReadOnlyDictionary _emojis = new Dictionary { @@ -91,7 +91,7 @@ public class StarboardService : IStarboardService }.OrderByDescending(k => k.Key).ToDictionary(k => k.Key, k => k.Value); public StarboardService( - IDesignatedChannelService designatedChannelService, + DesignatedChannelService designatedChannelService, IMessageRepository messageRepository) { _designatedChannelService = designatedChannelService; @@ -115,7 +115,7 @@ private async Task GetStarboardEntry(IGuild guild, IMessage messag private async Task GetStarboardChannel(IGuild guild) { var starboardChannels = await _designatedChannelService - .GetDesignatedChannelsAsync(guild, DesignatedChannelType.Starboard); + .GetDesignatedChannels(guild, DesignatedChannelType.Starboard); return starboardChannels.First() as ITextChannel; } diff --git a/src/Modix.Web/Components/Configuration/Channels.razor b/src/Modix.Web/Components/Configuration/Channels.razor index 2a57d855b..c87131dd5 100644 --- a/src/Modix.Web/Components/Configuration/Channels.razor +++ b/src/Modix.Web/Components/Configuration/Channels.razor @@ -93,7 +93,7 @@ public DiscordHelper DiscordHelper { get; set; } = null!; [Inject] - public IDesignatedChannelService DesignatedChannelService { get; set; } = null!; + public DesignatedChannelService DesignatedChannelService { get; set; } = null!; [Inject] public ISnackbar Snackbar { get; set; } = null!; @@ -111,7 +111,7 @@ return; var currentGuild = DiscordHelper.GetUserGuild(); - var designatedChannels = await DesignatedChannelService.GetDesignatedChannelsAsync(currentGuild.Id); + var designatedChannels = await DesignatedChannelService.GetDesignatedChannels(currentGuild.Id); DesignatedChannelMappings = designatedChannels .Select(d => new DesignatedChannelData(d.Id, d.Channel.Id, d.Type, currentGuild?.GetChannel(d.Channel.Id)?.Name ?? d.Channel.Name)) @@ -143,7 +143,7 @@ var currentGuild = DiscordHelper.GetUserGuild(); var channel = (Discord.IMessageChannel)currentGuild.GetChannel(_selectedChannel!.Id); - var id = await DesignatedChannelService.AddDesignatedChannelAsync(currentGuild, channel, _selectedDesignatedChannelType!.Value); + var id = await DesignatedChannelService.AddDesignatedChannel(currentGuild, channel, _selectedDesignatedChannelType!.Value); _createDialogVisible = false; @@ -159,7 +159,7 @@ public async Task RemoveDesignation(long id, DesignatedChannelType designatedChannelType) { - await DesignatedChannelService.RemoveDesignatedChannelByIdAsync(id); + await DesignatedChannelService.RemoveDesignatedChannelById(id); var channelMappingsWithType = DesignatedChannelMappings![designatedChannelType]; var removedChannelMapping = channelMappingsWithType.First(x => x.Id == id); diff --git a/src/Modix/Extensions/ServiceCollectionExtensions.cs b/src/Modix/Extensions/ServiceCollectionExtensions.cs index 545105f44..76bc0e95a 100644 --- a/src/Modix/Extensions/ServiceCollectionExtensions.cs +++ b/src/Modix/Extensions/ServiceCollectionExtensions.cs @@ -161,6 +161,7 @@ public static IServiceCollection AddModix( services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddMemoryCache(); diff --git a/src/Modix/appsettings.Development.json b/src/Modix/appsettings.Development.json index 86d7fc55e..9d308bb1a 100644 --- a/src/Modix/appsettings.Development.json +++ b/src/Modix/appsettings.Development.json @@ -6,5 +6,6 @@ "Microsoft": "Information" } }, - "DbConnection": "Host=127.0.0.1;Username=postgres;Password=password;Database=MODiX-Dev" -} + "DbConnection": "Host=127.0.0.1;Username=postgres;Password=password;Database=MODiX-Dev", + "WebsiteBaseUrl": "https://localhost:5000/" +} \ No newline at end of file diff --git a/test/Modix.Data.Test/Repositories/DesignatedChannelMappingRepositoryTests.cs b/test/Modix.Data.Test/Repositories/DesignatedChannelMappingRepositoryTests.cs deleted file mode 100644 index 27413a043..000000000 --- a/test/Modix.Data.Test/Repositories/DesignatedChannelMappingRepositoryTests.cs +++ /dev/null @@ -1,582 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -using Microsoft.EntityFrameworkCore.Infrastructure; - -using Modix.Data.Models.Core; -using Modix.Data.Repositories; -using Modix.Data.Test.TestData; - -using NSubstitute; -using NUnit.Framework; - -using Shouldly; - -namespace Modix.Data.Test.Repositories -{ - public class DesignatedChannelMappingRepositoryTests - { - #region Test Context - - private static (ModixContext, DesignatedChannelMappingRepository) BuildTestContext() - { - var modixContext = TestDataContextFactory.BuildTestDataContext(x => - { - x.Set().AddRange(Users.Entities.Clone()); - x.Set().AddRange(GuildUsers.Entities.Clone()); - x.Set().AddRange(GuildChannels.Entities.Clone()); - x.Set().AddRange(DesignatedChannelMappings.Entities.Clone()); - x.Set().AddRange(ConfigurationActions.Entities.Where(y => !(y.DesignatedChannelMappingId is null)).Clone()); - }); - - var uut = new DesignatedChannelMappingRepository(modixContext); - - return (modixContext, uut); - } - - #endregion Test Context - - #region Constructor() Tests - - [Test] - public void Constructor_Always_InvokesBaseConstructor() - { - (var modixContext, var uut) = BuildTestContext(); - - uut.ModixContext.ShouldBeSameAs(modixContext); - } - - #endregion Constructor() Tests - - #region BeginCreateTransactionAsync() Tests - - [Test] - [NonParallelizable] - public async Task BeginCreateTransactionAsync_CreateTransactionIsInProgress_WaitsForCompletion() - { - (var modixContext, var uut) = BuildTestContext(); - - var existingTransaction = await uut.BeginCreateTransactionAsync(); - - var result = uut.BeginCreateTransactionAsync(); - - result.IsCompleted.ShouldBeFalse(); - - existingTransaction.Dispose(); - (await result).Dispose(); - } - - [Test] - [NonParallelizable] - public async Task BeginCreateTransactionAsync_CreateTransactionIsNotInProgress_ReturnsImmediately() - { - (var modixContext, var uut) = BuildTestContext(); - - var result = uut.BeginCreateTransactionAsync(); - - result.IsCompleted.ShouldBeTrue(); - - (await result).Dispose(); - } - - [Test] - [NonParallelizable] - public async Task BeginCreateTransactionAsync_DeleteTransactionIsInProgress_ReturnsImmediately() - { - (var modixContext, var uut) = BuildTestContext(); - - var deleteTransaction = await uut.BeginDeleteTransactionAsync(); - - var result = uut.BeginCreateTransactionAsync(); - - result.IsCompleted.ShouldBeTrue(); - - deleteTransaction.Dispose(); - (await result).Dispose(); - } - - [Test] - [NonParallelizable] - public async Task BeginCreateTransactionAsync_Always_TransactionIsForContextDatabase() - { - (var modixContext, var uut) = BuildTestContext(); - - var database = Substitute.ForPartsOf(modixContext); - modixContext.Database.Returns(database); - - using (var transaction = await uut.BeginCreateTransactionAsync()) { } - - await database.ShouldHaveReceived(1) - .BeginTransactionAsync(); - } - - #endregion BeginCreateTransactionAsync() Tests - - #region BeginDeleteTransactionAsync() Tests - - [Test] - [NonParallelizable] - public async Task BeginDeleteTransactionAsync_DeleteTransactionIsInProgress_WaitsForCompletion() - { - (var modixContext, var uut) = BuildTestContext(); - - var existingTransaction = await uut.BeginDeleteTransactionAsync(); - - var result = uut.BeginDeleteTransactionAsync(); - - result.IsCompleted.ShouldBeFalse(); - - existingTransaction.Dispose(); - (await result).Dispose(); - } - - [Test] - [NonParallelizable] - public async Task BeginDeleteTransactionAsync_DeleteTransactionIsNotInProgress_ReturnsImmediately() - { - (var modixContext, var uut) = BuildTestContext(); - - var result = uut.BeginDeleteTransactionAsync(); - - result.IsCompleted.ShouldBeTrue(); - - (await result).Dispose(); - } - - [Test] - [NonParallelizable] - public async Task BeginDeleteTransactionAsync_CreateTransactionIsInProgress_ReturnsImmediately() - { - (var modixContext, var uut) = BuildTestContext(); - - var createTransaction = await uut.BeginCreateTransactionAsync(); - - var result = uut.BeginDeleteTransactionAsync(); - - result.IsCompleted.ShouldBeTrue(); - - createTransaction.Dispose(); - (await result).Dispose(); - } - - [Test] - [NonParallelizable] - public async Task BeginDeleteTransactionAsync_Always_TransactionIsForContextDatabase() - { - (var modixContext, var uut) = BuildTestContext(); - - var database = Substitute.ForPartsOf(modixContext); - modixContext.Database.Returns(database); - - using (var transaction = await uut.BeginDeleteTransactionAsync()) { } - - await database.ShouldHaveReceived(1) - .BeginTransactionAsync(); - } - - #endregion BeginDeleteTransactionAsync() Tests - - #region CreateAsync() Tests - - [Test] - public async Task CreateAsync_DataIsNull_DoesNotUpdateDesignatedChannelMappingsAndThrowsException() - { - (var modixContext, var uut) = BuildTestContext(); - - await Should.ThrowAsync(uut.CreateAsync(null!)); - - modixContext.Set() - .AsQueryable() - .Select(x => x.Id) - .ShouldBe(DesignatedChannelMappings.Entities.Select(x => x.Id)); - - modixContext.Set().EachShould(x => x.ShouldNotHaveChanged()); - - await modixContext.ShouldNotHaveReceived() - .SaveChangesAsync(); - } - - [TestCaseSource(nameof(DesignatedChannelMappingCreationTestCases))] - public async Task CreateAsync_DataIsNotNull_InsertsDesignatedChannelMapping(DesignatedChannelMappingCreationData data) - { - (var modixContext, var uut) = BuildTestContext(); - - var id = await uut.CreateAsync(data); - - modixContext.Set().ShouldContain(x => x.Id == id); - var designatedChannelMapping = modixContext.Set().First(x => x.Id == id); - - designatedChannelMapping.GuildId.ShouldBe(data.GuildId); - designatedChannelMapping.Type.ShouldBe(data.Type); - designatedChannelMapping.ChannelId.ShouldBe(data.ChannelId); - designatedChannelMapping.DeleteActionId.ShouldBeNull(); - - modixContext.Set().Where(x => x.Id != designatedChannelMapping.Id).Select(x => x.Id).ShouldBe(DesignatedChannelMappings.Entities.Select(x => x.Id)); - modixContext.Set().Where(x => x.Id != designatedChannelMapping.Id).EachShould(x => x.ShouldNotHaveChanged()); - - modixContext.Set().ShouldContain(x => x.Id == designatedChannelMapping.CreateActionId); - var createAction = modixContext.Set().First(x => x.Id == designatedChannelMapping.CreateActionId); - - createAction.GuildId.ShouldBe(data.GuildId); - createAction.Type.ShouldBe(ConfigurationActionType.DesignatedChannelMappingCreated); - createAction.Created.ShouldBeInRange( - DateTimeOffset.UtcNow - TimeSpan.FromSeconds(1), - DateTimeOffset.UtcNow + TimeSpan.FromSeconds(1)); - createAction.CreatedById.ShouldBe(data.CreatedById); - createAction.ClaimMappingId.ShouldBeNull(); - createAction.DesignatedChannelMappingId.ShouldBe(designatedChannelMapping.Id); - createAction.DesignatedRoleMappingId.ShouldBeNull(); - - modixContext.Set().Where(x => x.Id != createAction.Id).Select(x => x.Id).ShouldBe(ConfigurationActions.Entities.Where(x => !(x.DesignatedChannelMappingId is null)).Select(x => x.Id)); - modixContext.Set().Where(x => x.Id != createAction.Id).EachShould(x => x.ShouldNotHaveChanged()); - - await modixContext.ShouldHaveReceived(2) - .SaveChangesAsync(); - } - - #endregion CreateAsync() Tests - - #region AnyAsync() Tests - - [TestCaseSource(nameof(ValidSearchCriteriaTestCases))] - public async Task AnyAsync_DesignatedChannelMappingsExist_ReturnsTrue(DesignatedChannelMappingSearchCriteria criteria) - { - (var modixContext, var uut) = BuildTestContext(); - - var result = await uut.AnyAsync(criteria, default); - - result.ShouldBeTrue(); - } - - [TestCaseSource(nameof(InvalidSearchCriteriaTestCases))] - public async Task AnyAsync_DesignatedChannelMappingsDoNotExist_ReturnsFalse(DesignatedChannelMappingSearchCriteria criteria) - { - (var modixContext, var uut) = BuildTestContext(); - - var result = await uut.AnyAsync(criteria, default); - - result.ShouldBeFalse(); - } - - #endregion AnyAsync() Tests - - #region SearchChannelIdsAsync() Tests - - [TestCaseSource(nameof(ValidSearchCriteriaAndResultIdsTestCases))] - public async Task SearchChannelIdsAsync_DesignatedChannelMappingsExist_ReturnsMatchingIds(DesignatedChannelMappingSearchCriteria criteria, long[] resultIds) - { - (var modixContext, var uut) = BuildTestContext(); - - var result = await uut.SearchChannelIdsAsync(criteria); - - result.ShouldNotBeNull(); - result.ShouldBe(resultIds.Select(x => DesignatedChannelMappings.Entities.First(y => y.Id == x).ChannelId)); - } - - [TestCaseSource(nameof(InvalidSearchCriteriaTestCases))] - public async Task SearchChannelIdsAsync_DesignatedChannelMappingsDoNotExist_ReturnsEmpty(DesignatedChannelMappingSearchCriteria criteria) - { - (var modixContext, var uut) = BuildTestContext(); - - var result = await uut.SearchChannelIdsAsync(criteria); - - result.ShouldNotBeNull(); - result.ShouldBeEmpty(); - } - - #endregion SearchChannelIdsAsync() Tests - - #region SearchBriefsAsync() Tests - - [TestCaseSource(nameof(ValidSearchCriteriaAndResultIdsTestCases))] - public async Task SearchBriefsAsync_DesignatedChannelMappingsExist_ReturnsMatchingBriefs(DesignatedChannelMappingSearchCriteria criteria, long[] resultIds) - { - (var modixContext, var uut) = BuildTestContext(); - - var result = await uut.SearchBriefsAsync(criteria); - - result.ShouldNotBeNull(); - result.Select(x => x.Id).ShouldBe(resultIds); - result.EachShould(x => x.ShouldMatchTestData()); - } - - [TestCaseSource(nameof(InvalidSearchCriteriaTestCases))] - public async Task SearchBriefsAsync_DesignatedChannelMappingsDoNotExist_ReturnsEmpty(DesignatedChannelMappingSearchCriteria criteria) - { - (var modixContext, var uut) = BuildTestContext(); - - var result = await uut.SearchBriefsAsync(criteria); - - result.ShouldNotBeNull(); - result.ShouldBeEmpty(); - } - - #endregion SearchBriefsAsync() Tests - - #region DeleteAsync() Tests - - [TestCaseSource(nameof(ValidSearchCriteriaAndValidUserIdAndResultIdsTestCases))] - public async Task DeleteAsync_DesignatedChannelMappingsAreNotDeleted_UpdatesDesignatedChannelMappingsAndReturnsCount(DesignatedChannelMappingSearchCriteria criteria, ulong deletedById, long[] designatedChannelMappingIds) - { - (var modixContext, var uut) = BuildTestContext(); - - var result = await uut.DeleteAsync(criteria, deletedById); - - result.ShouldBe(designatedChannelMappingIds - .Where(x => DesignatedChannelMappings.Entities - .Any(y => (y.Id == x) && (y.DeleteActionId == null))) - .Count()); - - modixContext.Set() - .AsQueryable() - .Select(x => x.Id) - .ShouldBe(DesignatedChannelMappings.Entities.Select(x => x.Id)); - - modixContext.Set() - .Where(x => designatedChannelMappingIds.Contains(x.Id) && (x.DeleteActionId == null)) - .EachShould(entity => - { - var originalEntity = DesignatedChannelMappings.Entities.First(x => x.Id == entity.Id); - - entity.GuildId.ShouldBe(originalEntity.GuildId); - entity.ChannelId.ShouldBe(originalEntity.ChannelId); - entity.Type.ShouldBe(originalEntity.Type); - entity.CreateActionId.ShouldBe(originalEntity.CreateActionId); - entity.DeleteActionId.ShouldNotBeNull(); - - modixContext.Set().ShouldContain(x => x.Id == entity.DeleteActionId); - var deleteAction = modixContext.Set().First(x => x.Id == entity.DeleteActionId); - - deleteAction.GuildId.ShouldBe(entity.GuildId); - deleteAction.Type.ShouldBe(ConfigurationActionType.DesignatedChannelMappingDeleted); - deleteAction.Created.ShouldBeInRange( - DateTimeOffset.UtcNow - TimeSpan.FromMinutes(1), - DateTimeOffset.UtcNow + TimeSpan.FromMinutes(1)); - deleteAction.CreatedById.ShouldBe(deletedById); - deleteAction.ClaimMappingId.ShouldBeNull(); - deleteAction.DesignatedChannelMappingId.ShouldBe(entity.Id); - deleteAction.DesignatedRoleMappingId.ShouldBeNull(); - }); - - modixContext.Set() - .AsEnumerable() - .Where(x => !designatedChannelMappingIds.Contains(x.Id) || DesignatedChannelMappings.Entities - .Any(y => (y.Id == x.Id) && (x.DeleteActionId == null))) - .EachShould(x => x.ShouldNotHaveChanged()); - - modixContext.Set() - .AsEnumerable() - .Where(x => DesignatedChannelMappings.Entities - .Any(y => (y.DeleteActionId == x.Id) && designatedChannelMappingIds.Contains(y.Id))) - .EachShould(x => x.ShouldNotHaveChanged()); - - await modixContext.ShouldHaveReceived(1) - .SaveChangesAsync(); - } - - [TestCaseSource(nameof(InvalidSearchCriteriaAndValidUserIdTestCases))] - public async Task DeleteAsync_DesignatedChannelMappingsDoNotExist_DoesNotUpdateDesignatedChannelMappingsAndReturns0(DesignatedChannelMappingSearchCriteria criteria, ulong deletedById) - { - (var modixContext, var uut) = BuildTestContext(); - - var result = await uut.DeleteAsync(criteria, deletedById); - - result.ShouldBe(0); - - modixContext.Set() - .AsQueryable() - .Select(x => x.Id) - .ShouldBe(DesignatedChannelMappings.Entities - .Select(x => x.Id)); - - modixContext.Set() - .EachShould(x => x.ShouldNotHaveChanged()); - - modixContext.Set() - .AsQueryable() - .Select(x => x.Id) - .ShouldBe(ConfigurationActions.Entities - .Where(x => x.DesignatedChannelMappingId != null) - .Select(x => x.Id)); - - modixContext.Set() - .EachShould(x => x.ShouldNotHaveChanged()); - - await modixContext.ShouldHaveReceived(1) - .SaveChangesAsync(); - } - - #endregion DeleteAsync() Tests - - #region TryDeleteAsync() Tests - - [TestCaseSource(nameof(ActiveDesignatedChannelMappingAndValidUserIdTestCases))] - public async Task TryDeleteAsync_DesignatedChannelMappingIsNotDeleted_UpdatesDesignatedChannelMappingAndReturnsTrue(long designatedChannelMappingId, ulong deletedById) - { - (var modixContext, var uut) = BuildTestContext(); - - var result = await uut.TryDeleteAsync(designatedChannelMappingId, deletedById); - - result.ShouldBeTrue(); - - modixContext.Set() - .ShouldContain(x => x.Id == designatedChannelMappingId); - var designatedChannelMapping = modixContext.Set() - .First(x => x.Id == designatedChannelMappingId); - - var originalDesignatedChannelMapping = DesignatedChannelMappings.Entities - .First(x => x.Id == designatedChannelMappingId); - - designatedChannelMapping.GuildId.ShouldBe(originalDesignatedChannelMapping.GuildId); - designatedChannelMapping.ChannelId.ShouldBe(originalDesignatedChannelMapping.ChannelId); - designatedChannelMapping.Type.ShouldBe(originalDesignatedChannelMapping.Type); - designatedChannelMapping.CreateActionId.ShouldBe(originalDesignatedChannelMapping.CreateActionId); - designatedChannelMapping.DeleteActionId.ShouldNotBeNull(); - - modixContext.Set() - .AsQueryable() - .Select(x => x.Id) - .ShouldBe(DesignatedChannelMappings.Entities - .Select(x => x.Id)); - - modixContext.Set() - .Where(x => x.Id != designatedChannelMappingId) - .EachShould(x => x.ShouldNotHaveChanged()); - - modixContext.Set() - .ShouldContain(x => x.Id == designatedChannelMapping.DeleteActionId); - var deleteAction = modixContext.Set() - .First(x => x.Id == designatedChannelMapping.DeleteActionId); - - deleteAction.GuildId.ShouldBe(designatedChannelMapping.GuildId); - deleteAction.Type.ShouldBe(ConfigurationActionType.DesignatedChannelMappingDeleted); - deleteAction.Created.ShouldBeInRange( - DateTimeOffset.UtcNow - TimeSpan.FromSeconds(1), - DateTimeOffset.UtcNow + TimeSpan.FromSeconds(1)); - deleteAction.CreatedById.ShouldBe(deletedById); - deleteAction.ClaimMappingId.ShouldBeNull(); - deleteAction.DesignatedChannelMappingId.ShouldBe(designatedChannelMapping.Id); - deleteAction.DesignatedRoleMappingId.ShouldBeNull(); - - modixContext.Set() - .Where(x => x.Id != deleteAction.Id) - .Select(x => x.Id) - .ShouldBe(ConfigurationActions.Entities - .Where(x => !(x.DesignatedChannelMappingId is null)) - .Select(x => x.Id)); - - modixContext.Set() - .Where(x => x.Id != deleteAction.Id) - .EachShould(x => x.ShouldNotHaveChanged()); - - await modixContext.ShouldHaveReceived(1) - .SaveChangesAsync(); - } - - [TestCaseSource(nameof(DeletedDesignatedChannelMappingAndValidUserIdTestCases))] - [TestCaseSource(nameof(InvalidDesignatedChannelMappingAndValidUserIdTestCases))] - public async Task TryDeleteAsync_DesignatedChannelMappingIsDeleted_DoesNotUpdateDesignatedChannelMappingsAndReturnsFalse(long designatedChannelMappingId, ulong deletedById) - { - (var modixContext, var uut) = BuildTestContext(); - - var result = await uut.TryDeleteAsync(designatedChannelMappingId, deletedById); - - result.ShouldBeFalse(); - - modixContext.Set() - .AsQueryable() - .Select(x => x.Id) - .ShouldBe(DesignatedChannelMappings.Entities - .Select(x => x.Id)); - - modixContext.Set() - .EachShould(x => x.ShouldNotHaveChanged()); - - modixContext.Set() - .AsQueryable() - .Select(x => x.Id) - .ShouldBe(ConfigurationActions.Entities - .Where(x => !(x.DesignatedChannelMappingId is null)) - .Select(x => x.Id)); - - modixContext.Set() - .EachShould(x => x.ShouldNotHaveChanged()); - - await modixContext.ShouldNotHaveReceived() - .SaveChangesAsync(); - } - - #endregion TryDeleteAsync() Tests - - #region Test Data - - public static readonly IEnumerable DesignatedChannelMappingCreationTestCases - = DesignatedChannelMappings.Creations - .Select(x => new TestCaseData(x) - .SetName($"{{m}}({x.GuildId}, {x.ChannelId}, {x.Type})")); - - public static readonly IEnumerable ValidSearchCriteriaTestCases - = DesignatedChannelMappings.Searches - .Where(x => x.resultIds.Any()) - .Select(x => new TestCaseData(x.criteria) - .SetName($"{{m}}({x.name})")); - - public static readonly IEnumerable ValidSearchCriteriaAndResultIdsTestCases - = DesignatedChannelMappings.Searches - .Where(x => x.resultIds.Any()) - .Select(x => new TestCaseData(x.criteria, x.resultIds) - .SetName($"{{m}}({x.name})")); - - public static readonly IEnumerable ValidSearchCriteriaAndValidUserIdAndResultIdsTestCases - = DesignatedChannelMappings.Searches - .Where(x => x.resultIds.Any()) - .SelectMany(x => Users.Entities - .Where(y => DesignatedChannelMappings.Entities - .Where(z => x.resultIds.Contains(z.Id)) - .Select(z => z.GuildId) - .Distinct() - .All(z => GuildUsers.Entities - .Any(w => (w.UserId == y.Id) && (w.GuildId == z)))) - .Select(y => new TestCaseData(x.criteria, y.Id, x.resultIds) - .SetName($"{{m}}(\"{x.name}\", {y.Id})"))); - - public static readonly IEnumerable InvalidSearchCriteriaTestCases - = DesignatedChannelMappings.Searches - .Where(x => !x.resultIds.Any()) - .Select(x => new TestCaseData(x.criteria) - .SetName($"{{m}}({x.name})")); - - public static readonly IEnumerable InvalidSearchCriteriaAndValidUserIdTestCases - = DesignatedChannelMappings.Searches - .Where(x => !x.resultIds.Any()) - .SelectMany(x => Users.Entities - .Select(y => new TestCaseData(x.criteria, y.Id) - .SetName($"{{m}}(\"{x.name}\", {y.Id})"))); - - public static readonly IEnumerable ActiveDesignatedChannelMappingAndValidUserIdTestCases - = DesignatedChannelMappings.Entities - .Where(x => x.DeleteActionId is null) - .SelectMany(x => GuildUsers.Entities - .Where(y => y.GuildId == x.GuildId) - .Select(y => new TestCaseData(x.Id, y.UserId))); - - public static readonly IEnumerable DeletedDesignatedChannelMappingAndValidUserIdTestCases - = DesignatedChannelMappings.Entities - .Where(x => !(x.DeleteActionId is null)) - .SelectMany(x => GuildUsers.Entities - .Where(y => y.GuildId == x.GuildId) - .Select(y => new TestCaseData(x.Id, y.UserId))); - - public static readonly IEnumerable InvalidDesignatedChannelMappingAndValidUserIdTestCases - = Enumerable.Empty() - .Append(DesignatedChannelMappings.Entities - .Select(x => x.Id) - .Max() + 1) - .SelectMany(x => Users.Entities - .Select(y => new TestCaseData(x, y.Id))); - - #endregion Test Data - } -} From f1daedeb15c233ab52bf755d259827b6dd717fd2 Mon Sep 17 00:00:00 2001 From: Patrick Klaeren Date: Wed, 13 Nov 2024 22:34:06 +0000 Subject: [PATCH 2/2] Move service --- ...nelDesignationsModule.cs => DesignatedChannelsModule.cs} | 6 +++--- src/Modix.Bot/Responders/StarboardReactionResponder.cs | 2 +- src/Modix.Services/{Core => }/DesignatedChannelService.cs | 3 ++- src/Modix.Web/Components/Configuration/Channels.razor | 4 +--- 4 files changed, 7 insertions(+), 8 deletions(-) rename src/Modix.Bot/Modules/{ChannelDesignationsModule.cs => DesignatedChannelsModule.cs} (94%) rename src/Modix.Services/{Core => }/DesignatedChannelService.cs (99%) diff --git a/src/Modix.Bot/Modules/ChannelDesignationsModule.cs b/src/Modix.Bot/Modules/DesignatedChannelsModule.cs similarity index 94% rename from src/Modix.Bot/Modules/ChannelDesignationsModule.cs rename to src/Modix.Bot/Modules/DesignatedChannelsModule.cs index 901d7cff9..2b49c92b2 100644 --- a/src/Modix.Bot/Modules/ChannelDesignationsModule.cs +++ b/src/Modix.Bot/Modules/DesignatedChannelsModule.cs @@ -9,20 +9,20 @@ using Modix.Bot.Preconditions; using Modix.Common.Extensions; using Modix.Data.Models.Core; +using Modix.Services; using Modix.Services.CommandHelp; -using Modix.Services.Core; namespace Modix.Bot.Modules { [ModuleHelp("Channel Designations", "Configures channel designation for various bot services.")] [Group("channel-designations", "Configures channel designation for various bot services.")] [DefaultMemberPermissions(GuildPermission.BanMembers)] - public class ChannelDesignationsModule : InteractionModuleBase + public class DesignatedChannelsModule : InteractionModuleBase { private readonly DesignatedChannelService _designatedChannelService; private readonly ModixConfig _config; - public ChannelDesignationsModule(DesignatedChannelService designatedChannelService, IOptions config) + public DesignatedChannelsModule(DesignatedChannelService designatedChannelService, IOptions config) { _designatedChannelService = designatedChannelService; _config = config.Value; diff --git a/src/Modix.Bot/Responders/StarboardReactionResponder.cs b/src/Modix.Bot/Responders/StarboardReactionResponder.cs index 76f1bf549..79501474c 100644 --- a/src/Modix.Bot/Responders/StarboardReactionResponder.cs +++ b/src/Modix.Bot/Responders/StarboardReactionResponder.cs @@ -5,7 +5,7 @@ using Modix.Bot.Notifications; using Modix.Bot.Responders.MessageQuotes; using Modix.Data.Models.Core; -using Modix.Services.Core; +using Modix.Services; using Modix.Services.Starboard; using Modix.Services.Utilities; diff --git a/src/Modix.Services/Core/DesignatedChannelService.cs b/src/Modix.Services/DesignatedChannelService.cs similarity index 99% rename from src/Modix.Services/Core/DesignatedChannelService.cs rename to src/Modix.Services/DesignatedChannelService.cs index 380c01c92..0343d33d8 100644 --- a/src/Modix.Services/Core/DesignatedChannelService.cs +++ b/src/Modix.Services/DesignatedChannelService.cs @@ -7,8 +7,9 @@ using Microsoft.EntityFrameworkCore; using Modix.Data; using Modix.Data.Models.Core; +using Modix.Services.Core; -namespace Modix.Services.Core; +namespace Modix.Services; public class DesignatedChannelService( ModixContext db, diff --git a/src/Modix.Web/Components/Configuration/Channels.razor b/src/Modix.Web/Components/Configuration/Channels.razor index c87131dd5..f9521657a 100644 --- a/src/Modix.Web/Components/Configuration/Channels.razor +++ b/src/Modix.Web/Components/Configuration/Channels.razor @@ -1,12 +1,10 @@ @using Modix.Data.Models.Core; -@using Modix.Services.Core; @using Modix.Web.Models.Common; @using Modix.Web.Models.Configuration; -@using Modix.Web.Models.UserLookup; @using Modix.Web.Services; @using MudBlazor @using Humanizer; -@using System.Security.Claims; +@using Modix.Services Modix - Channels Channel Designations