Skip to content

Commit 063417e

Browse files
authoredNov 13, 2024··
Merge pull request #1038 from discord-csharp/channel-designations-refactor
V3: Refactor channel designations, add relay service
2 parents 03e7b69 + f1daede commit 063417e

23 files changed

+406
-1278
lines changed
 

‎src/Modix.Bot/Behaviors/ModerationLoggingBehavior.cs

+14-9
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,14 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Threading.Tasks;
5-
65
using Discord;
7-
86
using Microsoft.Extensions.DependencyInjection;
97
using Microsoft.Extensions.Options;
10-
11-
using Modix.Bot.Extensions;
128
using Modix.Common.Extensions;
139
using Modix.Data.Models.Core;
1410
using Modix.Data.Models.Moderation;
1511
using Modix.Data.Repositories;
12+
using Modix.Services;
1613
using Modix.Services.Core;
1714
using Modix.Services.Moderation;
1815
using Modix.Services.Utilities;
@@ -24,15 +21,19 @@ namespace Modix.Behaviors
2421
/// </summary>
2522
public class ModerationLoggingBehavior : IModerationActionEventHandler
2623
{
24+
private readonly DiscordRelayService _discordRelayService;
25+
2726
/// <summary>
2827
/// Constructs a new <see cref="ModerationLoggingBehavior"/> object, with injected dependencies.
2928
/// </summary>
3029
public ModerationLoggingBehavior(
3130
IServiceProvider serviceProvider,
3231
IDiscordClient discordClient,
33-
IDesignatedChannelService designatedChannelService,
32+
DesignatedChannelService designatedChannelService,
33+
DiscordRelayService discordRelayService,
3434
IOptions<ModixConfig> config)
3535
{
36+
_discordRelayService = discordRelayService;
3637
DiscordClient = discordClient;
3738
DesignatedChannelService = designatedChannelService;
3839
Config = config.Value;
@@ -43,7 +44,9 @@ public ModerationLoggingBehavior(
4344
/// <inheritdoc />
4445
public async Task OnModerationActionCreatedAsync(long moderationActionId, ModerationActionCreationData data)
4546
{
46-
if (!await DesignatedChannelService.AnyDesignatedChannelAsync(data.GuildId, DesignatedChannelType.ModerationLog))
47+
var designatedChannels = await DesignatedChannelService.GetDesignatedChannelIds(data.GuildId, DesignatedChannelType.ModerationLog);
48+
49+
if (!designatedChannels.Any())
4750
return;
4851

4952
var moderationAction = await ModerationService.GetModerationActionSummaryAsync(moderationActionId);
@@ -73,8 +76,10 @@ public async Task OnModerationActionCreatedAsync(long moderationActionId, Modera
7376
moderationAction.OriginalInfractionReason,
7477
string.IsNullOrEmpty(moderationAction.Infraction?.RescindReason) ? "" : $"for reason: ```\n{moderationAction.Infraction?.RescindReason}```");
7578

76-
await DesignatedChannelService.SendToDesignatedChannelsAsync(
77-
await DiscordClient.GetGuildAsync(data.GuildId), DesignatedChannelType.ModerationLog, message);
79+
foreach (var channel in designatedChannels)
80+
{
81+
await _discordRelayService.SendMessageToChannel(channel, message);
82+
}
7883
}
7984

8085
/// <summary>
@@ -85,7 +90,7 @@ await DesignatedChannelService.SendToDesignatedChannelsAsync(
8590
/// <summary>
8691
/// An <see cref="IDesignatedChannelService"/> for logging moderation actions.
8792
/// </summary>
88-
internal protected IDesignatedChannelService DesignatedChannelService { get; }
93+
internal protected DesignatedChannelService DesignatedChannelService { get; }
8994

9095
/// <summary>
9196
/// An <see cref="IModerationService"/> for performing moderation actions.

‎src/Modix.Bot/Behaviors/PromotionLoggingHandler.cs

+23-8
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Modix.Common.Messaging;
1313
using Modix.Data.Models.Core;
1414
using Modix.Data.Models.Promotions;
15+
using Modix.Services;
1516
using Modix.Services.Core;
1617
using Modix.Services.Promotions;
1718
using Modix.Services.Utilities;
@@ -24,17 +25,21 @@ namespace Modix.Behaviors
2425
public class PromotionLoggingHandler :
2526
INotificationHandler<PromotionActionCreatedNotification>
2627
{
28+
private readonly DiscordRelayService _discordRelayService;
29+
2730
/// <summary>
2831
/// Constructs a new <see cref="PromotionLoggingHandler"/> object, with injected dependencies.
2932
/// </summary>
3033
public PromotionLoggingHandler(
3134
IAuthorizationService authorizationService,
3235
DiscordSocketClient discordSocketClient,
33-
IDesignatedChannelService designatedChannelService,
36+
DesignatedChannelService designatedChannelService,
3437
IUserService userService,
3538
IPromotionsService promotionsService,
39+
DiscordRelayService discordRelayService,
3640
IOptions<ModixConfig> modixConfig)
3741
{
42+
_discordRelayService = discordRelayService;
3843
AuthorizationService = authorizationService;
3944
DiscordSocketClient = discordSocketClient;
4045
DesignatedChannelService = designatedChannelService;
@@ -49,26 +54,36 @@ public async Task HandleNotificationAsync(PromotionActionCreatedNotification not
4954
if (AuthorizationService.CurrentUserId is null)
5055
await AuthorizationService.OnAuthenticatedAsync(DiscordSocketClient.CurrentUser);
5156

52-
if (await DesignatedChannelService.AnyDesignatedChannelAsync(notification.Data.GuildId, DesignatedChannelType.PromotionLog))
57+
if (await DesignatedChannelService.HasDesignatedChannelForType(notification.Data.GuildId, DesignatedChannelType.PromotionLog))
5358
{
5459
var message = await FormatPromotionLogEntryAsync(notification.Id);
5560

5661
if (message == null)
5762
return;
5863

59-
await DesignatedChannelService.SendToDesignatedChannelsAsync(
60-
await DiscordSocketClient.GetGuildAsync(notification.Data.GuildId), DesignatedChannelType.PromotionLog, message);
64+
var designatedChannels = await DesignatedChannelService.GetDesignatedChannelIds(notification.Data.GuildId,
65+
DesignatedChannelType.PromotionLog);
66+
67+
foreach (var channel in designatedChannels)
68+
{
69+
await _discordRelayService.SendMessageToChannel(channel, message);
70+
}
6171
}
6272

63-
if (await DesignatedChannelService.AnyDesignatedChannelAsync(notification.Data.GuildId, DesignatedChannelType.PromotionNotifications))
73+
if (await DesignatedChannelService.HasDesignatedChannelForType(notification.Data.GuildId, DesignatedChannelType.PromotionNotifications))
6474
{
6575
var embed = await FormatPromotionNotificationAsync(notification.Id, notification.Data);
6676

6777
if (embed == null)
6878
return;
6979

70-
await DesignatedChannelService.SendToDesignatedChannelsAsync(
71-
await DiscordSocketClient.GetGuildAsync(notification.Data.GuildId), DesignatedChannelType.PromotionNotifications, "", embed);
80+
var designatedChannels = await DesignatedChannelService.GetDesignatedChannelIds(notification.Data.GuildId,
81+
DesignatedChannelType.PromotionNotifications);
82+
83+
foreach (var channel in designatedChannels)
84+
{
85+
await _discordRelayService.SendMessageToChannel(channel, string.Empty, embed);
86+
}
7287
}
7388
}
7489

@@ -144,7 +159,7 @@ private async Task<string> FormatPromotionLogEntryAsync(long promotionActionId)
144159
/// <summary>
145160
/// An <see cref="IDesignatedChannelService"/> for logging moderation actions.
146161
/// </summary>
147-
internal protected IDesignatedChannelService DesignatedChannelService { get; }
162+
internal protected DesignatedChannelService DesignatedChannelService { get; }
148163

149164
/// <summary>
150165
/// An <see cref="IUserService"/> for retrieving user info
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System.Threading;
2+
using System.Threading.Tasks;
3+
using Discord;
4+
using Discord.WebSocket;
5+
using MediatR;
6+
using Modix.Services;
7+
8+
namespace Modix.Bot.Handlers;
9+
10+
public class SendMessageHandler(DiscordSocketClient discordSocketClient)
11+
: IRequestHandler<SendMessageInDiscordRequest, bool>
12+
{
13+
public async Task<bool> Handle(SendMessageInDiscordRequest request, CancellationToken cancellationToken)
14+
{
15+
var channel = await discordSocketClient.GetChannelAsync(request.ChannelId);
16+
17+
if (channel is ITextChannel textChannel)
18+
{
19+
await textChannel.SendMessageAsync(request.Message, false, request.Embed, allowedMentions: AllowedMentions.None);
20+
return true;
21+
}
22+
23+
return false;
24+
}
25+
}

‎src/Modix.Bot/Modules/DesignatedChannelModule.cs renamed to ‎src/Modix.Bot/Modules/DesignatedChannelsModule.cs

+21-18
Original file line numberDiff line numberDiff line change
@@ -9,45 +9,48 @@
99
using Modix.Bot.Preconditions;
1010
using Modix.Common.Extensions;
1111
using Modix.Data.Models.Core;
12+
using Modix.Services;
1213
using Modix.Services.CommandHelp;
13-
using Modix.Services.Core;
1414

15-
namespace Modix.Modules
15+
namespace Modix.Bot.Modules
1616
{
1717
[ModuleHelp("Channel Designations", "Configures channel designation for various bot services.")]
1818
[Group("channel-designations", "Configures channel designation for various bot services.")]
1919
[DefaultMemberPermissions(GuildPermission.BanMembers)]
20-
public class DesignatedChannelModule : InteractionModuleBase
20+
public class DesignatedChannelsModule : InteractionModuleBase
2121
{
22-
private readonly IDesignatedChannelService _designatedChannelService;
22+
private readonly DesignatedChannelService _designatedChannelService;
2323
private readonly ModixConfig _config;
2424

25-
public DesignatedChannelModule(IDesignatedChannelService designatedChannelService, IOptions<ModixConfig> config)
25+
public DesignatedChannelsModule(DesignatedChannelService designatedChannelService, IOptions<ModixConfig> config)
2626
{
2727
_designatedChannelService = designatedChannelService;
2828
_config = config.Value;
2929
}
3030

3131
[SlashCommand("list", "Lists all of the channels designated for use by the bot.")]
3232
[RequireClaims(AuthorizationClaim.DesignatedChannelMappingRead)]
33-
public async Task ListAsync()
33+
public async Task List()
3434
{
35-
var channels = await _designatedChannelService.GetDesignatedChannelsAsync(Context.Guild.Id);
36-
37-
// https://mod.gg/config/channels
38-
var url = new UriBuilder(_config.WebsiteBaseUrl)
39-
{
40-
Path = "/config/channels"
41-
}.RemoveDefaultPort().ToString();
35+
var channels = await _designatedChannelService.GetDesignatedChannels(Context.Guild.Id);
4236

4337
var builder = new EmbedBuilder()
4438
{
4539
Title = "Assigned Channel Designations",
46-
Url = url,
4740
Color = Color.Gold,
4841
Timestamp = DateTimeOffset.UtcNow
4942
};
5043

44+
if (!string.IsNullOrWhiteSpace(_config.WebsiteBaseUrl))
45+
{
46+
var url = new UriBuilder(_config.WebsiteBaseUrl)
47+
{
48+
Path = "/config/channels"
49+
}.RemoveDefaultPort().ToString();
50+
51+
builder.Url = url;
52+
}
53+
5154
foreach (var type in Enum.GetValues<DesignatedChannelType>())
5255
{
5356
var designatedChannels = channels
@@ -69,25 +72,25 @@ public async Task ListAsync()
6972

7073
[SlashCommand("add", "Assigns a designation to the given channel.")]
7174
[RequireClaims(AuthorizationClaim.DesignatedChannelMappingCreate)]
72-
public async Task AddAsync(
75+
public async Task Add(
7376
[Summary(description: "The channel to be assigned a designation.")]
7477
IMessageChannel channel,
7578
[Summary(description: "The designation to assign.")]
7679
DesignatedChannelType designation)
7780
{
78-
await _designatedChannelService.AddDesignatedChannelAsync(Context.Guild, channel, designation);
81+
await _designatedChannelService.AddDesignatedChannel(Context.Guild, channel, designation);
7982
await Context.AddConfirmationAsync();
8083
}
8184

8285
[SlashCommand("remove", "Removes a designation from the given channel.")]
8386
[RequireClaims(AuthorizationClaim.DesignatedChannelMappingDelete)]
84-
public async Task RemoveAsync(
87+
public async Task Remove(
8588
[Summary(description: "The channel whose designation is to be unassigned.")]
8689
IMessageChannel channel,
8790
[Summary(description: "The designation to be unassigned.")]
8891
DesignatedChannelType designation)
8992
{
90-
await _designatedChannelService.RemoveDesignatedChannelAsync(Context.Guild, channel, designation);
93+
await _designatedChannelService.RemoveDesignatedChannel(Context.Guild, channel, designation);
9194
await Context.AddConfirmationAsync();
9295
}
9396
}

‎src/Modix.Bot/Modules/InfractionModule.cs

-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ public async Task SearchAsync(
8686

8787
var counts = await _moderationService.GetInfractionCountsForUserAsync(user.Id);
8888

89-
// https://modix.gg/infractions?subject=12345
9089
var url = new UriBuilder(_config.WebsiteBaseUrl)
9190
{
9291
Path = "/infractions",

‎src/Modix.Bot/Responders/StarboardReactionResponder.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
using Modix.Bot.Notifications;
66
using Modix.Bot.Responders.MessageQuotes;
77
using Modix.Data.Models.Core;
8-
using Modix.Services.Core;
8+
using Modix.Services;
99
using Modix.Services.Starboard;
1010
using Modix.Services.Utilities;
1111

1212
namespace Modix.Bot.Responders
1313
{
14-
public class StarboardReactionResponder(IStarboardService starboardService, IDesignatedChannelService designatedChannelService)
14+
public class StarboardReactionResponder(IStarboardService starboardService, DesignatedChannelService designatedChannelService)
1515
: INotificationHandler<ReactionAddedNotificationV3>, INotificationHandler<ReactionRemovedNotificationV3>
1616
{
1717
public Task Handle(ReactionAddedNotificationV3 notification, CancellationToken cancellationToken)
@@ -35,10 +35,10 @@ private async Task HandleReactionAsync(Cacheable<IUserMessage, ulong> cachedMess
3535
}
3636

3737
var isIgnoredFromStarboard = await designatedChannelService
38-
.ChannelHasDesignationAsync(channel.Guild.Id, channel.Id, DesignatedChannelType.IgnoredFromStarboard, default);
38+
.ChannelHasDesignation(channel.Guild.Id, channel.Id, DesignatedChannelType.IgnoredFromStarboard, default);
3939

4040
var starboardExists = await designatedChannelService
41-
.AnyDesignatedChannelAsync(channel.GuildId, DesignatedChannelType.Starboard);
41+
.HasDesignatedChannelForType(channel.GuildId, DesignatedChannelType.Starboard);
4242

4343
if (isIgnoredFromStarboard || !starboardExists)
4444
{

‎src/Modix.Data/Models/Core/ConfigurationActionEntity.cs

+58-98
Original file line numberDiff line numberDiff line change
@@ -5,106 +5,66 @@
55
using Microsoft.EntityFrameworkCore;
66
using Microsoft.EntityFrameworkCore.Metadata.Builders;
77

8-
namespace Modix.Data.Models.Core
8+
namespace Modix.Data.Models.Core;
9+
10+
[Table("ConfigurationActions")]
11+
public class ConfigurationActionEntity
912
{
10-
/// <summary>
11-
/// Describes an action that was performed, that somehow changed the application's configuration.
12-
/// </summary>
13-
[Table("ConfigurationActions")]
14-
public class ConfigurationActionEntity
15-
{
16-
/// <summary>
17-
/// A unique identifier for this configuration action.
18-
/// </summary>
19-
[Key, Required, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
20-
public long Id { get; set; }
21-
22-
/// <summary>
23-
/// The snowflake ID, within the Discord API, of the guild to which this configuration action applies.
24-
/// </summary>
25-
[Required]
26-
public ulong GuildId { get; set; }
27-
28-
/// <summary>
29-
/// The type of configuration action that was performed.
30-
/// </summary>
31-
[Required]
32-
public ConfigurationActionType Type { get; set; }
33-
34-
/// <summary>
35-
/// A timestamp indicating when this configuration action was performed.
36-
/// </summary>
37-
[Required]
38-
public DateTimeOffset Created { get; set; }
39-
40-
/// <summary>
41-
/// The <see cref="GuildUserEntity.UserId"/> value of <see cref="CreatedBy"/>.
42-
/// </summary>
43-
[Required]
44-
public ulong CreatedById { get; set; }
45-
46-
/// <summary>
47-
/// The Discord user that performed this action.
48-
/// </summary>
49-
[Required]
50-
public virtual GuildUserEntity CreatedBy { get; set; } = null!;
51-
52-
/// <summary>
53-
/// The <see cref="ClaimMappingEntity.Id"/> value (if any) of <see cref="ClaimMapping"/>.
54-
/// </summary>
55-
[ForeignKey(nameof(ClaimMapping))]
56-
public long? ClaimMappingId { get; set; }
57-
58-
/// <summary>
59-
/// The claim mapping that was affected by this action, if any.
60-
/// </summary>
61-
public ClaimMappingEntity? ClaimMapping { get; set; }
62-
63-
/// <summary>
64-
/// The <see cref="DesignatedChannelMappingEntity.Id"/> value (if any) of <see cref="DesignatedChannelMappingEntity"/>.
65-
/// </summary>
66-
[ForeignKey(nameof(DesignatedChannelMapping))]
67-
public long? DesignatedChannelMappingId { get; set; }
68-
69-
/// <summary>
70-
/// The designated channel mapping that was affected by this action, if any.
71-
/// </summary>
72-
public DesignatedChannelMappingEntity? DesignatedChannelMapping { get; set; }
73-
74-
/// <summary>
75-
/// The <see cref="DesignatedRoleMappingEntity.Id"/> value (if any) of <see cref="DesignatedRoleMappingEntity"/>.
76-
/// </summary>
77-
[ForeignKey(nameof(DesignatedRoleMapping))]
78-
public long? DesignatedRoleMappingId { get; set; }
79-
80-
/// <summary>
81-
/// The designated role mapping that was affected by this action, if any.
82-
/// </summary>
83-
public DesignatedRoleMappingEntity? DesignatedRoleMapping { get; set; }
84-
}
13+
[Key, Required, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
14+
public long Id { get; set; }
15+
16+
[Required]
17+
public ulong GuildId { get; set; }
18+
19+
[Required]
20+
public ConfigurationActionType Type { get; set; }
21+
22+
[Required]
23+
public DateTimeOffset Created { get; set; }
24+
25+
[Required]
26+
public ulong CreatedById { get; set; }
27+
28+
[Required]
29+
public virtual GuildUserEntity CreatedBy { get; set; } = null!;
8530

86-
public class ConfigurationActionEntityConfigurator
87-
: IEntityTypeConfiguration<ConfigurationActionEntity>
31+
[ForeignKey(nameof(ClaimMapping))]
32+
public long? ClaimMappingId { get; set; }
33+
34+
public ClaimMappingEntity? ClaimMapping { get; set; }
35+
36+
[ForeignKey(nameof(DesignatedChannelMapping))]
37+
public long? DesignatedChannelMappingId { get; set; }
38+
39+
public DesignatedChannelMappingEntity? DesignatedChannelMapping { get; set; }
40+
41+
[ForeignKey(nameof(DesignatedRoleMapping))]
42+
public long? DesignatedRoleMappingId { get; set; }
43+
44+
public DesignatedRoleMappingEntity? DesignatedRoleMapping { get; set; }
45+
}
46+
47+
public class ConfigurationActionEntityConfigurator
48+
: IEntityTypeConfiguration<ConfigurationActionEntity>
49+
{
50+
public void Configure(
51+
EntityTypeBuilder<ConfigurationActionEntity> entityTypeBuilder)
8852
{
89-
public void Configure(
90-
EntityTypeBuilder<ConfigurationActionEntity> entityTypeBuilder)
91-
{
92-
entityTypeBuilder
93-
.Property(x => x.Type)
94-
.HasConversion<string>();
95-
96-
entityTypeBuilder
97-
.Property(x => x.GuildId)
98-
.HasConversion<long>();
99-
100-
entityTypeBuilder
101-
.Property(x => x.CreatedById)
102-
.HasConversion<long>();
103-
104-
entityTypeBuilder
105-
.HasOne(x => x.CreatedBy)
106-
.WithMany()
107-
.HasForeignKey(x => new { x.GuildId, x.CreatedById });
108-
}
53+
entityTypeBuilder
54+
.Property(x => x.Type)
55+
.HasConversion<string>();
56+
57+
entityTypeBuilder
58+
.Property(x => x.GuildId)
59+
.HasConversion<long>();
60+
61+
entityTypeBuilder
62+
.Property(x => x.CreatedById)
63+
.HasConversion<long>();
64+
65+
entityTypeBuilder
66+
.HasOne(x => x.CreatedBy)
67+
.WithMany()
68+
.HasForeignKey(x => new { x.GuildId, x.CreatedById });
10969
}
11070
}

‎src/Modix.Data/Models/Core/DesignatedChannelMappingCreationData.cs

-15
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,5 @@ public class DesignatedChannelMappingCreationData
2626
/// See <see cref="ConfigurationActionEntity.CreatedById"/>.
2727
/// </summary>
2828
public ulong CreatedById { get; set; }
29-
30-
internal DesignatedChannelMappingEntity ToEntity()
31-
=> new DesignatedChannelMappingEntity()
32-
{
33-
GuildId = GuildId,
34-
ChannelId = ChannelId,
35-
Type = Type,
36-
CreateAction = new ConfigurationActionEntity()
37-
{
38-
GuildId = GuildId,
39-
Type = ConfigurationActionType.DesignatedChannelMappingCreated,
40-
Created = DateTimeOffset.UtcNow,
41-
CreatedById = CreatedById
42-
}
43-
};
4429
}
4530
}

‎src/Modix.Data/Models/Core/DesignatedChannelMappingEntity.cs

+42-43
Original file line numberDiff line numberDiff line change
@@ -4,59 +4,58 @@
44
using Microsoft.EntityFrameworkCore;
55
using Microsoft.EntityFrameworkCore.Metadata.Builders;
66

7-
namespace Modix.Data.Models.Core
7+
namespace Modix.Data.Models.Core;
8+
9+
[Table("DesignatedChannelMappings")]
10+
public class DesignatedChannelMappingEntity
811
{
9-
[Table("DesignatedChannelMappings")]
10-
public class DesignatedChannelMappingEntity
11-
{
12-
[Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
13-
public long Id { get; set; }
12+
[Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
13+
public long Id { get; set; }
1414

15-
public ulong GuildId { get; set; }
15+
public ulong GuildId { get; set; }
1616

17-
[ForeignKey(nameof(Channel))]
18-
public ulong ChannelId { get; set; }
17+
[ForeignKey(nameof(Channel))]
18+
public ulong ChannelId { get; set; }
1919

20-
public virtual GuildChannelEntity Channel { get; set; } = null!;
20+
public virtual GuildChannelEntity Channel { get; set; } = null!;
2121

22-
public DesignatedChannelType Type { get; set; }
22+
public DesignatedChannelType Type { get; set; }
2323

24-
public long CreateActionId { get; set; }
24+
public long CreateActionId { get; set; }
2525

26-
public virtual ConfigurationActionEntity CreateAction { get; set; } = null!;
26+
public virtual ConfigurationActionEntity CreateAction { get; set; } = null!;
2727

28-
public long? DeleteActionId { get; set; }
28+
public long? DeleteActionId { get; set; }
2929

30-
public virtual ConfigurationActionEntity? DeleteAction { get; set; }
31-
}
30+
public virtual ConfigurationActionEntity? DeleteAction { get; set; }
31+
}
3232

33-
public class DesignatedChannelMappingEntityConfiguration
34-
: IEntityTypeConfiguration<DesignatedChannelMappingEntity>
33+
public class DesignatedChannelMappingEntityConfiguration
34+
: IEntityTypeConfiguration<DesignatedChannelMappingEntity>
35+
{
36+
public void Configure(
37+
EntityTypeBuilder<DesignatedChannelMappingEntity> entityTypeBuilder)
3538
{
36-
public void Configure(
37-
EntityTypeBuilder<DesignatedChannelMappingEntity> entityTypeBuilder)
38-
{
39-
entityTypeBuilder
40-
.Property(x => x.Type)
41-
.HasConversion<string>();
42-
43-
entityTypeBuilder
44-
.Property(x => x.GuildId)
45-
.HasConversion<long>();
46-
47-
entityTypeBuilder
48-
.Property(x => x.ChannelId)
49-
.HasConversion<long>();
50-
51-
entityTypeBuilder
52-
.HasOne(x => x.CreateAction)
53-
.WithOne()
54-
.HasForeignKey<DesignatedChannelMappingEntity>(x => x.CreateActionId);
55-
56-
entityTypeBuilder
57-
.HasOne(x => x.DeleteAction)
58-
.WithOne()
59-
.HasForeignKey<DesignatedChannelMappingEntity>(x => x.DeleteActionId);
60-
}
39+
entityTypeBuilder
40+
.Property(x => x.Type)
41+
.HasConversion<string>();
42+
43+
entityTypeBuilder
44+
.Property(x => x.GuildId)
45+
.HasConversion<long>();
46+
47+
entityTypeBuilder
48+
.Property(x => x.ChannelId)
49+
.HasConversion<long>();
50+
51+
entityTypeBuilder
52+
.HasOne(x => x.CreateAction)
53+
.WithOne()
54+
.HasForeignKey<DesignatedChannelMappingEntity>(x => x.CreateActionId);
55+
56+
entityTypeBuilder
57+
.HasOne(x => x.DeleteAction)
58+
.WithOne()
59+
.HasForeignKey<DesignatedChannelMappingEntity>(x => x.DeleteActionId);
6160
}
6261
}

‎src/Modix.Data/Repositories/DesignatedChannelMappingRepository.cs

-207
This file was deleted.

‎src/Modix.Services/Core/CoreSetup.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,10 @@ public static IServiceCollection AddModixCore(this IServiceCollection services)
3535
.AddScoped<IGuildChannelRepository, GuildChannelRepository>()
3636
.AddScoped<IGuildRoleRepository, GuildRoleRepository>()
3737
.AddScoped<IGuildUserRepository, GuildUserRepository>()
38-
.AddScoped<IDesignatedChannelService, DesignatedChannelService>()
38+
.AddScoped<DesignatedChannelService>()
3939
.AddScoped<IDesignatedRoleService, DesignatedRoleService>()
4040
.AddScoped<IClaimMappingRepository, ClaimMappingRepository>()
4141
.AddScoped<IConfigurationActionRepository, ConfigurationActionRepository>()
42-
.AddScoped<IDesignatedChannelMappingRepository, DesignatedChannelMappingRepository>()
4342
.AddScoped<IDesignatedRoleMappingRepository, DesignatedRoleMappingRepository>()
4443
.AddScoped<IMessageRepository, MessageRepository>()
4544
.AddScoped<IMessageService, MessageService>();

‎src/Modix.Services/Core/DesignatedChannelService.cs

-265
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using Discord;
7+
using Microsoft.EntityFrameworkCore;
8+
using Modix.Data;
9+
using Modix.Data.Models.Core;
10+
using Modix.Services.Core;
11+
12+
namespace Modix.Services;
13+
14+
public class DesignatedChannelService(
15+
ModixContext db,
16+
IAuthorizationService authorizationService)
17+
{
18+
public async Task<long> AddDesignatedChannel(IGuild guild, IMessageChannel logChannel,
19+
DesignatedChannelType type)
20+
{
21+
authorizationService.RequireAuthenticatedUser();
22+
authorizationService.RequireClaims(AuthorizationClaim.DesignatedChannelMappingCreate);
23+
24+
var designationId = await db
25+
.Set<DesignatedChannelMappingEntity>()
26+
.Where(x => x.GuildId == guild.Id)
27+
.Where(x => x.ChannelId == logChannel.Id)
28+
.Where(x => x.Type == type)
29+
.Where(x => x.DeleteActionId == null)
30+
.Select(x => (long?)x.Id)
31+
.SingleOrDefaultAsync();
32+
33+
if (designationId is not null)
34+
{
35+
return designationId.Value;
36+
}
37+
38+
var newEntity = new DesignatedChannelMappingEntity
39+
{
40+
GuildId = guild.Id,
41+
ChannelId = logChannel.Id,
42+
Type = type,
43+
CreateAction = new ConfigurationActionEntity
44+
{
45+
GuildId = guild.Id,
46+
Type = ConfigurationActionType.DesignatedChannelMappingCreated,
47+
Created = DateTimeOffset.UtcNow,
48+
CreatedById = authorizationService.CurrentUserId!.Value,
49+
}
50+
};
51+
52+
db.Add(newEntity);
53+
await db.SaveChangesAsync();
54+
55+
return newEntity.Id;
56+
}
57+
58+
public async Task RemoveDesignatedChannel(IGuild guild, IMessageChannel logChannel, DesignatedChannelType type)
59+
{
60+
var designationId = await db
61+
.Set<DesignatedChannelMappingEntity>()
62+
.Where(x => x.GuildId == guild.Id)
63+
.Where(x => x.ChannelId == logChannel.Id)
64+
.Where(x => x.Type == type)
65+
.Where(x => x.DeleteActionId == null)
66+
.Select(x => (long?)x.Id)
67+
.SingleOrDefaultAsync();
68+
69+
if (designationId is null)
70+
{
71+
return;
72+
}
73+
74+
await RemoveDesignatedChannelById(designationId.Value);
75+
}
76+
77+
public async Task RemoveDesignatedChannelById(long designationId)
78+
{
79+
authorizationService.RequireAuthenticatedUser();
80+
authorizationService.RequireClaims(AuthorizationClaim.DesignatedChannelMappingDelete);
81+
82+
var designation = await db
83+
.Set<DesignatedChannelMappingEntity>()
84+
.Where(x => x.Id == designationId)
85+
.SingleAsync();
86+
87+
var deleteAction = new ConfigurationActionEntity
88+
{
89+
Type = ConfigurationActionType.DesignatedChannelMappingDeleted,
90+
Created = DateTimeOffset.UtcNow,
91+
CreatedById = authorizationService.CurrentUserId!.Value,
92+
DesignatedChannelMappingId = designation.Id,
93+
GuildId = designation.GuildId,
94+
};
95+
96+
db.Add(deleteAction);
97+
await db.SaveChangesAsync();
98+
}
99+
100+
public async Task<bool> HasDesignatedChannelForType(ulong guildId, DesignatedChannelType type)
101+
{
102+
return await db
103+
.Set<DesignatedChannelMappingEntity>()
104+
.Where(x => x.GuildId == guildId)
105+
.Where(x => x.Type == type)
106+
.Where(x => x.DeleteActionId == null)
107+
.AnyAsync();
108+
}
109+
110+
public async Task<IReadOnlyCollection<ulong>> GetDesignatedChannelIds(ulong guildId, DesignatedChannelType type)
111+
{
112+
return await db
113+
.Set<DesignatedChannelMappingEntity>()
114+
.Where(x => x.GuildId == guildId)
115+
.Where(x => x.Type == type)
116+
.Select(x => x.ChannelId)
117+
.ToListAsync();
118+
}
119+
120+
public async Task<IReadOnlyCollection<IMessageChannel>> GetDesignatedChannels(IGuild guild,
121+
DesignatedChannelType type)
122+
{
123+
var channelIds = await GetDesignatedChannelIds(guild.Id, type);
124+
125+
if (!channelIds.Any())
126+
{
127+
return [];
128+
}
129+
130+
var channels = await Task.WhenAll(channelIds.Select(d => guild.GetChannelAsync(d)));
131+
132+
return channels
133+
.OfType<IMessageChannel>()
134+
.ToArray();
135+
}
136+
137+
public async Task<IReadOnlyCollection<DesignatedChannelMappingBrief>> GetDesignatedChannels(ulong guildId)
138+
{
139+
authorizationService.RequireClaims(AuthorizationClaim.DesignatedChannelMappingRead);
140+
141+
return await db
142+
.Set<DesignatedChannelMappingEntity>()
143+
.Where(x => x.GuildId == guildId)
144+
.Where(x => x.DeleteActionId == null)
145+
.Select(x => new DesignatedChannelMappingBrief
146+
{
147+
Id = x.Id,
148+
Channel = new GuildChannelBrief
149+
{
150+
Id = x.ChannelId,
151+
Name = x.Channel.Name,
152+
ParentChannelId = x.Channel.ParentChannelId,
153+
},
154+
Type = x.Type,
155+
}).ToListAsync();
156+
}
157+
158+
public async Task<bool> ChannelHasDesignation(
159+
ulong guildId,
160+
ulong channelId,
161+
DesignatedChannelType type,
162+
CancellationToken cancellationToken)
163+
{
164+
return await db
165+
.Set<DesignatedChannelMappingEntity>()
166+
.Where(x => x.GuildId == guildId)
167+
.Where(x => x.ChannelId == channelId)
168+
.Where(x => x.Type == type)
169+
.Where(x => x.DeleteActionId == null)
170+
.AnyAsync(cancellationToken: cancellationToken);
171+
}
172+
}
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System.Threading.Tasks;
2+
using Discord;
3+
using MediatR;
4+
5+
namespace Modix.Services;
6+
7+
public class DiscordRelayService(IMediator mediator)
8+
{
9+
public async Task<bool> SendMessageToChannel(ulong channelId, string message, Embed embed = null)
10+
{
11+
return await mediator.Send(new SendMessageInDiscordRequest(channelId, message, embed));
12+
}
13+
}
14+
15+
public class SendMessageInDiscordRequest(ulong channelId, string message, Embed embed) : IRequest<bool>
16+
{
17+
public ulong ChannelId { get; } = channelId;
18+
public string Message { get; } = message;
19+
public Embed Embed { get; } = embed;
20+
}

‎src/Modix.Services/MessageLogging/MessageLoggingBehavior.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public class MessageLoggingBehavior
2424
INotificationHandler<MessageUpdatedNotification>
2525
{
2626
public MessageLoggingBehavior(
27-
IDesignatedChannelService designatedChannelService,
27+
DesignatedChannelService designatedChannelService,
2828
DiscordSocketClient discordSocketClient,
2929
ILogger<MessageLoggingBehavior> logger)
3030
{
@@ -175,7 +175,7 @@ private async Task TryLogAsync(
175175
return;
176176
}
177177

178-
var channelIsUnmoderated = await _designatedChannelService.ChannelHasDesignationAsync(
178+
var channelIsUnmoderated = await _designatedChannelService.ChannelHasDesignation(
179179
guild.Id,
180180
channel.Id,
181181
DesignatedChannelType.Unmoderated,
@@ -187,7 +187,7 @@ private async Task TryLogAsync(
187187
}
188188
MessageLoggingLogMessages.ModeratedChannelIdentified(_logger);
189189

190-
var messageLogChannels = await _designatedChannelService.GetDesignatedChannelsAsync(
190+
var messageLogChannels = await _designatedChannelService.GetDesignatedChannels(
191191
guild,
192192
DesignatedChannelType.MessageLog);
193193
if (messageLogChannels.Count == 0)
@@ -207,7 +207,7 @@ private async Task TryLogAsync(
207207
}
208208
}
209209

210-
private readonly IDesignatedChannelService _designatedChannelService;
210+
private readonly DesignatedChannelService _designatedChannelService;
211211
private readonly DiscordSocketClient _discordSocketClient;
212212
private readonly ILogger _logger;
213213
}

‎src/Modix.Services/Moderation/AttachmentBlacklistBehavior.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public class AttachmentBlacklistBehavior
9595
};
9696

9797
public AttachmentBlacklistBehavior(
98-
IDesignatedChannelService designatedChannelService,
98+
DesignatedChannelService designatedChannelService,
9999
DiscordSocketClient discordSocketClient,
100100
ILogger<AttachmentBlacklistBehavior> logger,
101101
IModerationService moderationService)
@@ -136,7 +136,7 @@ public async Task HandleNotificationAsync(
136136
}
137137

138138
AttachmentBlacklistLogMessages.ChannelModerationStatusFetching(_logger);
139-
var channelIsUnmoderated = await _designatedChannelService.ChannelHasDesignationAsync(
139+
var channelIsUnmoderated = await _designatedChannelService.ChannelHasDesignation(
140140
guild.Id,
141141
channel.Id,
142142
DesignatedChannelType.Unmoderated,
@@ -180,7 +180,7 @@ await message.Channel.SendMessageAsync(
180180
AttachmentBlacklistLogMessages.ReplySent(_logger);
181181
}
182182

183-
private readonly IDesignatedChannelService _designatedChannelService;
183+
private readonly DesignatedChannelService _designatedChannelService;
184184
private readonly DiscordSocketClient _discordSocketClient;
185185
private readonly ILogger _logger;
186186
private readonly IModerationService _moderationService;

‎src/Modix.Services/Moderation/MessageContentCheckBehaviour.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ public class MessageContentCheckBehaviour :
1818
INotificationHandler<MessageUpdatedNotification>,
1919
INotificationHandler<VoiceChannelStatusUpdatedNotification>
2020
{
21-
private readonly IDesignatedChannelService _designatedChannelService;
21+
private readonly DesignatedChannelService _designatedChannelService;
2222
private readonly IAuthorizationService _authorizationService;
2323
private readonly IModerationService _moderationService;
2424
private readonly IMessageContentPatternService _messageContentPatternService;
2525
private readonly DiscordSocketClient _discordSocketClient;
2626

2727
public MessageContentCheckBehaviour(
28-
IDesignatedChannelService designatedChannelService,
28+
DesignatedChannelService designatedChannelService,
2929
DiscordSocketClient discordSocketClient,
3030
IAuthorizationService authorizationService,
3131
IModerationService moderationService, IMessageContentPatternService messageContentPatternService)
@@ -91,7 +91,7 @@ private async Task TryCheckMessageAsync(IMessage message)
9191
return;
9292
}
9393

94-
if (await _designatedChannelService.ChannelHasDesignationAsync(channel.Guild.Id,
94+
if (await _designatedChannelService.ChannelHasDesignation(channel.Guild.Id,
9595
channel.Id, DesignatedChannelType.Unmoderated, default))
9696
{
9797
return;

‎src/Modix.Services/Moderation/ModerationService.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public class ModerationService : IModerationService
8282
private readonly IDeletedMessageRepository _deletedMessageRepository;
8383
private readonly IDeletedMessageBatchRepository _deletedMessageBatchRepository;
8484
private readonly IRoleService _roleService;
85-
private readonly IDesignatedChannelService _designatedChannelService;
85+
private readonly DesignatedChannelService _designatedChannelService;
8686

8787
// 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?
8888
private const string MuteRoleName = "MODiX_Moderation_Mute";
@@ -100,7 +100,7 @@ public ModerationService(
100100
IDeletedMessageRepository deletedMessageRepository,
101101
IDeletedMessageBatchRepository deletedMessageBatchRepository,
102102
IRoleService roleService,
103-
IDesignatedChannelService designatedChannelService)
103+
DesignatedChannelService designatedChannelService)
104104
{
105105
_discordClient = discordClient;
106106
_authorizationService = authorizationService;
@@ -128,7 +128,7 @@ private async Task SetUpMuteRole(IGuild guild)
128128
var muteRole = await GetOrCreateDesignatedMuteRoleAsync(guild, _authorizationService.CurrentUserId!.Value);
129129

130130
var unmoderatedChannels =
131-
await _designatedChannelService.GetDesignatedChannelIdsAsync(guild.Id,
131+
await _designatedChannelService.GetDesignatedChannelIds(guild.Id,
132132
DesignatedChannelType.Unmoderated);
133133

134134
var nonCategoryChannels =
@@ -169,7 +169,7 @@ public async Task AutoConfigureChannelAsync(IChannel channel)
169169

170170
if (channel is IGuildChannel guildChannel)
171171
{
172-
var isUnmoderated = await _designatedChannelService.ChannelHasDesignationAsync(guildChannel.Guild.Id,
172+
var isUnmoderated = await _designatedChannelService.ChannelHasDesignation(guildChannel.Guild.Id,
173173
channel.Id, DesignatedChannelType.Unmoderated, default);
174174

175175
if (isUnmoderated)

‎src/Modix.Services/Starboard/StarboardService.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public interface IStarboardService
8080
/// <inheritdoc />
8181
public class StarboardService : IStarboardService
8282
{
83-
private readonly IDesignatedChannelService _designatedChannelService;
83+
private readonly DesignatedChannelService _designatedChannelService;
8484
private readonly IMessageRepository _messageRepository;
8585
private static readonly IReadOnlyDictionary<int, string> _emojis = new Dictionary<int, string>
8686
{
@@ -91,7 +91,7 @@ public class StarboardService : IStarboardService
9191
}.OrderByDescending(k => k.Key).ToDictionary(k => k.Key, k => k.Value);
9292

9393
public StarboardService(
94-
IDesignatedChannelService designatedChannelService,
94+
DesignatedChannelService designatedChannelService,
9595
IMessageRepository messageRepository)
9696
{
9797
_designatedChannelService = designatedChannelService;
@@ -115,7 +115,7 @@ private async Task<IUserMessage> GetStarboardEntry(IGuild guild, IMessage messag
115115
private async Task<ITextChannel> GetStarboardChannel(IGuild guild)
116116
{
117117
var starboardChannels = await _designatedChannelService
118-
.GetDesignatedChannelsAsync(guild, DesignatedChannelType.Starboard);
118+
.GetDesignatedChannels(guild, DesignatedChannelType.Starboard);
119119

120120
return starboardChannels.First() as ITextChannel;
121121
}

‎src/Modix.Web/Components/Configuration/Channels.razor

+5-7
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
@using Modix.Data.Models.Core;
2-
@using Modix.Services.Core;
32
@using Modix.Web.Models.Common;
43
@using Modix.Web.Models.Configuration;
5-
@using Modix.Web.Models.UserLookup;
64
@using Modix.Web.Services;
75
@using MudBlazor
86
@using Humanizer;
9-
@using System.Security.Claims;
7+
@using Modix.Services
108

119
<PageTitle>Modix - Channels</PageTitle>
1210
<MudText Typo="Typo.h4">Channel Designations</MudText>
@@ -93,7 +91,7 @@
9391
public DiscordHelper DiscordHelper { get; set; } = null!;
9492

9593
[Inject]
96-
public IDesignatedChannelService DesignatedChannelService { get; set; } = null!;
94+
public DesignatedChannelService DesignatedChannelService { get; set; } = null!;
9795

9896
[Inject]
9997
public ISnackbar Snackbar { get; set; } = null!;
@@ -111,7 +109,7 @@
111109
return;
112110

113111
var currentGuild = DiscordHelper.GetUserGuild();
114-
var designatedChannels = await DesignatedChannelService.GetDesignatedChannelsAsync(currentGuild.Id);
112+
var designatedChannels = await DesignatedChannelService.GetDesignatedChannels(currentGuild.Id);
115113

116114
DesignatedChannelMappings = designatedChannels
117115
.Select(d => new DesignatedChannelData(d.Id, d.Channel.Id, d.Type, currentGuild?.GetChannel(d.Channel.Id)?.Name ?? d.Channel.Name))
@@ -143,7 +141,7 @@
143141
var currentGuild = DiscordHelper.GetUserGuild();
144142
var channel = (Discord.IMessageChannel)currentGuild.GetChannel(_selectedChannel!.Id);
145143

146-
var id = await DesignatedChannelService.AddDesignatedChannelAsync(currentGuild, channel, _selectedDesignatedChannelType!.Value);
144+
var id = await DesignatedChannelService.AddDesignatedChannel(currentGuild, channel, _selectedDesignatedChannelType!.Value);
147145

148146
_createDialogVisible = false;
149147

@@ -159,7 +157,7 @@
159157

160158
public async Task RemoveDesignation(long id, DesignatedChannelType designatedChannelType)
161159
{
162-
await DesignatedChannelService.RemoveDesignatedChannelByIdAsync(id);
160+
await DesignatedChannelService.RemoveDesignatedChannelById(id);
163161

164162
var channelMappingsWithType = DesignatedChannelMappings![designatedChannelType];
165163
var removedChannelMapping = channelMappingsWithType.First(x => x.Id == id);

‎src/Modix/Extensions/ServiceCollectionExtensions.cs

+1
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ public static IServiceCollection AddModix(
161161
services.AddScoped<MessageQuoteEmbedHelper>();
162162
services.AddScoped<PasteService>();
163163
services.AddScoped<CommandErrorService>();
164+
services.AddScoped<DiscordRelayService>();
164165

165166
services.AddMemoryCache();
166167

‎src/Modix/appsettings.Development.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@
66
"Microsoft": "Information"
77
}
88
},
9-
"DbConnection": "Host=127.0.0.1;Username=postgres;Password=password;Database=MODiX-Dev"
10-
}
9+
"DbConnection": "Host=127.0.0.1;Username=postgres;Password=password;Database=MODiX-Dev",
10+
"WebsiteBaseUrl": "https://localhost:5000/"
11+
}

‎test/Modix.Data.Test/Repositories/DesignatedChannelMappingRepositoryTests.cs

-582
This file was deleted.

0 commit comments

Comments
 (0)
Please sign in to comment.